Действительно, конкретный пример, что слияние в Git проще, чем SVN?

Вопрос Как и/или почему слияние в Git лучше, чем в SVN? - отличный вопрос с некоторыми отличными ответами. Однако ни один из них не показывает простой пример, в котором слияние в Git работает лучше, чем SVN.

В случае, если этот вопрос будет закрыт как дубликат, то что

  • Конкретный сценарий слияния
  • Как это сложно в SVN?
  • Насколько проще слияние в Git?

Несколько точек:

  • Никакой философии или глубоких объяснений того, что такое DVCS, пожалуйста. Это действительно здорово, но я не хочу, чтобы их детали обманывали ответ на этот вопрос (IMHO).
  • На данный момент меня не интересует "исторический SVN". Пожалуйста, сравните современный Git (1.7.5) с современным SVN (1.6.15).
  • Никаких переименований - я знаю, что Git обнаруживает переименования и перемещения, а SVN - нет. Это здорово, но я ищу что-то более глубокое и пример, который не включает переименования или перемещения.
  • Нет переадресации или другой расширенной операции Git. Просто покажите мне, пожалуйста, слияние.

Ответ 1

С практической точки зрения слияние традиционно было "трудным" из-за того, что я называю проблемой "большого слияния". Предположим, что разработчик некоторое время отработал некоторый код и еще не выполнил свою работу (возможно, разработчик привык работать в Subversion против trunk и не делает незавершенного кода). Когда разработчик, наконец, совершит сделку, будет внесено множество изменений, которые будут свернуты в один коммит. Для других разработчиков, которые хотят объединить свою работу с этим "большим взломом", инструмент VCS не будет иметь достаточной информации о том, как первый разработчик добрался до точки, которую они совершили, поэтому вы просто получите "здесь гигантский конфликт" в этой целой функции, исправьте это ".

С другой стороны, обычный стиль работы с Git и другими DVCS, которые имеют дешевые локальные ветки, заключается в регулярной фиксации. После того, как вы сделали один бит работы, который в значительной степени имеет смысл, вы его совершаете. Он не должен быть совершенным, но он должен быть последовательной единицей работы. Когда вы вернетесь к слиянию, у вас все еще есть история мелких коммитов, которая показывает, как вы попали из исходного состояния в текущее состояние. Когда DVCS собирается объединить это с работой других, у него есть намного больше информации о том, какие изменения были внесены, когда вы заканчиваете тем, что становились все меньше и меньше конфликтов.

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

Ответ 2

Я могу только рассказать вам о небольшом эксперименте: Git НЕ лучше, чем Subversion (те же проблемы).

Мне было интересно узнать об этом случае: Вы начинаете с двух ветвей "mytest1" и "mytest2", основанных на одной и той же фиксации. У вас есть файл C, содержащий функцию blub(). В ветке mytest1 вы перемещаете "blub()" в другую позицию в файле и фиксируете. В ветке mytest2 вы изменяете blub() и фиксируете. На ветке mytest2 вы пытаетесь использовать "git merge mytest1".

Кажется, возникает конфликт слиянием. Я надеялся, что Git распознает, что "blub()" был перемещен в mytest1, а затем сможет автоматически объединить модификацию в mytest2 с перемещением в mytest1. Но по крайней мере, когда я попробовал, это не сработало автоматически...

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

Теперь, поскольку этот вопрос давно искал меня, я действительно пытался создать конкретный пример, где Git лучше, тогда как слияние в SVN не выполняется.

Я нашел здесь fooobar.com/questions/7600/..., но это включает переименование, и вопрос здесь был для случая без переименования.

Итак, вот пример SVN, который в основном пытается это сделать:

bob        +-----r3----r5---r6---+
          /                /      \
anna     /  +-r2----r4----+--+     \
        /  /                  \     \
trunk  r1-+-------------------r7-- Conflict

