Почему слияние филиалов на общую сумму коммитов? - PullRequest
0 голосов
/ 19 мая 2019

Если у ветви A 5 коммитов, а у ветви B 7, когда я объединяю B в A, теперь у A 12 коммитов.

Это ожидаемый результат? Я бы подумал, что слияние будет считаться одним коммитом?

Ответы [ 3 ]

2 голосов
/ 19 мая 2019

Когда вы говорите "ветвь А имеет пять коммитов", вы, вероятно, не учитываете все коммиты, которые содержит ветвь А.То же самое относится к вашим семи коммитам в ветви B. Чтобы действительно понять это, важно понимать, что в Git ветви - или, точнее, ветви names - на самом деле не имеют никакого значения.Только коммиты имеют значение.

Чтобы увидеть, как это работает, давайте начнем с очень маленького репозитория, содержащего всего три коммита.Три коммита имеют большие длинные уродливые имена Git-hash-ID, но давайте просто назовем их коммитами A, B и C, как если бы в коммитах были настоящие имена из одной заглавной буквы.(Мы закончим довольно быстро, и это одна из причин, по которой Git использует эти большие уродливые хеш-идентификаторы.)

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

A <-B <-C

Коммит C хранит хэш-идентификатор B, поэтому C указывает на B.B хранит хэш-идентификатор A, поэтому B указывает на A.A это, конечно, самый первый коммит, который мы когда-либо сделали: он не может указывать дальше.Это особый случай - коммит root , из которого всегда есть хотя бы один, если хранилище не пустое.Обычно существует ровно один корневой коммит, причем этот является первым коммитом.

Имена ветвей

Следующий большой важный секрет - это простое продолжение первого, и этоветвь name наподобие master или develop просто указывает на один коммит .Единственный коммит, на который указывает master, в этом случае будет коммитом C:

A--B--C   <-- master

Мне всегда лень рисовать внутренние стрелки между коммитами по разным причинам.Во-первых, когда мы делаем коммит, ничто и никто - даже сам Git - не может изменить коммит.Фиксация C заморожена во времени навсегда, всегда указывая на B, который заморожен и указывает на A, и так далее.Поэтому внутренние стрелки неизменно указывают назад .Git называет их родителей коммита: родительский элемент C равен B, а родительский B равен A.

Указатели имени ветви отличаются,В отличие от замороженного содержимого каждого коммита, указатель имени ветви может и действительно меняет .

Let's git checkout master, который извлекает коммит C в наше рабочее дерево , давая нам файлы, которые мы можем видеть и работать с / с.Затем мы внесем некоторые изменения, git add обновленные файлы и git commit, чтобы сделать новый коммит, который мы назовем D.Git упакует наши новые файлы 1 и сделает этот новый коммит D, указывая на тот коммит, который у нас был, т. Е. C, так что теперь у нас есть:

A--B--C--D

, а затем в качестве заключительного акта git commit записывает хэш-идентификатор D в имя master, так что master теперь указывает не на C, а на D:

A--B--C--D   <-- master

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

Создание новых веток

Чтобы создать новую ветку , Git просто добавляет новое имя , указывающее на некоторый существующий коммит.Давайте теперь сделаем ветку branch-a в нашем хранилище с четырьмя коммитами:

A--B--C--D   <-- master, branch-a (HEAD)

Помимо добавления имени branch-a, указывающего на D, я прикрепил специальное имя HEAD - во всехпрописные, хотя вы можете использовать @, если вам нравится более короткое имя - к одному из двух названий ветвей.Вот как Git запоминает текущую ветку .

Прежде чем мы сделаемНовые коммиты, ответьте сами: сколько коммитов в master и сколько в branch-a?Если вы не ответили «четыре» каждый раз, почему бы и нет?Если вы спросите Git, ответ будет четыре: есть четыре коммита: D, C, B, A, , оба ответвления.

Давайте добавим пятьфиксирует нашу новую branch-a сейчас, изменяя вещи и используя git add и git commit обычным способом.Git создаст пять новых уникальных больших уродливых хеш-идентификаторов, но мы назовем новые коммиты E-F-G-H-I и нарисуем их:

           E--F--G--H--I   <-- branch-a (HEAD)
          /
