GIT: Как сохранить одинаковую историю коммитов в двух разных ветках - PullRequest
0 голосов
/ 27 марта 2019

Использование gitbash для слияния и фиксации.

Позвольте мне сначала объяснить основную структуру. Итак, у нас есть origin / dev, над которым мы работаем. После внесения изменений мы помещаем изменения в origin / dev.

Затем, используя gitbash для объединения dev с qa, я делаю ниже

git checkout qa

# for all recent changes in origin/qa(similar have parallel origing/dev and uat as well.)
git pull

# for checking out changes in dev to my local qa space which will be merged
# to origin/qa by the below commands
git checkout dev -- directorynameToCheckoutCodeFrom

git commit
git push

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

Итак, мои проблемы: я делаю 5 коммитов для 5 проблем в DEV, все имеют разные идентификаторы коммитов. Поэтому, когда я сливаюсь из DEV в QA, когда я фиксирую все пять изменений в 1, я получаю 1 идентификатор фиксации, и все изменения будут объединены в 1. То же самое происходит при объединении в UAT.

Есть ли способ сохранить одну и ту же историю в разных средах. Реальные проблемы возникают в QA, мы можем объединяться 4-5 раз за 10 дней, а в UAT мы хотели бы сохранить нетронутыми и объединяться только один раз в месяц. В этом случае, если мы передадим все изменения из QA в UAT как один, история, отличающаяся в QA, будет потеряна. Есть ли способ справиться с этим?

Пролистал некоторые посты в сети, но не смог понять, что я понял, что единственный способ - делать частые коммиты, как мы делали в DEV env. Для меня 1 проблема слить в dev> then qa> uat, это единственный способ сохранить ту же историю, насколько я понимаю, правильно.

Ответы [ 3 ]

3 голосов
/ 27 марта 2019

Нет истории коммитов .Есть только коммитов; коммитов являются историей.

Каждый коммит уникально идентифицируется по хеш-идентификатору.Этот хэш-идентификатор является истинным именем коммита.Если у вас есть этот коммит, у вас есть этот хэш-идентификатор.Если у вас есть этот хэш-идентификатор, у вас есть этот коммит.Считайте большой некрасивый хеш-идентификатор и посмотрите, есть ли в вашей базе данных «все коммиты, которые у меня есть в этом хранилище»: т.е. посмотрите, знает ли Git это.Если это так, у вас есть этот коммит.Например, b5101f929789889c2e536d915698f58d5c5c6b7a является допустимым идентификатором хеша: это коммит в Git-репозитории для Git.Если у вас есть этот хэш-идентификатор в вашем Git-хранилище, у вас есть , который commit.

Люди обычно не вводят или не используют эти хэш-идентификаторы вообще. Git использует их, но Git - это компьютерная программа, а не человек.Людям не очень хорошо с этими вещами - я должен вырезать и вставить вышеупомянутый хэш-идентификатор, или я ошибаюсь - поэтому люди используют другой способ, чтобы начать.Люди используют названия ветвей .Но у многих различных Git-репозиториев есть master, и это master не всегда (или никогда!) Означает тот большой уродливый хеш-идентификатор, который я напечатал выше.Таким образом, name , как master, относится к одному конкретному репозиторию Git, а хеш-идентификаторы - нет.

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

Итак,если у вас есть b5101f929789889c2e536d915698f58d5c5c6b7a, то у вас есть это:

$ git cat-file -p b5101f929789889c2e536d915698f58d5c5c6b7a | sed 's/@/ /'
tree 3f109f9d1abd310a06dc7409176a4380f16aa5f2
parent a562a119833b7202d5c9b9069d1abb40c1f9b59a
author Junio C Hamano <gitster pobox.com> 1548795295 -0800
committer Junio C Hamano <gitster pobox.com> 1548795295 -0800

Fourth batch after 2.20

Signed-off-by: Junio C Hamano <gitster pobox.com>

(строка tree представляет сохраненный снимок, который идет с этим коммитом. Вы можете проигнорировать это здесь.) parentстрока дает идентификатор хеша коммита, который приходит до b5101f929789889c2e536d915698f58d5c5c6b7a.

Если у вас есть b5101f929789889c2e536d915698f58d5c5c6b7a, то почти наверняка у вас также есть a562a119833b7202d5c9b9069d1abb40c1f9b59a.История для последующего коммита равна раннего коммита.

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