Идея здесь:

  • Анна и Боб оба разработчики имеют свои собственные ветки (созданные в r2, r3).
  • Анна делает некоторые изменения (r4),
  • Боб делает некоторые изменения (r5).
  • Боб объединяет модификации от Анны в свою ветку; это дает конфликты, которые Боб фиксирует, а затем совершает (r6).
  • Модификации Annas объединяются обратно в багажник (r7).
  • Боб пытается объединить свою модификацию обратно в багажник, и это снова дает конфликт.

Вот Bash script, который вызывает этот конфликт (используя SVN 1.6.17, а также SVN 1.7.9)

#!/bin/bash
cd /tmp
rm -rf rep2 wk2
svnadmin create rep2
svn co file:///tmp/rep2 wk2
cd wk2
mkdir trunk
mkdir branches
echo -e "A\nA\nB\nB" > trunk/f.txt
svn add trunk branches
svn commit -m "Initial file"
svn copy ^/trunk ^/branches/anna -m "Created branch anna"
svn copy ^/trunk ^/branches/bob  -m "Created branch bob"
svn up 
echo -e "A\nMA\nA\nB\nB" > branches/anna/f.txt
svn commit -m "anna added text"
echo -e "A\nMB\nA\nB\nMB\nB" > branches/bob/f.txt
svn commit -m "bob added text"
svn up
svn merge --accept postpone ^/branches/anna branches/bob
echo -e "A\nMAB\nA\nB\nMB\nB" > branches/bob/f.txt
svn resolved branches/bob/f.txt
svn commit -m "anna merged into bob with conflict"
svn up
svn merge --reintegrate ^/branches/anna trunk
svn commit -m "anna reintegrated into trunk"
svn up
svn merge --reintegrate --dry-run ^/branches/bob trunk

Последний "-dry-run" говорит вам, что будет конфликт. Если вы вместо этого сначала попытаетесь объединить реинтеграцию Анны в отделение Боба, тогда вы также столкнетесь с конфликтом; поэтому, если вы замените последний svn merge на

svn merge ^/trunk branches/bob

это также показывает конфликты.

То же самое с Git 1.7.9.5:

#!/bin/bash
cd /tmp
rm -rf rep2
mkdir rep2
cd rep2
git init .
echo -e "A\nA\nB\nB" > f.txt
git add f.txt
git commit -m "Initial file"
git branch anna
git branch bob
git checkout anna
echo -e "A\nMA\nA\nB\nB" > f.txt
git commit -a -m "anna added text"
git checkout bob
echo -e "A\nMB\nA\nB\nMB\nB" > f.txt
git commit -a -m "bob added text"
git merge anna
echo -e "A\nMAB\nA\nB\nMB\nB" > f.txt
git commit -a -m "anna merged into bob with conflict"
git checkout master
git merge anna
git merge bob

Содержимое f.txt изменяется следующим образом.

Исходная версия

A
A
B
B

Изменения Анны

A
MA
A
B
B

Изменения Боба

A
MB
A
B
MB
B

После того, как ветвь Анна слита в ветвь Боба

A
MAB
A
B
MB
B

Как уже многие люди уже указывали: проблема в том, что подрывная деятельность не может вспомнить, что Боб уже разрешил конфликт. Поэтому, когда вы пытаетесь объединить ветвь Боба в багажник, вам необходимо повторно разрешить конфликт.

Git работает совершенно иначе. Здесь некоторое графическое представление, что делает Git

bob         +--s1----s3------s4---+
           /                /      \
anna      /  +-s1----s2----+--+     \
         /  /                  \     \
master  s1-+-------------------s2----s4

s1/s2/s3/s4 - это моментальные снимки рабочего каталога Git.