A--B--C--D   <-- master

Когда мы сделали E, Git сделал это с parentD, а затем изменили имя branch-a, указав E.Когда мы сделали F, его родитель был E, а Git обновил branch-a, указав F.Мы повторили это пять раз, и у нас есть 11 коммитов на branch-a, которые не на главном, плюс четыре коммита на обе ветви .Так что branch-a имеет не пять, а девять коммитов.Просто пять из них только на branch-a.

Теперь давайте сделаем branch-b, сначала переключившись обратно на master, а затем создав новое имя branch-b,указывая на фиксацию D:

           E--F--G--H--I   <-- branch-a
          /
A--B--C--D   <-- master, branch-b (HEAD)

Обратите внимание, что здесь ничего не изменилось внутри самого хранилища.Наше рабочее дерево (и индекс) изменилось - они вернулись к фиксации D - и мы добавили новое имя branch-b, которое, подобно master, идентифицирует фиксацию D, но все коммиты не потревожены.

Теперь давайте добавим семь коммитов, уникальных для branch-b:

           E--F--G--H--I   <-- branch-a
          /
A--B--C--D
          \
           J--K--L--M--N--O--P   <-- branch-b (HEAD)

На самом деле branch-b есть 11 коммитов, но четыреони делятся (с master, который я перестал рисовать из-за лени, и с branch-a).

Слияние

Теперь вы хотите объединить branch-b в branch-a.Таким образом, команды, которые вы запускаете, будут:

git checkout branch-a
git merge branch-b

Первый шаг выбирает коммит I в качестве текущего коммита и branch-a в качестве имени, к которому должен быть присоединен HEAD.Он копирует содержимое commit I в рабочее дерево (и index / staging-area).В самом графике нет никаких изменений, но теперь HEAD обозначает branch-a и, следовательно, фиксирует I:

           E---F----G---H----I   <-- branch-a (HEAD)
          /
A--B--C--D
          \
           J--K--L--M--N--O--P   <-- branch-b

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

Команда git merge затем что-то делаетнемного сложно.Сначала он находит базу слияния между текущим коммитом I и другим коммитом P.Основой слияния является, грубо говоря, точка, в которой две ветви разошлись.В данном случае это очень очевидно из графика: это commit D.

Git теперь выясняет, что "мы" изменили на branch-a, выполнив:

git diff --find-renames <hash-of-D> <hash-of-I>   # what we changed

Получаетвторой анализ, чтобы выяснить, что они изменили на branch-b:

git diff --find-renames <hash-of-D> <hash-of-P>   # what they changed

Затем Git объединяет два набора изменений, применяя объединенные изменения к тому, что находится в снимке в коммите.D.

Этот процесс «создайте две разницы, объедините их и примените их к базе слияния» - это форма действия слияния.Мне нравится называть это глаголом , чтобы объединить , то есть объединить изменения.Поскольку коммиты - это снимки, а не наборы изменений, Git должен выполнить два сравнения.Чтобы иметь разумную отправную точку, Git должен найти базу слияния.Вот почему у нас есть вся эта работа, которая происходит как часть глагола для объединения , когда мы объединяем коммиты I и P.

Слияние коммитов

Теперь, когда Git выполнил всю эту работу по слиянию, Git сделает коммит слияния . Ну, он будет часто или обычно делать один - мы увидим исключения через мгновение. Обратите внимание, что здесь используется слово merge в качестве прилагательного, хотя оно модифицирует слово commit . Мы также можем ссылаться на этот новый коммит слияния как слияние , используя слово merge как существительное. Мне нравится называть это «слияние как существительное» или «слияние как прилагательное», чтобы отличить его от process , от глагола to merge . Для команды git merge мы сначала выполняем процесс, а затем делаем коммит слияния в конце. Но давайте нарисуем это:

           E---F----G---H----I
          /                   \
A--B--C--D                     Q   <-- branch-a (HEAD)
          \                   /
           J--K--L--M--N--O--P   <-- branch-b

Этот новый коммит, коммит слияния Q, особенный точно в одном отношении: у него два родителя вместо одного. Сначала он указывает на коммит I, например, коммит I был на кончике branch-a минуту назад и является родителем коммита Q, но затем он также указывает на коммит P, говоря коммит P также является родителем коммита Q.

