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