Як і / або чому злиття в Git краще, ніж у SVN?


400

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


Я досі не отримав повної відповіді, прочитавши тут чудові відповіді. Повторно - stackoverflow.com/questions/6172037 / ...
ripper234


це залежить від вашої моделі. у більш простих випадках svn часто кращий, тому що він випадково не викликає двосторонніх злиттях, тристоронні злиття, як це може робити git, якщо натиснути / злити / потягнути / натиснути на одну гілку розвитку. дивіться: svnvsgit.com
Ерік Аронесті

Відповіді:


556

Твердження, чому об’єднання краще в 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 and pull ).

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

Структура версії - це не дерево , а скоріше графік . Більш конкретно, спрямований ациклічний графік (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's, що в даному випадку не відповідає. Будь-яка спроба натиснення призведе до того, що система скаже щось схоже на "А-а ... Боюся, не можу дозволити вам зробити це Боб ".

Таким чином, Боб повинен здійснити і потім об'єднати зміни (з 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<--+

Існує ще один варіант , щоб об'єднати в мерзотникові і рт.ст., називається перебазуватися , which'll перемістити зміни Боба в після новітніх змін. Оскільки я не хочу, щоб ця відповідь була більш багатослівною, я дам вам змогу прочитати документи про git , mercurial або базар про це.

Як вправу для читача, спробуйте скласти, як це буде працювати з іншим залученим користувачем. Це робиться аналогічно як приклад вище з Боб. Об’єднання між сховищами простіше, ніж ви думаєте, оскільки всі зміни / комісії однозначно ідентифікуються.

Існує також проблема надсилання патчів між кожним розробником, що було величезною проблемою в Subversion, яка пом'якшується в git, hg та bzr шляхом однозначних ідентифікаційних змін. Після того, як хтось об'єднав свої зміни (тобто здійснив злиття) і відправить їх усім іншим в команді, щоб спожити, натиснувши в центральний сховище або відправивши патчі, тоді їм не доведеться турбуватися про злиття, оскільки це вже сталося . Мартін Фаулер називає такий спосіб роботи безладною інтеграцією .

Оскільки структура відрізняється від Subversion, вона замість цього використовує DAG, вона дозволяє розгалуження та злиття проводити простішим способом не лише для системи, але й для користувача.


6
Я не згоден з вашими гілками == шумовий аргумент. Багато гілок не бентежать людей, оскільки головний розробник повинен сказати людям, яку гілку використовувати для великих функцій ... тож два диви можуть працювати на гілці X, щоб додати "літаючих динозаврів", 3 може працювати на Y, "щоб ви кинули машини у людей »
Містер Хлопчик

16
Джон: Так, для невеликої кількості гілок мало шуму і керовано. Але поверніться після того, як ви стали свідками 50+ гілок і тегів або так у підривному або явному випадку, коли більшість з них ви не можете сказати, активні вони чи ні. Питання про зручність використання інструментів убік; чому все те сміття навколо у вашому сховищі? Принаймні в p4 (оскільки "робоча область" користувача - це по суті гілка на кожного користувача), git або hg ви маєте можливість не повідомляти всім про зміни, які ви робите, доки не натиснете на них вгору, що є безпечним, слідкуйте за тим, коли зміни стосуються інших.
Спойк

24
Я також не знаю, що "занадто багато експериментальних гілок є аргументом шуму, @Spoike. У нас є папка" Користувачі ", де кожен користувач має власну папку. Там він може розгалужуватися так часто, як йому хочеться. Гілки недорогі в Subversion і якщо ви ігноруєте папки інших користувачів (чому б вам це все одно було байдуже), то ви не бачите шуму. Але для мене злиття в SVN не смокче (і я це роблю часто, і ні, це не мало . проект) Так що, може бути , я що - то неправильно;) тим не менше, злиття Git і Mercurial перевершує і ви вказали на це приємно.
Джон Смізерс

11
У svn легко вбити неактивні гілки, їх просто видалити. Те, що люди не знімають невикористані гілки, тому створює безлад - це лише питання ведення господарства. Ви можете так само легко завершити багато тимчасових відділень у Git. На моєму робочому місці ми використовуємо довідник верхнього рівня "temp-branch" на додаток до стандартних - особисті гілки та експериментальні гілки заходять туди замість того, щоб захаращувати каталог гілок, де зберігаються "офіційні" рядки коду (ми не використовувати гілки функцій).
Кен Лю

10
Чи означає це тоді, що з v1.5 підривна робота може принаймні зливатися, як і git can?
Сем

29

Історично Subversion змогла здійснити прямого двостороннього злиття, оскільки не зберігала жодної інформації про злиття. Це передбачає прийняття набору змін та застосування їх до дерева. Навіть з інформацією про злиття, це все ще є найбільш часто використовуваною стратегією злиття.

Git використовує тристоронній алгоритм злиття за замовчуванням, який передбачає пошук спільного предка для об'єднання голів та використання знань, які існують по обидва боки злиття. Це дозволяє Git бути більш розумним уникнути конфліктів.

У Git також є складний код пошуку перейменування, який також допомагає. Він не зберігає набори змін і не зберігає інформацію про відстеження - він просто зберігає стан файлів при кожній комісії і використовує евристику для пошуку перейменувань і переміщень коду, як потрібно (зберігання на диску складніше, ніж це, але інтерфейс він представляє логічний шар, не відкриває відстеження).


4
Чи є у вас приклад того, що svn має конфлікт злиття, але git ні?
Gqqnbig

17

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

Також SVN вимагає відстежувати переймена, щоб сприяти об'єднанню переміщених файлів. Але насправді він все ще зберігає їх як копію та окрему дію видалення, і алгоритм злиття все ще натикається на них у ситуаціях модифікації / перейменування, тобто коли файл модифікується на одній гілці та перейменується на іншій, а ці гілки - бути злитим. Такі ситуації все ще створюватимуть помилкові конфлікти злиття, а у випадку перейменування каталогів це навіть призводить до беззвучної втрати модифікацій. (Потім люди SVN, як правило, зазначають, що зміни все ще є в історії, але це не дуже допомагає, коли вони не мають результату злиття, де вони повинні з'явитися.

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

У представництві злиття SVN також є проблеми; в 1.5 / 1.6 ви могли зливатись від стовбура до гілки так часто, як тільки сподобалось, автоматично, але злиття в іншому напрямку потрібно було оголосити ( --reintegrate) і залишити гілку в непридатному стані. Набагато пізніше вони з’ясували, що це насправді не так, і що а) --reintegrate може визначити автоматично, і б) можливі повторні злиття в обох напрямках.

