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

397
задан ks1322 14 September 2016 в 11:07
поделиться

4 ответа

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

Структура версий - это не дерево , а скорее граф . В частности, направленный ациклический граф (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 , что приведет к ускоренному слиянию, поскольку фиксация слияния является прямым потомком последнего в источнике ] репозиторий:

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

553
ответ дан 22 November 2019 в 23:41
поделиться

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

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

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

8
ответ дан 22 November 2019 в 23:41
поделиться

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

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

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

29
ответ дан 22 November 2019 в 23:41
поделиться

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

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

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

6
ответ дан 22 November 2019 в 23:41
поделиться