... <-F <-G <-H

, где H - это последний коммит в длинной цепочке коммитов.Поскольку H содержит хэш-идентификатор G, нам не нужно записывать большой некрасивый хэш-идентификатор G, мы можем просто записать хэш-код H.Мы используем это, чтобы Git нашел идентификатор G внутри H.Если мы хотим F, мы используем H, чтобы найти G, чтобы найти ID F, что позволяет Git получить F.

Но мы все равно должны записать это последний идентификатор хеша.Именно здесь приходят имена ветвей. Имена ветвей, такие как master, служат нашим способом сохранения хэш-идентификатора last commit.

Для создания new commit, у нас есть Git, сохраняющий хеш-код H в нашего нового коммита.У нас есть Git, сохраняющий снимок и наше имя, адрес электронной почты и все остальное, а также все остальное - «остальное» включает метку времени, точную секунду , когда у нас был Git, делающий все это.Теперь Git вычисляет фактический хэш-идентификатор всех этих данных, включая отметку времени.Коммит теперь сохраняется в нашей базе данных всех коммитов, и Git дал нам новый хэш-идентификатор I:

...--F--G--H   <-- master
            \
             I

У нас Git автоматически запись IID хэша в наше имя master:

...--F--G--H--I   <-- master

и мы добавили новую историю, которая сохраняет всю существующую историю.


1 Конечно, если бы мы использовали только одну заглавную букву, подобную этой, мы бы исчерпали способность создавать коммиты в любой точке мира после создания всего 26 коммитов.Вот почему Git хеш-идентификаторы такие большие.Они содержат 160 битов, поэтому число возможных коммитов или других объектов составляет 2 160 или 1 461 501 637 330 090 918 203 684 832 716 283 019 655 942 542 976.Как оказалось, этого на самом деле недостаточно, и Git, вероятно, перейдет к большему хешу, который может содержать в 79,228,162,514,264,337,593,543,950,336 раз больше объектов.Хотя первое число достаточно велико, чтобы перечислить все атомы во вселенной, существуют определенные неприятные атаки, поэтому 256-битный хэш - хорошая идея.См. Как недавно обнаруженное столкновение SHA-1 влияет на Git?


Здесь рассказывается, как иметь такую ​​же историю

История равна совершает.Чтобы иметь одинаковую историю в двух ветвях, вам нужно, чтобы оба имени ветви указывали на один и тот же коммит:

...--F--G--H--I   <-- master, dev

Теперь история в master: Начиная сI, покажите I, затем вернитесь к H и покажите H, затем вернитесь к G ... Аналогично, история в dev: Начиная сI, покажите I, затем вернитесь к H ...

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

...--F--G--H   <-- master
            \
             I   <-- dev

Здесь история в dev начинается (заканчивается?) С I, затем возвращается к H, а затем G и т. Д.,История в master начинается (заканчивается?) С H, возвращается к G и так далее.Поскольку мы добавляем больше коммитов, мы добавляем больше истории, и если мы делаем это следующим образом:

             K--L   <-- master
            /
...--F--G--H
            \
             I--J   <-- dev

, тогда история двух ветвей расходится .Теперь master начинается с L и работает в обратном направлении, а dev начинается с J и работает в обратном направлении.Есть два коммита на dev, которые не на master, и два коммита на , которые *1179* не на dev, а затем все от H на спине - на обеих ветвях.

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

Объединяет историю объединения (и снимки)

Теперь вы можете сделать коммит слияния .Основной способ сделать коммит слияния - это использование команды git merge.Это состоит из двух частей:

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

Чтобы сделать слияние, вы начинаете с выбора одного кончика ветви.Вы запускаете git checkout master или git checkout dev здесь.Какой бы из них вы ни выбрали, это коммит, который у вас есть сейчас, и Git прикрепляет специальное имя HEAD к названию этой ветви, чтобы запомнить, какое из них вы выбрали:

             K--L   <-- master (HEAD)
            /
...--F--G--H
            \
             I--J   <-- dev

Теперь вы запускаете git merge и даетеэто идентификатор для выбора коммита слияния .Если вы используете master = L, вы можете использовать dev = J в качестве коммита для слияния:

git merge dev         # or git merge --no-ff dev