Если мы теперь спросим Git, сколько и какие коммиты находятся на branch-a, Git начинается с Q, затем работает в обратном направлении через и I и P, в конечном итоге достигнув D (на который master все еще указывает), а затем полностью вернувшись к A. Таким образом, число коммитов теперь составляет 17: A до D плюс E до I плюс J до P плюс Q. Если мы спросим, ​​сколько коммитов на branch-a, а не на master, мы получим 13: пять для E до I, семь для J до P, и один для Q.

Есть много способов нарисовать это

Вот еще один способ нарисовать, что произошло:

...--D--E--F--G--H--I------Q   <-- branch-a (HEAD)
      \                   /
       J--K--L--M--N--O--P   <-- branch-b

число достижимых коммитов остается неизменным, хотя: Git начинается с Q, возвращается к обоим I и P, возвращается к обоим H и O, и так далее до достижения D, когда он возвращается к тому, что предшествует общему коммиту D.

Если вы git log рисуете график, используя git log --graph или git log --graph --oneline, Git будет рисовать его вертикально, с коммитом Q вверху и структурой ветвления, представленной в виде отдельных линий:

* hashofQ (HEAD -> branch-a) Merge ..
|\
| * hashofP commit message for P
* | hashofI commit message for I
...

или аналогичный - точная позиция каждой * и строки зависит от дополнительных параметров сортировки, которые вы можете передать git log, например --author-date-order, хотя --graph всегда обеспечивает как минимум параметр --topo-order. Средства просмотра графики, такие как gitk и различные графические интерфейсы, могут имитировать git log --graph --oneline, но делают все это красивее (хотя, как всегда, красота в глазах смотрящего).

Слияние сквоша: git merge не всегда сливается

Команда git merge может сделать больше, чем построить слияние (существительное), используя для слияния (глагол) процесса. Аркус упомянул git merge --squash, который выполняет для слияния часть процесса, но затем просто останавливается, не делая фиксацию и не записывая тот факт, что следующий коммит должен быть слиянием , В этом конкретном случае мы бы сами запустили git commit, чтобы сделать коммит Q. Новый коммит Q будет обычным коммитом , а не коммитом слияния, и мы можем нарисовать его следующим образом:

...--D--E--F--G--H--I--Q   <-- branch-a (HEAD)
      \
       J--K--L--M--N--O--P   <-- branch-b

Поскольку нет никакой связи между Q и P, кто-то, кто придет позже - включая вас или Git - и, глядя на этот график, может и не догадываться, что фиксация Q является результатом слияния. Семь коммитов, которые являются эксклюзивными для branch-b, все еще являются эксклюзивными для branch-b. В общем, если вы сделали это, вы должны немедленно удалить имя branch-b из этого хранилища и из каждого клона этого хранилища , чтобы полностью забыть, что коммиты J-K-L-M-N-O-P когда-либо существовали.

Это яИногда, но не всегда, жизнеспособный, полезный и хороший рабочий процесс.Это особенно полезно, когда отдельные коммиты на branch-b никогда не были замечены где-либо еще, так что вы знаете , никто их не имеет, и вы сделали их только как временные коммиты с намерениемзаменить их всех одним коммитом «добавить функцию», то есть, коммитом Q, в конце.После слияния в сквош вы заставляете Git удалить ваше имя branch-b и забыть, что когда-либо делали какие-либо отдельные коммиты.У вас есть один последний хороший коммит, и вы притворяетесь, что знаете, как сделать этот коммит одновременно.

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

...--D--E--F--G--H--I--Q   <-- branch-a (HEAD)
      \               /
       R-------S-----T   <-- branch-b

Если теперь выяснится, что у вас есть введенная ошибка, возможно, возможно проверить коммиты R и S и T и посмотреть , какой из этих коммитов представил ошибку .Затем вы можете сравнить R против D, S против R или T против S, чтобы помочь вам выяснить, как появилась ошибка, и выяснить, что нужно сделать, чтобы ее исправить.

