Заявление о том, почему слияние лучше в DVCS, чем в Subversion, во многом основывалось на том, как ветвление и слияние работали в Subversion некоторое время назад. Subversion до 1.5.0 не хранила никакой информации о том, когда были объединены ветки, поэтому, когда вы хотели объединить, вам нужно было указать, какой диапазон ревизий необходимо объединить.
Обдумайте этот пример:
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, виртуальные каталоги - отстой. Почему? Потому что у всех есть доступ к их просмотру ... даже экспериментальным мусором. Ветвление - это хорошо, если вы хотите поэкспериментировать , но не хотите видеть эксперименты всех и их теток . Это серьезный когнитивный шум. Чем больше веток вы добавите, тем больше чуши увидите.
Чем больше у вас открытых веток в репозитории, тем сложнее будет отслеживать все разные ветки. Таким образом, у вас возникнет вопрос, находится ли ветка все еще в разработке или она действительно мертва, что трудно сказать в любой централизованной системе контроля версий.
В большинстве случаев, из того, что я видел, организация все равно по умолчанию использует одну большую ветвь.Это позор, потому что, в свою очередь, будет сложно отслеживать версии тестирования и выпуска, а также все остальное хорошее, что приходит от ветвления.
Причина очень проста: ветвление - первоклассная концепция . Виртуальных каталогов нет по замыслу, а ветки - это жесткие объекты в 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, что позволяет выполнять ветвление и слияние более простым способом не только для системы, но и для пользователя.
SVN отслеживает файлы, в то время как Git отслеживает изменения содержимого . Достаточно умен, чтобы отслеживать блок кода, который был преобразован из одного класса / файла в другой. Они используют два совершенно разных подхода к отслеживанию вашего источника.
Я по-прежнему активно использую SVN, но очень доволен тем, что несколько раз использовал Git.
Хорошее прочтение, если у вас есть время: Почему я выбрал Git
Исторически Subversion использовалась только может выполнить прямое двустороннее слияние, потому что он не хранит никакой информации о слиянии. Для этого нужно взять набор изменений и применить их к дереву. Даже при наличии информации о слиянии это по-прежнему наиболее часто используемая стратегия слияния.
Git по умолчанию использует трехсторонний алгоритм слияния, который включает поиск общего предка для сливаемых голов и использование знаний, существующих на обеих сторонах слияния. Это позволяет Git более разумно избегать конфликтов.
Git также имеет сложный код поиска переименования, который также помогает. Он не хранит наборы изменений и не хранит какую-либо информацию отслеживания - он просто сохраняет состояние файлов при каждой фиксации и использует эвристику для определения местоположения переименований и перемещений кода по мере необходимости (хранение на диске более сложно чем этот, но интерфейс, который он представляет для логического уровня, не предоставляет никакого отслеживания).
Только что прочитал статью в блоге Джоэла (к сожалению, последнюю). Эта статья о Mercurial, но на самом деле в ней говорится о преимуществах распределенных систем ВК, таких как Git.
При распределенном управлении версиями распределенная часть на самом деле не является самая интересная часть. Самое интересное то, что эти системы думают в терминах изменений, а не в терминах версий.
Читайте статью здесь.