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

400

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

Мистер Бой
источник
Я до сих пор не получил полный ответ от прочтения великих ответов здесь. Повторно размещено - stackoverflow.com/questions/6172037/…
ripper234
Смотрите также: stackoverflow.com/questions/2475831/merging-hg-git-vs-svn
Якуб Наребски
это зависит от вашей модели. в более простых случаях svn часто лучше, потому что он не вызывает двухсторонние слияния, как это делает git, если вы нажимаете / merge / pull / push на одну ветку разработки. смотрите: svnvsgit.com
Эрик Аронесты

Ответы:

556

Утверждение о том, что слияние лучше в 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

Когда мы хотим объединить изменения 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 все еще отстой…

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

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

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

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

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

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

Структура версий - это не дерево , а график . Точнее говоря, ориентированный ациклический граф (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 .

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

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

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

Spoike
источник
6
Я не согласен с вашими ветками == шумовой аргумент. Множество веток не смущает людей, потому что ведущий разработчик должен сказать людям, какую ветвь использовать для больших функций ... поэтому два ветки могут работать над веткой X, чтобы добавить «летающих динозавров», 3 может работать с Y, чтобы «позволить вам бросить». автомобили у людей "
Mr. Boy
16
Джон: Да, для небольшого количества веток мало шума и он управляем. Но вернитесь после того, как вы увидели более 50 веток и тегов или около того в подрывной деятельности или явном случае, когда большинство из них вы не можете определить, активны они или нет. Юзабилити проблема от инструментов в стороне; почему весь этот мусор в вашем хранилище? По крайней мере, в p4 (поскольку «рабочее пространство» пользователя по сути является ветвью для каждого пользователя), git или hg у вас есть возможность не сообщать всем об изменениях, которые вы делаете, до тех пор, пока вы не отправите их вверх по течению, что следить за тем, когда изменения актуальны для других.
Спойк
24
Я не понимаю, что "слишком много экспериментальных веток также являются аргументом шума", @Spoike. У нас есть папка "Users", где у каждого пользователя есть своя собственная папка. Там он может переходить так часто, как ему хочется. если вы игнорируете папки других пользователей (почему вы все равно должны заботиться о них), то вы не видите шума, но для меня объединение в SVN не отстой (и я делаю это часто, и нет, это не маленький проект). Так что, возможно, я делаю что-то не так;) Тем не менее, слияние Git и Mercurial превосходно, и вы хорошо это отметили
Джон Смитерс
11
В svn легко убивать неактивные ветки, вы просто удаляете их. Тот факт, что люди не удаляют неиспользуемые ветки, поэтому создают беспорядок, это просто вопрос домашнего хозяйства. Вы также можете легко найти множество временных веток в Git. На моем рабочем месте мы используем каталог верхнего уровня «temp-ветки» в дополнение к стандартным - туда идут личные ветки и экспериментальные ветки вместо того, чтобы загромождать директорию веток, где хранятся «официальные» строки кода (мы не делаем использовать функцию ветки).
Кен Лю
10
Значит ли это, что из v1.5 subversion может по крайней мере сливаться так же, как и git?
Сэм
29

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

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

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

Эндрю Айлетт
источник
4
У вас есть пример, что у SVN есть конфликт слияния, а у Git нет?
Gqqnbig
17

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

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

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

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

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

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

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

Андреас Крей
источник
11

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

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

Даниэль Куллманн
источник
10

РЕДАКТИРОВАТЬ: Это в первую очередь касается этой части вопроса:
это на самом деле из-за внутренних различий в работе этих двух систем, или конкретные реализации DVCS, такие как Git / Mercurial, просто имеют более умные алгоритмы слияния, чем SVN?
TL; DR - эти конкретные инструменты имеют лучшие алгоритмы. Распределение имеет некоторые преимущества рабочего процесса, но ортогонально преимуществам объединения.
КОНЕЦ РЕДАКТИРОВАНИЯ

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

