los_t ([info]los_t) wrote,
@ 2008-01-20 19:48:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:git guts

Git Guts (Part 5)
Для тех, кто раньше работал только с 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

27,19 КБ

Каждый из женихов отличается от общего предка - "Обычного человека" персонализированным набором качеств.

Агафья Тихоновна хотела бы создать идеального жениха, скомбинировав эти персонализированные отличия. В этом нам поможет слияние.

В классическом случае операция слияния - это

  1. Формирование нового дерева, которое каким-то образом включает в себя изменения, произошедшие в сливаемых ветках со времени их общего предка.

  2. Формирование нового коммита с этим деревом, в качестве предков которого указаны все сливаемые коммиты



Автоматическая система слияния git в многих случаях может сама "слить" ветки, без участия пользователя. Например, если изменения в сливаемых ветках затрагивают разные файлы, или один и тот же файл, но изменяемые строчки не пересекаются. Новое дерево в таком случае формируется автоматически.

В нашем же запущенном случае в каждом коммите-женихе все строчки изначального "Обычного человека" заменены - поэтому при слиянии получается конфликт. Например, чьи губы должны быть у результата слияния - Никанора Иваныча или Балтазара Балтазарыча? Или может Ивана Павловича?

В таких ситуациях единственное решение принять должен человек. В нашем случае - Агафья Тихоновна. Благодаря Гоголю Агафья уже разрешила все конфликты слияния, постановив, что у идеального жениха должно быть:

  • Губы Никанора Иваныча

  • Нос Ивана Кузьмича

  • Развязность Балтазара Балтазарыча

  • Дородность Ивана Павловича



Вот с таким вот идеальным деревом мы и создадим коммит-слияние:

$ echo "Губы Никанора Иваныча" > ideal-template
$ echo "Нос Ивана Кузьмича" >> ideal-template
$ echo "Развязность Балтазара Балтазарыча" >> ideal-template
$ echo "Дородность Ивана Павловича" >> ideal-template
$ cat ideal-template
Губы Никанора Иваныча
Нос Ивана Кузьмича
Развязность Балтазара Балтазарыча
Дородность Ивана Павловича

$ iconv -t utf-8 <ideal-template >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 <SHA1>

Посмотрим же на результат в gitk:
gitk 31e839af8dbd1315ceaa9dbbcc2c2c71ff91d797

34,36 КБ

Как видно, коммит-слияние в gitk графически отображается как соединение всех веток в одну точку. В классическом случае (без использования всяческих хаков или низкоуровневых команд), когда git видит коммит-слияние, он считает что все изменения, которые были в сливаемых ветках, в точке слияния были согласованы, и все конфликты поправлены.

Если в дальнейшем сливаемые ветки будут развиваться дальше по отдельности, то при очередном слиянии git будет считать коммит-слияние общим предком, и конфликтовать будут только изменения, произошедшие после коммита-слияния.

Итак, подведем итоги:
Коммит-слияние с технической точки зрения ненамного сложнее обычного коммита. Главной проблемой при слияниях является "Право, такое затруднение -- выбор!", говоря словами Агафьи Тихоновны. Во многих случаях этот выбор может сделать сам git, предоставляя несколько стратегий автоматического слияния. Но в сложных случаях без помощи человека в решении конфликтов не обойтись.


Обзор стратегий автоматического слияния я пожалуй оставлю на потом, а в следующем выпуске расскажу о текстовых ссылках (refs), которые значительно облегчают работу с git. Именно они, а не SHA1 имена объектов, используются для повседневной работы в git. Stay tuned!



(Post a new comment)


[info]svpv
2008-01-21 10:22 am UTC (link)
Мёрж -- нематематическая операция. Его не всегда можно представить как суперпозицию двух веток относительно common ancestor. То есть при мёрже может быть конфилкт, который потом нельзя однозначно посмотреть. Ну и вообще в точке мёржа можно выдать произвольное дерево. Этим он плох.

(Reply to this) (Thread)


