Вы хотите посмотреть в индексном слоте № 1 и сравнить его с тем, что находится в индексном слоте № 3, для пути:
git diff :1:path :3:path
Вы также можете извлечь различный индекс-слоты, используя git checkout-index
, а затем проверяйте их с помощью обычных манипуляций с файлами, а не просто с помощью инструментов Git.Программа git mergetool
выполняет последнее, поэтому, если вы используете git mergetool
, у вас будут обе версии файла.(Я никогда не использую git mergetool
сам.)
Что все это значит?
Любое слияние по определению имеет три входа.Три входа: коммиты , 1 , которые содержат моментальные снимки файлов, поэтому обычно три входа приводят к трем версиям каждого файла.Эти три версии файлов сводят эти пронумерованные слоты в индексе: то есть для некоторого файла path/to/file.ext
, который объединяется, есть :1:path/to/file.ext
, :2:path/to/file.ext
и :3:path/to/file.ext
.(Когда - как в этом случае - одна из ветвей удаляет , файл, в котором отсутствует одна из этих трех записей.)
Давайте перечислим слоты индекса в обратном порядке, потому что последняяключ здесь:
Слот 3 (Theirs) содержит их файл, на кончике ветви develop
: вы запустили git merge develop
.Помните, что каждый коммит является полным и полным снимком всех файлов.Это не набор изменений , это просто снимок.Тем не менее, Git знает, что они изменили некоторый конкретный файл.Изменено относительно что?
Слот 2 (наш) содержит файл из вашего текущего текущего коммита (также известного как * 1048)*).В этом случае некоторый файл в HEAD
был удален.Но: как Git узнает, что вы удалили файл?Удалено, относительно что?
Слот 1 (база) содержит файл из базы слияния .Это что в обоих из них.
База слияния - это (единственный / лучший) общий коммит, с которого начинались ваши расходящиеся кончики ветвей.Git сравнивает этот коммит (в действительности, коммит в целом) с каждым из двух коммитов с ветвями (в целом) для сопоставления файлов. 2 Затем, сопоставив все файлыв трех коммитах Git запустит весь процесс «положить некоторые файлы в специальные слоты».
То есть, если мы рисуем график фиксации, когда вы запускаете git merge
, он выглядит примерно так - хотяточные детали будут различаться, и обычно график намного сложнее сканировать, чтобы найти B
и R
(HEAD
или совершить L
обычно очень легко найти):
o--...--L <-- our-branch (HEAD)
/
...--A--B
\
o--...--R <-- develop
Commits A
и B
(и все, что раньше A тоже) находятся на обеих ветвях и, следовательно, совместно используются, но B
является лучшим , потому что это last shared one.
Чтобы выполнить слияние, Git сравнил B
против L
, чтобы увидеть, что мы изменили, и B
против R
чтобы увидеть, что они изменились.Файлы, которые никто не изменял, одинаковы во всех трех коммитах, поэтому Git использует любую версию таких файлов.Для файлов, которые изменились только , они , Git берет свою версию этого файла из commit R
.Для файлов, которые изменились только , которых мы , Git берет нашу версию этого файла из commit L
.
Для файлов, которые мы оба изменили, в некотором роде -в том числе «удалить файл целиком» - Git должен работать усерднее.Теперь становится важно понять, что индекс - то, из чего Git создает new commit - играет расширенную роль во время слияния.
Обычно индекс имеет только один слот для каждого файла.Этот слот нумеруется, но это слот с нулевым номером, так что вы обычно не делаете ничего особенного, чтобы ссылаться на него: вы просто указываете Git git add <em>somefile</em>
скопировать файл somefile
из работы-дерево в индекс, что делает его готовым к фиксации.
Для объединенияОднако в случае, когда и мы, и они что-то сделали с файлом, Git требуется три - ну, до три - копии каждого файла.Таким образом, в этом конкретном случае Git помещает версию base файла слияния из commit B
в индексный слот # 1.Git перемещает нашу версию файла (из коммита L
и уже в индексном слоте № 0) в индексный слот № 2 и помещает другую версию файла из коммита R
в индексный слот № 3.
Для удаленного файла (в данном конкретном случае) Git оставляет слот № 2 или № 3 пустым, в зависимости от того, кто удалил файл.Для конфликта добавления / добавления - когда файл не существует в B
, но существует как в L
, так и R
- Git оставляет слот № 1 пустым.(Нет такой вещи как конфликт удаления / удаления: если мы оба удалили файл, Git просто удаляет файл и переходит. Но есть некоторые случаи переименования, которые более хитры.)
Когда слияние прекращаетсяв случае конфликта слияния эти слоты индекса остаются заполненными тремя или в данном случае двумя версиями файла.Поэтому вы можете проверить индекс, посмотреть на более высокие (ненулевые) слоты и узнать, какие файлы имеют конфликты.Это делают различные инструменты Git, в том числе git status
и git diff
.
Когда вы исправите конфликты любым способом, вы должны затем сказать Git очистить слоты более высокой стадии и поставитьхорошая копия файла в индексный слот № 0.Самый простой способ сделать это - git add
правильная версия файла.(Если нет правильной версии - если она должна отсутствовать - вы можете git rm
файл, удалив его из всех слотов индекса и рабочего дерева. В общем, если он не находится в рабочем дереве, git add
такжеудаляет его из индекса, хотя у меня есть привычка git rm
-ить конфликтующие файлы, которые должны просто исчезнуть, поэтому я не проверял, является ли git add
последовательным при удалении записей более высокого уровня здесь. Если это не такв рабочем дереве git rm
удаляет его из индекса, жалуется, что его нет в рабочем дереве, и тогда все хорошо.)
1 Этов особых случаях возможно объединить файлы, которые еще не были зафиксированы.Это происходит, например, с git checkout -m
или git stash apply
.В этом случае Git обычно просто перемещает элементы из слота 0 в слот 2 по мере необходимости ... и изменения, которые находятся в рабочем дереве, но никогда не были зафиксированы, могут быть искажены и / или потеряны инструментамикоторые ожидают, что слот № 2 будет безопасной копией того, что было в рабочем дереве!(Это одна из причин, по которой мне не нравится git stash
.) Но запуск git merge
не вызывает этот странный путь, и на самом деле он жалуется и прерывается, если ваш репозиторий не находится в хорошем состоянии готовности начать слияние.
2 Именно здесь начинается обнаружение переименования. Если вы переименовали какой-то файл в своем коммите, то, что было :1:path/to/file.ext
, теперь может быть :2:path/different/file.ext
или :2:path/to/different.ext
.Они были обнаружены как один и тот же файл, хотя теперь у него два разных имени.Здесь есть небольшой недостаток, потому что слоты не связаны друг с другом в течение всей продолжительности слияния.Если слияние заканчивается конфликтом, трудно восстановить тот факт, что, например, 2:path/to/different.ext
идет с :1:path/to/file.ext
.Git печатает информацию, поэтому она все еще может находиться в окне, которое вы можете видеть на своем ноутбуке или где-либо еще, но это не записано в другом месте.
Сноска: более запутанные графики
Вот пример рисунка ветви с несколькими повторяющимися слияниями:
...--A--B--C--D--G--H--K <-- branch1
\ \ \
E-----F--I--J--L <-- branch2
Здесь * F
и J
- оба слияния, с двумя родителями: два родителя F
являются E
и D
, а два родителя J
являются I
и H
.Если вы запустите git checkout branch2; git merge branch1
, вы попытаетесь сделать новый коммит слияния M
, чей первый родительский элемент - L
, а второй - K
.Основой слияния здесь является commit H
, потому что начиная с K
и работая в обратном направлении, мы получаем H
, а начиная с L
и работая в обратном направлении, мы получаем J
, а затем - одновременно, как это было- к I
и H
, и, как мы уже достигли H
вдоль вершины, это база слияния.
Обратите внимание, что merРасчет базы ge симметричен. Если мы git checkout branch1 && git merge branch2
, Git по-прежнему выбирает H
в качестве базы слияния. Если база слияния не очевидна, вы можете запустить:
git merge-base --all branch1 branch2
, который произведет всех кандидатов на "лучшую базу слияния".
В идеале, есть только один. Однако история выглядит так:
...--o--o---A--... <-- branch1
\ /
X
/ \
...--o--o---B--... <-- branch2
, где A
и B
являются коммитами слияния, которые "одинаково близки" к обоим кончикам ветвей, имеет две базы слияния. Это происходит путем выполнения слияния из branch1 в branch2 и немедленного выполнения слияния, с git merge --no-ff
, из branch2 в branch1, иногда называемой слиянием крест-накрест.
В этом неоднозначном базовом случае слияния нет единственного лучшего кандидата. Так как слияния обычно симметричны в любом случае, содержание коммитов A
и B
здесь вполне может быть одинаковым. В этом случае не имеет значения, какой из A
и B
Git выбрать. Но если содержимое различается, это имеет значение, и это то, где Git рекурсивное слияние вступает. Когда есть несколько баз слияния, Git сначала, как внутреннее слияние, объединит две базы слияния (используя их лучший общий предок, что бы это ни было) и сделать новый коммит из результата. Git будет использовать содержимое этого нового коммита в качестве базы слияния для внешнего слияния.
Стратегия слияния -s resolve
(не по умолчанию) выбирает одно из двух, A
или B
, в случайном порядке (на самом деле, все, что более удобно в алгоритме). Если A
и B
имеют одинаковое содержимое, это работает нормально; в противном случае внутреннее слияние из рекурсивного «слияния А и В сначала» может дать лучший результат. Это производит что-то вроде беспорядка, если у рекурсивного слияния есть конфликты. (Обычно целесообразно избегать перекрестных слияний.)