Але після всього цього (що IMHO виявляє нерозуміння того, що вони роблять), я б (гаразд, я) дуже застеріг використовувати SVN у будь-якому нетривіальному сценарії розгалуження, і в ідеалі спробував би зрозуміти, що думає Git результат злиття.

Інші моменти, зроблені у відповідях, як вимушена глобальна видимість філій у SVN, не стосуються можливостей злиття (але для зручності використання). Крім того, "Git магазини змінюються, а магазини SVN (щось інше)" здебільшого не вдається. Git концептуально зберігає кожну команду як окреме дерево (як файл tar ), а потім використовує досить евристику для ефективного зберігання. Обчислення змін між двома комітами є окремим від реалізації сховища. Що правда, це те, що Git зберігає історію DAG у набагато простішій формі, ніж SVN робить свою об'єднану інформацію. Кожен, хто намагається зрозуміти останнє, дізнається, що я маю на увазі.

Коротше кажучи: Git використовує набагато простішу модель даних для зберігання ревізій, ніж SVN, і, таким чином, вона може вкласти багато енергії в фактичні алгоритми злиття, а не намагатися впоратися з поданням => практично краще злиття.


11

Одне, що не було згадано в інших відповідях, і що насправді є великою перевагою DVCS, - це те, що ви можете скористатися локально, перш ніж натиснути на зміни. У SVN, коли я змінився, я хотів зареєструватися, і хтось тим часом вже здійснив поступку на одній гілці, це означало, що я повинен був зробити ще до того, svn updateяк я міг здійснити. Це означає, що мої зміни та зміни від іншої людини зараз змішані між собою, і немає ніякого способу відмінити злиття (як, наприклад, з ) git resetабо hg update -Cтому, що немає ніяких зобов’язань повертатися назад. Якщо злиття нетривіальне, це означає, що ви не можете продовжувати працювати над вашою функцією, перш ніж очистити результат злиття.

Але тоді, можливо, це лише перевага для людей, які занадто німі використовують окремі гілки (якщо я правильно пам’ятаю, у нас була лише одна гілка, яка використовувалася для розвитку ще в компанії, де я використовував SVN).


10

EDIT: Це в першу чергу стосується цієї частини питання:
чи насправді це пов'язано з притаманними відмінностями в роботі обох систем, або конкретні реалізації DVCS, такі як Git / Mercurial, просто мають розумніші алгоритми злиття, ніж SVN?
TL; DR - ці конкретні інструменти мають кращі алгоритми. Розподіл має певні переваги робочого процесу, але є ортогональним для переваг, що об'єднуються.
END EDIT

