Intereting Posts
Git merge – ручное слияние – форсирование conflic с полной версией старого и нового файла Git: фатальный: не удалось найти удаленный ref my_repo исправить произвольное размещение навсегда Структура и инструменты для большого приложения Java? SVN Двунаправленная синхронизация с использованием svnsync Архитектура управления версиями для большого проекта со многими группами разработчиков Git: Есть способ очистить мой проект для развертывания? лучшая система управления версиями для личного / домашнего использования при разработке с помощью eclipse и python / django Использование системы управления версиями в качестве базы данных Mercurial всегда говорит, что файл обновлялся при изменении ветки Альтернативы управления версиями TortoiseSVN для одного разработчика, только локальное использование Как hg забыть, что .hgignore списки? проверка репозитория SVN, который импортировал каталог с символическими ссылками Есть ли способ удалить историю для одного файла в Mercurial? Интеграция Subversion и Ant

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

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

Solutions Collecting From Web of "Как и / или почему слияние в Git лучше, чем в SVN?"

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

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

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

Первое, что вы делаете, когда работаете с DVCS, – клонировать репозитории ( clone git, clone hg и branch 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 

Решение довольно простое, единственное, что нужно сделать репозиторию оригинала, – это взять все новые версии и переместить его в новую версию (что 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's pull , или hg's pull и merge , или merge bzr). Это двухэтапный процесс. Сначала Боб должен получить новые версии, которые будут скопировать их, как они есть из 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 docs об этом.

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

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

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

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

По умолчанию Git использует трехсторонний алгоритм слияния, который включает в себя поиск общего предка с объединенными головами и использование знаний, существующих по обе стороны слияния. Это позволяет 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 можно --reintegrate автоматически, и б) возможны повторные слияния в обоих направлениях.

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

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

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

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

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

Предположим, вы хотите сделать «какую-то умную вещь». Гит «лучше в». И вы попали в 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.

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

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

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

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

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

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

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

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

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