(Заметьте, но это важно: не думайте так много о ветвях , во-первых. Файлы находятся в коммитах . Git хранилищах коммитах . Git объединяет коммитов . Все сводится к коммитов , а имена веток, такие как master
, просто служат для find коммитов.)
Если бы вы удалили файл, вы бы сделали это в коммите. (Этот коммит может содержаться или не содержаться в наборе коммитов, доступном по имени master
; в том методе, о котором вы думаете, он будет в этом наборе комитов.) Но это не поможет: вы просто получил бы другой конфликт, в частности конфликт модификации / удаления. Ответ на конфликты никогда не бывает сложным «удалить файл здесь или там». Это всегда разрешить конфликт.
Вы должны начать с самого начала, а именно: Объединение - это объединение изменений . Это включает в себя поиск базы слияния коммит. Вы указываете Git объединить фиксацию - обычно путем присвоения имени ветви, как в git merge otherbranch
, но это все еще указывает один конкретный c коммит. Git находит этот коммит базы слияния сам по себе как коммит # 1, использует ваш текущий коммит - конец вашей текущей ветви - как коммит # 2, и использует коммит, который вы назвали коммитом # 3.
Каждый коммит хранит полный и полный снимок всех ваших файлов, поэтому прежде чем Git сможет объединить изменений, Git сначала должен найти изменений. Это включает запуск git diff
, который может сравнивать две фиксации. Он может сравнивать только два коммита! 1 Таким образом, база слияния, которую выбирает git merge
, должна быть на обеих ветвях:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
Если вы используете branch1
и запускаете git merge branch2
, то три коммита, которые Git будет использовать на этом этапе, будут H
, J
и L
: J
- ваш текущий коммит, на кончике branch1
; L
- это коммит в конце branch2
, который вы объединяете; H
- это лучший общий коммит , который находится на обеих ветвях, поэтому H
является базой слияния.
Git теперь, по сути, выполняется :
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed on branch1
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed on branch2
Процесс слияния теперь прост: я sh собираю для каждого файла, который мы оба изменили, набор изменений, которые мы оба сделали, и объединяем эти изменения. Эти объединенные изменения затем применяются ко всему, что было в файле в коммите H
- не к нашему коммиту J
, ни к их коммиту L
, а скорее к base commit! Таким образом, мы получаем оба набора изменений.
Конфликт возникает потому, что вы и они изменили одинаковые строки одного и того же файла. Возможно, вы и они также изменили разных строк этого же файла; Git сможет комбинировать их самостоятельно. Только когда ваши изменения и их изменения «соприкасаются» (перекрываются или примыкают), и вы и они не сделали точно такое же изменение , вы получаете конфликт.
Пока конфликт продолжается, Git дает вам доступ к всем трем исходным входным файлам. То, что вы видите в своем файле рабочего дерева, является его лучшей попыткой объединить изменения с маркерами конфликтов; но вы можете получить доступ к базовой копии файла слияния, вашей branch1
commit- J
копии файла и их branch2
commit- L
копии файла, если хотите. (Команда git mergetool
делает такие вещи за вас, извлекая три копии файла и запуская выбранный вами инструмент слияния. Однако я не использую это: я обычно просто редактирую копию рабочего дерева напрямую.)
Если вы абсолютно уверены, что хотите отбросить все ваших изменений - даже тех, которые не вступают в конфликт с изменениями другой стороны - вы можете сделать это довольно легко на этом этапе, используя git checkout --theirs
. Например, если конфликты есть в файле lib.py
:
git checkout --theirs lib.py
, будет извлечена копия файла № 3 (из commit L
) в копию рабочего дерева. Затем вы можете проверить его, убедиться, что он хорош, а затем использовать git add lib.py
, чтобы отметить разрешенный конфликт.
1 Git имеет git diff
, который может сравнивать три или более коммитов, производя то, что он называет комбинированным diff , но это не так здесь действительно важно: это не работает для слияний.
Что произойдет, если вы делаете удалите файл перед слиянием?
Предположим, вы продолжили план. Вы сделаете новый коммит в ветви branch1
, в котором этот файл - давайте продолжим называть его lib.py
для конкретности - не существует:
I--J--N <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
Git теперь будет git diff
commit H
vs commit N
чтобы увидеть, что вы изменили. Среди всех других изменений, которые он обнаружит, вы обнаружите, что вы полностью удалили lib.py
. Тогда Git будет отличаться коммитом H
против коммита L
. Здесь он должен найти какое-то изменение в lib.py
, потому что когда вы попытались выполнить слияние с коммитом J
, у него возник конфликт, и он может возникнуть только в том случае, если вы и они внесли изменения в одни и те же строки этого файла. Таким образом, будут некоторые изменения, которые Git захочет применить с H
против L
.
. Вы получите конфликт modify / delete . Git может временно go с удалением или может оставить вас со своей версией lib.py
от коммита L
. Если Git удалил файл, теперь вам нужно git checkout --theirs lib.py
, чтобы получить их. Если бы Git оставил их в вашем рабочем дереве, вам бы не пришлось ничего делать здесь - сэкономив вам здесь один шаг, но за счет дополнительного шага создания коммита N
. Тогда вам нужно будет git add lib.py
разрешить конфликт.
Вы только что проделали почти столько же работы! Почему бы просто не сделать это правильно, не делая дополнительного коммита?