Git теперь будет ходить по графику как обычно, чтобынайдите лучший общий коммит - лучший коммит, который находится на обеих ветвях, для использования в качестве отправной точки для этого слияния.Здесь это коммит H, где две ветви сначала расходятся.

Теперь Git будет сравнивать сохраненный снимок с коммитом H - базой слияния - с текущим коммитом L. Что бы не отличалось , вы, должно быть, изменились на master. Git помещает эти изменения в один список:

git diff --find-renames <hash-of-H> <hash-of-L>   # what we changed

Git повторяет это, но с их коммитом J:

git diff --find-renames <hash-of-H> <hash-of-J>   # what they changed

Теперь Git объединяет два набора изменений . Что бы мы ни изменили, мы хотим измениться. Что бы они ни изменили, мы тоже хотим использовать эти изменения. Если они изменились README.md, а мы нет, мы примем их изменение. Если мы изменили файл, а они этого не сделали, мы примем наши изменения. Если мы оба изменили один и тот же файл , Git попытается объединить эти изменения. Если Git преуспевает, у нас есть объединенное изменение для этого файла.

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

Git теперь делает новый коммит с одной специальной функцией. Вместо просто , запомнившего наш предыдущий коммит L, Git имеет коммит слияния запоминание двух предыдущих коммитов, L и J:

             K--L   <-- master (HEAD)
            /    \
...--F--G--H      M
            \    /
             I--J   <-- dev

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

             K--L
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             I--J   <-- dev

Обратите внимание, что если мы выполним слияние, запустив git checkout dev; git merge master, Git сделает те же две разницы и получит такой же коммит слияния M (ну, пока мы сделали это в ту же секунду, чтобы отметки времени совпадали). Но тогда Git записал бы хэш-идентификатор M в dev, а не в master.

В любом случае, если мы теперь спросим об истории master, Git начнется с M. Затем он вернется к и L и J и покажет оба из них. (Он должен выбрать один, чтобы показать первым, и у git log есть много флагов, которые помогут вам выбрать , который должен показывать первым.) Затем он отойдет от того, который выбрал первым, так что теперь он должен показывать оба K и J, или оба L и I. Затем он вернется от того, кого выбрал для показа.

В большинстве случаев Git показывает всех детей раньше, чем кто-либо из родителей, то есть, в конце концов, он покажет все четыре из I, J, K и L и будет иметь только H показывать. Итак, отсюда Git покажет H, затем G и так далее - теперь есть только одна цепочка, по которой нужно идти, один коммит за раз. Но имейте в виду, что когда вы возвращаетесь из слияния, вы сталкиваетесь с , который обязуется показать следующую проблему.

git merge не всегда делает коммит слияния

Предположим, у вас есть эта история:

...--F--G--H   <-- master
            \
             I--J   <-- dev

То есть, нет дивергенции , dev просто строго впереди master. Вы делаете git checkout master, чтобы выбрать коммит H:

...--F--G--H   <-- master (HEAD)
            \
             I--J   <-- dev

, а затем git merge dev, чтобы объединить работу, которую вы проделали после создания базы слияния, с работой , которую они выполнили после создания базы слияния.

База слияния - лучший общий коммит. То есть мы начинаем с H и продолжаем возвращаться по мере необходимости, а также начинаем с dev и продолжаем возвращаться по мере необходимости, пока не достигнем общей отправной точки. Итак, с J мы возвращаемся к I и H, а с H мы просто сидим тихо в H, пока J не вернется сюда.

База слияния, другими словами, - это текущий коммит . Если Git побежал:

git diff --find-renames <hash-of-H> <hash-of-H>

было бы без изменений . Акт объединения без изменений H до H через master) с с некоторыми изменениями H до J через dev), затем применение этих изменений к H будет таким же, как в J. Git говорит: ну, это было слишком просто , и вместо нового коммита он просто двигается имя master вперед в противоположном от обычного обратном направлении. (На самом деле, Git действительно работал задом наперед - с J до I до H - для того, чтобы понять это. Он просто помнит, что он начался с J.) Итак, что вы получаете по умолчанию Это 1400 *

...--F--G--H
            \
             I--J   <-- dev, master (HEAD)

Когда Git может перемещать метку наподобие master вперед, как это, он называет эту операцию fast-forward . Когда вы делаете это с git merge, Git называет это ускоренным слиянием , но на самом деле это не слияние вообще. На самом деле Git извлек commit J и заставил master указать J.

