Утверждение о том, что слияние лучше в 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 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
, что в данном случае не происходит. Любая попытка толкнуть приведет к тому, что система скажет что-то вроде « Э-э ... Боюсь, я не могу позволить тебе сделать это, Боб ».
Таким образом, Боб должен вставить и затем объединить изменения (с 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
, что приведет к ускоренному слиянию, поскольку коммит слияния является прямым потомком последних в 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, вместо этого используется группа обеспечения доступности баз данных, что позволяет выполнять ветвление и объединение более простым способом не только для системы, но и для пользователя.