Примечания:

  • Когда anna и bob создают свои ветки разработки, это будет НЕ создавать любые коммиты в git. Git будет только помнить, что обе ветки первоначально относятся к одному и тому же объекту фиксации, главная ветвь. (Это обязательство, в свою очередь, будет ссылаться на моментальный снимок s1).
  • Когда anna реализует ее модификацию, это создаст новый снимок "s2" + объект фиксации. Объект фиксации включает в себя:
    • Ссылка на снимок (s2 здесь)
    • Сообщение о фиксации
    • Информация о предках (другие объекты фиксации)
  • Когда bob реализует свою модификацию, это создаст другой снимок s3 + объект фиксации
  • Когда bob объединяет модификации annas в свою ветку разработки это создаст еще один снимок s4 (содержащий слияние его изменения и изменения анны) + еще один объект фиксации
  • Когда anna объединяет свои изменения обратно в главную ветку, это будет быть "быстрым" слиянием в показанном примере, поскольку мастер пока не изменилось. Что означает "перемотка вперед", означает, что мастер просто укажет на снимок s2 из anna без слияния. При такой "быстрой перемотке" даже не быть другим объектом фиксации. "Мастер" будет прямо сейчас относятся к последнему фиксации из ветки "anna"
  • Когда bob теперь объединяет свои изменения в туловище, произойдет следующее:
    • Git обнаружит, что commit из anna, который создал s2-снимок является (прямым) предком для фиксации bobs, который создал снимок s4.
    • из-за этого Git снова "ускорит перемотку" главной ветки на последняя фиксация ветки "bob".
    • снова это даже не создаст новый объект commit. В ветке "хозяин" будет просто указана последняя фиксация ветки "bob".

Вот результат "git ref-log", который показывает все это:

88807ab [email protected]{0}: merge bob: Fast-forward
346ce9f [email protected]{1}: merge anna: Fast-forward
15e91e2 [email protected]{2}: checkout: moving from bob to master
88807ab [email protected]{3}: commit (merge): anna merged into bob with conflict
83db5d7 [email protected]{4}: commit: bob added text
15e91e2 [email protected]{5}: checkout: moving from anna to bob
346ce9f [email protected]{6}: commit: anna added text
15e91e2 [email protected]{7}: checkout: moving from master to anna
15e91e2 [email protected]{8}: commit (initial): Initial file

Как вы можете видеть из этого:

  • когда мы переходим к ветке разработки anna (HEAD @{7}), мы не меняем. к другой фиксации, мы сохраняем фиксацию; Git просто помнит, что мы теперь находимся на другой ветке
  • В HEAD @{5} мы переходим к начальной ветки bob; это приведет к копировать в то же состояние, что и главная ветвь, потому что bob не изменился ничего еще
  • В HEAD @{2} мы возвращаемся к ведущей ветке, поэтому к той же фиксации объект все началось с.
  • Head @{1}, HEAD @{0} показывают слияние "fast-forward", которые не создают новые объекты фиксации.

С помощью git cat-file HEAD @{8} -p "вы можете проверить все детали исходного объекта фиксации. В приведенном выше примере я получил:

tree b634f7c9c819bb524524bcada067a22d1c33737f
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

Initial file

Линия "дерево" идентифицирует snapshot s1 (== b634f7c9c819bb524524bcada067a22d1c33737f) к которому относится это сообщение.

Если я делаю "git cat-file HEAD @{3} -p" Получаю:

tree f8e16dfd2deb7b99e6c8c12d9fe39eda5fe677a3
parent 83db5d741678908d76dabb5fbb0100fb81484302
parent 346ce9fe2b613c8a41c47117b6f4e5a791555710
author Ingo <***> 1475066831 +0200
committer Ingo <***> 1475066831 +0200

anna merged into bob with conflict

Это выше показывает объект commit, bob, созданный при слиянии ветки разработки anna. Опять же, строка "tree" относится к созданному снимку (s3 здесь). Также обратите внимание на "родительские" строки. Второй, который начинается с "parent 346ce9f", позже сообщает git, когда вы пытаетесь объединить ветку развития bob в основную ветвь, что последнее комманда bob имеет последнее коммит в качестве предка. Вот почему Git знает, что слияние ветки развития bob в главной ветке является "быстрой перемоткой вперед".