Во многих случаях это нормально! История теперь: Для master, начните с J и идите назад. Для dev начните с J и идите назад. Если это все, что вам нужно и вам нужно, это нормально. Но если вы хотите реальный коммит слияния - так что вы можете, например, позже разнести master и dev друг от друга - вы можете сказать Git: Даже если вы можете сделать быструю перемотку вперед вместо слияния, все равно сделайте реальное слияние. Git пойдет дальше и сравнит H с H, затем сравнит H с J, объединит изменения и сделает новым коммит:

...--F--G--H------K   <-- master (HEAD)
            \    /
             I--J   <-- dev

Теперь вы получаете реальный коммит слияния K, с двумя родителями, необходимыми для коммита слияния. Первый родитель H, как обычно, а второй J, как обычно для коммита слияния. История master сейчас включает историю dev, но остается отличной от истории dev, потому что история dev не включает коммит K.

Обратите внимание, что если вы теперь переключитесь обратно на dev и сделаете больше коммитов, результат будет выглядеть следующим образом:

...--F--G--H------K   <-- master
            \    /
             I--J--L--M--N   <-- dev (HEAD)

Теперь вы можете git checkout master и git merge dev снова. На этот раз вам не понадобится --no-ff, потому что есть коммит на master, который не на dev, а именно K, и, конечно, есть коммиты на dev that are not on master , namely LMN . The *merge base* this time is shared commit J (not H H is also shared, but J` лучше ). Так что Git объединит изменения, выполнив:

git diff --find-renames <hash-of-J> <hash-of-K>   # what did we change?
git diff --find-renames <hash-of-J> <hash-of-N>   # what did they change?

Что сделал мы изменили с J на K? (Это упражнение для вас, читатель.)

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

...--F--G--H------K--------O   <-- master (HEAD)
            \    /        /
             I--J--L--M--N   <-- dev

, где новый коммит слияния O объединяет изменения J -vs- K с изменениями J -vs- N. История master начинается с O и будет включать N и M и L и K и J и I и H и так далее. История dev начинается с N и включает в себя M и L и J (не K!) И I и H и так далее. Git всегда работает в обратном направлении , от ребенка к родителю. Слияния позволяют / заставляют Git работать в обратном направлении вдоль обеих строк одновременно (но показываются вам по одному, в некотором порядке, в зависимости от аргументов, которые вы предоставляете git log).

1 голос
/ 27 марта 2019

В процессе, который вы описываете, вы хотите «объединить» изменения из отдельного каталога в репо.Это противоречит тому, как работает git, и именно поэтому у вас возникают проблемы с сохранением хорошей истории.

Важно понимать, что то, что вы делаете, на самом деле не является слиянием [1].Коммит слияния имеет два (или более) родительских коммитов, и таким образом сохраняется полная история.Чтобы быть справедливым, git имеет тенденцию быть «гибким» до такой степени непоследовательности в том, как он использует определенные термины;есть операции, которые он называет «слиянием», которые не приводят к фиксации слияния. Но даже с этими операциями вы объединяете весь контент, а не отдельный каталог.

Если у вас есть отдельные модули - или, тем не менее, выможет описывать их, разный контент в разных каталогах - которые изменяются независимо (что, безусловно, применимо, если вы продвигаете их между ветвями / средами отдельно), они должны быть в отдельных репозиториях. Я полагаю, если это поможет вам собрать их как подмодулирепо родителей, чтобы иметь возможность клонировать с одного URL или чего-то еще. Но помимо этого, если этот тип разделения не приемлем по какой-то причине, вам может понадобиться подумать, является ли git лучшим инструментом для удовлетворения вашего конкретного источникатребования к контролю.


[1] Я мог бы также поспорить с семантикой по поводу слияния из-за того факта, что, если бы и dev, и qa имели изменения, изменения из qa были бы перезаписаны и потеряны - что, как правило, не то, чтожелательно в слиянии. Но вы будетеТогда я, вероятно, утверждаю, что изменения всегда переходят от dev к qa, поэтому это не применимо;и в любом случае, git иногда описывает слияние одной ветви с другой как слияние (т. е. «наша стратегия слияния»).

1 голос
/ 27 марта 2019

вы можете попробовать с помощью

git checkout qa

git merge dev --no-ff

git push

git merge dev --no-ff

в основном используется для извлечения всех изменений из ветви dev в qa с их историей.

...