[英]Keep commits in feature branch after squash merge to master
目標:在feature-branch
開發了一個新功能后,我想將它“合並”到master
並在master
的提交歷史記錄中進行一次提交。 但是,即使在刪除feature-branch
之后,我仍然希望能夠訪問每個更改行的原始提交消息。
基本原理:這就像使用 Subversion 將分支合並到trunk
的默認行為。 優點是trunk
/ master
的歷史保持精簡,即它只包含一個單一的、高級的提交消息,如Develop feature x
。 但是,如果我不確定為什么將代碼的特定部分更改為現在的樣子,在 Subversion 中,我可以使用svn blame --use-merge-history
進行更深入的挖掘,並查看原始提交消息。
潛在的解決方案:據我所知,使用git
可以使用git merge --squash
策略實現master
的單次提交。 然而,這似乎並沒有真正創建一個合並提交,而只是一個不保留feature-branch
完整歷史的常規提交。 事實上,一旦我之后刪除了feature-branch
,它的提交最終將被垃圾收集,因為它的提交對象現在基本上是無法訪問的。
因此,我的問題最終是:如何在將功能分支合並到 master 並刪除它之后保留功能分支中的提交,並且沒有任何額外要求(例如為每個已刪除的分支創建標簽)?
不要那樣做。 進行常規合並。
當您要查看的功能為一體,使用git log --first-parent
。 這會引導您的git log
避免探索側分支。
讓我們簡要地看一下提交圖的樣子。 提交圖是每個提交的繪圖,顯示它如何連接回其父提交。 常規(非合並)之間這里的區別提交和合並提交的是一個普通提交連接回只是一個以前的承諾,同時合並連接回兩個(或更多,但你不會做出這樣的合並,所以無需嘗試在此處繪制它們)。
請記住,每個提交都有一個唯一的哈希 ID——一串丑陋的數字和字母表示該提交,世界上每個 Git 都同意為該提交保留——但這些哈希 ID 對人類毫無意義,因此我們可以繪制它們作為小圓o
s,或使用大寫字母來代替它們。 還要記住,您可以以任何您喜歡的方式繪制圖形:重要的是提交,以及它們的連接箭頭,它們總是向后指向(從較晚的提交到較早的提交)。
那么,一個簡單的提交字符串可能如下所示:
... <-F <-G <-H ...
不知何故,您已經找到了現有提交H
的實際哈希 ID。 您可以使用它讓您的 Git 找出提交,包括其作者姓名和日志消息等內容以供查看。 提交H
本身包括早期提交G
的實際哈希 ID。 因此,您的 Git 可以找出提交G
並向您顯示作者姓名和日志消息。 該提交包含較早提交F
的哈希 ID。 這個過程一直持續到 Git 到達第一個提交,它不會指向更早的任何東西,因為它不能,或者直到你厭倦了git log
輸出並停止查找。😀
你是如何找到哈希 ID H
? 好吧,如果有一個稍后的提交,你——或者你的 Git——從那個稍后的提交中得到了H
但是,如果H
是分支master
的最后一次提交,則您從名稱master
獲得了H
的哈希 ID。 當您向master
添加新提交時,您的 Git 在新提交I
記錄H
的哈希,然后將新提交的哈希 ID 寫入名稱master
。 因此,根據定義,分支名稱始終包含最新提交的哈希 ID。 Git 從那里開始並反向工作。
現在讓我們看看一組更復雜的分支。 我們不再費心將連接箭頭繪制為箭頭,因為一旦它們處於某些提交中,它們就是只讀的,始終處於凍結狀態。 (每次提交的所有部分都像這樣永遠凍結。)不過,名稱會隨着時間移動,所以讓我們繪制這些箭頭:
...--F--G--H <-- master
\
I--J <-- feature
名稱master
選擇 commit H
; name feature
選擇 commit J
。 如果由於某種原因我們回到master
並添加更多提交,我們會得到:
...--F--G--H--K--L <-- master
\
I--J <-- feature
如果我們願意,我們可以這樣畫,目前,我這樣做:
K--L <-- master
/
...--F--G--H
\
I--J <-- feature
如果我們現在git checkout master; git merge feature
git checkout master; git merge feature
我們將得到一個真正的合並提交:
K--L
/ \
...--F--G--H M <-- master (HEAD)
\ /
I--J <-- feature
附加的HEAD
提醒我們master
是我們現在已經檢出的分支,用於重要的情況。 這包括當我們運行git log
不用說其呈交git log
應該看看先。 Git 將使用HEAD
查找當前提交,即現在的M
。 當我們運行它也很重要git commit
做出了新的承諾:新提交的父母將是當前提交和Git會更新-the一個當前分支名HEAD
連接到要記住的新提交的哈希值ID。 這就是為什么M
的第一個父級是L
並且為什么master
現在提交M
。 合並提交的特殊之處在於它有兩個父項。 第一個是L
,第二個是J
。
如果您現在運行git log
,Git 將首先從 commit M
開始,向您顯示合並的日志消息。 然后,它會同時查看提交L
和J
並試圖在同一時間,你都顯示。 它實際上不能,所以它首先選擇一個顯示。 它選擇哪一個取決於您提供給git log
的排序選項。 默認是首先顯示具有最新提交者時間戳的任何一個。
但是,如果您說--first-parent
,則git log
根本不會查看提交J
它只會查看M
的第一個父級,即L
。 它將顯示提交L
,然后后退一步提交K
並顯示,然后后退一步提交H
,並顯示,依此類推。
(請注意,我們現在可以安全地刪除名稱feature
。)
我插入 commits KL
的原因是為了使繪制圖形更容易和更對稱。 更現實的是,如果您在分支上開發功能,然后將它們合並到 master,您將只擁有:
...--F--G--H <-- master (HEAD)
\
I--J <-- feature
當你去合並feature
。 運行git merge feature
,你的 Git 會注意到上次提交H
的合並基仍然是提交H
,但這次提交H
也是master
的最后一次提交。 這意味着 Git 可以跳過合並的實際工作。
Git 將這種非合並操作稱為快進合並。 為避免這種情況,您必須使用git merge --no-ff
一次(或使用 GitHub 的“合並”按鈕,它始終執行非快進的真正合並)。
--no-ff
真正合並如果我們進行--no-ff
合並,Git 將進行真正的合並。 它會將提交H
的快照與提交H
的快照進行比較,並將H
與J
比較,因為真正的合並必須; 然后它將合並這些更改並進行合並提交(這次我將其稱為K
)。 這給了我們這個圖:
...--F--G--H------K <-- master (HEAD)
\ /
I--J <-- feature
當我們在這里運行git log
,Git 會訪問 commit K
並顯示它,然后訪問H
和J
。 默認情況下,排序順序將使其下一個打印J
,然后是I
,然后是H
。 所以我們會看到所有的功能提交。
但是如果我們將--first-parent
添加到我們的git log
,Git 將訪問 commit K
。 然后它將跟隨第一個父鏈接返回提交H
,並顯示。 然后它將返回到提交G
,並顯示它,依此類推。
如果我們願意,我們現在可以刪除名稱feature
,但如果我們願意,我們也可以繼續開發feature
:
...--F--G--H------K <-- master
\ /
I--J <-- feature (HEAD)
HEAD
在這里的新位置意味着我們運行了git checkout feature
。 現在新提交擴展feature
:
...--o--o--o------o <-- master
\ /
o--o--o--o--o <-- feature (HEAD)
如果我們現在git checkout master
和git merge feature
,即使不強制git merge feature
,我們也會得到真正的合並。 (不過,將--no-ff
添加到合並命令中沒有壞處。)看起來像這樣:
...--o--o--o------o--------o <-- master (HEAD)
\ / /
o--o--o--o--o <-- feature
使用git log --first-parent
,Git會顯示最后一次提交的master
,那么先前的合並上master
,等:我們從來沒有看到工作上完成feature
。
一切都在那里,如果我們想要它很容易找到:只需運行git log
而不使用--first-parent
。 當功能真正完成,並且最后一次合並到位時,您可以安全地刪除名稱feature
。 同時,您可以隨時創建新功能,從圖表中您喜歡的任何提交開始,處理它們,並最終合並它們。 例如,假設您需要快速修復master
:
...--o--o--o------o--------o--o <-- master (HEAD)
\ / /
o--o--o--o--o <-- feature
現在做一個輔助feature2
:
...--o--o--o------o--------o--o <-- master, feature2 (HEAD)
\ / /
o--o--o--o--o <-- feature
並開始提交feature2
:
o--o <-- feature2 (HEAD)
/
...--o--o--o------o--------o--o <-- master
\ / /
o--o--o--o--o <-- feature
在繼續開發feature
:
o--o <-- feature2
/
...--o--o--o------o--------o--o <-- master
\ / /
o--o--o--o--o--o--o--o <-- feature (HEAD)
准備好后,您可以合並feature2
,這再次需要--no-ff
:
X--Y <-- feature2
/ \
...--o--o--o------o--------o--W------Z <-- master (HEAD)
\ / /
o--o--o--o--o--o--o--o <-- feature
(注意Z
的第一個父級是W
,而不是Y
;注意我們的字母用完了,這就是為什么 Git 不使用簡單的短數字或字母作為提交 ID 的原因!)。
也許 feature2 現在就快完成了:
o--o <-- feature2
/ \
...--o--o--o------o--------o--o------o----o <-- master (HEAD)
\ / / /
o--o--o--o--o--o--o--o--o--o
--first-parent
標志繼續執行僅跟蹤master
到 previously-on- master
鏈接的工作,就在圖的中間,沒有偷看側視圖。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.