Давайте рассмотрим простой пример ветвления и слияния с таким процессом работы, который вы могли бы использовать в настоящей разработке. Вы будете делать следующее:
- Работать над веб-сайтом.
- Создадите ветку для новой истории, над которой вы работаете.
- Выполните некоторую работу на этой ветке.
- Вернётесь на производственную ветку.
- 2. Создадите ветку для исправления ошибки.
- После его тестирования, сольёте ветку с исправлением и отправите в продакшн.
- Переключитесь к прерванной истории и продолжите работу.
Основы ветвления
Для начала представим, что вы работаете над своим проектом и уже имеете пару коммитов (см. Рисунок 3-10).
Рисунок 3-10. Рисунок 3-10.
git checkout
с ключом -b
:
$ git checkout -b iss53
Switched to a new branch "iss53"
Это сокращение для:
$ git branch iss53
$ git checkout iss53
Рисунок 3-11 показывает результат.
Рисунок 3-11. Создание новой ветки / указателя
iss53
вперёд потому, что вы на неё перешли (то есть ваш HEAD
указывает на неё; см. Рисунок 3-12):
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Рисунок 3-12. Ветка iss53
передвинулась вперёд во время работы
iss53
. А также не надо прикладывать много усилий, чтобы отменить эти изменения перед тем, как вы сможете начать работать над решением срочной проблемы. Всё, что вам нужно сделать, это перейти на ветку master
.
Однако, прежде чем сделать это, учтите, что если в вашем рабочем каталоге или индексе имеются незафиксированные изменения, которые конфликтуют с веткой, на которую вы переходите, Git не позволит переключить ветки. Лучше всего при переключении веток иметь чистое рабочее состояния. Существует несколько способов добиться этого (а именно, прятанье (stash
) работы и правка (amend
) коммита), которые мы рассмотрим позже. А на данный момент представим, что вы зафиксировали все изменения, и можете переключиться обратно на ветку master
:
$ git checkout master
Switched to branch "master"
Теперь рабочий каталог проекта находится точно в таком же состоянии, что и в момент начала работы над проблемой №53, так что вы можете сконцентрироваться на срочном изменении. Очень важно запомнить: Git возвращает ваш рабочий каталог к снимку состояния того коммита, на который указывает ветка, на которую вы переходите. Он добавляет, удаляет и изменяет файлы автоматически, чтобы гарантировать, что состояние вашей рабочей копии идентично последнему коммиту на ветке.
Итак, вам надо срочно исправить ошибку. Давайте создадим для этого ветку, на которой вы будете работать (см. Рисунок 3-13):
$ git checkout -b 'hotfix'
Switched to a new branch "hotfix"
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix]: created 3a0874c: "fixed the broken email address"
1 files changed, 0 insertions(+), 1 deletions(-)
Рисунок 3-13. Ветка для решения срочной проблемы базируется на ветке master
merge
) изменения назад в ветку master
, чтобы включить его в продукт. Это делается с помощью команды git merge
:
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast forward
README | 1 -
1 files changed, 0 insertions(+), 1 deletions(-)
Наверное, вы заметили фразу “Fast forward
” в этом слиянии. Так как ветка, которую вы сливали, указывала на коммит, являющийся прямым потомком коммита, на котором вы находитесь, Git передвигает указатель вперёд. Иными словами, когда вы пытаетесь слить один коммит с другим, который может быть достигнут идя по истории первого коммита, Git упрощает вещи, перемещая указатель вперёд, так как нету расходящихся изменений для слияния их воедино. Это называется “fast forward
” (перемотка).
Ваши изменения теперь в снимке состояния коммита, на который указывает ветка master
, и вы можете включить изменения в продукт (см. Рисунок 3-14).
Рисунок 3-14. После слияния ветка master
указывает туда же, куда и ветка hotfix
hotfix
, так как она больше не нужна — ветка master
уже указывает на то же место. Вы можете удалить ветку с помощью опции -d
к git branch
:
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
Теперь вы можете вернуться обратно к рабочей ветке для проблемы №53 и продолжить работать над ней (см. Рисунок 3-15):
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53]: created ad82d7a: "finished the new footer [issue 53]"
1 files changed, 1 insertions(+), 0 deletions(-)
Рисунок 3-15. Ветка iss53 может двигаться вперёд независимо
hotfix
, не включена в файлы на ветке iss53
. Если вам это необходимо, вы можете выполнить слияние ветки master
в ветку iss53
посредством команды git merge master
. Или же вы можете подождать с интеграцией изменений до тех пор, пока не решите включить изменения на iss53
в продуктовую ветку master
.
Основы слияния
Представьте себе, что вы разобрались с проблемой №53 и готовы объединить эту ветку и свойmaster
. Чтобы сделать это, вы выполните слияние вашей ветки iss53
в ветку master
точно так же, как делали ранее с веткой hotfix
. Все что вы должны сделать ― перейти на ту ветку, в которую вы хотите внести свои изменения и выполнить команду git merge
:
$ git checkout master
$ git merge iss53
Merge made by recursive.
README | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
Сейчас слияние выглядит немного не так, как для ветки hotfix
, которое вы делали ранее. В данном случае ваша история разработки разделилась в некоторой точке. Так как коммит на той ветке, на которой вы находитесь, не является прямым предком для ветки, которую вы сливаете, Git-у придётся проделать кое-какую работу. В этом случае Git делает простое трехходовое слияние, используя при этом два снимка состояния репозитория, на которые указывают вершины веток, и общий снимок-прародитель для этих двух веток. На рисунке 3-16 выделены три снимка, которые Git будет использовать для слияния в этом случае.
Рисунок 3-16. Git автоматически определяет наилучшего общего предка для слияния веток
Рисунок 3-17. Git автоматически создает новый коммит, содержащий результаты слияния
iss53
вам больше не нужна. Можете удалить ее и затем вручную закрыть карточку (ticket) в вашей системе:
$ git branch -d iss53
Основы конфликтов при слиянии
Иногда процесс слияния не идет гладко. Если вы изменили одну и ту же часть файла по-разному в двух ветках, которые собираетесь объединить, Git не сможет сделать это чисто. Если ваше решение проблемы №53 изменяет ту же часть файла, что иhotfix
, вы получите конфликт слияния, и выглядеть он будет примерно следующим образом:
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git не создал новый коммит для слияния. Он приостановил этот процесс до тех пор, пока вы не разрешите конфликт. Если вы хотите посмотреть, какие файлы не прошли слияние (на любом этапе после возникновения конфликта), можете выполнить команду git status
:
[master*]$ git status
index.html: needs merge
# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# unmerged: index.html
#
Всё, что имеет отношение к конфликту слияния и что не было разрешено, отмечено как unmerged
. Git добавляет стандартные маркеры к файлам, которые имеют конфликт, так что вы можете открыть их вручную и разрешить эти конфликты. Ваш файл содержит секцию, которая выглядит примерно так:
HEAD:index.html
В верхней части блока (всё что выше
=======
>>>>>>> iss53:index.html
=======
) это версия из HEAD
(вашей ветки master
, так как именно на неё вы перешли перед выполнением команды merge), всё что находится в нижней части ― версия в iss53
. Чтобы разрешить конфликт вы должны либо выбрать одну из этих частей, либо как-то объединить содержимое по своему усмотрению. Например, вы можете разрешить этот конфликт заменой всего блока, показанного выше, следующим блоком:
Это решение содержит понемногу из каждой части, и я полностью удалил строки
, =======
и >>>>>>>
. После того, как вы разрешили каждую из таких секций с каждым из конфликтных файлов, выполните git add
для каждого конфликтного файла. Индексирование будет означать для Git, что все конфликты в файле теперь разрешены. Если вы хотите использовать графические инструменты для разрешения конфликтов, можете выполнить команду git mergetool
, которая запустит соответствующий графический инструмент и покажет конфликтные ситуации:
$ git mergetool
merge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiff
Merging the files: index.html
Normal merge conflict for 'index.html':
{local}: modified
{remote}: modified
Hit return to start merge resolution tool (opendiff):
Если вы хотите использовать другой инструмент для слияния, нежели выбираемый по умолчанию (Git выбрал opendiff
для меня, так как я выполнил команду на Mac). Вы можете увидеть все поддерживаемые инструменты, указанные выше после “merge tool candidates
”. Укажите название предпочтительного для вас инструмента. В Главе Настройка Git мы обсудим, как изменить это значение по умолчанию для вашего окружения.
После того, как вы выйдете из инструмента для выполнения слияния, Git спросит вас, было ли оно успешным. Если вы отвечаете, что да ― файл индексируется (добавляется в область для коммита), чтобы дать вам понять, что конфликт разрешен.
Можете выполнить git status
ещё раз, чтобы убедиться, что все конфликты были разрешены:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: index.html
#
Если вы довольны тем, что получили, и удостоверились, что всё, имевшее конфликты, было проиндексировано, можете выполнить git commit
для завершения слияния. По умолчанию сообщение коммита будет выглядеть примерно так:
Если вы довольны тем, что получили, и удостоверились, что всё, имевшее конфликты, было проиндексировано, можете выполнить git commit для завершения слияния. По умолчанию сообщение коммита будет выглядеть примерно так:
Вы можете дополнить это сообщение информацией о том, как вы разрешили конфликт, если считаете, что это может быть полезно для других в будущем. Например, можете указать почему вы сделали то, что сделали, если это не очевидно конечно.
Pro Git