2. Ветвление в git
Цель работы
Отработать базовые операции с ветками: создание, переключение между ветками, слияние (merge), перебазирование (rebase). Для выполнения вам предоставляется ряд подготовленных репозиториев. В них нужно выполнить слияние различными способами: формируя линейную или псевдолинейную историю, а также выполнить операции merge и rebase, устранив возникшие в процессе конфликты.
После выполнения работы ознакомьтесь с A simple git branching model.
Материал для подготовки к работе
Команды:
Настройка отображения конфликтов
Git позволяет настроить более подробное отображение конфликтов:
git config --global merge.conflictstyle diff3
По умолчанию конфликты отображаются в виде:
<<<<<<< HEAD
Левая ветка
=======
Правая ветка
>>>>>>> c2392943
В формате diff3 отображается также состояние общего предка сливаемых веток:
<<<<<<< HEAD
Левая ветка
||||||| merged common ancestors
Общий предок
=======
Правая ветка
>>>>>>> c2392943
Руководство
Часть 1: создание веток
Создать новый репозиторий
branches-basics
.В основной ветке
master
создать несколько коммитов.Создать новую ветку
develop
. Выполнить несколько коммитов.Вернуться на ветку
master
. Создать коммит.Внести изменения в рабочую копию репозитория, не коммитить. Переключиться на ветку
develop
Часть 2: merge и rebase
В этой части нужно выполнить слияние веток одним из предложенных способов.
Предварительно необходимо изучить историю каждой ветки репозитория и понять,
какие изменения были сделаны. В результате слияния вы должны получить
работоспособный исходный код, объединяющий в себе изменения, выполненные
в ветках main
и develop
.
Представьте, что вы работали в ветке develop
, а другой разработчик опередил
вас и влил свои изменения в main
раньше. В его и в вашей ветках рабочий
код. После слияния код должен остаться корректным, и работа, выполненная
в main
, не должна быть удалена, как и работа, выполненная в develop
.
Формального алгоритма разрешения конфликтов нет, нужно анализировать изменения и принимать решения, руководствуясь здравым смыслом.
Дополнительные коммиты с исправлениями после мержа или ребейза нежелательны, поскольку замусоривают историю.
В случае ошибки вы можете откатить ветку к ее исходному состоянию с помощью
команды git reset с опцией --hard
.
Склонируйте репозитории:
git clone https://github.com/branches-sandbox/merge-simple.git 1-merge-ff git clone https://github.com/branches-sandbox/merge-simple.git 2-merge-no-ff git clone https://github.com/branches-sandbox/merge-dirty.git 3-merge-dirty git clone https://github.com/branches-sandbox/no-conflict.git 4-rebase-no-conflict git clone https://github.com/branches-sandbox/conflict-easy.git 5-merge-conflict-easy git clone https://github.com/branches-sandbox/conflict-easy.git 6-rebase-conflict-easy git clone https://github.com/branches-sandbox/conflict-hard.git 7-merge-conflict-hard git clone https://github.com/branches-sandbox/conflict-hard.git 8-rebase-conflict-hard
В результате вы должны получить такой список каталогов:
$ ls -1 1-merge-ff 2-merge-no-ff 3-merge-dirty 4-rebase-no-conflict 5-merge-conflict-easy 6-rebase-conflict-easy 7-merge-conflict-hard 8-rebase-conflict-hard
1-merge-ff
Нужно выполнить простейшее слияние. История исходного репозитория:
* de873d1 (origin/develop, develop) Extract printing array to function
* 03b93ef Count array items with macro
* 5cbd705 (HEAD -> main, origin/main) Implement min_element function
* 04e3251 Initial commit
История после слияния:
* de873d1 (HEAD -> main, origin/develop, develop) Extract printing array to function
* 03b93ef Count array items with macro
* 5cbd705 (origin/main, origin/HEAD) Implement min_element function
* 04e3251 Initial commit
Мы получили историю, в которой у каждого коммита не более одного предка. Историю такого вида будем называть линейной.
2-merge-no-ff
Исходный репозиторий такой же, как и в задании 1-merge-ff
:
* de873d1 (origin/develop, develop) Extract printing array to function
* 03b93ef Count array items with macro
* 5cbd705 (HEAD -> main, origin/main) Implement min_element function
* 04e3251 Initial commit
Нужно выполнить слияние веток с опцией --no-ff
. Результат:
* a7f1782 (HEAD -> main) Merge branch 'develop' into main
|\
| * de873d1 (origin/develop, develop) Extract printing array to function
| * 03b93ef Count array items with macro
|/
* 5cbd705 (origin/main) Implement min_element function
* 04e3251 Initial commit
Мы получили историю, в которой:
У каждого коммита не более двух предков
Между началом ветки (
5cbd705
) и мерж-коммитом (a7f1782
) коммиты присутствуют только в правой ветке.
Историю такого вида будем называть псевдолинейной.
Преимущества псевдолинейной истории:
Если каждая задача решается в отдельной ветке, то при псевдолинейной истории очевидно, какая группа коммитов относится к задаче.
Появляется потенциальная возможность откатить задачу целиком. Для этого достаточно выполнить
revert
мерж-коммита.Мерж-коммит не содержит разрешения конфликтов, как это может быть при нелинейной истории.
Поскольку история остается линейной, легко искать проблемный коммит методом бинарного поиска (git bisect).
Недостатки:
Некоторые рабочие процессы крайне усложняют формирование псевдолинейной истории. Например, если параллельно разрабатывается несколько версий продукта или существуют долгоживущие ветки из десятков сотен коммитов. В этих случаях принимают другие соглашения по работе с репозиторием.
Если требуется выполнить вливание нескольких веток подряд, то разработчикам приходится договариваться об очередности вливания, иначе приходится многократно выполнять ребейз фиче-ветки на
main
.
По соглашениям курса в лабораторных работах и курсовом проекте следует формировать псевдолинейную историю.
3-merge-dirty
Исходная история:
* 899302d (develop, origin/develop) Extract printing array to function
* 9513c27 Count array items with macro
| * fe2320b (HEAD -> main, origin/main, origin/HEAD) Expand abbreviation 'min' in message
|/
* 5855f29 Implement min_element function
* 155841b Initial commit
Поскольку коммит fe2320b (main)
не является предком коммита
899302d (develop)
, в этой ситуации fast-forward merge невозможен.
Изучите историю изменений. Для этого воспользуйтесь коммантами git hist -p
или gitk --all
. Посмотрите изменения в каждом коммите и подумайте,
какое поведение должно быть у приложения после слияния веток.
Выполните слияние. В процессе у вас возникнет несложный конфликт. Если вы внимательно изучили историю, то вы заметите, что для разрешения конфликта недостаточно выбрать одну из веток, нужно объединять изменения вручную.
После слияния приложение должно выводить:
Array: 3 1 4 1 5 9 2
Minimum element: 1
Если у вас вывод отличается, выполните откат и повторите слияние.
Результат:
* 8edb092 (HEAD -> main) Merge branch 'develop' into main
|\
| * 899302d (origin/develop, develop) Extract printing array to function
| * 9513c27 Count array items with macro
* | fe2320b (origin/main, origin/HEAD) Expand abbreviation 'min' in message
|/
* 5855f29 Implement min_element function
* 155841b Initial commit
Мы получили историю, в которой:
У коммита может быть более одного предка.
Между началом ветки и мерж-коммитом как в левой, так и в правой ветке могут присутствовать другие коммиты.
Историю такого вида будем называть нелинейной.
Преимущества нелинейной истории:
Сравнительно легко формировать.
Недостатки:
Топология истории значительно усложняется.
Нарушается принцип «один коммит — одно изменение», поскольку мерж-коммит может содержать:
Изменения из левой ветки
Изменения из правой ветки
Разрешения конфликтов
Случайные изменения, внесенные в процессе разрешения конфликтов
В лабораторных работах и курсовом не следует формировать нелинейную историю.
4-rebase-no-conflict
В исходной истории fast-forward merge невозможен:
* f5275c7 (origin/develop, develop) Extract printing array to function
* 3314b69 Count array items with macro
| * 4e235e6 (HEAD -> main, origin/main, origin/HEAD) Remove unused header
|/
* 41653c8 Implement min_element function
* 7e36880 Initial commit
В этой ситуации нужно:
Переключиться на ветку
develop
.Выполнить ее rebase на
main
.Переключиться на ветку
main
.Выполнить merge.
В результате получим псевдолинейную историю:
* d3a9b2f (HEAD -> main) Merge branch 'develop' into main
|\
| * b3f6746 (develop) Extract printing array to function
| * 155a81a Count array items with macro
|/
* 4e235e6 (origin/main, origin/HEAD) Remove unused header
| * f5275c7 (origin/develop) Extract printing array to function
| * 3314b69 Count array items with macro
|/
* 41653c8 Implement min_element function
* 7e36880 Initial commit
В склонированном репозитории у вас нет прав на push. При работе со своими репозиториями ветку фиче-ветку можно удалять после мержа, тогда история примет вид:
* d3a9b2f (HEAD -> main, origin/main, origin/HEAD) Merge branch 'develop' into main
|\
| * b3f6746 Extract printing array to function
| * 155a81a Count array items with macro
|/
* 4e235e6 Remove unused header
* 41653c8 Implement min_element function
* 7e36880 Initial commit
Такой подход рекомендуется при выполнении лабораторных работ и курсового.
5-merge-conflict-easy
Нужно выполнить мерж и разрешить конфликты.
Исходная история:
* 330f3ad (origin/develop, develop) Separate elements with comma
* d9796db Count array items with macro
| * 7c1c684 (HEAD -> main, origin/main, origin/HEAD) Add more elements to array
|/
* 586e5bd Implement min_element function
* 9e8374a Initial commit
Результат (нелинейная история):
* 642abc8 (HEAD -> main) Merge branch 'develop' into main
|\
| * 330f3ad (origin/develop, develop) Separate elements with comma
| * d9796db Count array items with macro
* | 7c1c684 (origin/main, origin/HEAD) Add more elements to array
|/
* 586e5bd Implement min_element function
* 9e8374a Initial commit
Разультат работы приложения после мержа:
Array: 3, 1, 4, 1, 5, 9, 2, 6, 5, 3
Min element: 1
6-rebase-conflict-easy
Те же исходные данные, что и в 5-merge-conflict-easy
, но нужно сформировать
псевдолинейную историю.
Исходная история:
* 330f3ad (origin/develop, develop) Separate elements with comma
* d9796db Count array items with macro
| * 7c1c684 (HEAD -> main, origin/main, origin/HEAD) Add more elements to array
|/
* 586e5bd Implement min_element function
* 9e8374a Initial commit
Результат (псевдолинейная история):
* 41a27f4 (HEAD -> main) Merge branch 'develop' into main
|\
| * 495d413 (develop) Separate elements with comma
| * 4890b37 Count array items with macro
|/
* 7c1c684 (origin/main, origin/HEAD) Add more elements to array
| * 330f3ad (origin/develop) Separate elements with comma
| * d9796db Count array items with macro
|/
* 586e5bd Implement min_element function
* 9e8374a Initial commit
Повторим: после ребейза в истории не сохраняются разрешения конфликтов, это упрощает ее чтение.
7-merge-conflict-hard
Прочитайте и осознайте изменения в каждом коммите.
Прочитайте и осознайте изменения в каждом коммите. Вас предупредили дважды.
До сих пор конфликты разрешались относительно легко. В этом примере приходится вручную собирать код из конфликрующих фрагментов, полностью понимая мотивацию изменений.
Исходная история:
* 2a5b4e7 (origin/develop, develop) Extract printing array to function
* 9284a54 Count array items with macro
| * 85c6c1b (HEAD -> main, origin/main, origin/HEAD) Add more elements to array
|/
* 96568b0 Implement max_element function
* c61eb1d Initial commit
Результат (нелинейная история):
* b9f54e9 (HEAD -> main) Merge branch 'develop' into main
|\
| * 2a5b4e7 (origin/develop, develop) Extract printing array to function
| * 9284a54 Count array items with macro
* | 85c6c1b (origin/main, origin/HEAD) Add more elements to array
|/
* 96568b0 Implement max_element function
* c61eb1d Initial commit
Результат работы программы:
Array: 3 1 4 1 5 9 2 6 5 3
Max element: 9
8-rebase-conflict-hard
Исходные данные те же, что и в 7-merge-conflict-hard
, но теперь разрешать
конфликты придется дважды: при применении каждого коммита из ветки develop
.
Это также пример ситуации, в которой формирование псевдолинейной истории может
быть более трудоемким, чем простой мерж.
Исходная история:
* 2a5b4e7 (origin/develop, develop) Extract printing array to function
* 9284a54 Count array items with macro
| * 85c6c1b (HEAD -> main, origin/main, origin/HEAD) Add more elements to array
|/
* 96568b0 Implement max_element function
* c61eb1d Initial commit
Результат (псевдолинейная история):
* 74c74d1 (HEAD -> main) Merge branch 'develop' into main
|\
| * 150787b (develop) Extract printing array to function
| * 2a05362 Count array items with macro
|/
* 85c6c1b (origin/main, origin/HEAD) Add more elements to array
| * 2a5b4e7 (origin/develop) Extract printing array to function
| * 9284a54 Count array items with macro
|/
* 96568b0 Implement max_element function
* c61eb1d Initial commit
Результат работы программы:
Array: 3 1 4 1 5 9 2 6 5 3
Max element: 9
Заключение
В рамках нашего курса следует формировать псевдолинейную историю. Оптимизируйте результат своей работы для удобства читателя.
На практике возможны процессы, в которых трудозатраты на операции rebase+merge слишком высоки, поэтому на уровне соглашений принято создавать нелинейную историю.
Подробнее о соглашениях по работе с репозиторием можно почитать в A simple git branching model.
Контрольные вопросы
Что такое ветка?
Что такое
HEAD
?Способы создания веток.
Как узнать текущую ветку?
Как переключаться между ветками?
Что такое merge? Что такое fast-forward merge?
Что такое rebase? Как он работает?
Как можно перемещать метки?