Существует замечательная книга Pro Git, в которой подробно описаны все команды и возможности гита. Но после ее прочтения у многих остается непонимание того, как это все использовать на практике. В частности, у программистов разного уровня часто возникают вопросы о том, как работать с ветками в Git, когда их заводить и как мержить между собой. Порой мне попадались очень «оригинальные» и неоправданно усложненные схемы работы с гитом. В то время как в сообществе программистов уже сформировалась схема работы с гитом и ветками в нем. В этой статье я хочу дать краткий обзор основных моментов при работе с Git, и описать «классическую» схему работы с ветками. Многое из того что описано в этой статье будет справедливо и для других систем управления версиями.

Эта статья может быть полезна для программистов, которые только начинают осваивать Git, или какую-то другую систему управления версиями. Для опытных программистов эта статья покажется очень простой и банальной.

Для начала давайте разберемся с тем что такое ветка и коммит.

Коммит


Можно сказать, что коммит это основной объект в любой системе управления версиями. В нем содержится описание тех изменений, которые вносит пользователь в код приложения. В Git коммит состоит из нескольких так называемых объектов. Для простоты понимания можно считать, что коммиты это односвязный список, состоящий из объектов в которых содержаться измененные файлы, и ссылка на предыдущий коммит.



У коммита есть и другие свойства. Например, дата коммита, автор, комментарий к коммиту и т.п.
В качестве комментария обычно указывают те изменения, которые вносит этот коммит в код, или название задачи которую он решает.

Git это распределенная система управления версиями. Это значит, что у каждого участника проекта есть своя копия репозитория, которая находиться в папке “.git”, которая расположена в корне проекта. Именно в этой папке хранятся все коммиты и другие объекты Git. Когда вы работаете с Git, он в свою очередь работает с этой папкой.

Завести новый репозиторий очень просто, это делается командой

git init

Таким образом у вас получиться новый пустой репозиторий. Если вы хотите присоединиться к разработке уже имеющегося проекта, то вам нужно будет скопировать этот репозиторий в свою локальную папку с удаленного репозитория. Делается это так:

git clone <url удаленного репозитория>

После чего в текущей папке появляется директория .git в которой и будет содержаться копия удаленного репозитория.

Существует несколько основных областей в которых находиться код.

  • Рабочая директория – это файлы в корне проекта, тот код с которым вы работаете.
  • Локальный репозиторий — она же директория “.git”. В ней хранятся коммиты и другие объекты.
  • Удаленный репозиторий – тот самый репозиторий который считается общим, в который вы можете передать свои коммиты из локального репозитория, что бы остальные программисты могли их увидеть. Удаленных репозиториев может быть несколько, но обычно он бывает один.
  • Есть еще одна область, с пониманием которой обычно бывают проблемы. Это так называемая область подготовленных изменений (staging area). Дело в том, что перед тем как включить какие-то изменения в коммит, нужно вначале отметить что именно вы хотите включить в этот коммит. В своей рабочей директории вы можете изменить несколько файлов, но в один конкретный коммит включать не все эти изменения.

В целом работа с гитом выглядит так: вы меняете файлы в своей рабочей директории, затем добавляете эти изменения в staging area используя команду

git add <имя/файла>

При этом можно использовать маски со звездочкой.
Потом вы делаете коммит в свой локальный репозиторий

git commit –m “Комментарий к коммиту”

Когда коммитов накопиться достаточно много, чтобы ими можно было поделиться, вы выполняете команду

git push

После чего ваши коммиты уходят в удаленный репозиторий.

Если нужно получить изменения из удаленного репозитория, то нужно выполнить команду

git pull

После этого, в вашем локальном репозитории появятся те изменения, которые были отправлены другими программистами.

Код в рабочей области проекта образуется применением тех изменений, которые содержаться в коммитах. У каждого коммита есть свое имя, которое представляет собой результат хеш функции sha-1 от содержимого самого коммита.

Просмотреть коммиты можно при помощи команды

git log

Формат ответа этой команды по дефолту не очень удобен. Вот такая команда выведет ответ в более читаемом виде

git log --pretty=format:"%H [%cd]: %an - %s" --graph --date=format:%c