То, что сводится к тому, что сквош слияния не плохие, они просто инструмент.Используйте свои инструменты, чтобы делать вещи таким образом, чтобы в будущем вам было легче. Если это означает раздавливание, продолжайте и давите.Если нет, не надо.

Перемотка вперед: git merge не всегда объединяет

Мы также должны охватить операции перемотки вперед .Рассмотрим ситуацию, в которой вы создаете ветку:

...--C--D   <-- master, feature (HEAD)

Затем вы делаете некоторые коммиты на этой ветке:

...--C--D   <-- master
         \
          E--F--G--H   <-- feature (HEAD)

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

git checkout master
git merge feature

Git скажет что-то о fast-forward , и у вас останется следующий график:

...--C--D--E--F--G--H   <-- master (HEAD), feature

Имя feature не переместился - он по-прежнему указывает на фиксацию H - но имя master переместило , и теперь также указывает на фиксацию H.Нет нового коммита слияния!

Что Git сделал здесь, так это то, что он выполнил поиск базы слияния так же, как и для реального слияния, и обнаружил, что лучший общий коммит между master и feature былсовершить D.Имя master указывало на коммит D, поэтому, если бы Git должен был сделать обычный , чтобы объединить глагол , он бы запустил:

git diff --find-renames *hash-of-D* *hash-of-D*   # what we changed

и ответ был бы,конечно, если бы мы ничего не изменили! Тогда Git нужно было бы проанализировать D против H, чтобы узнать, что они изменили, что, конечно, будет тем, что они изменили.Git применил бы эти изменения к D и снова получил бы ... commit H.

Если бы Git сделал из этого реальное слияние, это выглядело бы как:

...--C--D------------I   <-- master (HEAD)
         \          /
          E--F--G--H   <-- feature

Снимок для фиксации I будет соответствовать снимку для фиксации H.

Вы можете force Git сделать этот коммит слияния:

git checkout master; git merge --no-ff feature

Таким образомвы получаете то же самое истинное слияние, которое вы получили бы, если бы master имел некоторый коммит после D.Вы можете сделать это, если хотите подчеркнуть для будущего зрителя - который может стать самим собой через год или два - что коммиты E-F-G-H были сделаны группой, и вместе они реализуют какую-то функцию.Или вам может быть все равно: вы и будущий год спустя предпочитаете рассматривать коммиты E-F-G-H как логическое расширение master, при этом вам не нужно помнить, что эти четыре были сделаны специально для какой-то конкретной функции..

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

Если вы думаете, что предпочтете увидеть слияние в git log --graph или графическом средстве просмотра, форсируйтепрямое слияние с git merge --no-ff.Если вы думаете, что вы предпочтете , а не , чтобы увидеть слияние, вы даже можете использовать git merge --ff-only, чтобы убедиться, что Git просто не удастся , если требуется истинное слияние (после чего вынужно будет сделать что-то другое, и это выходит за рамки этого уже слишком длинного ответа).

1 голос
/ 19 мая 2019

Это зависит от истории ветвей ... это может быть между 7 ревизиями (перемотка вперед) и 13 ревизиями (объединение 2 совершенно не связанных историй).Все зависит от историй и от количества расходящихся ревизий, о которых вы говорите (или если вы заставляете --no-ff).Один из возможных способов получить 12 ревизий - это иметь одного общего предка между двумя ветвями, поэтому у вас есть общий предок, 4 ревизии на одной ветви и 6 на другой (после общего предка) плюс ревизия слияния: 1 + 4 +6 + 1 = 12. Но, как я уже сказал, все зависит от истории.8 может быть достигнуто за счет того, что все 5 ревизий одной ветви будут первыми 5 ревизиями другой ветви, а затем слиться --no-ff.Это создаст коммит слияния для того, что было бы ff.Результат: 8 ревизий.С 4 общими предками и слиянием вы получите 9 ревизий ... и т. Д.

0 голосов
/ 19 мая 2019

Если вы хотите объединить ветку с 1 коммитом, вы можете использовать --squash параметр fo git merge.

Что он делает, это создает один коммит из ветви, переданной в git merge --squash <branch>, чтоВы можете зафиксировать.

По умолчанию git merge branch:

git merge --squash branch:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...