it-swarm.com.ru

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

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

397
Mr. Boy

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

Так почему же слияния 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, которые должны быть такими, чтобы просто работать с синхронизацией репозиториев (т. Е. Нажмите и потяните ).

Первое, что вы делаете при работе с DVCS - это клонирование репозиториев (git clone , hg's 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, что в данном случае не происходит. Любая попытка Push приведет к тому, что система скажет что-то похожее на " э-э ... боюсь, я не могу позволить вам сделать это, Боб ".

Таким образом, Боб должен вставить и затем объединить изменения (с git pull ; или hg's 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, вместо этого используется группа обеспечения доступности баз данных, что позволяет выполнять ветвление и объединение более простым способом не только для системы, но и для пользователя.

551
Spoike

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

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

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

29
Andrew Aylett

Проще говоря, реализация слияния выполняется лучше в 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, и, таким образом, он мог бы вкладывать много энергии в реальные алгоритмы слияния, а не пытаться справиться с представлением => практически лучшего слияния.

17
Andreas Krey

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

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

11
daniel kullmann

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

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

10
Peter

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

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

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

8
used2could

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

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

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

6
rubayeet