Что бы закончить просмотр нужно нажать на клавишу q
Посмотреть, что находиться в рабочей директории и staging area можно командой

git status

Рабочую директорию можно переключить на предыдущее состояние выполнив команду

git checkout <hash коммита>

Только перед тем как это делать выполните git status и убедитесь, что у вас нет никаких локальных и не зафиксированных изменений. Иначе Git не поймет, как ему переключаться. git status подскажет вам что можно сделать с локальными изменениями что бы можно было переключиться. Этого правила следует придерживаться и при всяких других переключениях рабочей области.

Ветка


Ветка в Git это подвижный указатель на один из коммитов. Обычно ветка указывает на последний коммит в цепочке коммитов. Ветка берет свое начало от какого-то одного коммита. Визуально это можно представить вот так.



Сделать новую ветку и переключиться на нее можно выполнив команды

git pull
git checkout –b <имя новой ветки>


Просто сделать ветку, не переключаясь на нее можно командой

git branch <имя ветки>

переключиться на ветку

git checkout <имя ветки>

Важно понимать, что ветка берет свое начало не от ветки, а от последнего коммита который находиться в той ветке, в которой вы находились.

Ветка обычно заканчивается специальным merge коммитом, который говорит, что ветку нужно объединить с какой-то другой веткой. В merge коммите содержатся две ссылки на два коммита которые объединяются в одну ветку.



Существует другая ситуация при объединении веток, в которой merge может произойти без merge commit. Дело в том, что если в одной из веток не произошло никаких изменений, то необходимость в merge commit с двумя предками отпадает. В таком случае, при слиянии веток, Git просто сделает пометку о том, что дальше будут идти коммиты той ветки с которой эта ветка была объединена. Такая схема merge называется слияние-перемотка (fast-forward merge), визуально это можно представить вот так.



Во всех этих случаях после того, как ветка объединяется с другой веткой все коммиты сделанные в ней попадают в ту ветку, с которой она была объединена. Так же важно понимать, что merge это не двунаправленная операция. Если смержить ветку задачи в мастер ветку, то в мастер ветке появится код, который находился в ветке задачи, а в ветке задачи не появиться новый код из мастер ветки. Если нужно что бы это произошло, нужно смержить мастер ветку в ветку задачи.
Что бы смержить одну ветку в другую нужно вначале переключиться на ту ветку, в которую вы хотите смержить

git checkout <имя ветки>

затем выполнить команду

git merge <имя ветки>

Так выглядит работа с ветками в общих чертах.

Обратите внимание на то, что перед тем как заводить новую ветку нужно выполнить git pull. Делается это по нескольким причинам.
  • Другой программист мог изменить код, в том числе внести такие изменения, которые повлияют на решение задачи, для которой вы заводите новую ветку. Эти изменения могут вам пригодиться при решении своей задачи.
  • Из-за этих изменений вы можете получить конфликт при мерже.
  • Больше шанс что у вас получится merge commit. Это не так плох, как два предыдущих пункта. Но если можно избежать лишнего коммита, то почему бы этого не сделать?


Популярные схемы работы с ветками в Git



Теперь можно описать популярные схемы работы с ветками в гите.

Ветки нужны для того, чтобы программисты могли вести совместную работу над проектом и не мешать друг-другу при этом. При создании проекта, Git создает базовую ветку. Она называется master веткой. Она считается центральной веткой, т.е. в ней содержится основной код приложения.

Классическая схема работы с ветками


Обычно перед тем как взяться за решение какой-то задачи программист заводит новую ветку от последнего рабочего коммита мастер ветки и решает задачу в этой новой ветке. В ходе решения он делает ряд коммитов, после этого тестирует код непосредственно в ветке задачи. А после того как задача решена, делают merge обратно в мастер ветку. Такую схему работы часто используют с юнит тестами и автоматизированным деплоем. Если юнит тесты будут покрывать весь код, то можно настроить деплой так что вначале будут прогоняться все тесты в ветке задачи. А после этого, если они прошли успешно будет происходить merge и деплой. При такой схеме можно добиться полной автоматизации при тестировании и деплои.

Именная ветка


