Как и/или почему слияние в Git лучше, чем в SVN?

В нескольких местах я слышал, что одна из основных причин, по которым блистают системы управления распределенной версией, намного лучше объединяется, чем в традиционных инструментах, таких как SVN. На самом деле это связано с присущими различиями в работе двух систем или конкретными реализациями DVCS, такими как Git/Mercurial, только есть более умные алгоритмы слияния, чем SVN?

Ответы

Ответ 1

Утверждение о том, почему слияние лучше в DVCS, чем в Subversion, во многом было основано на том, как ветвление и слияние работали в Subversion некоторое время назад. Subversion до 1.5.0 не хранит никакой информации о том, когда ветки были объединены, поэтому, когда вы хотели слить, вам нужно было указать, какой диапазон изменений, которые должны были быть объединены.

Итак, почему Subversion сливается сосать?

Обдумайте этот пример:

      1   2   4     6     8
trunk o-->o-->o---->o---->o
       \
        \   3     5     7
b1       +->o---->o---->o

Когда мы хотим merge, b1 меняет в туловище, что мы выпустим следующую команду, стоя в папке с проверкой соединительной линии из:

svn merge -r 2:7 {link to branch b1}

..., который попытается объединить изменения из b1 в ваш локальный рабочий каталог. И затем вы фиксируете изменения после устранения любых конфликтов и проверяете результат. При фиксации дерева ревизий будет выглядеть так:

      1   2   4     6     8   9
trunk o-->o-->o---->o---->o-->o      "the merge commit is at r9"
       \
        \   3     5     7
b1       +->o---->o---->o

Однако этот способ определения диапазонов ревизий быстро выходит из-под контроля, когда дерево версий растет, поскольку подрывная деятельность не содержит метаданных о том, когда и какие изменения слились вместе. Подумайте, что будет дальше:

           12        14
trunk  …-->o-------->o
                                     "Okay, so when did we merge last time?"
              13        15
b1     …----->o-------->o

В основном это проблема дизайна репозитория, который имеет Subversion, для создания ветки вам нужно создать новый виртуальный каталог в репозитории, в котором будет размещена копия соединительной линии, но она не хранит никакой информации о когда и какие вещи снова слились. Это иногда приводит к неприятным конфликтам слияния. Что еще хуже, так это то, что Subversion использовала двухстороннее слияние по умолчанию, что имеет некоторые ограничения в автоматическом слиянии, когда две ветки ветки не сравниваются с их общим предком.

Чтобы смягчить эту Subversion, теперь хранятся метаданные для ветки и слияния. Это правильно разрешит все проблемы?

И, кстати, Subversion все еще сосет...

В централизованной системе, такой как подрывная деятельность, виртуальные каталоги сосут. Зачем? Потому что у каждого есть доступ, чтобы просмотреть их... даже мусорные экспериментальные. Ветвление хорошее, если вы хотите поэкспериментировать , но вы не хотите экспериментировать с вашими тетками. Это серьезный когнитивный шум. Чем больше веток вы добавите, тем больше дерьма вы увидите.

Чем больше государственных веток у вас в репозитории, тем сложнее будет отслеживать все разные ветки. Таким образом, вопрос, который у вас будет, заключается в том, что ветка все еще находится в разработке или если она действительно мертва, что трудно сказать в любой централизованной системе контроля версий.

В большинстве случаев, из того, что я видел, организация по умолчанию будет использовать одну большую ветку. Какой позор, потому что, в свою очередь, будет сложно отслеживать версии тестирования и выпуска, а также что бы то ни было хорошее от ветвления.

Итак, почему DVCS, например Git, Mercurial и Bazaar, лучше, чем Subversion при ветвлении и слиянии?

Существует очень простая причина: ветвление - это первоклассная концепция. Нет никаких виртуальных каталогов по дизайну, а ветки - это жесткие объекты в DVCS, которые должны быть такими, чтобы работать просто с синхронизацией репозиториев (т.е. Push и pull).

