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

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

7 ответов


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

так почему же Subversion сливается отстой?

задуматься над этим пример:

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

когда мы хотим слияние изменения 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
этот способ указания диапазонов ревизий быстро выходит из-под контроля, когда дерево версий растет, поскольку subversion не имеет метаданных о том, когда и какие ревизии были объединены вместе. Подумайте о том, что произойдет позже:
           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, которые должны быть такими, чтобы работать просто с синхронизацией репозиториев (т. е. пуш и тянуть).

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

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

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

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

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

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

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

         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, что в данном случае не так. Любая попытка нажать приведет к тому, что система скажет что-то похожее на "эээ... Боюсь, я не могу позволить тебе сделать это, Боб!--31-->."

так что Боб должен остановиться и затем объединить изменения (с помощью Git-ы pull; или НД по pull и merge; или 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 и рт. ст., под названием rebase, который переместит изменения Боба после новейших изменений. Поскольку я не хочу, чтобы этот ответ был более подробным, я позволю вам прочитать git, ртутный или базар документы об этом вместо этого.

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

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

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


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

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

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


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

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

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

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

но после всего этого (что ИМХО показывает отсутствие понимания того, что они делают), я бы (хорошо, я) очень предостерегал использовать SVN в любом нетривиальном сценарий ветвления и в идеале попытается увидеть, что Git думает о результате слияния.

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

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


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

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.

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

это не значит, что это не лучшие инструменты!


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

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


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

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

хорошая читать если у вас есть время: почему я выбрал Git


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

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

Читать статью здесь.