Давайте рассмотрим простой пример рабочего процесса, который может быть полезен в вашем проекте. Ваша работа построена так:
-
Вы работаете над сайтом.
-
Вы создаёте ветку для реализации новой функциональности в соответствии с пользовательской историей.
-
Вы работаете в этой ветке.
В этот момент вы получаете сообщение, что обнаружена критическая ошибка, требующая скорейшего исправления. Ваши действия:
-
Переключиться на основную ветку.
-
Создать ветку для добавления исправления.
-
После тестирования слить ветку, содержащую исправление, с основной веткой.
-
Переключиться назад в ветку для реализации пользовательской истории и продолжить работать.
Предположим, вы работаете над проектом и уже имеете несколько коммитов.
Вы выбрали задачу #53 из какая-там-у-вас-система-отслеживания-задач.
Чтобы создать ветку и сразу переключиться на неё, можно выполнить команду git checkout
с параметром -b
:
$ git checkout -b iss53
Switched to a new branch "iss53"
Это то же самое, что и:
$ git branch iss53
$ git checkout iss53
Вы работаете над сайтом и делаете коммиты.
Это приводит к тому, что ветка iss53
движется вперёд, так как вы переключились на неё ранее (HEAD
указывает на неё).
$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
И тут вы получаете сообщение об обнаружении на сайте уязвимости, и эту уязвимость устранить нужно немедленно.
Благодаря Git вам не придётся ни пытаться реализовать исправление вместе с изменениями, которые вы сделали в ходе разработки iss53
, ни прилагать усилия для отката этих изменений и возвращения к исходному состоянию перед началом разработки исправления.
Всё, что вам нужно — переключиться на ветку master
.
Имейте в виду, что если рабочий каталог или индекс содержат незафиксированные изменения, конфликтующие с веткой, на которую вы хотите переключиться, то Git не позволит переключить ветки.
Лучше всего переключаться из чистого рабочего состояния проекта: все изменённые файлы добавить в индекс и сделать коммит.
Есть способы обойти это (припрятать изменения (stash) или добавить их в последний коммит (amend)), но об этом мы поговорим позже в разделе ch07-git-tools.asc главы 7.
Теперь предположим, что вы зафиксировали все свои изменения и можете переключиться на ветку master
:
$ git checkout master
Switched to branch 'master'
С этого момента ваш рабочий каталог имеет точно такой же вид, какой был перед началом работы над задачей #53, и вы можете сосредоточиться на работе над исправлением. Важно запомнить: когда вы переключаете ветки, Git возвращает состояние рабочего каталога к тому виду, какой он имел в момент последнего коммита в переключаемую ветку. Он добавляет, удаляет и изменяет файлы автоматически, чтобы состояние рабочего каталога соответствовало тому, когда был сделан последний коммит.
Теперь вы можете перейти к написанию исправления. Давайте создадим новую ветку, в которой реализуем исправление.
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'Fix broken email address'
[hotfix 1fb7853] Fix broken email address
1 file changed, 2 insertions(+)
Вы можете прогнать тесты, чтобы убедиться, что ваше уязвимость в самом деле исправлена.
И если это так — выполнить слияние ветки hotfix
с веткой master
для включения изменений в продукт.
Это делается командой git merge
:
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
Заметили фразу «fast-forward» в этом слиянии?
Git просто переместил указатель ветки вперёд, потому что коммит C4
, на который указывает слитая ветка hotfix
, был прямым потомком коммита C2
, на котором вы находились до этого.
Другими словами, если коммит сливается с тем, до которого можно добраться, двигаясь по истории вперёд, Git упрощает слияние, просто перенося указатель ветки вперёд, потому что в этом случае нет никаких разнонаправленных изменений, которые нужно было бы свести воедино.
Это называется «fast-forward».
Теперь ваши изменения включены в коммит, на который указывает ветка master
, и исправление можно внедрять.
После внедрения вашего архиважного исправления вы готовы вернуться к работе над тем, что были вынуждены отложить.
Но сначала нужно удалить ветку hotfix
, потому что она больше не нужна — ветка master
указывает на то же самое место.
Для удаления ветки выполните команду git branch
с параметром -d
:
$ git branch -d hotfix
Deleted branch hotfix (3a0874c).
Теперь вы можете переключиться обратно на ветку iss53
и продолжить работу над задачей #53:
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'Finish the new footer [issue 53]'
[iss53 ad82d7a] Finish the new footer [issue 53]
1 file changed, 1 insertion(+)
Стоит обратить внимание на то, что все изменения из ветки hotfix
не включены в вашу ветку iss53
.
Если их нужно включить, вы можете влить ветку master
в вашу ветку iss53
командой git merge master
, а можете отложить слияние этих изменений до завершения работы, и затем влить ветку iss53
в master
.
Предположим, вы решили, что работа по проблеме #53 закончена и её можно влить в ветку master
.
Для этого нужно выполнить слияние ветки iss53
точно так же, как вы делали это с веткой hotfix
ранее.
Всё, что нужно сделать — переключиться на ветку, в которую вы хотите включить изменения, и выполнить команду git merge
:
$ git checkout master
Switched to branch 'master'
$ git merge iss53
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
Результат этой операции отличается от результата слияния ветки hotfix
.
В данном случае процесс разработки ответвился в более ранней точке.
Так как коммит, на котором мы находимся, не является прямым родителем ветки, с которой мы выполняем слияние, Git придётся немного потрудиться.
В этом случае Git выполняет простое трёхстороннее слияние, используя последние коммиты объединяемых веток и общего для них родительского коммита.
Вместо того, чтобы просто передвинуть указатель ветки вперёд, Git создаёт новый результирующий снимок трёхстороннего слияния, а затем автоматически делает коммит. Этот особый коммит называют коммитом слияния, так как у него более одного предка.
Теперь, когда изменения слиты, ветка iss53
больше не нужна.
Вы можете закрыть задачу в системе отслеживания ошибок и удалить ветку:
$ 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
:
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
Всё, где есть неразрешённые конфликты слияния, перечисляется как неслитое. В конфликтующие файлы Git добавляет специальные маркеры конфликтов, чтобы вы могли исправить их вручную. В вашем файле появился раздел, выглядящий примерно так:
<<<<<<< HEAD:index.html
<div id="footer">contact : [email protected]</div>
=======
<div id="footer">
please contact us at [email protected]
</div>
>>>>>>> iss53:index.html
Это означает, что версия из HEAD
(вашей ветки master
, поскольку именно её вы извлекли перед запуском команды слияния) — это верхняя часть блока (всё, что над =======
), а версия из вашей ветки iss53
представлена в нижней части.
Чтобы разрешить конфликт, придётся выбрать один из вариантов, либо объединить содержимое по-своему.
Например, вы можете разрешить конфликт, заменив весь блок следующим:
<div id="footer">
please contact us at [email protected]
</div>
В этом разрешении есть немного от каждой части, а строки <<<<<<<
, =======
и >>>>>>>
полностью удалены.
Разрешив каждый конфликт во всех файлах, запустите git add
для каждого файла, чтобы отметить конфликт как решённый.
Добавление файла в индекс означает для Git, что все конфликты в нём исправлены.
Если вы хотите использовать графический инструмент для разрешения конфликтов, можно запустить git mergetool
, который проведёт вас по всем конфликтам:
$ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html
Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (opendiff):
Если вы хотите использовать инструмент слияния не по умолчанию (в данном случае Git выбрал opendiff
, поскольку команда запускалась на Mac), список всех поддерживаемых инструментов представлен вверху после фразы «one of the following tools».
Просто введите название инструмента, который хотите использовать.
Note
|
Мы рассмотрим более продвинутые инструменты для разрешения сложных конфликтов слияния в разделе ch07-git-tools.asc главы 7. |
После выхода из инструмента слияния Git спросит об успешности процесса.
Если вы ответите скрипту утвердительно, то он добавит файл в индекс, чтобы отметить его как разрешённый.
Теперь можно снова запустить git status
, чтобы убедиться в отсутствии конфликтов:
$ git status
On branch master
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: index.html
Если это вас устраивает и вы убедились, что все файлы, где были конфликты, добавлены в индекс — выполните команду git commit
для создания коммита слияния.
Комментарий к коммиту слияния по умолчанию выглядит примерно так:
Merge branch 'iss53'
Conflicts:
index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: index.html
#
Если вы считаете, что коммит слияния требует дополнительных пояснений — опишите как были разрешены конфликты и почему были применены именно такие изменения, если это не очевидно.