[info]los_t
2008-01-21 10:50 am UTC (link)
Альтернатива - rebase. как в CVS/SVN, но при этом возни с решением конфликтов может быть гораздо больше. и хуже того, один и тот же конфликт надо будет решать несколько раз, при каждом rebase. И история от этого портится.

(Reply to this) (Parent)


[info]rigidus
2008-04-28 01:35 pm UTC (link)
Окей, а что делать, если у меня не просто различающиеся строки, а вставка нескольких строк в середину файла? Как в этом случае разрешить конфликт?

(Reply to this) (Thread)


[info]los_t
2008-04-28 02:07 pm UTC (link)
Если обе конфликтующие ветки написали Вы - то Вы сами должны знать какой вариант правильный. Если же нет - попросите авторов конфликтующих коммитов самим разобраться, что надо включать, а что оставить. И пусть они предоставят вариант без конфликтов.

(Reply to this) (Parent)(Thread)


[info]rigidus
2008-04-28 05:51 pm UTC (link)
А diff может показать такие вещи?

(Reply to this) (Parent)(Thread)


[info]los_t
2008-04-28 06:53 pm UTC (link)
Конфликты? Они ж маркерами в тексте обозначены <<<<< ==== >>>>>. Любым поиском сразу находятся, в любом текстовом редакторе.

(Reply to this) (Parent)(Thread)


[info]rigidus
2008-04-28 08:09 pm UTC (link)
Пардон, плохо выразился.
Показывает ли дифф конфликты перестановки - вставки строк, или просто сравнивает строкуN в одном файле со строкойN в другом?

(Reply to this) (Parent)(Thread)


[info]cblp.su
2008-08-30 02:02 pm UTC (link)
Вставку и удаление, как правило, распознаёт. Перестановку видит как удаление из одного места и вставку в другое.

(Reply to this) (Parent)


(Anonymous)
2008-07-14 04:04 pm UTC (link)
echo "Никанор Иваныч" | iconv -t utf-8 | faketime -t 200001010100 git-commit-tree 0ade4416fb17c0eb8037265a2e0405db102164eb -p $PARENT

faketime: invalid option -- p

bash расценил -p $PARENT как параметр к комманде faketime. может подскажете как это исправить?
ps: \ и "" не подошли. faketime взял из alt. дистрибутив archlinux

(Reply to this) (Thread)


[info]los_t
2008-07-14 05:06 pm UTC (link)
поставить -- перед git-commit-tree? По идее должно помочь.

(Reply to this) (Parent)(Thread)