Слияние 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.

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

Питер
источник
1
Да, я ответил на вопрос в деталях, а не в заголовке. svn и git имеют доступ к одной и той же информации (на самом деле обычно svn имеет больше), поэтому svn может делать все, что делает git. Но они принимали разные дизайнерские решения, и на самом деле это не так. На DVC / centralized доказательство состоит в том, что вы можете запускать git как централизованный VC (возможно, с некоторыми наложенными правилами), и вы можете запускать распределенный svn (но он полностью отстой). Однако, это слишком академично для большинства людей - git и hg делают ветвления и слияния лучше, чем svn. Это действительно важно при выборе инструмента :-).
Питер
5
До версии 1.5 Subversion не хранила всю необходимую информацию. Wven с SVN после 1.5 хранит информацию иначе: Git хранит всех родителей коммитов слияния, в то время как Subversion хранит, какие ревизии уже были объединены в ветке.
Якуб Наребски
4
Инструмент, который трудно повторно реализовать в репозитории SVN git merge-base. С помощью git вы можете сказать: «ветви a и b разделены на ревизии x». Но svn хранит «файлы были скопированы из foo в bar», поэтому вам нужно использовать эвристику, чтобы понять, что копирование в bar создавало новую ветку вместо копирования файлов в проекте. Хитрость в том, что ревизия в svn определяется номером ревизии и базовым путем. Несмотря на то, что большую часть времени можно предположить «ствол», он кусается, если на самом деле есть ветви.
Дуглас
2
Re: «Нет информации, которую git хранит или может получить, которую svn также не хранит или может извлечь». - Я обнаружил, что SVN не помнит, когда все было объединено. Если вам нравится тянуть работу из ствола в вашу ветку и идти туда-сюда, то объединение может стать трудным делом. В Git каждый узел в своем графе ревизий знает, откуда он взялся. У него до двух родителей и некоторые локальные изменения. Я бы поверил, что Git сможет объединить больше, чем SVN. Если вы объединяетесь в SVN и удаляете ветку, тогда история ветки теряется. Если вы объединитесь в GIT и удалите ветку, график останется, а вместе с ним и плагин "обвинить".
Ричард Корфилд
1
Разве это не тот случай, когда у git и mercurial есть вся необходимая информация локально, хотя svn нужно искать как локальные, так и центральные данные для получения информации?
Уоррен Дью
8

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

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

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

used2could
источник
Это то, что я тоже читал, и на это я рассчитывал, но на практике это не работает.
Рольф
Git отслеживает содержимое файлов, он показывает содержимое только как изменения
Ferrybig
6

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

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

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

rubayeet
источник
5
Это была одна из статей, о которой я думал, прежде чем писать здесь. Но «мыслить в терминах изменений» - это очень расплывчатый маркетинговый термин (помните, что компания Джоэла сейчас продает DVCS)
Мистер Бой
2
Я также думал, что это было расплывчато ... Я всегда думал, что наборы изменений являются неотъемлемой частью версий (или, скорее, ревизий), что удивляет меня, что некоторые программисты не думают с точки зрения изменений.
Спойк
Для системы, которая действительно "думает в терминах изменений", проверьте Darcs
Макс
@Max: конечно, но когда дело доходит до толчка, Git показывает, где Darcs в основном так же болезнен, как Subversion, когда дело доходит до слияния.
tripleee
Git имеет три недостатка: а) он не так хорош для бинарных файлов, как управление документами, где очень маловероятно, что люди захотят разветвляться и объединяться б) он предполагает, что вы хотите клонировать ВСЕ, в) он хранит историю всего в клоне, даже для часто меняющихся двоичных файлов, вызывающих раздувание клонов. Я думаю, что централизованная VCS намного лучше для этих случаев использования. Git лучше подходит для регулярной разработки, особенно для слияния и ветвления.
Лока