Что является доминирующей ветвью при выполнении git merge?
Учитывая, что моя текущая ветвь ветвь А
Ветвь А содержит:
line 1 -- "i love you Foo"
Ветка B содержит:
line 1 -- "i love you Bar"
если я сделаю:
git merge Branch B
что бы я получил?
3 ответа
Модификатор "доминантный" не определяется Git. То, как вы используете это слово, кажется мне неверным предположением, которое, как мне кажется, делает вопрос безответственным как таковой. Однако с одним небольшим изменением ответ становится простым: это ни то,ни другое. Никакая ветвь не является "доминирующей" здесь; обе ветви являются равноправными партнерами, и результат слияния одинаков, независимо от того, объединяете ли вы A в B или B в A, но вы можете изменить это несколькими способами.
Внизу есть немало полезных моментов, которые раскрывает этот вопрос, поэтому давайте рассмотрим их. В конце мы увидим, как правильно сформулировать вопрос и каковы возможные ответы.
Напоминание: Git хранит снимки с родительской связью
Каждый коммит хранит полную копию всех файлов в этом коммите, нетронутыми (хотя и сжатыми). Некоторые другие системы управления версиями запускаются с первоначального снимка, а затем для каждого коммита сохраняют набор изменений с момента предыдущего коммита или с момента предыдущего снимка. Поэтому эти другие VCS могут легко показать вамизменения(поскольку это то, что у них есть), но с трудом получают фактические файлы (потому что им нужно собрать множество изменений). Git использует противоположный подход, сохраняя файлы каждый раз и вычисляя изменения только тогда, когда вы их запрашиваете.
Это не имеет большого значения с точки зрения использования, так как при наличии двух снимков мы можем найти изменение, а при наличии одного снимка и одного изменения мы можем применить это изменение, чтобы получить новый снимок. Но это имеет какое-то значение, и я ссылаюсь на эти снимки ниже. Подробнее об этом см. В разделе Как git хранит файлы? и являются ли файлы пакета Git дельтами, а не снимками?
Между тем, каждый коммит в Git также записываетродительский коммит. Эти родительские связи образуют обратную цепочку коммитов, в которой мы нуждаемся, поскольку идентификаторы коммитов кажутся совершенно случайными:
4e93cf3 <- 2abedd2 <- 1f0c91a <- 3431a0f
Четвертый коммит "указывает назад" на третий, который указывает на второй, который указывает на первый. (Конечно, первый коммит не имеет родителя, потому что это первый коммит.) Вот как Git находит предыдущие коммиты, учитывая последние или коммиты. Слово tip появляется в глоссарии Git, но только под определением ветки.
Цель слияния
Цель любого слияния - объединить работу. В Git, как и в любой другой современной системе контроля версий, у нас могут быть разные авторы, работающие над разными ветвями, или "мы" могут быть одним человеком (один автор, королевское "мы":-)), работающим над разными ветвями. В этих различных ветвях мы - независимо от того, означает ли "мы" - один человек или несколько человек) - могут вносить различные изменения в наши файлы с разными намерениями и разными результатами.
В конце концов, тем не менее, мы решили, что хотели бы каким-то образом объединить некоторые из них. Объединение этих различных наборов изменений для достижения определенного результата и, по крайней мере, в обычном порядке, для записи того факта, что мы их объединяли, является объединением. В Git эта версия глагола слияния - чтобы объединить несколько веток - делается с git merge
и результатом является слияние, существительная форма слияния. 1 Существительное может стать прилагательным:коммит слияния - это любой коммит с двумя или более родителями. Все это правильно определено в глоссарии Git.
Каждый родитель коммита слияния является предыдущейголовой(см. Ниже). Первым таким родителем является голова, которая была HEAD
(см. также ниже). Это делает первый родитель коммитов слияния особенным, и именно поэтомуgit log
а также git rev-list
иметь --first-parent
опция: это позволяет вам смотреть только на "основную" ветвь, в которую объединяются все "боковые" ветки. Для того, чтобы это работало как нужно, важно, чтобы все слияния (форма глагола) выполнялись аккуратно и с надлежащим намерением, что требует, чтобы ни одно из них не было выполнено с помощьюgit pull
,
(Это одна из нескольких причин того, что новички в Git должны избегать git pull
команда. Важность или отсутствие этого --first-parent
Свойство зависит от того, как вы собираетесь использовать Git. Но если вы новичок в Git, вы, вероятно, еще не знаете, как собираетесь использовать Git, поэтому вы не знаете, будет ли это свойство важным для вас. С помощью git pull
случайно облажается, так что вам следует избегатьgit pull
.)
1 Смущает, git merge
также может реализовать глагол действия, но создать обычный коммит без слияния, используя --squash
, --squash
опция на самом деле подавляет сам коммит, но так же --no-commit
, В любом случае это возможныйgit commit
вы запускаете, что делает коммит, и это коммит слияния, если вы не использовали --squash
когда ты побежал git merge
, Зачем --squash
подразумевает --no-commit
когда вы можете на самом деле бежать git merge --squash --no-commit
если вы хотите пропустить шаг автоматической фиксации, это немного загадка.
Git Merge стратегии
git merge
В документации отмечается, что существует пять встроенных стратегий, названных resolve
, recursive
, octopus
, ours
, а также subtree
, Я отмечу здесь, что subtree
это просто небольшая настройка recursive
так что, возможно, было бы лучше заявить только о четырех стратегиях. Более того, resolve
а также recursive
на самом деле довольно похожи, в том, что рекурсивный просто рекурсивный вариант resolve
, что приводит нас к трем.
Все три стратегии работают с тем, что Git называет головами. Git определяет слово head:
Именованная ссылка на коммит в конце ветки.
но то, как Git использует это с git merge
тоже не совсем соответствует этому определению. В частности, вы можете запустить git merge 1234567
объединить совершить 1234567
, даже если у него нет именованной ссылки. Это просто обрабатывается так, как если бы это была верхушка ветви. Это работает, потому что само слово ветвь довольно слабо определено в Git (см. Что именно мы подразумеваем под "ветвью"?): Фактически Git создает анонимную ветку, так что у вас есть безымянная ссылка на коммит, который верхушка этой безымянной ветви.
Одна голова всегдаHEAD
ИмяHEAD
- что также может быть написано@
- зарезервирован в Git и всегда ссылается на текущий коммит(всегда есть текущий коммит). 2 ВашHEAD
может быть либо отсоединенным (указывающим на конкретный коммит), либо присоединенным (содержащим имя ветви, с именем ветки, в свою очередь, обозначающим конкретный коммит, который, следовательно, является текущим коммитом).
Для всех стратегий слияния,HEAD
является одной из глав, которые будут объединены.
octopus
Стратегия действительно немного отличается, но когда дело доходит до решения объединяемых элементов, она работает так же, какresolve
за исключением того, что он не может терпеть конфликты. Это позволяет избежать остановки конфликта слияния, что позволяет решить более двух глав. За исключением его нетерпимости к конфликтам и способности решать три и более головы, вы можете думать об этом как о регулярном слиянии решений, к которому мы скоро доберемся.
ours
Стратегия совершенно иная: онаполностью игнорирует все остальные головы. Конфликты слияния никогда не возникают, потому что нет других входных данных: результат слияния, снимок в новом HEAD
так же, как и в предыдущемHEAD
, Это также позволяет этой стратегии разрешать более двух голов, а также дает нам возможность определить "доминантную голову" или "доминантную ветвь", хотя сейчас это определение не особенно полезно. Для ours
Стратегия, "доминирующая ветвь" является текущей ветвью, но целью ours
слияние - это запись в истории того, что произошло слияние, без фактического отвлечения работы от других руководителей. То есть, этот тип слияния тривиален: форма глагола "слить" вообще ничего не делает, и тогда результирующая форма существительного "слияние" - это новый коммит, первый родитель которого имеет такой же снимок, с оставшимися родителями записывать другие головы.
2 Есть одно исключение из этого правила, когда вы используете то, что Git по-разному называет "нерожденная ветвь" или "сиротская ветвь". Пример, с которым чаще всего сталкиваются люди, - это состояние недавно созданного репозитория: вы находитесь на веткеmaster
, но нет никаких коммитов вообще. ИмяHEAD
все еще существует, но название ветви master
еще не существует, так как нет коммита, на который он может указывать. Git разрешает эту сложную ситуацию, создавая имя ветви, как только вы создадите первый коммит.
Вы можете снова подключиться к нему в любое время, используяgit checkout --orphan
создать новую ветку, которая на самом деле еще не существует. Детали выходят за рамки этого ответа.
Как работает разрешение / рекурсивное слияние
Остальные (неours
виды слияния - это те, о которых мы обычно думаем, когда говорим о слиянии. Здесь мы действительнообъединяем изменения. У нас есть наши изменения в нашей отрасли; и у них есть свои изменения, на их ветке. Но так как Git хранит снимки, сначала мы должны найти изменения. Какие именно изменения?
Единственный способ, которым Git может создать список наших изменений и список их изменений, - это сначала найтиобщую отправную точку. Он должен найти коммит - снимок - который мы оба использовали и оба использовали. Это требует просмотра истории, которую Гит реконструирует, глядя на родителей. Когда Гит возвращается к истории HEAD
- наша работа - и с другой стороны, он в конечном итоге находитбазу слияния: коммит, с которого мы оба начали. 3 Это часто визуально очевидно (в зависимости от того, насколько тщательно мы рисуем график коммитов):
o--o--o <-- HEAD (ours)
/
...--o--*
\
o--o <-- theirs
Здесь база слияния - коммит*
: мы оба начали с этого коммита, и мы сделали три коммита, а они сделали два.
Поскольку Git хранит снимки, он находитизменения, выполняя, по сути, git diff base HEAD
а такжеgit diff base theirs
, с base
являющийся идентификатором базового коммита слияния *
, Git объединяет эти изменения.
3 База слияния технически определяется как самыйнизкий общий предок(LCA) направленного ациклического графа или DAG, образованный односторонними дугами коммитов, связывающими каждый коммит с его родителем (ями). Коммиты - это вершины / узлы графа. LCA на деревьях просты, но DAG более общие, чем на деревьях, поэтому иногда нет единого LCA. Это где recursive
слияние отличается отresolve
слияния: resolve
работает, выбирая один из этих "лучших" предковых узлов по существу произвольно, в то время как recursive
выбирает все из них, объединяя их, чтобы сформировать некий вид притязания-коммита: виртуальную базу слияния. Детали выходят за рамки этого ответа, но я показал их в другом месте.
Для разрешения / рекурсии нет доминирующей ветви по умолчанию
Теперь мы наконец добрались до ответа на ваш вопрос:
Учитывая, что моя текущая ветвь ветвь А
Ветвь А содержит [в файле
file.txt
]:line 1 -- "i love you Foo"
Ветка B содержит:
line 1 -- "i love you Bar"
если я сделаю:
git merge BranchB
что бы я получил?
Чтобы ответить на этот вопрос, нам нужна еще одна информация:что находится в базе слияний?Что ты изменил наBranchA
и что ониизменили наBranchB
? Не то, чтов двух ветвях, а то, что каждый из васизменил со времени основания?
Давайте предположим, что мы нашли идентификатор базы слияния,4 и это (как-то)ba5eba5
, Затем мы запускаем:
git diff ba5eba5 HEAD
чтобы узнать, чтомы изменили, и:
git diff ba5eba5 BranchB
чтобы узнать, что они изменили. (Или, аналогично, мы используемgit show ba5eba5:file.txt
и посмотрите на строку 1, хотя просто делать дваgit diff
с проще.) Очевидно, что по крайней мере один из нас что-то изменил, иначе строка 1 была бы одинаковой в обоих файлах.
Если мы изменили строку 1,а они этого не сделали, результатом слияния будет наша версия.
Если мы не изменили строку 1,и они сделали, результатом слияния является их версия.
Если мы оба изменили строку 1, результатом слияния будет то, что слияниезавершится неудачно с конфликтом слияния. Git записываетобе строки в файл, останавливает слияние с ошибкой и заставляет нас убрать беспорядок:
Auto-merging file.txt
CONFLICT (content): Merge conflict in file.txt
Со стилем по умолчанию мы видим:
$ cat file.txt
<<<<<<< HEAD
i love you Foo
=======
i love you Bar
>>>>>>> BranchB
Если мы установимmerge.conflictStyle
вdiff3
(git config merge.conflictStyle diff3
или жеgit -c merge.conflictStyle=diff3 merge BranchB
или аналогичный),5 Git пишет не только две наши строки, но и то, что было там изначально:
$ cat file.txt
<<<<<<< HEAD
i love you Foo
||||||| merged common ancestors
original line 1
=======
i love you Bar
>>>>>>> BranchB
Заметьте, кстати, что Git не смотрит ни на один изпромежуточных коммитов. Он просто сравнивает базу слияния с двумя головками, с двумяgit diff
команды.
4 Это предполагает наличие единой базы слияния. Обычно это так, и я не хочу вдаваться в основы виртуального слияния, за исключением некоторых из этих сносок. Мы можем найти все базы слияния сgit merge-base --all HEAD BranchB
и обычно он печатает только один идентификатор коммита; это (единая) база слияния.
5 Я использую это diff3
стиль, установив его в моем--global
Конфигурация Git, потому что я считаю, что при разрешении конфликтов хорошо видеть, что находится в базе слияния. Мне не нравится искать базу слияния и проверять ее; и для действительно рекурсивного слияния, когда Git создал виртуальную базу слияния, нет ничего, что можно было бы проверить. Следует признать, что при наличии виртуальной базы слияния это может быть довольно сложно, так как в виртуальной базе слияния могут возникать конфликты слияния! Посмотрите Git - diff3 Conflict Style - Временная ветвь слияния для примера этого.
Как вы можете установить доминирование
Давайте определим доминантный заголовок с целью обработки конфликтов слияния или потенциальных конфликтов слияния как версию, изменения которой предпочтительны автоматически. В рекурсивных и решающих стратегиях есть простой способ установить это.
Конечно, Git дает нам -s ours
стратегия слияния, которая исключает даже потенциальные конфликты слияния. Но если мы не изменили строку 1, и они это сделали, это все равно использует нашу строку 1, так что это не то, что мы хотим. Мы хотим взять нашу или их строку 1, если только мы или только они изменили ее; мы просто хотим, чтобы Git предпочел нашу голову или их голову строке 1 в случае, когда мыоба ее изменили.
Эти-X ours
а также-X theirs
аргументыстратегии-варианта. 6 Мы по-прежнему используем рекурсивную или решающую стратегию, как и раньше, но мы работаем с-X ours
сказать Git, что в случае конфликта он должен предпочесть наше изменение. Или мы бежим с-X theirs
предпочесть их изменение. В любом случае, Gitне останавливается на конфликте: он принимает предпочтительное (или доминирующее) изменение и давит на него.
Эти опции несколько опасны, потому что они зависят от того, как Git правильно понимает контекст. Git ничего не знает о нашем коде или текстовых файлах: он просто выполняет построчную разность, 7 пытается найти минимальный набор инструкций: "Удалите эту строку здесь, добавьте ее туда, и это выведет вас из версии в базе зафиксировать версию в одной из глав." Следует признать, что это справедливо и для слияний "без предпочтительной / доминирующей головы", но в этих случаях мы не берем одно из их изменений, а затемпереопределяем другое близлежащее "их" изменение с нашим, что, как мы можем попал в беду, особенно в тех файлах, с которыми я работаю.
В любом случае, если вы запускаете обычныйgit merge
без одного из них -X
аргументы, вы получите конфликт, как обычно. Вы можете запуститьgit checkout --ours
в любом файле выбрать нашу версию файла (без каких-либо конфликтов, игнорируя все их изменения), илиgit checkout --theirs
выбрать версию файла (опять же, без конфликтов и игнорируя все наши изменения), или git checkout -m
воссоздать конфликты слияния. Было бы неплохо, если бы была ориентированная на пользователя оболочка для git merge-file
что бы извлечь все три версии из индекса8 и позволить вам использовать --ours
или же --theirs
вести себя как -X ours
или же -X theirs
только для этого одного файла (это то, что означают эти флаги, в git merge-file
). Обратите внимание, что это также должно позволить вам использовать --union
, Увидеть git merge-file
документация для описания того, что это делает. подобно --ours
а также --theirs
это немного опасно и должно использоваться с осторожностью и наблюдением; например, это не работает при объединении файлов XML.
6 Название "вариант стратегии", я думаю, является плохим выбором, так как звучит так же, как -s strategy
аргумент, но на самом деле это совсем другое. Мнемоника, которую я использую, заключается в том, что -s
принимает стратегию, в то время как -X
выбирает расширенную стратегию, которая передается на любую стратегию, которую мы уже выбрали.
7 Вы можете контролировать разницу через .gitattributes
или варианты git diff
, но я не уверен, как первое влияет на встроенные стратегии слияния, поскольку я на самом деле не пробовал.
8 Во время конфликтующего слияния все три версии каждого конфликтующего файла присутствуют в индексе, хотя git checkout
имеет только специальный синтаксис для двух из них. Ты можешь использовать gitrevisions
синтаксис для извлечения базовой версии.
git merge Branch B
Эта команда объединяет B в текущую ветвь (A)
случай 1. Он должен прикреплять содержимое ветви B к A. Когда имена файлов различны или содержимое одних и тех же файлов различно в разных строках B и A.
случай 2. Конфликт слияния, если содержимое одних и тех же файлов отличается в одной строке.
Вы вносите изменения в свою текущую ветвь, поэтому, предполагая, что в данный момент у вас есть ветвь А, вы переносите изменения из ветви В в ветвь А.