Re point # 2, вы правы, что git сохраняет моментальные снимки. Однако, учитывая любые два моментальных снимка a
и b
, мы можем получить набор изменений (разницу) путем сравнения b
против a
-and git делает именно это, при необходимости.
Что касается самого слияния, во-первых, следует отметить, что мерзавец держит фиксации граф (также называют «ДАГ» или «фиксации DAG», как граф является D irected циклический G raph или DAG). Это то, что показывает ваш вывод git log --graph
, хотя в этом конкретном случае у нас есть такая простая форма графика, что это просто дерево (пока не произойдет слияние).
С любым деревом, и со многими группами DAG, данными два узла в дереве можно найти уникальный L owest C ommon ncestor узел или LCA. Два узла здесь - концы двух ветвей - коммиты cf3456b
(конец вашей текущей ветви) и 81e6490
(кончик master
) - и LCA в данном конкретном случае является первым фиксатором, c667d3b
. В простых случаях, таких как LCA, легко заметить визуально: вы просто просматриваете граф фиксации, чтобы найти место, в которое соединяются две ветви (все оттуда, обратно к корню, находятся на и ветвей).
Этот узел LCA является базовым основанием . Git сначала находит базу слияния текущего фиксации и аргумент, который вы ему даете. (Для «слияния осьминогов», где вы направляете git, чтобы объединить несколько коммитов в текущую ветвь, работа немного больше задействована, но мы можем просто игнорировать их здесь.)
Далее, учитывая существующее основание слияния и два фиксированных кончика, git должен вычислить два набора изменений: один из базы слияния и текущий фиксатор, а один из базы слияния в аргумент commit. Обратите внимание, что git делает это один раз, спереди, для всего процесса, после чего он может продолжить действие слияния.
Теперь, для каждого файла, для которого есть изменения, git должен объединить изменения. Для большинства простых модификаций метод прост: если файл 1
изменен только в одной ветви, выполните модификацию как есть. Если он изменен в обоих ветвях, попытайтесь объединить изменения, взяв только одну копию, где обе ветви сделали одно и то же изменение. Конечно, вы получаете конфликт, если обе ветви делали разные изменения в одну и ту же область одного файла.
Для случаев создания или удаления файлов или при переименовании все становится немного сложнее. Если файл удаляется в одном ветви и нетронутым в другом, git может разрешить это, удалив файл («сохраняя его», если он уже удален в HEAD
, удалив его, если он удален в другой фиксации). Если файл переименован в одну ветку и изменен в другом, git также может объединить эти изменения (выполнение или сохранение переименования при одновременном импорте или сохранении других изменений). Однако для чего-то еще, git просто объявляет конфликт, бросает свои метафорические руки и заставляет вас разрешить конфликт.
В этом случае файл 1
действительно был изменен в вашей текущей ветке и действительно был удален в master
. Git не уверен, следует ли удалять файл (по указанию слияния-базы до master
diff) или сохранять изменения (как указано в базе слиянием до HEAD
diff), поэтому он оставляет вас с файлом и конфликтом.
Если вы создали новый файл 5
в обеих ветвях, git снова даст вам конфликт (за исключением, возможно, если новое содержимое файла одинаково в обеих ветвях - я не тестировал этот случай).
Со сложным DAGs может быть несколько минимальными общими кандидатами предок. Для «рекурсивного» слияния git git обрабатывает это путем слияния каждого кандидата LCA для создания новой «виртуальной базы». Эта виртуальная база становится базой слияния, против которой сравниваются две коммиты.Если есть только два кандидата LCA, виртуальная база слияния получается делать грубый эквивалент:
git checkout -b temp candidate_1
git merge candidate_2
git commit -a -m ""
Любые слияния конфликтов, которые происходят во время этого «внутреннего слияния» игнорируется: конфликтующее слияние базы используются с его конфликты на месте. Это может привести к некоторым конфликтам, похожим на фанки, особенно когда в одном и том же регионе «внешнего» слияния есть конфликт: см. this SO question.
Если есть более двух кандидатов LCA, merge-recursive
берет первые два и объединяет их, затем объединяет третье, затем объединяет четвертое и так далее, итеративно. Он делает это, даже если он может объединять пары, чтобы сократить число пополам, затем объединить объединенные пары, чтобы разрезать его пополам и так далее. То есть, учитывая кандидатов N LCA, можно было бы слить ceil (log2 (N)), а не слияние N-1, но git doesn't-N редко превышает 2 в любом случае.
Потенциальный Дубликат (хотя они и не вдаваться в подробности): http://stackoverflow.com/q/14961255/ 1256452 – torek