Неопытные программисты заводят себе именную ветку и работают всегда в ней. Они решают по одной задачи за раз, и когда заканчивают решение одной из задач, делают новый Pull запрос через Web интерфейсе (об этом чуть ниже). Недостаток этого подхода в том, что так можно решать только одну задачу и нельзя быстро переключиться на решение другой задачи. Еще один недостаток в том, что ветки так со временем будут все сильнее расходиться и код в ветке программиста рано или поздно устареет относительно мастер ветки и его придется обновить. Для этого можно либо смержить мастер ветку в ветку программиста, либо завести новую ветку для этого программиста от последнего рабочего состояния в мастер ветке. Правда к тому времени, как это произойдет программист уже может освоить гит в достаточной мере что бы перейти на “классическую” схему работы. Таким образом эта схема имеет место быть для неопытных пользователей Git.

Схема с dev веткой


Другая схема очень похожа на классическую, только в ней помимо мастер ветки есть еще девелоперская ветка, которая деплоится на тестовый сервер. Такую ветку обычно называют dev. Схема работы при этом такая. Программист перед выполнением новой задачи заводит для нее ветку от последнего рабочего состояния в мастер ветке. Когда он заканчивает работу над задачей, то мержит ветку задачи в dev ветку самостоятельно. После этого, совместными усилиями задача тестируется на тестовом сервере вместе с остальными задачами. Если есть ошибки, то задачу дорабатывают в той же ветке и повторно мержат с dev веткой. Когда тестирование задачи заканчивается, то ВЕТКУ ЗАДАЧИ мержат с мастер веткой. Важно заметить, что в этой схеме работы с мастер веткой нужно мержить ветку задачи, а не dev ветку. Ведь в dev ветке будут содержаться изменения, сделанные не только в этой задаче, но и в других и не все эти изменения могут оказаться рабочими. Мастер ветка и dev ветка со временем будут расходиться, поэтому при такой схеме работы периодически заводят новую dev ветку от последнего рабочего состояния мастер ветки. Недостатком этого подхода является избыточность, по сравнению с классической схемой. Такую схему работы с ветками часто используют если в проекте нет автоматизированных тестов и все тестирование происходит вручную на сервере разработки.

Так же следует отметить что эти схемы работы можно комбинировать между собой, если в этом есть какая-то необходимость.

Pull запросы


С этим понятием имеется путаница. Дело в том, что в Git есть две совершенно разные вещи, которые можно назвать Pull запросом. Одна из них, это консольная команда git pull. Другая это кнопка в web интерфейсе репозитория. На github.com она выглядит вот так



Про эту кнопку и пойдет речь дальше.

Если программист достаточно опытный и ответственный, то он обычно сам сливает свой код в мастер ветку. В противном случае программист делает так называемый Pull запрос. Pull запрос это по сути дела запрос на разрешение сделать merge. Pull запрос можно сделать из web интерфейса Git, или при помощи команды git request-pull. После того как Pull запрос создан остальные участники могут увидеть это, просмотреть тот код, который программист предлагает внести в проект и либо одобрить этот код, либо нет. Merge через pull запросы имеет свои плюсы и минусы. Минус в том, что для тесной команды опытных программистов такой подход будет лишним. Это будет только тормозить работу и вносить в нее оттенки бюрократии.

С другой стороны, если в проекте есть не опытные программисты, которые могут сломать код, то Pull запросы могут помочь избежать ошибок, и быстрее обучить этих программистов наблюдая за тем какие изменения они предлагают внести в код.

Так же Pull запросы подходят для широкого сообщества программистов, работающих с открытым исходным кодом. В этом случае нельзя заранее сказать что-то о компетенции таких разработчиков и о том, что они хотят изменить в коде.

Конфликты


Конфликты возникают при мердже веток если в этих ветках одна и та же строка кода была изменена по-разному. Тогда получается, что Git не может сам решить какое из изменений нужно применить и он предлагает вручную решить эту ситуацию. Это замедляет работу с кодом в проекте. Избежать этого можно разными методами. Например, можно распределять задачи так, чтобы связанные задачи не выполнялись одновременно различными программистами.
Другой способ избежать этого, это договориться о каком-то конкретном стиле кода. Тогда программисты не будут менять форматирование кода и вероятность того, что они изменят одну и ту же строчку станет ниже.