Первое, что вы делаете, когда работаете с DVCS, - клонировать репозитории (git clone, hg clone и bzr branch). Клонирование концептуально то же самое, что создание ветки в управлении версиями. Некоторые называют это разветвление или разветвление (хотя последнее часто также используется для обозначения совместно расположенных ветвей), но это одно и то же. Каждый пользователь запускает собственный репозиторий, что означает, что вы продолжаете разветвление каждого пользователя.

Структура версии не дерево, а скорее graph. Более конкретно направленный ациклический график (DAG, что означает график, который не имеет циклов). Вам действительно не нужно останавливаться на специфике DAG, кроме того, что каждая команда имеет одну или несколько родительских ссылок (на основе которых была основана фиксация). Таким образом, следующие графики показывают стрелки между ревизиями в обратном направлении из-за этого.

Очень простой пример слияния будет таким; представьте себе центральный репозиторий под названием origin и пользователь Алиса, клонирующий репозиторий к своей машине.

         a…   b…   c…
origin   o<---o<---o
                   ^master
         |
         | clone
         v

         a…   b…   c…
alice    o<---o<---o
                   ^master
                   ^origin/master

Что происходит во время клонирования, так это то, что каждая ревизия копируется в Алису точно так, как она была (которая проверена однозначно идентифицируемым идентификатором хэша), и отмечает, где находятся ветки происхождения.

Затем Алиса работает над своим репо, совершая в своем собственном репозитории и решает подтолкнуть ее изменения:

         a…   b…   c…
origin   o<---o<---o
                   ^ master

              "what'll happen after a push?"


         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                   ^origin/master

Решение довольно просто, единственное, что нужно сделать репозиторию origin, - это взять все новые ревизии и перенести его ветвь на новую версию (которая git вызывает "быструю перемотку вперед" ):

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

         a…   b…   c…   d…   e…
alice    o<---o<---o<---o<---o
                             ^master
                             ^origin/master

В примере использования, который я проиллюстрировал выше, даже не нужно объединять что-либо. Таким образом, проблема действительно заключается не в слиянии алгоритмов, поскольку алгоритм трехстороннего слияния практически одинаковый между всеми системами управления версиями. Проблема больше связана с структурой, чем чем-либо.

Итак, как насчет того, чтобы вы показали мне пример с реальным слиянием?

Правда, приведенный выше пример - очень простой пример использования, поэтому позволяет сделать гораздо более скрученный, хотя и более общий. Помните, что origin началось с трех версий? Ну, парень, который их сделал, позвонил ему Бобу, работал самостоятельно и сделал фиксацию в своем собственном хранилище:

         a…   b…   c…   f…
bob      o<---o<---o<---o
                        ^ master
                   ^ origin/master

                   "can Bob push his changes?" 

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Теперь Боб не может напрямую перенаправить свои изменения в репозиторий origin. Как система обнаруживает это, проверяя, что Боб пересмотрит прямые спуска с origin, что в этом случае нет. Любая попытка подтолкнуть приведет к тому, что система скажет что-то похожее на "Э-э... Я боюсь, не могу позволить вам сделать этого Боба. "

Итак, Бобу нужно втягивать, а затем объединять изменения (с помощью git pull; hg pull и merge; или bzr merge). Это двухэтапный процесс. Сначала Боб должен получить новые версии, которые будут скопировать их, как они из репозитория origin. Теперь мы можем видеть, что график расходится:

                        v master
         a…   b…   c…   f…
bob      o<---o<---o<---o
                   ^
                   |    d…   e…
                   +----o<---o
                             ^ origin/master

         a…   b…   c…   d…   e…
origin   o<---o<---o<---o<---o
                             ^ master

Второй шаг процесса вытягивания состоит в объединении расходящихся кончиков и совершении фиксации результата:

                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+
                             ^ origin/master

Надеемся, что слияние не столкнется с конфликтами (если вы их ожидаете, вы можете выполнить два шага вручную в git с помощью fetch и merge). Что еще нужно сделать, так это снова включить эти изменения в origin, что приведет к ускоренному слиянию, поскольку комманда слияния является прямым потомком последнего в репозитории origin:

                                 v origin/master
                                 v master
         a…   b…   c…   f…       1…
bob      o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

                                 v master
         a…   b…   c…   f…       1…