(Anonymous)
2008-07-15 03:24 pm UTC (link)
faketime: unrecognized option `--git-commit-tree'

что нибудь еще попробовать?

(Reply to this) (Parent)(Thread)


[info]los_t
2008-07-15 03:25 pm UTC (link)
пробел между -- и git-commit-tree

(Reply to this) (Parent)(Thread)


(Anonymous)
2008-07-15 03:38 pm UTC (link)
спасибо, работает, теперь можно дальше продолжать вникать в git ;-)

(Reply to this) (Parent)

Где мануал брал
(Anonymous)
2008-09-12 02:18 pm UTC (link)
Интересная статья
Только один вопрос а где собственно ты брал мануал так как например нигде не описано в офф документации что например #man git-reset не дает такой инфы 3 hours ago итд

git-reset --hard 'master@{3 hours ago}'

где в мануале такое есть master@{3 hours ago} например ?

(Reply to this) (Thread)

Re: Где мануал брал
[info]los_t
2008-09-12 03:03 pm UTC (link)
В man git-rev-parse.

(Reply to this) (Parent)(Thread)

Re: Где мануал брал
(Anonymous)
2008-09-13 08:42 am UTC (link)
Спасибо

(Reply to this) (Parent)

Как склонировать определенный коммит
(Anonymous)
2008-09-13 11:27 am UTC (link)
Хорошая серия статей, но
вопрос
Как с git-а выгягивают определенный коммит или как выбрать нужную версию?

(Reply to this) (Thread)

Re: Как склонировать определенный коммит
[info]los_t
2008-09-13 12:09 pm UTC (link)
git-checkout <коммит>

(Reply to this) (Parent)(Thread)

Re: Как склонировать определенный коммит
(Anonymous)
2008-09-13 12:14 pm UTC (link)
Спасибо!

(Reply to this) (Parent)

В продолжении темы Как склонировать определенный ком
(Anonymous)
2008-09-13 12:41 pm UTC (link)
Правильно ли я понял работу git-а?
1) То, что я скачиваю командой clon весь репозиторий, а потом уже переключаюся на нужный коммит и работаю в нем.

2) Существует ли возможность скопировать какую то часть репозитория (как в svn командой svn checkout -r200 например) или структура git repo не позволяет качать часть ?

(Reply to this) (Thread)

Re: В продолжении темы Как склонировать определенный к
[info]los_t
2008-09-13 12:46 pm UTC (link)
1) Да, но переключаются обычно на ветку или тег.
2) Есть (т.н. shallow repository - неполные репозитарии). man git-clone (параметр --depth). Но в такие репозитарии нельзя ни фетчить, ни пушить.

(Reply to this) (Parent)

команда cherry-pick
(Anonymous)
2008-09-13 01:08 pm UTC (link)
Раскажите вкраце для чего применяют команду cherry-pick и cherry в git

(Reply to this) (Thread)

Re: команда cherry-pick
[info]los_t
2008-09-13 03:44 pm UTC (link)
Для переноса патчей из ветки в ветку. Типичный пример - если есть ветка разработки и ветка стабильного релиза. Тогда если находится какой-то баг, и он исправляется в одной ветке (ветке разработи обычно), а потом переносится в остальные ветки с помощью git-cherry-pick.

git-cherry-pick на самом деле просто удобный интерфейс. Что на самом деле делается при git-cherry-pick - это выделение патча, соответствующего коммиту, и применение этого патча к другой ветке, с сохранением сообщения о коммите (и оригинального автора коммита). Для поддержки этого в гите и введено поле COMITTER в отличие от поля AUTHOR.

А git-cherry позволяет увидеть, какие патчи можно еще перетащить из одной ветки в другую через cherry-pick.

(Reply to this) (Parent)

странности git-commit
(Anonymous)
2008-09-13 07:05 pm UTC (link)
Hello
Вот еще одно свойство git-а заметил что если создать ветку и переименовать какую-нибудь папку в ней а потом сделать git-commit . или git-commit -a то получиться так:

# Please enter the commit message for your changes.
# (Comment lines starting with '#' will not be included)
# Explicit paths specified without -i nor -o; assuming --only paths...
# On branch feedback
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# renamed: folder/pattern/stat.txt -> folder/depo1/stat.txt

и если так git-commit то будет:

# Please enter the commit message for your changes.
# (Comment lines starting with '#' will not be included)
# On branch feedback
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# new file: folder/feedback/stat.txt

#
# Changed but not updated:
# (use "git add/rm ..." to update what will be committed)
#
# deleted: folder/pattern/stat.txt


То есть в первом случаи сразу переименовка во втором переименовка происходит через: добавить новое имя удалить старое имя

Почему так происходит в чем тайный смысл. Второй вариант я так подозреваю не есть хорошо?

(Reply to this) (Thread)

Re: странности git-commit
[info]konstantin.dmitriev.myopenid.com
2008-10-25 04:34 pm UTC (link)
Во втором случае вновь образовавшийся путь не добавлен в индекс, а старый не найден - значит удалён. То есть, 'git commit -a' означает 'git add . && git commit', где "git add" - добавление в индекс.

Подробнее об индексе можно ещё тут почитать: http://osteele.com/archives/2008/05/my-git-workflow. Правда на английском, зато там очень наглядная схемка.

(Reply to this) (Parent)

git config --get-regexp
(Anonymous)
2008-09-19 08:49 am UTC (link)
Hello
Расскажите о предназначении клонировании установки конфигурации переменных.
вроде это так
git config --get-regexp '^(remote|branch)\.' ( 2 )

(Reply to this)

команда stash
(Anonymous)
2008-09-19 09:26 am UTC (link)
Раскажите о практическом применении команды stash

Спасибо

(Reply to this)


Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…