Еще один хороший совет, который поможет вам избежать конфликтов при работе в команде, это вносить минимум изменений в код при решении задач. Чем меньше строчек вы поменяли, тем меньше вероятность что вы измените ту же самую строку что и другой программист в другой задаче.

После того, как в мастер ветке достигается состояние, которое можно считать стабильным оно отмечается тегом с версией этого состояния. Это и есть то что называют версией программы.
Делается это вот так

git tag -a v1.0

Что бы передать ветки в удаленный репозиторий нужно выполнить команду

git push –tags

Теги удобны еще и тем, что можно легко переключиться на то состояние кода которое отмечено тегом. Делается это с помощью все той же команды

git checkout <имя тега>

Различные системы деплоя и автоматизированной сборки используют теги для идентификации того состояния, которое нужно задеплоить или собрать. Так сделано, потому что если мы будем собирать или деплоить код последней версии, то есть риск что-какой-то другой программист в этот момент внесет какие-то изменения в мастер ветку, и мы соберем не то что хотели. К тому же так будет проще переключаться между рабочими и проверенными состояниями проектов.

Если вы будете придерживаться этих правил и “классической” схемы работы с ветками, то вам будет проще интегрировать ваш Git с другими системами. Например, с системой непрерывной интеграции или с репозиторием пакетов, таким как packagist.org. Обычно сторонние решения и всякие расширения рассчитаны именно на такую схему работы с гитом и если вы сразу начнете делать все правильно, то это может стать большим плюсом для вас в дальнейшем.

Это обзор основных моментов при работе с Git. Если вы хотите узнать про Git больше, то я вам посоветую прочитать книгу Pro Git. Вот здесь.

В этой статье была приведена упрощенная схема представления коммитов. Но перед тем как ее написать я решил разобраться как именно хранятся коммиты на диске. Если вас тоже заинтересует этот вопрос, то вы можете прочитать об этом вот здесь.