origin   o<---o<---o<---o<-------o
                   ^             |
                   |    d…   e…  |
                   +----o<---o<--+

Существует еще один вариант слияния в git и hg, называемый rebase, который переместит изменения Боба после новых изменений. Поскольку я не хочу, чтобы этот ответ был более подробным, я дам вам прочитать git, mercurial или bazaar docs об этом.

Как упражнение для читателя, попробуйте выяснить, как он будет работать с другим вовлеченным пользователем. Аналогичным образом это делается как пример выше с Бобом. Слияние между репозиториями проще, чем вы думаете, потому что все ревизии/фиксации однозначно идентифицируются.

Также существует проблема отправки патчей между каждым разработчиком, что является огромной проблемой в Subversion, которая смягчается в Git, hg и bzr с помощью однозначно идентифицируемых ревизий. Когда кто-то объединил свои изменения (т.е. Совершил слияние) и отправил их для всех остальных в команде, чтобы потреблять, либо нажав на центральный репозиторий, либо отправив патчи, тогда им не нужно беспокоиться о слиянии, потому что это уже произошло, Мартин Фаулер называет этот способ работы неразборчивой интеграции.

Поскольку структура отличается от Subversion, вместо этого использует DAG, она позволяет легче и проще выполнять разветвление и слияние не только для системы, но и для пользователя.

Ответ 2

Исторически, Subversion удалось выполнить прямое двухстороннее слияние, поскольку оно не хранило никакой информации о слиянии. Это включает в себя принятие набора изменений и применение их к дереву. Даже с информацией о слиянии, это по-прежнему наиболее часто используемая стратегия слияния.

Git по умолчанию использует трехсторонний алгоритм слияния, который включает поиск общего предка с объединенными головами и использование знаний, которые существуют по обе стороны слияния. Это позволяет Git быть более интеллектуальным во избежание конфликтов.

Git также имеет сложный код поиска переименования, что также помогает. Он не хранит набор изменений или не хранит никакой информации отслеживания - он просто сохраняет состояние файлов при каждой фиксации и использует эвристику для поиска переименований и перемещений кода по мере необходимости (хранилище на диске более сложное, чем это, но интерфейс он представляет логический уровень, не обнаруживает слежения).

Ответ 3

Проще говоря, реализация слияния выполняется лучше в Git, чем в SVN. До 1.5 SVN не записывал действие слияния, поэтому он не мог выполнять будущие слияния без помощи пользователя, который должен был предоставить информацию, которую SVN не записывал. С 1.5 он стал лучше, и действительно, модель хранения SVN немного более способна, чем Git DAG. Но SVN сохранил информацию о слиянии в довольно запутанной форме, которая позволяет слияниям брать больше времени, чем в Git - во время выполнения я наблюдал коэффициенты 300.

Кроме того, SVN заявляет, что отслеживает переименования, чтобы помочь слияния перемещенных файлов. Но на самом деле он по-прежнему хранит их как копию и отдельное действие удаления, и алгоритм слияния все еще натыкается на них в ситуациях изменения/переименования, то есть где файл изменяется на одну ветвь и переименовывается на другой, и эти ветки для объединения. Такие ситуации будут по-прежнему создавать ложные конфликты слияния, а в случае переименования каталогов это даже приводит к бесшумной потере модификаций. (Люди SVN затем склонны указывать, что изменения все еще находятся в истории, но это не очень помогает, когда они не находятся в результате слияния, где они должны появляться.

Git, с другой стороны, даже не отслеживает переименования, но показывает их после факта (во время слияния) и делает это довольно магически.

В представлении слияния SVN также есть проблемы; в 1.5/1.6 вы могли бы сливаться с туловища на ветку так часто, как просто любили, автоматически, но нужно было объявить о слиянии в другом направлении (--reintegrate) и оставить ветвь в непригодном для использования состоянии. Много позже они выяснили, что это фактически не так, и что a) --reintegrate можно вычислить автоматически, и b) возможны повторные слияния в обоих направлениях.

Но после всего этого (что имхо показывает отсутствие понимания того, что они делают), я бы (ОК, я) очень осторожно использовал SVN в любом нетривиальном ветвящемся сценарии и в идеале попытался бы увидеть, что Git думает о результате слияния.

