[英]GIT : How to maintain same history of commits in two different branches
使用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
因此,合並是在任何兩個不同環境之間通常遵循的過程。
所以我的問題是我對DEV中的5個問題進行了5次提交,所有提交都有不同的提交ID。 因此,當我在1中提交所有五個更改時,從DEV合並到QA時,我將獲得1個提交ID,並且所有更改都將在1中進行合並。在UAT中進行合並時也會發生同樣的情況。
有什么方法可以在不同環境之間維護相同的歷史記錄。 真正的問題來自質量檢查,我們可能會在10天之內合並4-5次,而在UAT中,我們希望保持完整並每月僅合並一次。 在這種情況下,如果我們將所有從QA更改到UAT的更改作為一次提交提交,則QA中不同的歷史記錄將丟失。 有什么辦法解決嗎?
上網瀏覽了一些帖子,但無法理解,我了解的唯一方法就是像在DEV env中一樣頻繁進行提交。 對於在dev> then qa> uat中合並的1個問題,我認為這是保存相同歷史記錄的唯一方法。
沒有提交歷史 。 只有提交; 提交就是歷史。
每個提交都由哈希ID唯一標識。 散列ID 是真名的承諾,因為它是。 如果具有該提交,則具有該哈希ID。 如果您具有該哈希ID,則具有該提交。 讀出大的丑陋哈希ID並查看它是否在“我在該存儲庫中擁有的所有提交”數據庫中:即查看Git是否知道。 如果是這樣,則您具有該提交。 例如, b5101f929789889c2e536d915698f58d5c5c6b7a
是有效的哈希ID:它是Git在Git存儲庫中的提交。 如果您在Git存儲庫中具有該哈希ID,則具有該提交。
人們通常根本不會鍵入或使用這些哈希ID。 Git使用它們,但是Git是計算機程序,而不是人。 人們在這些事情上做的不好-我必須剪切並粘貼上面的哈希ID否則我會弄錯的-所以人類使用了不同的入門方法。 人類使用分支名稱 。 但是許多不同的Git存儲庫都擁有master
而這個master
並不總是(或從來沒有!)意味着我在上面鍵入的丑陋的哈希ID大。 因此,像master
這樣的名稱特定於一個特定的Git存儲庫,而哈希ID不是。
現在,每個提交都存儲一些東西。 什么提交商店包括所有與承諾去文件的快照,這樣就可以得到它后來退了出去。 它還包括提出承諾的人的姓名和電子郵件地址,以便您可以說出要贊揚或責備的人。 😀其中包括一條日志消息: 為什么進行提交的人說他們進行了提交。 但是-這是第一個棘手的部分-幾乎每個提交還包括至少一個哈希ID ,這是此特定提交之前的提交。
因此,如果您有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
之前的提交的哈希ID。
如果您有b5101f929789889c2e536d915698f58d5c5c6b7a
,則幾乎可以肯定也有a562a119833b7202d5c9b9069d1abb40c1f9b59a
。 稍后提交的歷史記錄是先前提交的歷史。
如果我們用一個大寫字母1替換這些大的丑陋哈希ID,則可以更輕松地繪制這種歷史記錄:
... <-F <-G <-H
其中H
是一長串提交中的最后一個提交。 由於H
持有G
的哈希ID,因此我們無需寫下G
的大丑陋哈希ID,我們只需寫下H
的哈希。 我們用它來讓Git在H
本身內找到G
的ID。 如果需要F
,則使用H
來找到G
來找到F
的ID,這使Git可以檢索F
但是我們仍然必須寫下最后一個哈希ID。 這是分支名稱的來源。分支名稱(例如master
充當我們保存上一次提交的哈希ID的方式。
為了進行新的提交,我們讓Git 在新的提交中保存H
的哈希ID。 我們讓Git保存了快照以及我們的姓名和電子郵件地址,以及所有其余的內容-“其余”包括時間戳記,這是我們讓Git完成所有操作時的精確秒數。 現在,Git計算所有這些數據的實際哈希ID,包括時間戳。 現在,提交已保存到我們所有提交的數據庫中,並且Git為我們提供了新的哈希ID I
:
...--F--G--H <-- master
\
I
我們讓Git 自動將I
的哈希ID寫入我們的名稱master
:
...--F--G--H--I <-- master
並且我們添加了新的歷史記錄,保留了所有現有的歷史記錄。
1當然,如果我們只使用一個這樣的大寫字母,那么在僅創建26個提交之后,我們將無法在世界上任何地方創建提交。 這就是Git的哈希ID很大的原因。 它們持有160位,因此可能提交或其他對象的數量為2 160或1,461,501,637,330,902,918,203,684,832,716,283,019,655,932,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
提交,而master
上有兩個不在 dev
上的提交,那么從H
后面的所有東西都在兩個分支上。
這種分歧( 不在某個分支上的提交)是工作線分歧之處。 分支名稱仍然只記住每個提交一個提交 ,特別是每行開發的技巧或最后一個提交。 Git將以保存的哈希ID從該提交開始,並使用該提交的已保存的父哈希ID向后移動,一次提交一次。 線路重新合並的地方,歷史重新合並。 除了下一部分之外,這就是存儲庫中的所有內容。
您現在可以做的是合並提交 。 進行合並提交的主要方法是使用git merge
命令。 這包括兩個部分:
要進行合並,請先選擇一個分支提示。 您可以在此處運行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現在使用一項特殊功能進行新提交。 Git 不僅僅是記住我們之前的提交L
,還擁有合並提交,還記得兩個先前的提交L
和J
:
K--L <-- master (HEAD)
/ \
...--F--G--H M
\ /
I--J <-- dev
然后,與往常一樣,Git更新我們的當前分支以記住新提交的哈希ID:
K--L
/ \
...--F--G--H M <-- master (HEAD)
\ /
I--J <-- dev
請注意,如果我們通過運行git checkout dev; git merge master
進行合並git checkout dev; git merge master
git checkout dev; git merge master
,Git將執行相同的兩個比較並獲得相同的合並提交M
(好吧,只要我們在完全相同的第二秒執行它,以便時間戳匹配)。 但是隨后Git會將M
的哈希ID寫入dev
而不是master
。
無論如何,如果現在我們詢問master
的歷史,Git將從M
開始。 然后,它會走回兩個 L
和 J
,並顯示他們兩個 。 (它必須選擇一個首先顯示,而git log
有很多標志來幫助您選擇首先顯示哪個 。)然后它將從它首先選擇的那個后退,因此現在必須顯示兩個 K
和J
或兩者 L
和I
。 然后,它將從它選擇顯示的那一個走回去。
在大多數情況下,Git會在所有父母之前顯示所有孩子,也就是說,最終,它將顯示I
, J
, K
和L
全部四個,並且只顯示H
因此,從這里開始,Git將顯示H
,然后顯示G
,依此類推-現在只有一條鏈可以向后走,一次提交一次。 但是請注意,當您從合並中遍歷時,會遇到which提交以顯示下一個問題。
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
回到這里。
換句話說,合並基礎是 當前commit 。 如果Git跑了:
git diff --find-renames <hash-of-H> <hash-of-H>
不會有變化 。 不進行任何更改 (從H
到master
到H
)與某些更改 (通過dev
到H
到J
)相結合,然后將這些更改應用於H
,將等於J
任何值。 Git說: 太好了,這太容易了 ,而不是進行新提交,它只是將名稱master
向前 移動 ,而與通常向后的方向相反。 (實際上,Git確實做了從J
到I
到H
反向工作,以便弄清楚。它只記得它是從J
開始的。)因此,默認情況下,您得到的是:
...--F--G--H
\
I--J <-- dev, master (HEAD)
當Git能夠像這樣向前滑動諸如master
類的標簽時,它將該操作稱為快速前進 。 當您使用git merge
本身進行此操作時,Git稱其為快進合並 ,但這根本不是真正的合並。 Git真正做的是檢出提交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
上的提交dev that are not on
master上, namely
LMN . The *merge base* this time is shared commit
. 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
)。
你可以嘗試
git checkout qa
git merge dev --no-ff
git push
git merge dev --no-ff
主要用於將所有dev分支及其歷史記錄提交到qa。
在您描述的過程中,您想從存儲庫中的單個目錄中“合並”更改。 這與git的工作方式相反,這就是為什么您在保持良好歷史方面遇到困難。
重要的是要了解您正在做的實際上不是合並[1]。 合並提交有兩個(或多個)父提交,這樣就保留了完整的歷史記錄。 公平地講,git在使用某些術語時傾向於“靈活”到不一致的程度。 有一些它稱為“合並”的操作不會導致合並提交,但是即使使用這些操作,您也可以合並整個內容-而不是單個目錄。
如果您有不同的模塊(或者可能會描述它們,則它們在不同目錄中的不同內容)是獨立更改的(如果您分別在分支/環境之間進行升級,則肯定適用),它們應該位於單獨的存儲庫中。 我想如果它能幫助您將它們收集為“父”存儲庫的子模塊,從而能夠從單個url或其他任何URL進行克隆。 但是除此之外,如果由於某種原因不能接受這種分隔,您可能需要考慮git是否是滿足特定源代碼管理要求的最佳工具。
[1]我還可以爭論關於合並的語義,因為如果dev和qa都發生了更改,則來自qa的更改將被覆蓋並丟失-這通常不是合並所需要的。 但是您可能會爭辯說,更改總是從開發人員流向質量保證人員,因此不適用。 無論如何,git有時確實將一個分支從另一個分支的破壞描述為合並(即“我們的合並策略”)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.