Как Git решает проблему слияния?
SVN сделал ветвление намного проще, сделав ветвления действительно дешевыми, но слияния остаются настоящей проблемой в SVN, которую Git якобы решает.
Git достигает этого и как?
(отказ от ответственности: все, что я знаю о Git, основано на лекции Линуса - полный git noob здесь)
5 ответов
Git не предотвратит конфликт в слияниях, но может примирить историю, даже если у них нет общих предков.
(через файл прививки ( .git/info/grafts
), то есть список, по одному на строку, коммита, за которым следуют его родители, который вы можете изменить для этой цели "согласования".)
Так довольно мощный прямо там.
Но чтобы по-настоящему взглянуть на то, "как продуманы слияния", вы можете начать с обращения к самому Линусу и понять, что эта проблема не столько в "алгоритме":
Линус: Я лично, я хочу иметь что-то очень повторяемое и неумное. Что-то, что я понимаю или говорит мне, что это не может сделать это.
И, честно говоря, слияние истории одного файла без учета истории всех других файлов заставляет меня идти "тьфу".Важной частью слияния является не то, как оно обрабатывает конфликты (которые в любом случае должны проверять люди, если они вообще интересны), а то, что оно должно объединять историю вместе, чтобы у вас была новая прочная основа для будущих слияний.,
Другими словами, важной частью является тривиальная часть: именование родителей и отслеживание их отношений. Не столкновения.
И, похоже, 99% людей из SCM думают, что решение этой проблемы - быть более умным в отношении слияния контента. Что полностью упускает из виду.
Итак, Винсент Колаюта добавляет (выделение мое):
Там нет необходимости для причудливых метаданных, отслеживания переименования и так далее.
Единственное, что вам нужно сохранить - это состояние дерева до и после каждого изменения.Какие файлы были переименованы? Какие из них были скопированы? Какие из них были удалены? Какие строки были добавлены? Какие из них были удалены? В какие строки были внесены изменения? Какие текстовые фрагменты были скопированы из одного файла в другой?
Вам не нужно заботиться ни об одном из этих вопросов, и вам, конечно, не нужно хранить специальные данные отслеживания, чтобы помочь вам ответить на них: все изменения в дереве (добавления, удаления, переименования, изменения и т. Д.) Неявно закодирован в дельте между двумя состояниями дерева; Вы просто отслеживаете содержание.Абсолютно все может (и должно) быть выведено.
Git разрушает шаблон, потому что думает о содержимом, а не о файлах.
Он не отслеживает переименования, он отслеживает содержимое. И это происходит на уровне всего дерева.
Это радикальный отход от большинства систем контроля версий.
Не надо пытаться хранить истории файлов; вместо этого он хранит историю на уровне дерева.
Когда вы выполняете diff, вы сравниваете два дерева, а не два файла.Другое принципиально умное дизайнерское решение - как Git сливается.
Алгоритмы слияния умны, но они не пытаются быть слишком умными. Однозначные решения принимаются автоматически, но когда есть сомнение, решать пользователю.
Так и должно быть. Вы не хотите, чтобы машина принимала эти решения за вас. Вы никогда не захотите этого.
Это фундаментальное понимание подхода Git к слиянию: в то время как любая другая система контроля версий пытается стать умнее, Git с радостью называют себя "глупым менеджером контента", и это лучше для него.
В настоящее время общепризнано, что алгоритм трехстороннего слияния (возможно, с такими усовершенствованиями, как обнаружение переименования и работа с более сложной историей), который учитывает версию в текущей ветви ("наша"), версию в объединенной ветви ("их")), и версия общего предка объединенных ветвей ("предок") является (с практической точки зрения) лучшим способом разрешения слияний. В большинстве случаев и для большинства содержимого дерева уровня слияния (какую версию файла взять) достаточно; редко возникает необходимость иметь дело с конфликтами содержимого, и тогда алгоритм diff3 достаточно хорош.
Чтобы использовать трехстороннее объединение, вам необходимо знать общего предка объединенных ветвей (co называется base merge base). Для этого вам нужно знать полную историю между этими ветками. В Subversion до (текущей) версии 1.5 не было (без сторонних инструментов, таких как SVK или svnmerge) отслеживание слияний, то есть запоминание для фиксации слияния, какие родители (какие коммиты) использовались при слиянии. Без этой информации невозможно правильно рассчитать общего предка при наличии повторных слияний.
Примите к сведению следующую диаграмму:
---.---a---.---b---d---.---1 \ / \-.---c/------.---2
(что, вероятно, будет искажено... было бы неплохо иметь возможность рисовать диаграммы ASCII-art здесь).
Когда мы объединяли коммиты 'b' и 'c' (создавая коммит 'd'), общим предком была точка ветвления, коммит 'a'. Но когда мы хотим объединить коммиты '1' и '2', теперь общим предком является коммит 'c'. Без хранения информации о слиянии нам бы пришлось ошибочно сделать вывод, что это коммит 'a'.
Subversion (до версии 1.5) и более ранняя версия CVS затрудняли слияние, потому что вам приходилось самостоятельно вычислять общего предка и вручную предоставлять информацию о предке при выполнении слияния.
Git хранит информацию обо всех родителях коммита (более одного родителя в случае коммита слияния) в объекте коммита. Таким образом, вы можете сказать, что Git хранит DAG (прямой ациклический граф) ревизий, сохраняя и запоминая связи между коммитами.
(Я не уверен, как Subversion решает проблемы, упомянутые ниже)
Кроме того, слияние в Git может решить две дополнительные проблемы: переименование файлов (когда одна сторона переименовывает файл, а другая нет; мы хотим получить переименование и хотим применить изменения к правильному файлу) и слияния перекрещиваются. (более сложная история, когда существует более одного общего предка).
- Переименования файлов во время слияния управляются с использованием эвристического критерия подобия (учитывается как сходство содержимого файла, так и сходство имени пути) при обнаружении переименования. Git определяет, какие файлы соответствуют друг другу в объединенных ветвях (и предках). На практике это работает довольно хорошо для реальных случаев.
- Перекрестные слияния, см. Определение на revctrl.org wiki, (и наличие нескольких баз слияний) управляются с помощью стратегии рекурсивного слияния, которая генерирует единого виртуального общего предка.
Все приведенные выше ответы верны, но я думаю, что они упускают центральную часть легких слияний git для меня. Слияние SVN требует, чтобы вы отслеживали и помнили, что было объединено, и это огромная PITA. Из их документов:
svn merge -r 23:30 file:///tmp/repos/trunk/vendors
Теперь это не убийца, но если вы забудете, будет ли это 23-30 включительно или 23-30 эксклюзивно, или если вы уже объединили некоторые из этих коммитов, вы попадаете в ловушку и вам нужно найти ответы, чтобы избежать повторяющиеся или отсутствующие коммиты. Да поможет вам Бог, если вы ветвите ветку.
С git это просто git merge, и все это происходит без проблем, даже если вы выбрали пару коммитов или сделали множество фантастических вещей из git-land.
Насколько я знаю, алгоритмы слияния не умнее, чем в других системах контроля версий. Однако из-за распределенной природы git нет необходимости в централизованном объединении. Каждый разработчик может в любое время перебазировать или объединить небольшие изменения от других разработчиков в свое дерево, поэтому возникающие конфликты, как правило, меньше.
Git только затрудняет испортить хранилище всех остальных с плохим слиянием.
Единственным реальным преимуществом является то, что Git намного, намного быстрее объединяется, потому что все выполняется локально и написано на C.
SVN, правильно используемый, идеально подходит для использования.