Другие точки, сделанные в ответах, как принудительная глобальная видимость ветвей в SVN, не имеют отношения к возможностям слияния (но для удобства использования). Кроме того, "Git сохраняет изменения, в то время как хранилища SVN (что-то другое)" в основном не совпадают. Git концептуально сохраняет каждую фиксацию как отдельное дерево (например, файл tar), а затем использует довольно эвристику для эффективного хранения. Вычисление изменений между двумя коммитами отличается от реализации хранилища. Верно то, что Git хранит историю DAG в гораздо более простой форме, которую SVN выполняет с помощью mergeinfo. Любой, кто пытается понять последнего, будет знать, что я имею в виду.

В двух словах: Git использует гораздо более простую модель данных для хранения версий, чем SVN, и, таким образом, она может вложить много энергии в алгоритмы реального слияния, а не пытаться справиться с представлением = > практически лучшим слиянием.

Ответ 4

Я прочитал принятый ответ. Это просто неправильно.

SVN слияние может быть болью, и это также может быть громоздким. Но, игнорируйте, как это работает на минуту. Нет информации о том, что Git хранит или может выводить, что SVN не сохраняет и не может выводить. Что еще более важно, нет причин, по которым сохранение отдельных (иногда частичных) копий системы контроля версий предоставит вам более актуальную информацию. Две структуры полностью эквивалентны.

Предположим, вы хотите сделать "какую-то умную вещь" Git "лучше в". И вы попали в SVN.

Преобразуйте SVN в эквивалентную форму Git, сделайте это в Git, а затем проверьте результат, возможно, используя несколько коммитов, некоторые дополнительные ветки. Если вы можете представить себе автоматизированный способ превращения проблемы SVN в проблему Git, то Git не имеет фундаментального преимущества.

В конце дня любая система контроля версий позволит мне

1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.

Кроме того, для слияния это также полезно (или критично) знать

3. The set of changes have been merged into a given branch/revision.

Mercurial, Git и Subversion (теперь изначально, ранее используя svnmerge.py) могут предоставить все три части информации. Чтобы продемонстрировать что-то принципиально лучше с DVC, просьба указать четвертую часть информации, доступную в Git/Mercurial/DVC, недоступную в SVN/централизованном VC.

Чтобы не сказать, что они не лучшие инструменты!

Ответ 5

Одна вещь, которая не упоминалась в других ответах и ​​которая действительно является большим преимуществом DVCS, заключается в том, что вы можете совершать локальные действия, прежде чем нажимать свои изменения. В SVN, когда у меня было какое-то изменение, я хотел зарегистрироваться, и кто-то уже совершил фиксацию в той же ветки, тем временем, это означало, что я должен был сделать svn update, прежде чем смог бы совершить. Это означает, что мои изменения и изменения от другого человека теперь смешаны вместе, и нет возможности прервать слияние (например, с помощью git reset или hg update -C), потому что не требуется фиксации для возврата. Если слияние является нетривиальным, это означает, что вы не можете продолжать работу над своей функцией до того, как вы очистите результат слияния.

Но тогда, возможно, это только преимущество для людей, которые слишком глупы, чтобы использовать отдельные ветки (если я правильно помню, у нас была только одна ветка, которая использовалась для разработки в компании, где я использовал SVN).

Ответ 6

SVN отслеживает файлы, а Git отслеживает изменения . Он достаточно умен, чтобы отслеживать блок кода, который был реорганизован из одного класса/файла в другой. Они используют два совершенно разных подхода к отслеживанию вашего источника.

Я все еще использую SVN сильно, но я очень доволен тем, что я использовал Git.

Хорошее чтение, если у вас есть время: Почему я выбрал Git

Ответ 7

Просто прочитайте статью о блоге Джоэла (к сожалению, его последняя). Это касается Mercurial, но на самом деле он говорит о преимуществах распределенных систем VC, таких как Git.

При распределенном управлении версиями распределенная часть на самом деле не является самая интересная часть. Интересная часть состоит в том, что эти системы рассматривают с точки зрения изменений не в терминах версий.

Прочитайте статью здесь.