Алиса выполнила некоторую работунад своими файлами, и Боб поработал над своими файлами, и теперь Алиса, Боб или, может быть, даже какая-то третья персона Кэрол хочет объединить эти две линии работы.1011 * Git обычно работает с целыми коммитами одновременно, а не с отдельными файлами.Если вы запустите git merge
, вы объедините коммиты , а не файлы .То есть вы можете git checkout
последний коммит Алисы, затем запустить git merge Bob
, и Git найдет, с чего начали Алиса и Боб, посмотреть, что они сделали с всеми файлами, и объединить все этого.
Если вы действительно хотите выполнить трехстороннее объединение только одного файла, вам необходимо получить три версии этого файла.В конце концов, в этом состоит суть трехстороннего слияния: получение трех версий каждого файла, который должен быть объединен, а затем использование этих трех входных версий для создания окончательной объединенной версии.
Три операции ввода слияния на уровне файлов:
Версия файла --ours
.Если бы вы набрали git checkout Alice
(чтобы попасть в филиал Алиса), это была бы последняя Алиса.
Версия файла --theirs
.Если бы вы запустили git merge Bob
(для слияния из ветви Боба), это было бы последним Бобом.
Базовая версия файла слияния.Это версия файла, с которой начинали Алиса и Боб.Это сложная часть: какую версию файла сделал Алиса и Боб оба начинают с?
Нахождение базы слияния - и использование этой базы слияния для все файлы, а не только один - это то, что git merge
делает для вас.Когда вы запускаете git merge
, Git использует историю - коммиты в репозитории - чтобы найти базу слияния commit , т.е. лучший общий коммит в обеих ветвях.Если мы вспомним, что каждый коммит автоматически запоминает своего родителя, мы можем увидеть, как это работает:
o--o--A <-- alice
/
...--o--o--*
\
o--o--B <-- bob
Алиса и Боб оба начали с коммита *
.С тех пор Алиса сделала три коммита на ее ветви, а Боб сделал три коммита на его .Я пометил последний Алис как коммит A
, а последний Боб как коммит B
, хотя, конечно, оба из них - и даже коммит *
- имеют некоторые большие уродливые хеш-идентификаторы в качестве своих настоящих имен.
Commit *
находится на обеих ветвях, и поскольку это shared commit и commit - это постоянные снимки только для чтения всех файлов,очевидно, что Алиса и Боб в то время имели одинаковое содержимое в каждом файле.Поэтому команда git merge
будет принимать каждый файл из коммита *
и сравнивать каждый такой файл с каждым файлом в коммите A
.Что бы здесь ни изменилось, Алиса изменилась.Затем Git будет сравнивать каждый файл с *
с каждым файлом в коммите B
.Что бы здесь ни изменилось, Боб изменился.
Теперь команда git merge
будет изменяться для каждого файла, который изменяется в A
или B
по сравнению с *
, объединить изменения в каждом файле.То есть, если Алиса и Боб оба изменили README.md
, Git объединит изменения.Если Алиса изменила alice.txt
, а Боб - нет, Git просто примет изменения Алисы.Если Боб изменил blue.py
, а Алиса - нет, Git просто примет изменения Боба.
Объединив все эти изменения - при условии, что это объединяет все работы - Git применит объединенные изменения к тому, что было в *
,Полученные файлы теперь пригодны для нового коммита.Новый коммит - коммит слияния , который является особенным только потому, что у него два родителя вместо обычного: вместо запоминания мой родитель коммит A или мой родителькоммит B , новый коммит запоминает оба:
o--o--A
/ \
...--o--o--* M
\ /
o--o--B
Новый комmmit M
переходит на ту ветку, которую вы проверили прямо сейчас. Если это ветвь alice
, результат:
o--o--A
/ \
...--o--o--* M <-- alice (HEAD)
\ /
o--o--B <-- bob
Если вы начнете с создания новой ветви - например, carol
- что также указывает на фиксацию A
сначала:
o--o--A <-- alice, carol (HEAD)
/
...--o--o--*
\
o--o--B <-- bob
затем новый коммит M
меняет имя carol
, указывая на M
:
o--o--A <-- alice
/ \
...--o--o--* M <-- carol (HEAD)
\ /
o--o--B <-- bob
Помните, что все файлы в M
являются результатом объединения всех изменений. Сложнее было найти изменения , потому что коммиты - это моментальные снимки, и хитрость для этого заключалась в том, чтобы найти моментальный снимок *
, базу слияния. Git сравнил снимок *
со снимком A
:
git diff --find-renames <hash-of-*> <hash-of-A> # what Alice changed
и повторил это для снимка B
:
git diff --find-renames <hash-of-*> <hash-of-B> # what Bob changed
Затем слияние объединило эти изменения для всех файлов в трех снимках, чтобы создать коммит слияния M
, который переходит в текущую ветвь: ту, к которой прикреплен HEAD
.
Заключение № 1
Чтобы объединить все файлы в A
и B
, не затрагивая другие ветви, вы можете создать ветвь new , указывающую либо на коммит A
, либо на коммит B
, затем запустите git merge
с любым именем или идентификатором хэша, который указывает другой из двух коммитов. Если слияние завершится успешно, ваш новый коммит слияния M
будет указывать на A
и B
и будет исключительно в вашей новой ветви.
Чтобы объединить все файлы в A
и B
во время помещения нового коммита слияния в ветвь alice
(текущий коммит A
) или bob
(текущий коммит B
), выберите нужный ветвь и запустите git merge
с именем другой ветки. Если слияние выполнится успешно, ваш новый коммит слияния M
будет указывать на A
и B
, как и в другом случае, но на этот раз коммит слияния будет, скорее, на alice
или bob
чем в новой ветке, которую вы не создали.
Заключение № 2
Если вы действительно, действительно, хотите объединить один файл из коммитов A
и B
, вы не можете использовать git merge
для этого: он делает слишком много; он объединяет все файлы. Существует команда Git, которая может объединить один файл:
git merge-file
сделает работу. Но для этого нужны три входных файла. Требуется базовая версия файла слияния, изменения которого вы хотите объединить. Найдите базовую версию слияния плюс две другие версии, которые вы хотите объединить. Поместите их в три файла, например, file.base
, file.mine
и file.theirs
. Затем запустите:
git merge-file file.mine file.base file.theirs
Git будет сравнивать file.base
с file.mine
, чтобы увидеть, что вы изменили, и сравнить file.base
с file.theirs
, чтобы увидеть, что они изменились. Затем он попытается объединить эти два изменения, применив объединенные изменения к file.base
и записав результат в file.mine
.
Это, конечно, именно то, что git merge
делает для каждого файла. Просто git merge
находит три файла для вас и автоматизирует всю эту работу.