Комментарии (31)


  1. TheKnight
    08.12.2017 15:54

    Ветка заканчивается специальным merge коммитом который говорит, что ветку нужно объединить с какой-то другой веткой. В merge коммите содержатся две ссылки на два коммита которые объединяются в одну ветку.

    О_О А мужики то не знали…


    1. Andriell Автор
      08.12.2017 17:15

      Merge commit можно в логе увидеть. Затем выполнить

      git cat-file -p <hach>

      И увидеть, что у этого коммита два parent и одно tree


      1. TheKnight
        08.12.2017 17:43

        Я про обязательность merge commit.


        1. Andriell Автор
          08.12.2017 18:20

          Он вроде как обязателен
          https://git-scm.com/docs/git-merge
          Там написано
          ...and record the result in a new commit along with the names of the two parent commits and a log message from the user describing the changes…

          Ты наверное что-то путаешь и думаешь что merge commit создается только если были конфликты.


          1. TheKnight
            08.12.2017 18:36

            1. Andriell Автор
              08.12.2017 21:46

              У тебя так получилось потому, что ты не сделал коммиты в мастер ветке между тем как начал от нее новую ветку и тем как смержил ее обратно. Гиту было нечего мержить и потому он просто перенес коммиты в мастер ветку. По сути дела это даже не мердж веток, а перенос коммитов.
              Такое может происходить часто только если ты работаешь в команде один и выполняешь задачи последовательно по одной за раз. В таком случаи вообще нет особого смысла создавать ветки под задачи, можно просто делать коммиты в мастер ветку.


              1. TheKnight
                09.12.2017 18:27

                Блин. Попробую обьяснить на пальцах
                1) Твое утверждение — любой мердж заканчивается мердж коммитом.
                2) Мое утверждение — мердж коммит нужен не во всех случаях.
                3) Дальше я привожу пример мерджа двух веток, который не заканчивается мердж коммитом.
                4) Твое утверждение — это не мердж это перемещение коммитов.
                Мне кажется, ты просто не хочешь признавать ошибку, даже если тебе приводят пример, где твое утверждение неправильно.


                1. Andriell Автор
                  10.12.2017 08:42

                  Ошибки нету. В официальной документации написано про merge commit так же как и у меня. Другое дело, что есть исключительная ситуация в которой можно обойтись и без merge commit. Вы так хотите что бы эта ситуация была описана в этой статье. Давайте ее опишем.


                  1. lair
                    10.12.2017 10:35

                    В той же официальной документации явно, отдельным разделом, описан fast-forward, при котором мерж-коммита не бывает.


                    1. Andriell Автор
                      10.12.2017 11:39

                      Вот именно что отдельным разделом. Если следовать вашей логике, то в официальной документации тоже есть ошибки раз они в той же главе не написали что может быть и по другом. А что по мне, так именно в этой статье рассматривать другие варианты воде fast-forward merge было не обязательно.


                      1. lair
                        10.12.2017 11:43

                        Вот именно что отдельным разделом.

                        … внутри документации на git merge, ага.


                        А что по мне, так именно в этой статье рассматривать другие варианты воде fast-forward merge было не обязательно.

                        … хотя они на практике встречаются весьма часто?


                        1. Andriell Автор
                          10.12.2017 12:07

                          Если ты работаешь в команде один, и делаешь задачи последовательно, то такое у тебя получается постоянно. У меня такое бывает очень редко.
                          Дело не в том как часто это происходит а в том, что это можно было и не описывать в этой статье, для понимания что такое git merge первой схемы было достаточно.
                          Если описать все что есть в git, то получиться еще одна книга Pro Git, в которой очень много информации и вопрос, а что с этим со всем делать остается открытым после ее прочтения.


                          1. lair
                            10.12.2017 12:12

                            Если ты работаешь в команде один, и делаешь задачи последовательно, то такое у тебя получается постоянно.

                            … если работаешь не один, но не хочешь иметь мерж-коммиты (потому что они затрудняют чтение истории) — то тоже.


                            Пойнт-то как раз в том, что надо понимать, откуда и зачем берутся мерж-коммиты, как их получить и как их избежать.


                            Если описать все что есть в git, то получиться еще одна книга Pro Git, в которой очень много информации и вопрос, а что с этим со всем делать остается открытым после ее прочтения.

                            Ну, у меня вот после прочтения Pro Git вопросов "что делать" не оставалось.


                            1. Andriell Автор
                              11.12.2017 10:46

                              Пойнт-то как раз в том, что надо понимать, откуда и зачем берутся мерж-коммиты, как их получить и как их избежать.
                              Вообще ничего плохого в merge commit нету. При работе в команде их не избежать. Но ты мне напомнил про одну важную вещь о которой стоит написать более явно.


                              1. lair
                                11.12.2017 11:41

                                Вообще ничего плохого в merge commit нету.

                                … пока вам не надо ветку разбирать, ребейзить, черрипикать и так далее.


                                При работе в команде их не избежать.

                                Но можно радикально уменьшить их число.


                                1. Andriell Автор
                                  11.12.2017 12:10

                                  … пока вам не надо ветку разбирать, ребейзить, черрипикать и так далее.
                                  Это наверное лучше вообще не делать.
                                  Но можно радикально уменьшить их число.
                                  Что ты предлагаешь для этого сделать?


                                  1. lair
                                    11.12.2017 12:17

                                    Это наверное лучше вообще не делать.

                                    Почему это "лучше не делать"? Вам никогда не надо разбираться, какое изменение все сломало или починило? Переносить фиксы между версиями?


                                    Что ты предлагаешь для этого сделать?

                                    Например, pull --ff-only и pull --rebase вместо обычного pull. Радикально уменьшает число мерж-коммитов при совместной работе в одной ветке. Соответственно, при работе командой над одной фичей в разных ветках — аналогично, ребейз вместо мержа. Более того, даже при вливании фичи в транк, если сделать ребейз фичи на транк, можно получить в худшем случае один мерж-коммит вместо двух (и больше), а в лучшем — ни одного.


                                    1. Andriell Автор
                                      11.12.2017 15:23

                                      Почему это «лучше не делать»? Вам никогда не надо разбираться, какое изменение все сломало или починило? Переносить фиксы между версиями?

                                      Да, иногда если сразу не понятно по коду из-за чего что-то сломалось, то мне приходилось искать тот коммит который что-то сломал. Для этого вначале делается тест с помощью которого можно найти ту самую поломку. Или тест не делатся, а поломка проверяется в ручную. Потом код откатывается на прошлое состояние в котором все работало запускается тест. Дальше методом деления отрезка пополам находится тот коммит который все испортил. Смотрим что было в этом коммите. Дальше это фиксится с помощью нового коммита. Все просто. Переносить фиксы между версиями не надо. merge commit-ы абсолютно ничем не мешают. Фиксы не теряются, по новой их применять не надо. Вообще обычно до поисков конкретного коммита не доходит дело, обычно и так понятно в чем причина поломки.


                                      1. lair
                                        11.12.2017 15:27

                                        Смотрим что было в этом коммите.

                                        … а в этом коммите был мерж. В этот момент все и взрывается.


                                        Переносить фиксы между версиями не надо.

                                        Вам не надо, мне надо.


                                        merge commit-ы абсолютно ничем не мешают.

                                        Вам не мешают, мне мешают. Вы вот знаете (именно из головы, без справочника), как увидеть список коммитов только в вашей ветке, если в нее был сделан мерж? Т.е., A -> A1 -> A2 -> A3 -> (merge) -> A4 -> B?


          1. lair
            08.12.2017 19:10

            Не обязателен. Во-первых (и самых простых) бывает fast-forward, во-вторых, бывает rebase (правильно так делать или нет — вопрос отдельный).


            1. Andriell Автор
              08.12.2017 21:53

              Это в общем-то тоже не мердж, а перемещение… Которое, наверное, лучше не делать без острой на то необходимости.


              1. lair
                08.12.2017 22:34

                Fast-forward — это не перемещение, это нормальная ситуация для короткоживущей ветки. Именно поэтому ветка не обязана заканчиваться мерж-коммитом. А еще ветка — это не контейнер, ветка — это указатель.


  1. ilnuribat
    08.12.2017 15:58

    Pull запрос можно сделать только из web интерфейса Git

    я делаю через терминал
    ссылка
    github.com/github/hub — это надстройка на стандартным гитом, от гитхаба.
    вот установка через пакетный менеджер apt
    github.com/github/hub/issues/718#issuecomment-100411835


    1. Andriell Автор
      08.12.2017 16:17

      Спасибо за информацию. Не знал, что это можно сделать и из консоли. Я думал, что это реализовано на уровне веб интерфейса github.com. Поправлю это в статье


    1. ilnuribat
      08.12.2017 22:18

      поправочка
      вот ссылка на документацию по hub
      man page
      я у себя сделал alias git=hub
      пример

      git checkout -b <new feature>
      git commit <...>
      git push origin <new feature>
      git pull-request -b <куда делать pull request> -m <заголовок>
      


  1. redya69
    08.12.2017 17:02

    Подтягивать изменения лучше с дополнительной опцией rebase

    git pull --rebase origin branch

    Без нее, если у вас уже есть коммиты, то будет создан дополнительный мерж-коммит, загрязняющий лог. А с этой опцией гит попытается применить ваши коммиты поверх прилетевших


    1. Dreyk
      08.12.2017 17:26

      это именно то, что я всегда делаю, но я уже понял, что нельзя это советовать кому попало, через неделю ко мне прибегут с криками "ааа конфлиииктыыы, одни и те же конфликты, я их решаю, а они опять!!! почему??"


      1. TheKnight
        08.12.2017 19:26

        Потому что в идеале нужно использовать короткоживущие ветки…


  1. KirEv
    08.12.2017 18:01

    вроде все поверхностно, почему тогда нет вспоминается:

    git init 
    git clone 
    git remote 
    git reset
    git commit , ёМоё.. 
    ...
    

    этими командами пользуюсь чаще всего, но их в списке не нашел, кстати из терминала работаю.

    вот кажется, пишете статью…

    теги, пуш\пул, конечно хорошо, но чтобы этим пользоваться — нужно клонировать\инициировать репозиторий… логическая цепочка быть должна.


    1. Andriell Автор
      11.12.2017 12:15

      Про git commit, ёМоё… там было написано. git init и git clone добавил для хронологии.
      Ты говоришь что нужен git reset. Неужели тебе так часто приходиться что-то отменять? Переключиться на предыдущее состояние можно при помощи git checkout


  1. develop7
    10.12.2017 13:28

    Ура, наконец-то ещё одна статья про гит для новичков! Ну теперь заживём (нет)!