3.1 Ветвление в Git - О ветвлении в двух словах #
Почти каждая система контроля версий (СКВ) в какой-то форме поддерживает ветвление. Используя ветвление, Вы отклоняетесь от основной линии разработки и продолжаете работу независимо от неё, не вмешиваясь в основную линию. Во многих СКВ создание веток — это очень затратный процесс, часто требующий создания новой копии каталога с исходным кодом, что может занять много времени для большого проекта.
Некоторые люди, говоря о модели ветвления Git, называют ее «киллер-фича», что выгодно выделяет Git на фоне остальных СКВ. Что в ней такого особенного? Ветвление Git очень легковесно: операция создания ветки выполняется почти мгновенно, переключение между ветками туда-сюда, обычно, также быстро. В отличие от многих других СКВ, Git поощряет процесс работы, при котором ветвление и слияние выполняется часто, даже по несколько раз в день. Понимание и владение этой функциональностью дает вам уникальный и мощный инструмент, который может полностью изменить привычный процесс разработки.
О ветвлении в двух словах #
Для точного понимания механизма ветвлений, необходимо вернуться назад и изучить то, как Git хранит данные.
Как вы можете помнить из Что такое Git?, Git не хранит данные в виде последовательности изменений, он использует набор снимков (snapshot).
Когда вы делаете коммит, Git сохраняет его в виде объекта, который содержит указатель на снимок (snapshot) подготовленных данных. Этот объект так же содержит имя автора и email, сообщение и указатель на коммит или коммиты непосредственно предшествующие данному (его родителей): отсутствие родителя для первоначального коммита, один родитель для обычного коммита, и несколько родителей для результатов слияния двух и более веток.
Предположим, у вас есть каталог с тремя файлами и вы добавляете их все в индекс и создаёте коммит. Во время индексации вычисляется контрольная сумма каждого файла (SHA-1 как мы узнали из Что такое Git?), затем каждый файл сохраняется в репозиторий (Git называет такой файл блоб — большой бинарный объект), а контрольная сумма попадёт в индекс:
$ git add README test.rb LICENSE
$ git commit -m 'Initial commit'
Когда вы создаёте коммит командой git commit, Git вычисляет контрольные суммы каждого подкаталога (в нашем случае, только основной каталог проекта) и сохраняет его в репозитории как объект дерева каталогов. Затем Git создаёт объект коммита с метаданными и указателем на основное дерево проекта для возможности воссоздать этот снимок в случае необходимости.
Ваш репозиторий Git теперь хранит пять объектов: три блоб объекта (по одному на каждый файл), объект дерева каталогов, содержащий список файлов и соответствующих им блобов, а так же объект коммита, содержащий метаданные и указатель на объект дерева каталогов.
Рисунок 9. Коммит и его дерево
Если вы сделаете изменения и создадите ещё один коммит, то он будет содержать указатель на предыдущий коммит.
Рисунок 10. Коммит и его родители
Ветка в Git — это простой перемещаемый указатель на один из таких коммитов. По умолчанию, имя основной ветки в Git — master. Как только вы начнёте создавать коммиты, ветка master будет всегда указывать на последний коммит. Каждый раз при создании коммита указатель ветки master будет передвигаться на следующий коммит автоматически.
Примечание Ветка «master» в Git — это не какая-то особенная ветка. Она точно такая же, как и все остальные ветки. Она существует почти во всех репозиториях только лишь потому, что её создаёт команда git init, а большинство людей не меняют её название.
Рисунок 11. Ветка и история коммитов
Создание новой ветки #
Что же на самом деле происходит при создании ветки? Всего лишь создаётся новый указатель для дальнейшего перемещения. Допустим вы хотите создать новую ветку с именем testing. Вы можете это сделать командой git branch :
$ git branch testing
В результате создаётся новый указатель на текущий коммит.
Рисунок 12. Две ветки указывают на одну и ту же последовательность коммитов
Как Git определяет, в какой ветке вы находитесь? Он хранит специальный указатель HEAD. Имейте ввиду, что в Git концепция HEAD значительно отличается от других систем контроля версий, которые вы могли использовать раньше (Subversion или CVS). В Git — это указатель на текущую локальную ветку. В нашем случае мы все еще находимся в ветке master. Команда git branch только создаёт новую ветку, но не переключает на неё.
Рисунок 13. HEAD указывает на ветку
Вы можете легко это увидеть при помощи простой команды git log, которая покажет вам куда указывают указатели веток. Эта опция называется --decorate.
$ git log --oneline --decorate
f30ab (HEAD -> master, testing) Add feature #32 - ability to add new formats to the central interface
34ac2 Fix bug #1328 - stack overflow under certain conditions
98ca9 Initial commit
Здесь можно увидеть указывающие на коммит f30ab ветки: master и testing.
Переключение веток #
Для переключения на существующую ветку выполните команду git checkout. Давайте переключимся на ветку testing:
$ git checkout testing
В результате указатель HEAD переместится на ветку testing.
Рисунок 14. HEAD указывает на текущую ветку
Какой в этом смысл? Давайте сделаем ещё один коммит:
$ vim test.rb
$ git commit -a -m 'made a change'
Рисунок 15. Указатель на ветку HEAD переместился вперёд после коммита
Интересная ситуация: указатель на ветку testing переместился вперёд, а master указывает на тот же коммит, где вы были до переключения веток командой git checkout. Давайте переключимся назад на ветку master:
$ git checkout master
Примечание git log не показывает все ветки по умолчанию
Если выполнить команду git log прямо сейчас, то в её выводе только что созданная ветка «testing» фигурировать не будет.
Ветка никуда не исчезла; просто Git не знает, что именно она вас интересует, и выводит наиболее полезную по его мнению информацию. Другими словами, по умолчанию git log отобразит историю коммитов только для текущей ветки.
Для просмотра истории коммитов другой ветки необходимо явно указать её имя: git log testing Чтобы посмотреть историю по всем веткам — выполните команду с дополнительным флагом: git log --all.
Рисунок 16. HEAD перемещается когда вы делаете checkout
Эта команда сделала две вещи: переместила указатель HEAD назад на ветку master и вернула файлы в рабочем каталоге в то состояние, на снимок которого указывает master. Это также означает, что все вносимые с этого момента изменения будут относиться к старой версии проекта. Другими словами, вы откатили все изменения ветки testing и можете продолжать в другом направлении.
Примечание Переключение веток меняет файлы в рабочем каталоге
Важно запомнить, что при переключении веток в Git происходит изменение файлов в рабочем каталоге. Если вы переключаетесь на старую ветку, то рабочий каталог будет выглядеть так же, как выглядел на момент последнего коммита в ту ветку. Если Git по каким-то причинам не может этого сделать — он не позволит вам переключиться вообще.
Давайте сделаем еще несколько изменений и создадим очередной коммит:
$ vim test.rb
$ git commit -a -m 'made other changes'
Теперь история вашего проекта разошлась (см Разветвлённая история). Вы создали ветку и переключились на нее, поработали, а затем вернулись в основную ветку и поработали в ней. Эти изменения изолированы друг от друга: вы можете свободно переключаться туда и обратно, а когда понадобится — объединить их. И все это делается простыми командами: branch, checkout и commit.
Рисунок 17. Разветвлённая история
Все описанные действия можно визуализировать с помощью команды git log. Для отображения истории коммитов, текущего положения указателей веток и истории ветвления выполните команду git log --oneline --decorate --graph --all.
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Made other changes
| * 87ab2 (testing) Made a change
|/
* f30ab Add feature #32 - ability to add new formats to the central interface
* 34ac2 Fix bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
Ветка в Git — это простой файл, содержащий 40 символов контрольной суммы SHA-1 коммита, на который она указывает; поэтому операции с ветками являются дешёвыми с точки зрения потребления ресурсов или времени. Создание новой ветки в Git происходит так же быстро и просто как запись 41 байта в файл (40 знаков и перевод строки).
Это принципиально отличает процесс ветвления в Git от более старых систем контроля версий, где все файлы проекта копируются в другой подкаталог. В зависимости от размера проекта, операции ветвления в таких системах могут занимать секунды или даже минуты, когда в Git эти операции мгновенны. Поскольку при коммите мы сохраняем указатель на родительский коммит, то поиск подходящей базы для слияния веток делается автоматически и, в большинстве случаев, очень прост. Эти возможности побуждают разработчиков чаще создавать и использовать ветки.
Давайте посмотрим, почему и вам имеет смысл делать так же.
Примечание Одновременное создание новой ветки и переключение на неё
Как правило, при создании новой ветки вы хотите сразу на неё переключиться — это можно сделать используя команду git checkout -b <newbranchname>. Примечание Начиная с Git версии 2.23, вы можете использовать git switch вместо git checkout, чтобы:
- Переключиться на существующую ветку: git switch testing-branch.
- Создать новую ветку и переключиться на нее: git switch -c new-branch. Флаг -c означает создание, но также можно использовать полный формат: --create.
- Вернуться к предыдущей извлечённой ветке: git switch -.
3.2 Ветвление в Git - Основы ветвления и слияния #
Основы ветвления и слияния #
Давайте рассмотрим простой пример рабочего процесса, который может быть полезен в вашем проекте. Ваша работа построена так:
- Вы работаете над сайтом.
- Вы создаете ветку для новой статьи, которую вы пишете.
- Вы работаете в этой ветке.
В этот момент вы получаете сообщение, что обнаружена критическая ошибка, требующая скорейшего исправления. Ваши действия:
- Переключиться на основную ветку.
- Создать ветку для добавления исправления.
- После тестирования слить ветку содержащую исправление с основной веткой.
- Переключиться назад в ту ветку, где вы пишете статью и продолжить работать.
Основы ветвления #
Предположим, вы работаете над проектом и уже имеете несколько коммитов.
Рисунок 18. Простая история коммитов
Вы решаете, что теперь вы будете заниматься проблемой #53 из вашей системы отслеживания ошибок. Чтобы создать ветку и сразу переключиться на нее, можно выполнить команду git checkout с параметром -b:
$ git checkout -b iss53
Switched to a new branch "iss53"
Это то же самое что и:
$ git branch iss53
$ git checkout iss53
Рисунок 19. Создание нового указателя ветки
Вы работаете над своим сайтом и делаете коммиты. Это приводит к тому, что ветка iss53 движется вперед, так как вы переключились на нее ранее (HEAD указывает на нее).
$ vim index.html
$ git commit -a -m 'Create new footer [issue 53]'
Рисунок 20. Ветка iss53 двигается вперед
Тут вы получаете сообщение об обнаружении уязвимости на вашем сайте, которую нужно немедленно устранить. Благодаря Git, не требуется размещать это исправление вместе с тем, что вы сделали в iss53. Вам даже не придется прилагать усилий, чтобы откатить все эти изменения для начала работы над исправлением. Все, что вам нужно — переключиться на ветку master.
Но перед тем как сделать это — имейте в виду, что если рабочий каталог либо индекс содержат незафиксированные изменения, конфликтующие с веткой, на которую вы хотите переключиться, то Git не позволит переключить ветки. Лучше всего переключаться из чистого рабочего состояния проекта. Есть способы обойти это (припрятать изменения (stash) или добавить их в последний коммит (amend)), но об этом мы поговорим позже в разделе Припрятывание и очистка главы 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(+)
Рисунок 21. Ветка hotfix основана на ветке master
Вы можете прогнать тесты, чтобы убедиться, что ваше исправление делает именно то, что нужно. И если это так — выполнить слияние ветки 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, и исправление можно внедрять.
Рисунок 22. master перемотан до hotfix
После внедрения вашего архиважного исправления, вы готовы вернуться к работе над тем, что были вынуждены отложить. Но сначала нужно удалить ветку 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(+)
Рисунок 23. Продолжение работы над iss53
Стоит обратить внимание на то, что все изменения из ветки 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 выполняет простое трёхстороннее слияние, используя последние коммиты объединяемых веток и общего для них родительского коммита.
Рисунок 24. Использование трёх снимков при слиянии
Вместо того, чтобы просто передвинуть указатель ветки вперёд, Git создаёт новый результирующий снимок трёхстороннего слияния, а затем автоматически делает коммит. Этот особый коммит называют коммитом слияния, так как у него более одного предка.
Рисунок 25. Коммит слияния
Теперь, когда изменения слиты, ветка 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.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
Это означает, что версия из HEAD (вашей ветки master, поскольку именно её вы извлекли перед запуском команды слияния) — это верхняя часть блока (всё, что над =======), а версия из вашей ветки iss53 представлена в нижней части. Чтобы разрешить конфликт, придётся выбрать один из вариантов, либо объединить содержимое по-своему. Например, вы можете разрешить конфликт, заменив весь блок следующим:
<div id="footer">
please contact us at email.support@github.com
</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». Просто введите название инструмента, который хотите использовать.
Примечание Мы рассмотрим более продвинутые инструменты для разрешения сложных конфликтов слияния в разделе Продвинутое слияние главы 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
#
Если вы считаете, что коммит слияния требует дополнительных пояснений — опишите как были разрешены конфликты и почему были применены именно такие изменения, если это не очевидно.
3.3 Ветвление в Git - Управление ветками #
Управление ветками #
Теперь, когда вы уже попробовали создавать, объединять и удалять ветки, пора познакомиться с некоторыми инструментами для управления ветками, которые вам пригодятся, когда вы начнёте использовать ветки постоянно.
Команда git branch делает несколько больше, чем просто создаёт и удаляет ветки. При запуске без параметров, вы получите простой список имеющихся у вас веток:
$ git branch
iss53
* master
testing
Обратите внимание на символ *, стоящий перед веткой master: он указывает на ветку, на которой вы находитесь в настоящий момент (т. е. ветку, на которую указывает HEAD). Это означает, что если вы сейчас сделаете коммит, ветка master переместится вперёд в соответствии с вашими последними изменениями. Чтобы посмотреть последний коммит на каждой из веток, выполните команду git branch -v:
$ git branch -v
iss53 93b412c Fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 Add scott to the author list in the readme
Опции --merged и --no-merged могут отфильтровать этот список для вывода только тех веток, которые слиты или ещё не слиты в текущую ветку. Чтобы посмотреть те ветки, которые вы уже слили с текущей, можете выполнить команду git branch --merged:
$ git branch --merged
iss53
* master
Ветка iss53 присутствует в этом списке потому что вы ранее слили её в master. Те ветки из этого списка, перед которыми нет символа *, можно смело удалять командой git branch -d; наработки из этих веток уже включены в другую ветку, так что ничего не потеряется.
Чтобы увидеть все ветки, содержащие наработки, которые вы пока ещё не слили в текущую ветку, выполните команду git branch --no-merged:
$ git branch --no-merged
testing
Вы увидите оставшуюся ветку. Так как она содержит ещё не слитые наработки, попытка удалить её командой git branch -d приведёт к ошибке:
$ git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
Если вы действительно хотите удалить ветку вместе со всеми наработками, используйте опцию -D, как указано в подсказке.
ПодсказкаЕсли в качестве аргумента не указан коммит или ветка, то опции --merged и --no-merged покажут что уже слито или не слито с вашей текущей веткой соответственно.
Вы всегда можете указать дополнительный аргумент для вывода той же информации, но относительно указанной ветки предварительно не извлекая и не переходя на неё.
console<br>$ git checkout testing<br>$ git branch --no-merged master<br> topicA<br> featureB<br>
Переименование ветки #
ВниманиеНе переименовывайте ветки, которые всё ещё используются другими участниками. Не переименовывайте ветку в master/main/mainline, не прочитав раздел «Изменение имени главной ветки».
Предположим, у вас есть ветка с именем bad-branch-name, и вы хотите изменить её на corrected-branch-name, сохранив при этом всю историю. Вместе с этим, вы также хотите изменить имя ветки на удалённом сервере (GitHub, GitLab или другой сервер). Как это сделать?
Переименуйте ветку локально с помощью команды git branch --move:
$ git branch --move bad-branch-name corrected-branch-name
Ветка bad-branch-name будет переименована в corrected-branch-name, но это изменение пока только локальное. Чтобы все остальные увидели исправленную ветку в удалённом репозитории, отправьте её туда:
$ git push --set-upstream origin corrected-branch-name
Теперь проверим, где мы сейчас находимся:
$ git branch --all
* corrected-branch-name
main
remotes/origin/bad-branch-name
remotes/origin/corrected-branch-name
remotes/origin/main
Обратите внимание, что текущая ветка corrected-branch-name, которая также присутствует и на удалённом сервере. Однако, старая ветка всё ещё по-прежнему там, но её можно удалить с помощью команды:
$ git push origin --delete bad-branch-name
Теперь старое имя ветки полностью заменено исправленным.
Изменение имени главной ветки #
Предупреждение Изменение имени ветки, например master/main/mainline/default, сломает интеграции, службы, вспомогательные утилиты и скрипты сборки, которые использует ваш репозиторий. Прежде чем сделать это, обязательно проконсультируйтесь с коллегами. Также убедитесь, что вы выполнили тщательный поиск в своём репозитории и обновили все ссылки на старое имя ветки в вашем коде или скриптах.
Переименуйте локальную ветку master в main с помощью следующей команды:
$ git branch --move master main
После этого, локальной ветки master больше не существует, потому что она была переименована в ветку main.
Чтобы все остальные могли видеть новую ветку main, вам нужно отправить её в общий репозиторий. Это делает переименованную ветку доступной в удалённом репозитории.
$ git push --set-upstream origin main
В итоге, состояние репозитория становится следующим:
git branch --all
* main
remotes/origin/HEAD -> origin/master
remotes/origin/main
remotes/origin/master
Ваша локальная ветка master исчезла, так как она заменена веткой main. Ветка main доступна в удалённом репозитории. Старая ветка master всё ещё присутствует в удалённом репозитории. Остальные участники будут продолжать использовать ветку master в качестве основы для своей работы, пока вы не совершите ряд дополнительных действий.
Теперь, для завершения перехода на новую ветку перед вами стоят следующие задачи:
- Все проекты, которые зависят от текущего, должны будут обновить свой код и/или конфигурацию.
- Обновите конфигурацию всех запускаемых тестов.
- Исправьте скрипты сборки и публикации артефактов.
- Поправьте настройки репозитория на сервере: задайте новую ветку по умолчанию, обновите правила слияния, а также прочие настройки, которые зависят от имени веток.
- Обновите документацию, исправив ссылки, указывающие на старую ветку.
- Слейте или отмените запросы на слияние изменений, нацеленные на старую ветку.
После того, как вы выполнили все эти задачи и уверены, что ветка main работает так же, как ветка master, вы можете удалить ветку master:
$ git push origin --delete master
3.4 Ветвление в Git - Работа с ветками #
Работа с ветками #
Теперь, когда вы познакомились с основами ветвления и слияния, возникает вопрос: что можно или нужно делать с этим? В этом разделе мы разберём некоторые основные рабочие процессы, ставшие возможными благодаря облегчённой процедуре ветвления, которые вы возможно захотите применить в собственном цикле разработки.
Долгоживущие ветки #
Так как в Git применяется простое трёхстороннее слияние, ничто не мешает многократно объединять ветки в течение длительного времени. Это значит, что у вас может быть несколько постоянно открытых веток, которые вы используете для разных этапов вашего цикла разработки; вы можете регулярно сливать изменения из одной ветки в другую.
Многие разработчики, использующие Git, придерживаются именно такого подхода, оставляя полностью стабильный код только в ветке master — возможно, только тот код, который был или будет выпущен. При этом существует и параллельная ветка с именем develop или next, предназначенная для работы и тестирования стабильности; она не обязательно должна быть всегда стабильной, но при достижении стабильного состояния ее содержимое можно слить в ветку master. Она используется для слияния завершённых задач из тематических веток (временных веток наподобие iss53), чтобы гарантировать, что эти задачи проходят тестирование и не вносят ошибок.
По сути, мы говорим про указатели, перемещающиеся по линии создаваемых вами коммитов. Стабильные ветки находятся в нижнем конце истории коммитов, а самые свежие наработки — ближе к её верхней части
Рисунок 26. Линейное представление повышения стабильности веток
В общем случае это можно представить в виде накопителей, в которых наборы коммитов перемещаются на более стабильный уровень только после полного тестирования.
Рисунок 27. Представление диаграммы стабильности веток в виде многоуровневого накопителя
Число уровней стабильности можно увеличить. В крупных проектах зачастую появляется ветка proposed или pu (предлагаемые обновления), объединяющая ветки с содержимым, которое ещё не готово к включению в ветки next или master. Идея состоит в том, что каждая ветка представляет собой определённый уровень стабильности; как только он повышается, содержимое сливается в ветку уровнем выше. Разумеется, можно вообще обойтись без долгоживущих веток, но зачастую они имеют смысл, особенно при работе над большими и сложными проектами.
Тематические ветки #
А вот такая вещь, как тематические ветки, полезна вне зависимости от величины проекта. Тематической веткой называется временная ветка, создаваемая и используемая для работы над конкретной функциональной возможностью или решения сопутствующих задач. Скорее всего, при работе с другими СКВ вы никогда ничего подобного не делали, так как там создание и слияние веток — затратные операции. Но в Git это обычное дело — много раз в день создавать ветки, работать с ними, сливать их и удалять.
Пример тематических веток вы видели в предыдущем разделе, когда мы создавали ветки iss53 и hotfix. В каждой из них было создано несколько коммитов, после чего, сразу же после слияния с основной веткой, они были удалены. Такая техника позволяет быстро и полностью осуществлять переключения контекста — так как работа разделена по уровням и все изменения в конкретной ветке относятся к определённой теме, что позволяет легко увидеть что именно было сделано во время процедуры просмотра кода или аналогичной. Ветки с внесёнными в них изменениями можно хранить минуты, дни или даже месяцы, а слияние выполнить только когда это действительно потребуется, вне зависимости от порядка их создания.
Предположим, мы работаем в ветке master, ответвляемся для решения попутной проблемы (iss91), некоторое время занимаемся ею, затем создаём ветку, чтобы попробовать решить эту задачу другим способом (iss91v2), возвращаемся в ветку master и выполняем там некие действия, затем создаём новую ветку для изменений, в результате которых не уверены (ветка dumbidea). Результирующая история коммитов будет выглядеть примерно так:
Рисунок 28. Набор тематических веток
Предположим, вам больше нравится второй вариант решения задачи (iss91v2), а ветку dumbidea вы показали коллегам, и оказалось, что там содержится гениальная идея. Фактически вы можете удалить ветку iss91 (потеряв коммиты C5 и C6) и слить две другие ветки. После этого история будет выглядеть так:
Рисунок 29. История после слияния веток dumbidea и iss91v2
Более подробно возможные варианты рабочих схем для проектов рассматриваются в главе Распределенный Git, поэтому перед выбором схемы обязательно прочитайте эту главу.
Важно помнить, что во время всех этих манипуляций ветки полностью локальны. Ветвления и слияния выполняются только в вашем Git репозитории — связь с сервером не требуется.
3.5 Ветвление в Git - Удалённые ветки #
Удалённые ветки #
Удалённые ссылки — это ссылки (указатели) в ваших удалённых репозиториях, включая ветки, теги и так далее. Полный список удалённых ссылок можно получить с помощью команды git ls-remote <remote> или команды git remote show <remote> для получения удалённых веток и дополнительной информации. Тем не менее, более распространенным способом является использование веток слежения.
Ветки слежения — это ссылки на определённое состояние удалённых веток. Это локальные ветки, которые нельзя перемещать; Git перемещает их автоматически при любой коммуникации с удаленным репозиторием, чтобы гарантировать точное соответствие с ним. Представляйте их как закладки для напоминания о том, где ветки в удалённых репозиториях находились во время последнего подключения к ним.
Имена веток слежения имеют вид <remote>/<branch>. Например, если вы хотите посмотреть, как выглядела ветка master на сервере origin во время последнего соединения с ним, используйте ветку origin/master. Если вы с коллегой работали над одной задачей и он отправил на сервер ветку iss53, при том что у вас может быть своя локальная ветка iss53, удалённая ветка будет представлена веткой слежения с именем origin/iss53.
Возможно, всё это сбивает с толку, поэтому давайте рассмотрим на примере. Скажем, у вас в сети есть свой Git-сервер с адресом git.ourcompany.com. Если вы с него что-то клонируете, команда clone автоматически назовёт его origin, заберёт оттуда все данные, создаст указатель на то, на что там указывает ветка master, и назовёт его локально origin/master. Git также создаст вам локальную ветку master, которая будет начинаться там же, где и ветка master в origin, так что вам будет с чего начать.
Примечание «origin» — это не специальное название
Подобно названию ветки «master», «origin» не имеет какого-либо специального значения в Git. В то время как «master» — это название по умолчанию для ветки при выполнении git init только потому, что часто используется, «origin» — это название по умолчанию для удалённого сервера, когда вы запускаете git clone. Если вы выполните git clone -o booyah, то по умолчанию ветка слежения будет иметь вид booyah/master.
Рисунок 30. Серверный и локальный репозитории после клонирования
Если вы сделаете что-то в своей локальной ветке master, а тем временем кто-то отправит изменения на сервер git.ourcompany.com и обновит там ветку master, то ваши истории продолжатся по-разному. Пока вы не свяжетесь с сервером origin ваш указатель origin/master останется на месте.
Рисунок 31. Локальная и удалённая работа может расходиться
Для синхронизации ваших изменений с удаленным сервером выполните команду git fetch <remote> (в нашем случае git fetch origin). Эта команда определяет какому серверу соответствует «origin» (в нашем случае это git.ourcompany.com), извлекает оттуда данные, которых у вас ещё нет, и обновляет локальную базу данных, сдвигая указатель origin/master на новую позицию.
Рисунок 32. git fetch обновляет ветки слежения
Чтобы продемонстрировать, как будут выглядеть удалённые ветки в ситуации с несколькими удалёнными серверами, предположим, что у вас есть ещё один внутренний Git-сервер, который используется для разработки только одной из ваших команд разработчиков. Этот сервер находится на git.team1.ourcompany.com. Вы можете добавить его в качестве новой удалённой ссылки для текущего проекта с помощью команды git remote add, как было описано в главе Основы Git. Назовите этот удалённый сервер teamone — это имя будет сокращением вместо полного URL.
Рисунок 33. Добавление ещё одного сервера в качестве удалённой ветки
Теперь вы можете выполнить команду git fetch teamone для получения всех изменений с сервера teamone, которых у вас нет локально. Так как в данный момент на этом сервере есть только те данные, что содержит сервер origin, Git ничего не получит, но создаст ветку слежения с именем teamone/master, которая будет указывать на тот же коммит, что и ветка master на сервере teamone.
Рисунок 34. Ветка слежения teamone/master
Отправка изменений #
Когда вы хотите поделиться веткой, вам необходимо отправить её на удалённый сервер, где у вас есть права на запись. Ваши локальные ветки автоматически не синхронизируются с удалёнными при отправке — вам нужно явно указать те ветки, которые вы хотите отправить. Таким образом, вы можете использовать свои личные ветки для работы, которую не хотите показывать, а отправлять только те тематические ветки, над которыми вы хотите работать с кем-то совместно.
Если у вас есть ветка serverfix, над которой вы хотите работать с кем-то ещё, вы можете отправить её точно так же, как вы отправляли вашу первую ветку. Выполните команду git push <remote> <branch>:
$ git push origin serverfix
Counting objects: 24, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (24/24), 1.91 KiB | 0 bytes/s, done.
Total 24 (delta 2), reused 0 (delta 0)
To https://github.com/schacon/simplegit
* [new branch] serverfix -> serverfix
Это в некотором роде сокращение. Git автоматически разворачивает имя ветки serverfix до refs/heads/serverfix:refs/heads/serverfix, что означает «возьми мою локальную ветку serverfix и обнови ей удалённую ветку serverfix». Мы подробно рассмотрим часть с refs/heads/ в главе Git изнутри, но обычно её можно пропустить. Вы также можете выполнить git push origin serverfix:serverfix — произойдёт то же самое — здесь говорится «возьми мою ветку serverfix и сделай её удалённой веткой serverfix». Можно использовать этот формат для отправки локальной ветки в удалённую ветку с другим именем. Если вы не хотите, чтобы на удалённом сервере ветка называлась serverfix, то вместо предыдущей команды выполните git push origin serverfix:awesomebranch, которая отправит локальную ветку serverfix в ветку awesomebranch удалённого репозитория.
Примечание Не вводите каждый раз свой пароль
Если вы используете HTTPS URL для отправки изменений, Git-сервер будет спрашивать имя пользователя и пароль для аутентификации. По умолчанию вам будет предложено ввести эти данные в терминале, чтобы сервер мог определить разрешена ли вам отправка изменений.
Если вы не хотите вводить свои данные каждый раз при отправке изменений, вы можете настроить «credential cache». Проще всего держать их в памяти несколько минут, это легко настроить с помощью команды git config --global credential.helper cache.
Для получения более подробной информации о различных вариантах кэша учётных данных обратитесь к разделу Хранилище учётных данных.
В следующий раз, когда один из ваших соавторов будет получать обновления с сервера, он получит ссылку на то, на что указывает serverfix на сервере, как удалённую ветку origin/serverfix:
$ git fetch origin
remote: Counting objects: 7, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/schacon/simplegit
* [new branch] serverfix -> origin/serverfix
Необходимо отметить, что при получении данных создаются ветки слежения, вы не получаете автоматически для них локальных редактируемых копий. Другими словами, в нашем случае вы не получите новую ветку serverfix — только указатель origin/serverfix, который вы не можете изменять.
Чтобы слить эти наработки в свою текущую рабочую ветку, выполните git merge origin/serverfix. Если вам нужна локальная ветка serverfix, в которой вы сможете работать, то вы можете создать её на основе ветки слежения:
$ git checkout -b serverfix origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
Это даст вам локальную ветку, в которой можно работать и которая будет начинаться там же, где и origin/serverfix.
Отслеживание веток #
Получение локальной ветки из удалённой ветки автоматически создаёт то, что называется «веткой слежения» (а ветка, за которой следит локальная называется «upstream branch»). Ветки слежения — это локальные ветки, которые напрямую связаны с удалённой веткой. Если, находясь на ветке слежения, выполнить git pull, то Git уже будет знать с какого сервера получать данные и какую ветку использовать для слияния.
При клонировании репозитория, как правило, автоматически создаётся ветка master, которая следит за origin/master. Однако, при желании вы можете настроить отслеживание и других веток — следить за ветками на других серверах или отключить слежение за веткой master. Вы только что видели простейший пример, что сделать это можно с помощью команды git checkout -b <branch> <remote>/<branch>. Это часто используемая команда, поэтому Git предоставляет сокращённую форму записи в виде флага --track:
$ git checkout --track origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
В действительности, это настолько распространённая команда, что существует сокращение для этого сокращения. Если вы пытаетесь извлечь ветку, которая не существует, но существует только одна удалённая ветка с точно таким же именем, то Git автоматически создаст ветку слежения:
$ git checkout serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Switched to a new branch 'serverfix'
Чтобы создать локальную ветку с именем, отличным от имени удалённой ветки, просто укажите другое имя:
$ git checkout -b sf origin/serverfix
Branch sf set up to track remote branch serverfix from origin.
Switched to a new branch 'sf'
Теперь ваша локальная ветка sf будет автоматически получать изменения из origin/serverfix.
Если у вас уже есть локальная ветка и вы хотите настроить ее на слежение за удалённой веткой, которую вы только что получили, или хотите изменить используемую upstream-ветку, то воспользуйтесь параметрами -u или --set-upstream-to для команды git branch, чтобы явно установить новое значение.
$ git branch -u origin/serverfix
Branch serverfix set up to track remote branch serverfix from origin.
Примечание Сокращение Upstream
Если у вас настроена отслеживаемая ветка, вы можете ссылаться на нее с помощью сокращений @{upstream} или @{u}. Итак, если вы находитесь на ветке master и она следит за origin/master, при желании вы можете использовать git merge @{u} вместо git merge origin/master.
Если вы хотите посмотреть как у вас настроены ветки слежения, воспользуйтесь опцией -vv для команды git branch. Это выведет список локальных веток и дополнительную информацию о том, какая из веток отслеживается, отстаёт, опережает или всё сразу относительно отслеживаемой.
$ git branch -vv
iss53 7e424c3 [origin/iss53: ahead 2] Add forgotten brackets
master 1ae2a45 [origin/master] Deploy index fix
* serverfix f8674d9 [teamone/server-fix-good: ahead 3, behind 1] This should do it
testing 5ea463a Try something new
Итак, здесь мы видим, что наша ветка iss53 следит за origin/iss53 и «опережает» её на два изменения — это значит, что у нас есть два локальных коммита, которые не отправлены на сервер. Мы также видим, что наша ветка master отслеживает ветку origin/master и находится в актуальном состоянии. Далее мы можем видеть, что локальная ветка serverfix следит за веткой server-fix-good на сервере teamone, опережает её на три коммита и отстает на один — это значит, что на сервере есть один коммит, который мы ещё не слили, и три локальных коммита, которые ещё не отправлены на сервер. В конце мы видим, что наша ветка testing не отслеживает удаленную ветку.
Важно отметить, что эти цифры описывают состояние на момент последнего получения данных с каждого из серверов. Эта команда не обращается к серверам, а лишь говорит вам о том, какая информация с этих серверов сохранена в локальном кэше. Если вы хотите иметь актуальную информацию об этих числах, вам необходимо получить данные со всех ваших удалённых серверов перед запуском команды. Сделать это можно вот так:
$ git fetch --all; git branch -vv
Получение изменений #
Команда git fetch получает с сервера все изменения, которых у вас ещё нет, но не будет изменять состояние вашей рабочей копии. Эта команда просто получает данные и позволяет вам самостоятельно сделать слияние. Тем не менее, существует команда git pull, которая в большинстве случаев является командой git fetch, за которой непосредственно следует команда git merge. Если у вас настроена ветка слежения как показано в предыдущем разделе, или она явно установлена, или она была создана автоматически командами clone или checkout, git pull определит сервер и ветку, за которыми следит ваша текущая ветка, получит данные с этого сервера и затем попытается слить удалённую ветку.
Обычно, лучше явно использовать команды fetch и merge, поскольку магия git pull может часто сбивать с толку.
Удаление веток на удалённом сервере #
Скажем, вы и ваши соавторы закончили с нововведением и слили его в ветку master на удалённом сервере (или в какую-то другую ветку, где хранится стабильный код). Вы можете удалить ветку на удалённом сервере используя параметр --delete для команды git push. Для удаления ветки serverfix на сервере, выполните следующую команду:
$ git push origin --delete serverfix
To https://github.com/schacon/simplegit
- [deleted] serverfix
Всё, что делает эта строка — удаляет указатель на сервере. Как правило, Git сервер хранит данные пока не запустится сборщик мусора, поэтому если ветка была удалена случайно, чаще всего её легко восстановить.
3.6 Ветвление в Git - Перебазирование #
Перебазирование #
В Git есть два способа внести изменения из одной ветки в другую: слияние и перебазирование. В этом разделе вы узнаете, что такое перебазирование, как его осуществлять и в каких случаях этот удивительный инструмент использовать не следует.
Простейшее перебазирование #
Если вы вернётесь к более раннему примеру из Основы слияния, вы увидите, что разделили свою работу и сделали коммиты в две разные ветки.
Рисунок 35. История коммитов простого разделения
Как мы выяснили ранее, простейший способ выполнить слияние двух веток — это команда merge. Она осуществляет трёхстороннее слияние между двумя последними снимками сливаемых веток (C3 и C4) и самого недавнего общего для этих веток родительского снимка (C2), создавая новый снимок (и коммит).
Рисунок 36. Слияние разделённой истории коммитов
Тем не менее есть и другой способ: вы можете взять те изменения, что были представлены в C4, и применить их поверх C3. В Git это называется перебазированием. С помощью команды rebase вы можете взять все коммиты из одной ветки и в том же порядке применить их к другой ветке.
В данном примере переключимся на ветку experiment и перебазируем её относительно ветки master следующим образом:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Это работает следующим образом: берётся общий родительский снимок двух веток (текущей, и той, поверх которой вы выполняете перебазирование), определяется дельта каждого коммита текущей ветки и сохраняется во временный файл, текущая ветка устанавливается на последний коммит ветки, поверх которой вы выполняете перебазирование, а затем по очереди применяются дельты из временных файлов.
Рисунок 37. Перебазирование изменений из C4 поверх C3
После этого вы можете переключиться обратно на ветку master и выполнить слияние перемоткой.
$ git checkout master
$ git merge experiment
Рисунок 38. Перемотка ветки master
Теперь снимок, на который указывает C4' абсолютно такой же, как тот, на который указывал C5 в примере с трёхсторонним слиянием. Нет абсолютно никакой разницы в конечном результате между двумя показанными примерами, но перебазирование делает историю коммитов чище. Если вы взглянете на историю перебазированной ветки, то увидите, что она выглядит абсолютно линейной: будто все операции были выполнены последовательно, даже если изначально они совершались параллельно.
Часто вы будете делать так для уверенности, что ваши коммиты могут быть бесконфликтно слиты в удалённую ветку — возможно, в проекте, куда вы пытаетесь внести вклад, но владельцем которого вы не являетесь. В этом случае вам следует работать в своей ветке и затем перебазировать вашу работу поверх origin/master, когда вы будете готовы отправить свои изменения в основной проект. Тогда владельцу проекта не придётся делать никакой лишней работы — всё решится простой перемоткой или бесконфликтным слиянием.
Учтите, что снимок, на который ссылается ваш последний коммит — является ли он последним коммитом после перебазирования или коммитом слияния после слияния — в обоих случаях это один и тот же снимок, отличаются только истории коммитов. Перебазирование повторяет изменения из одной ветки поверх другой в том порядке, в котором эти изменения были сделаны, в то время как слияние берет две конечные точки и сливает их вместе.
Более интересные перемещения #
Также возможно сделать так, чтобы при перебазировании воспроизведение коммитов применялось к совершенно другой ветке. Для примера возьмём История разработки с тематической веткой, ответвлённой от другой тематической ветки. Вы создаёте тематическую ветку server, чтобы добавить в проект некоторую функциональность для серверной части, и делаете коммит. Затем вы выполнили ответвление, чтобы сделать изменения для клиентской части, и создали несколько коммитов. Наконец, вы вернулись на ветку server и сделали ещё несколько коммитов.
Рисунок 39. История разработки с тематической веткой, ответвлённой от другой тематической ветки
Предположим, вы решили, что хотите внести изменения клиентской части в основную линию разработки для релиза, но при этом не хотите добавлять изменения серверной части до полного тестирования. Вы можете взять изменения из ветки client, которых нет в server (C8 и C9), и применить их на ветке master при помощи опции --onto команды git rebase:
$ git rebase --onto master server client
В этой команде говорится: «Переключись на ветку client, найди изменения относительно ветки server и примени их для ветки master». Несмотря на некоторую сложность этого способа, результат впечатляет.
Рисунок 40. Перемещение тематической ветки, ответвлённой от другой тематической ветки
Теперь вы можете выполнить перемотку (fast-forward) для ветки master (см Перемотка ветки master для добавления изменений из ветки client):
$ git checkout master
$ git merge client
Рисунок 41. Перемотка ветки master для добавления изменений из ветки client
Представим, что вы решили добавить наработки и из ветки server. Вы можете выполнить перебазирование ветки server относительно ветки master без предварительного переключения на неё при помощи команды git rebase <basebranch> <topicbranch>, которая извлечёт тематическую ветку (в данном случае server) и применит изменения в ней к базовой ветке (master):
$ git rebase master server
Это повторит работу, сделанную в ветке server поверх ветки master, как показано на рисунке:
Рисунок 42. Перебазирование ветки server на вершину ветки master
После чего вы сможете выполнить перемотку основной ветки (master):
$ git checkout master
$ git merge server
Теперь вы можете удалить ветки client и server, поскольку весь ваш прогресс уже интегрирован и тематические ветки больше не нужны, а полную историю вашего рабочего процесса отражает рисунок Окончательная история коммитов:
$ git branch -d client
$ git branch -d server
Рисунок 43. Окончательная история коммитов
Опасности перемещения #
Но даже перебазирование, при всех своих достоинствах, не лишено недостатков, которые можно выразить одной строчкой:
Не перемещайте коммиты, уже отправленные в публичный репозиторий
Если вы будете придерживаться этого правила, всё будет хорошо. Если не будете, люди возненавидят вас, а ваши друзья и семья будут вас презирать.
Когда вы что-то перемещаете, вы отменяете существующие коммиты и создаёте новые, похожие на старые, но являющиеся другими. Если вы куда-нибудь отправляете свои коммиты и другие люди забирают их себе и в дальнейшем основывают на них свою работу, а затем вы переделываете эти коммиты командой git rebase и выкладываете их снова, то ваши коллеги будут вынуждены заново выполнять слияние для своих наработок. В итоге, когда вы в очередной раз попытаетесь включить их работу в свою, вы получите путаницу.
Давайте рассмотрим пример того, как перемещение публично доступных наработок может вызвать проблемы. Предположим, вы клонировали репозиторий с сервера и сделали какую-то работу. И ваша история коммитов выглядит так:
Рисунок 44. Клонирование репозитория и выполнение в нём какой-то работы
Теперь кто-то другой внёс свои изменения, слил их и отправил на сервер. Вы стягиваете их к себе, включая новую удалённую ветку, что изменяет вашу историю следующим образом:
Рисунок 45. Извлекаем ещё коммиты и сливаем их со своей работой
Затем автор коммита слияния решает вернуться назад и перебазировать свою ветку; выполнив git push --force, он перезаписывает историю на сервере. При получении изменений с сервера вы получите и новые коммиты.
Рисунок 46. Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа
Теперь вы оба в неловком положении. Если вы выполните git pull, вы создадите коммит слияния, включающий обе линии истории, и ваш репозиторий будет выглядеть следующим образом:
Рисунок 47. Вы снова выполняете слияние для той же самой работы в новый коммит слияния
Если вы посмотрите git log в этот момент, вы увидите два коммита с одинаковыми авторами, датой и сообщением, что может сбить с толку. Помимо этого, если вы отправите свою историю на удалённый сервер в таком состоянии, вы вернёте все эти перебазированные коммиты на сервер, что ещё больше всех запутает. Логично предположить, что разработчик не хочет, чтобы C4 и C6 были в истории, и именно поэтому она перебазируется в первую очередь.
Меняя базу, меняй основание #
Если вы попали в такую ситуацию, у Git есть особая магия чтобы вам помочь. Если кто-то в вашей команде форсирует отправку изменений на сервер, переписывающих работу, на которых базировалась ваша работа, то ваша задача будет состоять в определении того, что именно было ваше, а что было переписано ими.
Оказывается, что помимо контрольной суммы коммита SHA-1, Git также вычисляет контрольную сумму отдельно для патча, входящего в этот коммит. Это контрольная сумма называется «patch-id».
Если вы скачаете перезаписанную историю и перебазируете её поверх новых коммитов вашего коллеги, в большинстве случаев Git успешно определит, какие именно изменения были внесены вами, и применит их поверх новой ветки.
К примеру, если в предыдущем сценарии вместо слияния в Кто-то выложил перебазированные коммиты, отменяя коммиты, на которых основывалась ваша работа мы выполним git rebase teamone/master, Git будет:
- Определять, какая работа уникальна для вашей ветки (C2, C3, C4, C6, C7)
- Определять, какие коммиты не были коммитами слияния (C2, C3, C4)
- Определять, что не было перезаписано в основной ветке (только C2 и C3, поскольку C4 — это тот же патч, что и C4')
- Применять эти коммиты к ветке teamone/master
Таким образом, вместо результата, который мы можем наблюдать на Вы снова выполняете слияние для той же самой работы в новый коммит слияния, у нас получилось бы что-то вроде Перемещение в начало force-pushed перемещённой работы.
Рисунок 48. Перемещение в начало force-pushed перемещённой работы
Это возможно, если C4 и C4' фактически являются одним и тем же патчем, который был сделан вашим коллегой. В противном случае rebase не сможет определить дубликат и создаст ещё один патч, подобный C4 (который с большой вероятностью не удастся применить чисто, поскольку в нём уже присутствуют некоторые изменения).
Вы можете это упростить, применив git pull --rebase вместо обычного git pull. Или сделать это вручную с помощью git fetch, а затем git rebase teamone/master.
Если вы используете git pull и хотите использовать --rebase по умолчанию, вы можете установить соответствующее значение конфигурации pull.rebase с помощью команды git config --global pull.rebase true.
Если вы рассматриваете перебазирование как способ наведения порядка и работаете с коммитами локально до их отправки или ваши коммиты никогда не будут доступны публично — у вас всё будет хорошо. Однако, если вы перемещаете коммиты, отправленные в публичный репозиторий, и есть вероятность, что работа некоторых людей основывается на этих коммитах, то ваши действия могут вызвать существенные проблемы, а вы — вызвать презрение вашей команды.
Если в какой-то момент вы или ваш коллега находите необходимость в этом, убедитесь, что все знают, как применять команду git pull --rebase для минимизации последствий от подобных действий.
Перемещение vs. Слияние #
Теперь, когда вы увидели перемещение и слияние в действии, вы можете задаться вопросом, что из них лучше. Прежде чем ответить на этот вопрос, давайте вернёмся немного назад и поговорим о том, что означает история.
Одна из точек зрения заключается в том, что история коммитов в вашем репозитории — это запись того, что на самом деле произошло. Это исторический документ, ценный сам по себе, и его нельзя подделывать. С этой точки зрения изменение истории коммитов практически кощунственно; вы лжёте о том, что на самом деле произошло. Но что, если произошла путаница в коммитах слияния? Если это случается, репозиторий должен сохранить это для потомков.
Противоположная точка зрения заключается в том, что история коммитов — это история того, как был сделан ваш проект. Вы не публикуете первый черновик книги или инструкции по поддержке вашего программного обеспечения, так как это нуждается в тщательном редактировании. Сторонники этого лагеря считают использование инструментов rebase и filter-branch способом рассказать историю проекта наилучшим образом для будущих читателей.
Теперь к вопросу о том, что лучше — слияние или перебазирование: надеюсь, вы видите, что это не так просто. Git — мощный инструмент, позволяющий вам делать многое с вашей историей, однако каждая команда и каждый проект индивидуален. Теперь, когда вы знаете, как работают оба эти приёма, выбор — какой из них будет лучше в вашей ситуации — зависит от вас.
При этом, вы можете взять лучшее от обоих миров: использовать перебазирование для наведения порядка в истории ваших локальных изменений, но никогда не применять его для уже отправленных куда-нибудь изменений.
3.7 Ветвление в Git - Заключение #
Заключение #
Мы рассмотрели базовые функции ветвления и слияния в Git. Вы должны быть способны свободно создавать и переключаться на новую ветку, переключаться между ветками и сливать локальные ветки вместе. Также вы должны уметь выкладывать ветки на общий сервер, работать с другими людьми над общими ветками и перебазировать ваши ветки до того, как они станут доступны другим разработчикам. Далее мы поговорим о том, что вам необходимо для запуска собственного сервера с хостингом для Git-репозитория.