Как и / или почему слияние в Git лучше, чем в SVN? - PullRequest
397 голосов
/ 18 марта 2010

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

Ответы [ 7 ]

551 голосов
/ 18 марта 2010

Утверждение о том, что слияние лучше в 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, чтобы создать ветку, вам нужно создать в хранилище новый виртуальный каталог 1025 *, в котором будет храниться копия ствола, но это не так. Не храните информацию о том, когда и во что слились обратно. Это иногда может привести к неприятным конфликтам слияния. Еще хуже то, что Subversion по умолчанию использует двустороннее объединение, что имеет некоторые ограничивающие ограничения при автоматическом объединении, когда две ветви ветвей не сравниваются с их общим предком.

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

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

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

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

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

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

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

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

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

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

Так как насчет того, чтобы показать мне пример с real merge?

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

29 голосов
/ 18 марта 2010

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

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

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

17 голосов
/ 21 марта 2013

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

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

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

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

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

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

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

11 голосов
/ 23 марта 2012

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

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

10 голосов
/ 03 марта 2011

РЕДАКТИРОВАТЬ: Это в первую очередь относится к этой части вопроса:
Это на самом деле из-за внутренних различий в работе этих двух систем, или же у конкретных реализаций 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.

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

8 голосов
/ 18 марта 2010

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

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

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

6 голосов
/ 18 марта 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...