Для тех, кто раньше работал только с CVS или CVS++ (ну то есть Subversion), концепция коммитов-слияний (merge) может оказаться не очень понятной. Так что я решил обратиться к классике для иллюстрации слияний.
Помните, Николай Васильевич Гоголь, "Женитьба"... Если кто позабыл, я напомню монолог Агафьи Тихоновны (полный текст см. тут: http://az.lib.ru/g/gogolx_n_w/text_0080.shtml).
Право, такое затруднение -- выбор! Если бы еще один,
два человека, а то четыре. Как хочешь, так и выбирай. Никанор Иванович
недурен, хотя, конечно, худощав; Иван Кузьмич тоже недурен. Да если сказать
правду. Иван Павлович тоже хоть и толст, а ведь очень видный мужчина. Прошу
покорно, как тут быть? Балтазар Балтазарыч опять мужчина с достоинствами. Уж
как трудно решиться, так просто рассказать нельзя, как трудно! Если бы губы
Никанора Ивановича да приставить к носу Ивана Кузьмича, да взять
сколько-нибудь развязности, какая у Балтазара Балтазарыча, да, пожалуй,
прибавить к этому еще дородности Ивана Павловича -- я бы тогда тотчас же
решилась. А теперь поди подумай! просто голова даже стала болеть.
Бедная Агафья Тихоновна. Ведь в то доисторическое время еще не было современных систем контроля версий, разве что CVS, который был придуман еще во времена динозавров. А ведь задача создания идеального жениха из лучших качеств четырех претендентов - типичная задача слияния!
В нижеприведенном примере я намеренно не буду использовать встроенные в git автоматические системы слияния, чтобы показать внутреннюю кухню. В жизни все будет гораздо проще.
Итак, начнем с создания репозитория и инициализации переменных окружения:
$ mkdir ~/tmp/gitguts5
$ cd ~/tmp/gitguts5
$ git init-init
Initialized empty Git repository in .git/
$ export GIT_AUTHOR_NAME="Git Guts"
$ export GIT_AUTHOR_EMAIL="gitguts@localhost"
$ export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
$ export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
Теперь создадим файл-заготовку, который мы будем использовать для заполнения наших деревьев - перечень человеческих достоинств, которые ценит Агафья Тихоновна:
$ echo -e "Губы\nНос\nРазвязность\nДородность" > virtues-template
$ cat virtues-template
Губы
Нос
Развязность
Дородность
Обращаю внимание что текст в файле virtues-template записан в системной кодировке. Для того, чтобы была воспроизводимость всех проделанных действий, перед занесением в git я буду переводить текст из системной кодировки в utf-8. На самом деле git не предъявляет никаких требований к кодировке, но тем не менее я бы рекомендовал держать коммиты либо в ASCII (то есть писать их по английски), либо в utf-8, если вы хотите чтобы ваши коммиты читал кто-нибудь вне России.
Итак, следующим этапом будет создание начального коммита. Его дерево будет состоять из одного файла - virtues, который будет аналогичен файлу virtues-template, только переведен в utf-8 для воспроизводимости. Почему начальный коммит должен быть именно таким - я объясню позже.
Итак, создание начального коммита (ничего нового для тех, кто внимательно читал предыдущие выпуски):
$ iconv -t utf-8 < virtues-template > virtues
$ git-hash-object -w virtues
111f008f40b32148b325098b0b3ad1fe46df0aef
$ echo -e "100644 blob 111f008f40b32148b325098b0b3ad1fe46df0aef\tvirtues" | git-mktree
f387e3ef43d001f614ef1a5a8c6ac4a0996c7c3c
$ echo "Обычный человек" | iconv -t utf-8 | faketime -t 200001010000 git-commit-tree f387e3ef43d001f614ef1a5a8c6ac4a0996c7c3c
6173ad1924d1221b82fe940e96eca4ec914b4b6c
Итак, у нас есть начальный коммит (без предков), с сообщением "Обычный человек". Зачем? Потому что именно так работает автоматическое слияние. Ему нужен "общий предок" всех сливаемых коммитов, чтобы понять, что у них общее, а что - различается.
Теперь давайте создадим коммиты, соответствующие женихам Агафьи Тихоновны: Никанор Иваныч, Иван Кузьмич, Балтазар Балтазарыч и Иван Павлович. Отличаться эти коммиты будут тем, что вместо
Губы
Нос
Развязность
Дородность
будет
Губы Никанора Иваныча
Нос Никанора Иваныча
Развязность Никанора Иваныча
Дородность Никанора Иваныча
Ну, вы надеюсь поняли. Добавлять "Никанора Иваныча" в конце каждой строчки мы будем с помощью простейшего скрипта на sed, вот иллюстрация:
$ sed 's/$/ Никанора Иваныча/' virtues-template
Губы Никанора Иваныча
Нос Никанора Иваныча
Развязность Никанора Иваныча
Дородность Никанора Иваныча
Итак, создадим эти четыре коммита:
Никанор Иваныч:
$ PARENT="6173ad1924d1221b82fe940e96eca4ec914b4b6c"
$ sed 's/$/ Никанора Иваныча/' virtues-template | iconv -t utf-8 > virtues-NI
$ git-hash-object -w virtues-NI
929db472b24b02eb991257c26376609e4da6966b
$ echo -e "100644 blob 929db472b24b02eb991257c26376609e4da6966b\tvirtues" | git-mktree
0ade4416fb17c0eb8037265a2e0405db102164eb
$ echo "Никанор Иваныч" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree 0ade4416fb17c0eb8037265a2e0405db102164eb -p $PARENT
f683f1e38e0339885c5ff31ed3efa6f5060c57b3
Иван Кузьмич:
$ sed 's/$/ Ивана Кузьмича/' virtues-template | iconv -t utf-8 > virtues-IK
$ git-hash-object -w virtues-IK
b4bd4d3eae566ac8d58a5a4dc8dccf06a8a8602c
$ echo -e "100644 blob b4bd4d3eae566ac8d58a5a4dc8dccf06a8a8602c\tvirtues" | git-mktree
f7509f166ee816355654e1fd8b21bfa616272d38
$ echo "Иван Кузьмич" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree f7509f166ee816355654e1fd8b21bfa616272d38 -p $PARENT
ff7a5afbdf16e8ade231e1adec6e9a44838c44d0
Балтазар Балтазарыч:
$ sed 's/$/ Балтазар Балтазарыча/' virtues-template | iconv -t utf-8 > virtues-BB
$ git-hash-object -w virtues-BB
66d2a243ba12d21ba95ce44e757681a4d4e05428
$ echo -e "100644 blob 66d2a243ba12d21ba95ce44e757681a4d4e05428\tvirtues" | git-mktree
f56b93f223725f10602f0c404114671ed04ad743
$ echo "Балтазар Балтазарыч" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree f56b93f223725f10602f0c404114671ed04ad743 -p $PARENT
c89d03e1e07c2a2fdb52bc85615bed628b4de202
Иван Павлович:
$ sed 's/$/ Ивана Павловича/' virtues-template | iconv -t utf-8 > virtues-IP
$ git-hash-object -w virtues-IP
9c9c6c6f479e13ce061e82863c17e3bc03ce8960
$ echo -e "100644 blob 9c9c6c6f479e13ce061e82863c17e3bc03ce8960\tvirtues" | git-mktree
3d2459538e8ff3809d557758649a5a9c9393c124
$ echo "Иван Павлович" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree 3d2459538e8ff3809d557758649a5a9c9393c124 -p $PARENT
2762e87bf446e3f886996d8e984b69a6204b4305
Дерево этих коммитов будет выглядеть в gitk примерно так:
gitk 2762e87bf446e3f886996d8e984b69a6204b4305\
c89d03e1e07c2a2fdb52bc85615bed628b4de202\
ff7a5afbdf16e8ade231e1adec6e9a44838c44d0\
f683f1e38e0339885c5ff31ed3efa6f5060c57b3
Каждый из женихов отличается от общего предка - "Обычного человека" персонализированным набором качеств.
Агафья Тихоновна хотела бы создать идеального жениха, скомбинировав эти персонализированные отличия. В этом нам поможет слияние.
В классическом случае операция слияния - это:
1. Формирование нового дерева, которое каким-то образом включает в себя изменения, произошедшие в сливаемых ветках со времени их общего предка.
2. Формирование нового коммита с этим деревом, в качестве предков которого указаны все сливаемые коммиты
Автоматическая система слияния git в многих случаях может сама "слить" ветки, без участия пользователя. Например, если изменения в сливаемых ветках затрагивают разные файлы, или один и тот же файл, но изменяемые строчки не пересекаются. Новое дерево в таком случае формируется автоматически.
В нашем же запущенном случае в каждом коммите-женихе все строчки изначального "Обычного человека" заменены - поэтому при слиянии получается конфликт. Например, чьи губы должны быть у результата слияния - Никанора Иваныча или Балтазара Балтазарыча? Или может Ивана Павловича?
В таких ситуациях единственное решение принять должен человек. В нашем случае - Агафья Тихоновна. Благодаря Гоголю Агафья уже разрешила все конфликты слияния, постановив, что у идеального жениха должно быть:
- Губы Никанора Иваныча
- Нос Ивана Кузьмича
- Развязность Балтазара Балтазарыча
- Дородность Ивана Павловича
Вот с таким вот идеальным деревом мы и создадим коммит-слияние:
$ echo "Губы Никанора Иваныча" > ideal-template
$ echo "Нос Ивана Кузьмича" >> ideal-template
$ echo "Развязность Балтазара Балтазарыча" >> ideal-template
$ echo "Дородность Ивана Павловича" >> ideal-template
$ cat ideal-template
Губы Никанора Иваныча
Нос Ивана Кузьмича
Развязность Балтазара Балтазарыча
Дородность Ивана Павловича
$ iconv -t utf-8 ideal
$ git-hash-object -w ideal
aaad89b8229eab40cde73cd3afe05cfb689f8a85
$ echo -e "100644 blob aaad89b8229eab40cde73cd3afe05cfb689f8a85\tvirtues" | git-mktree
3bb4ea25e93d5962d6a568330aea334161d55009
$ echo "Идеальный жених Агафьи Тихоновны" | iconv -t utf-8 | faketime -t 200001010200 git-commit-tree 3bb4ea25e93d5962d6a568330aea334161d55009\
-p 2762e87bf446e3f886996d8e984b69a6204b4305\
-p c89d03e1e07c2a2fdb52bc85615bed628b4de202\
-p ff7a5afbdf16e8ade231e1adec6e9a44838c44d0\
-p f683f1e38e0339885c5ff31ed3efa6f5060c57b3
31e839af8dbd1315ceaa9dbbcc2c2c71ff91d797
Как видно, от обычных коммитов с одним предком, коммит-слияние отличается лишь тем, что у него несколько предков, каждый указан как -p
Посмотрим же на результат в gitk:
gitk 31e839af8dbd1315ceaa9dbbcc2c2c71ff91d797Как видно, коммит-слияние в gitk графически отображается как соединение всех веток в одну точку. В классическом случае (без использования всяческих хаков или низкоуровневых команд), когда git видит коммит-слияние, он считает что все изменения, которые были в сливаемых ветках, в точке слияния были согласованы, и все конфликты поправлены.
Если в дальнейшем сливаемые ветки будут развиваться дальше по отдельности, то при очередном слиянии git будет считать коммит-слияние общим предком, и конфликтовать будут только изменения, произошедшие после коммита-слияния.
Итак, подведем итоги:
Коммит-слияние с технической точки зрения ненамного сложнее обычного коммита. Главной проблемой при слияниях является "Право, такое затруднение -- выбор!", говоря словами Агафьи Тихоновны. Во многих случаях этот выбор может сделать сам git, предоставляя несколько стратегий автоматического слияния. Но в сложных случаях без помощи человека в решении конфликтов не обойтись.
Обзор стратегий автоматического слияния я пожалуй оставлю на потом, а в следующем выпуске расскажу о текстовых ссылках (refs), которые значительно облегчают работу с git. Именно они, а не SHA1 имена объектов, используются для повседневной работы в git. Stay tuned!
Комментариев нет:
Отправить комментарий