Я читаю прийняту відповідь. Це просто неправильно.

Злиття 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 / централізованому ЦЗ.

Це не означає, що вони не кращі інструменти!


1
Так, я відповів на питання детально, а не в заголовку. svn і git мають доступ до тієї ж інформації (насправді типово svn є більше), тому svn може робити все, що робить git. Але вони приймали різні дизайнерські рішення, і це насправді не так. Доказом DVC / централізованого є те, що ви можете запускати git як централізований VC (можливо, з певними правилами) і ви можете запускати svn розподілений (але він повністю відстійний). Однак для більшості людей це все занадто академічно - git і hg роблять розгалуження та злиття краще, ніж svn. Це дійсно важливо при виборі інструменту :-).
Петро

5
До версії 1.5 Subversion не зберігала всю необхідну інформацію. Інформація, що зберігається з SVN після 1,5, зберігається в іншому: Git зберігає всіх батьків об'єднання, тоді як Subversion зберігає те, що редакції вже були об'єднані у відділення.
Якуб Нарубський

4
Інструмент, який важко повторно реалізувати у сховищі svn, це git merge-base. За допомогою git ви можете сказати, "гілки a і b розділені при редакції x". Але в svn магазинах "файли були скопійовані з foo в бар", тому вам потрібно використовувати евристику, щоб визначити, що копія в бар створювала нову гілку замість копіювання файлів в рамках проекту. Хитрість полягає в тому, що ревізія в svn визначається номером версії та базовим шляхом. Хоча більшу частину часу можна припустити "стовбур", він кусається, якщо насправді є гілки.
Дуглас

2
Re: "Немає інформації, яка git зберігає або може вивести, що svn також не зберігається і не може виводитись." - Я виявив, що SVN не пам'ятав, коли речі були об'єднані. Якщо ви хочете перетягнути роботу зі стовбура у свою гілку та йти туди-сюди, то злиття може стати важким. У Git кожен вузол у своєму графіку перегляду знає, звідки він узявся. У ньому є до двох батьків та деякі місцеві зміни. Я би довіряв Git мати можливість об'єднати більше, ніж SVN. Якщо ви об'єднаєтесь у SVN та видалите гілку, то історія гілки втрачається. Якщо ви об'єднаєтесь у GIT та видалите гілку, графік залишається, а з ним плагін "винуватий".
Річард Корфілд

1
Чи не так, що git і mercurial мають всю необхідну інформацію на локальному рівні, хоча svn для одержання інформації має шукати як локальні, так і центральні дані?
Warren Dew

8

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

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

Приємно прочитайте, якщо у вас є час: Чому я вибрав Git


Це я теж читав, і саме на це я розраховував, але на практиці це не працює.
Рольф

Git відстежує вміст файлів, він показує вміст лише як зміни
Ferrybig

6

Просто прочитайте статтю в блозі Джоеля (на жаль, його останню). Це стосується Mercurial, але насправді йдеться про переваги розподілених систем ВК, таких як Git.

З розподіленим контролем версій розподілена частина насправді не найцікавіша частина. Цікава частина полягає в тому, що ці системи думають з точки зору змін, а не з приводу версій.

Прочитайте статтю тут .


5
Це була одна із статей, про які я думав, перш ніж розміщувати тут. Але "думає з точки зору змін" - це дуже невиразний маркетинговий звуковий термін (згадаймо, що компанія Джоела зараз продає DVCS)
Пан Хлопчик

2
Я також вважав, що це було невиразно ... Я завжди вважав, що набори змін є невід'ємною частиною версій (або, скоріше, версій), що дивує мене, що деякі програмісти не думають з точки зору змін.
Спойк

Для системи, яка насправді "думає з точки зору змін", перегляньте Darcs
Макс

@Max: звичайно, але коли наштовхнеться на поштовх, Git доставляє туди, де Darcs є таким же болючим, як Subversion, коли справа стосується насправді злиття.
трійчатка

Три недоліки Git: а) це не так добре для таких бінарних файлів, як управління документами, де дуже малоймовірно, що люди захочуть розгалужуватися та об'єднуватись b) він передбачає, що ви хочете клонувати ВСЕ, c) він зберігає історію всього в клоні навіть для часто мінливих бінарних файлів, що спричиняють клоновий набряк. Я думаю, що централізований VCS набагато кращий для тих випадків використання. Git набагато кращий для регулярного розвитку, особливо для злиття та розгалуження.
locka
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.