Git не поставляется с какими-либо причудливыми алгоритмами слияния, но включает в себя механизм, с помощью которого вы можете предоставить свой собственный. На самом деле, существует два механизма, но один гораздо удобнее в использовании.
В частности, в вашем файле .gitattributes
вы можете указать драйвер слияния для определенных шаблонов имен файлов. Важно понимать, что этот драйвер слияния только вызывается в некоторых особых случаях.
На верхнем уровне мы вызываем git merge
с использованием командной строки, включая эти параметры. Существуют и другие варианты, но я хочу их особо назвать:
git merge [-s <em>strategy</em>] [-X <em>extended-options</em>] <em>commit-specifier</em>
Аргумент -s
принимает стратегию , и там пять встроенных стратегий, но на самом деле имеет значение только одна (или две или три в зависимости от того, как вы считаете):
recursive
и resolve
по сути одинаковы, за исключением случаев, когда существует более одной базы слияния (случай, который я не буду здесь обсуждать). Это те, которые имеют значение, и для рассматриваемого нами случая они практически идентичны.
octopus
предназначен для использования при указании более одного commit-specifier
аргумент; он строит слияние осьминога , то есть с более чем двумя родителями. Я также не буду обсуждать это, потому что слияния осьминога не разрешают разрешение конфликтов в любом случае.
ours
полностью игнорирует все другие коммиты и просто сохраняет текущее дерево, поэтому оно здесь не подходит .
subtree
- это своего рода взломанный вариант recursive
для переименования поддеревьев, и, следовательно, действительно относится к категории recursive
.
Вы можете предоставить свою собственную стратегию слияния! Все, что вам нужно сделать, это написать команду, назвать ее git-merge-<em>whatever</em>
и запустить git merge -s <em>whatever</em>
, а Git вызовет вашу стратегию слияния. Однако если вы делаете и пишете свою собственную стратегию слияния, вы должны заставить ее делать все , и это довольно сложно, о чем свидетельствует тот факт, что третьего, кажется, не существует. -страницы доступны как Git дополнения. Другими словами, фраза все, что вам нужно сделать , охватывает множество грехов, некоторые из которых, по-видимому, довольно серьезны. : -)
extended-options - Git называет эти опции стратегии , что мне кажется плохим именем, поскольку -s
означает опция стратегии - просто передается стратегии как опции. Стандартная стратегия (или стратегии, в зависимости от того, считаете ли вы разрешение и / или поддерево как отдельные стратегии), позволяет -X ours
и -X theirs
, что в данном случае не то, что вам нужно, но стоит упомянуть, плюс целый хост дополнительных -X
опций для настройки параметров в алгоритмах.
Однако, по большей части, стандартная стратегия работает следующим образом:
- нахождение базы слияния (или оснований, множественное число , для
-s recursive
), чтобы мы имели один коммит для использования в качестве базы слияния; - сравнение базы слияния с
HEAD
и с другим коммитом, как будто на два git diff --find-rename
операций; - объединение наборов изменений, произведенных двумя
git diff
s.
Если рекурсивная стратегия находит две или более базисов слияния, она объединяет их, как если бы git merge -s recursive
, по одной паре коммитов за раз, фиксируя каждый результат. Каждый такой коммит является временным и не имеет имени ветви. Он имеет необработанный га sh ID— каждый коммит, так что этот временный коммит делает тоже самое - и окончательный результат результата слияния ha sh ID является базовым коммитом слияния. В основном, однако, мы в итоге -s recursive
находим некоторую существующую (единственную) базу слияния, поэтому в качестве базы слияния используется тот коммит. Если стратегия разрешения находит две или более баз слияния, она выбирает одну из них, что также может быть случайным. (Это на самом деле не случайно, но это то, что сначала выходит из алгоритма поиска базы, и порядок не указан, а алгоритм поиска базы может измениться в будущем.)
Итак: на данный момент у нас есть три коммита:
- база слияния;
- текущий коммит, который всегда
HEAD
, как --ours
; и - коммит, который вы указали в командной строке, как
--theirs
.
В базе существует некоторый набор файлов. Эти файлы в паре с некоторым набором файлов в --ours
, в соответствии с алгоритмом поиска переименования. Любые файлы, которые остаются непарными - которые не могут быть идентифицированы как «один и тот же файл» слева и справа - либо добавляются (ничего слева, новый файл справа), либо удаляются (ничего справа, файл левой стороны удаляется). Файлы, чье имя изменилось, переименовываются. Файлы, которые были объединены в пару и которые имеют измененное содержимое, «модифицируются» (и, возможно, также переименовываются). Оставшиеся спаренные файлы вообще не изменены.
То же самое происходит с базовым коммитом и --theirs
коммитом, создавая списки файлов, которые изменены и / или переименованы, добавлены, удалены или неизменны вообще.
Обратите внимание, что в принципе на этом этапе все три коммита попадают в индекс. (Существует оптимизация, при которой индекс фактически не расширяется, если это возможно, но результат этого оптимизированного подхода должен соответствовать результату фактического помещения всех трех коммитов в индекс.) Способ, которым это работает, заключается в том, что каждая запись в Индекс имеет номер промежуточного слота . Таким образом, файл может занимать промежуточные слоты 1 (база слияния), 2 (--ours
) и 3 (--theirs
) одновременно. Это три копии файла или, если быть точным, три ссылки на BLOB-объекты, все из которых используют одно и то же имя файла в индексе.
Нулевой интервал подготовки индекса используется для разрешено файлов. В этом случае нет конфликта слияния для этого файла.
Следующим шагом является операция слияния high level : файлы, которые переименованы, должны быть переименованы из базы в конечный коммит. Если обе стороны переименовали файл, у нас возникает конфликт переименования / переименования. Git объявляет конфликт для этих файлов и выбирает одно из двух новых имен в качестве целевого имени для использования в рабочем дереве. Однако внутри index исходное имя в базе слияния занимает слот 1 для имени base-commit, новое имя в --ours
занимает слот 2 для имени ours-commit и новое имя в --theirs
занимает слот 3 для имени их коммита. Это просто тот факт, что файл рабочего дерева должен иметь некоторое имя, которое заставляет Git выбрать одно (и я думаю, Git использует здесь имя --ours
, но я должен был поэкспериментировать чтобы быть уверенным).
Если одна сторона добавила файл с именем F , а другая - нет, конфликт отсутствует, но если обе стороны добавили файл с именем F существует конфликт добавления / добавления файла F .
Если одна сторона удалила файл, а другая сторона изменила и / или переименовала файл, происходит изменение / удаление или конфликт переименования / удаления для этого файла.
Во всех этих случаях конфликта высокого уровня все три файла остаются в индексе под разными именами. Файл рабочего дерева может также иметь или не содержать некоторые конфликты слияния, если он также существует на низком уровне. Но к этому моменту все конфликты высокого уровня регистрируются в индексе и объявляются как конфликты, которые остановят слияние и получат помощь; файл не будет разрешен в индексе. Примечание: все это находится под контролем стратегии слияния.
Теперь, когда обрабатываются конфликты высокого уровня, мы переходим к тому, существуют ли конфликты низкого уровня:
Если ни одна из сторон не изменила файл - если он имеет одинаковое значение ha sh во всех трех слотах индекса - то здесь нет проблем. Файл можно переместить в нулевой слот, используя идентификатор ha sh из любого из трех промежуточных слотов (если, конечно, нет конфликтов высокого уровня).
Если одна сторона слияния изменила файл, а другая - нет, тогда проблем нет. Файл можно переместить из любого промежуточного слота, содержащего измененный файл , в нулевой слот подготовки. То есть файл F имеет три хэша в трех слотах. Какой бы из них не совпадал с в слоте-1 га sh, мы отбрасываем его и принимаем другой в качестве результата слияния. Это переходит к нулевому слоту, а остальные три слота стираются (опять же, только если нет конфликтов высокого уровня). Файл рабочего дерева заменяется сохраненной индексной копией.
В последнем случае, когда все три входных файла различаются, мы (наконец!) Вызываем фактическое слияние низкого уровня driver.
Драйвер слияния низкого уровня отвечает за фактическое слияние
Именно здесь ваша цель становится видимой. Низкоуровневый драйвер слияния по умолчанию - это линейный драйвер, который генерирует конфликты слияния, которые вы видели. Эта программа встроена в стратегию, но доступна и как вызываемая программа, используя git merge-file
. Он принимает три имени входных файлов, объединяет их, как может, самостоятельно, записывает результат обратно в одно из трех имен файлов и завершает работу с нулевым статусом, если считает, что слил три файла правильно, или ненулевой, если он оставил некоторые маркеры конфликта в копии рабочего дерева.
Когда стратегия слияния вызывает этот драйвер слияния низкого уровня, если драйвер слияния выходит из нуля, стратегия слияния копирует полученный файл в нулевой слот индекса и стирает слоты 1 –3, чтобы отметить разрешенный файл. Вы никогда не видите конфликта здесь. Однако, когда он выходит из нуля, файл рабочего дерева не является правильным результатом. Три входа остаются в индексе в слотах 1–3, и объединение в конечном итоге прекратится с конфликтом. (Конечно, стратегия в первую очередь переходит к оставшимся необработанным файлам.)
Если вы установите драйвер слияния , Git будет запускать вашу команду вместо использования его встроенный эквивалент git merge-file
. Ваш драйвер должен выйти из нуля или отличного от нуля, как обычно, и независимо от того, как он выходит, должен приложить все усилия для фактического объединения трех файлов в рабочем дереве. Вы можете достичь этого так, как вам нравится: весь процесс зависит от вас.
Итак, если вам нужен более изящный драйвер слияния, который может понимать используемый язык программирования, или использовать слово-ориентированный diff вместо line diff, напиши один! Это просто маленький вопрос программирования .