Ответ 3

У меня нет конкретных примеров, но любой тип повторного слияния затруднен, в частности, так называемый criss-cross merge.

   a
  / \
 b1  c1
 |\ /|
 | X |
 |/ \|
 b2  c2

слияние b2 и c2


Страница wiki на вики Subversion, описывающая различия между merge info на основе ассиметрического слияния Subversion (с 'sync' и 'reintegrate' direction) и отслеживание слияния на основе симметричного слияния в DVCS имеет раздел " Симметричное объединение с Criss-Cross Merge"

Ответ 4

Самый конкретный пример, о котором я могу думать, - это самое простое слияние, которое не приводит к конфликтам слиянием. Однако (TL; DR) с этим примером Git по-прежнему по своей сути является более простой процедурой, чем с Subversion. Давайте рассмотрим, почему:

Subversion

Рассмотрим следующий сценарий в subversion; соединительная линия и ветвь функции:

   1  2  3
…--o--o--o              trunk
          \4  5
           o--o         branches/feature_1

Чтобы слить, вы можете использовать следующую команду в subversion:

# thank goodness for the addition of the --reintegrate flag in SVN 1.5, eh?
svn merge --reintegrate central/repo/path/to/branches/feature_1

# build, test, and then... commit the merge
svn commit -m "Merged feature_1 into trunk!"

В слиянии subversion изменения требуют другой фиксации. Это должно публиковать изменения, которые произошло слиянием, с внесением изменений в виртуальный каталог ветки объектов обратно в магистраль. Таким образом, все, кто работает с багажником, теперь могут использовать его, и граф ревизий выглядит примерно так:

   1  2  3      6
…--o--o--o------o       /trunk
          \4  5/
           o--o         /branches/feature_1

Давайте посмотрим, как это делается в git.

Git

В Git это объединение слиянием действительно не требуется, поскольку ветки - это прославленные закладки на графике ревизии. Таким образом, с такой же структурой диаграмм ревизий он выглядит примерно так:

         v-- master, HEAD

   1  2  3
…--o--o--o
          \4  5
           o--o

              ^-- feature_branch

С головкой в ​​настоящее время на главной ветке мы можем выполнить простое слияние с ветвью функции:

# Attempt a merge
git merge feature_branch

# build, test, and then... I am done with the merge

... и он переадресует ветку на фиксацию, на которую указывает ветвь функции. Это стало возможным благодаря тому, что Git знает, что цель слияния является прямым потомком, а текущая ветвь должна принимать все изменения, которые произошли. График пересмотра будет выглядеть следующим образом:

   1  2  3  4  5
…--o--o--o--o--o
               ^-- feature_branch, master, HEAD

Изменения не нуждаются в новой фиксации, так как все Git сделали, чтобы переместить ссылки на ветку дальше вперед. Осталось только опубликовать это в публичном репозитории, если у вас есть:

# build, test, and then... just publish it
git push

Заключение

Учитывая этот простой сценарий, вы можете утверждать две вещи в разнице между Subversion и Git:

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

Учитывая, что это самый простой сценарий слияния, трудно утверждать, что подрывная деятельность проще, чем git.

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

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

Ответ 5

Сохранение ответа коротким - в DVCS, поскольку у вас есть локальный элемент управления источником, если что-то запутается в процессе слияния (что, вероятно, произойдет при больших слияниях), вы всегда можете откат к предыдущей локальной версии, которая имеет изменения, которые вы сделали до слияния, а затем повторите попытку.

Таким образом, в основном вы можете объединиться без опасения, что ваши локальные изменения могут быть повреждены во время процесса.

Ответ 6

Если вы исключите "Merge-Refactored Hell", вы не получите образцы fair, потому что они просто не существуют