關(guān)于 Git 重寫提交歷史的一些筆記

寫在前面
今天和小伙伴們分享一些 Git 重寫提交歷史的筆記
提交代碼遇到相關(guān)問題,這里整理筆記
博文為《Pro Git》讀書筆記整理
感謝開源這本書的作者和把這本書翻譯為中文的大佬們
理解不足小伙伴幫忙指正,書很不錯(cuò),感興趣小伙伴可以去拜讀下
傍晚時(shí)分,你坐在屋檐下,看著天慢慢地黑下去,心里寂寞而凄涼,感到自己的生命被剝奪了。當(dāng)時(shí)我是個(gè)年輕人,但我害怕這樣生活下去,衰老下去。在我看來,這是比死亡更可怕的事。--------王小波

公司要求在代碼提交的時(shí)候,需要提交信息中包含任務(wù)單號(hào),我在一次需求代碼提交中,一個(gè)分支只有最后的幾次提交信息中包含了任務(wù)單號(hào),在最初的提交中沒有包含任務(wù)單號(hào),所以一直 push 不上去,提示沒有包含任務(wù)單信息。所以需要修改之前的提交信息。

在 Git 中 這樣的操作叫做 重寫歷史(本質(zhì)上是些變基操作)

許多時(shí)候,在使用Git時(shí),可能想要修訂提交歷史。Git可以通過git stash來決定不與某些內(nèi)容同時(shí)提交,也可以重寫已經(jīng)發(fā)生的提交就像它們以另一種方式發(fā)生的一樣。比如 改變提交的順序,改變提交中的信息或修改文件,將提交壓縮或是拆分,或完全地移除提交,當(dāng)然這些操作的前提是 在將你的工作成果與他人共享之前完成

修改最后一次提交
修改你最近一次提交可能是所有修改歷史提交的操作中最常見的一個(gè)。對(duì)于你的最近一次提交,你往往想做兩件事情:簡(jiǎn)單地修改提交信息,或者通過添加、移除或修改文件來更改提交實(shí)際的內(nèi)容。

如果,你只是想修改最近一次提交的提交信息,那么很簡(jiǎn)單:
git commit --amend
上面這條命令會(huì)將最后一次的提交信息載入到編輯器(Vi/Vim)中供你修改。當(dāng)保存并關(guān)閉編輯器后,編輯器會(huì)將更新后的提交信息寫入新提交中,它會(huì)成為新的最后一次提交。

需要注意的是,這里編輯會(huì)使用 Vim  編輯器,修改內(nèi)容作為一個(gè)變基腳本存在,所以它無法識(shí)別你帶 # 的提交信息,他會(huì)當(dāng)作 Vim 中的注釋存在。

如果你想要修改最后一次提交的實(shí)際內(nèi)容,那么流程很相似:
首先作出你想要補(bǔ)上的修改,暫存 stash 它們,然后用git commit--amend 以新的改進(jìn)后的提交來替換掉舊有的最后一次提交,使用這個(gè)技巧的時(shí)候需要小心,因?yàn)樾拚龝?huì)改變提交的SHA-1校驗(yàn)和。它類似于一個(gè)小的變基一一如果已經(jīng)推送了最后一次提交就不要修正它。

另一方面,如果你的修補(bǔ)是瑣碎的(如修改了一個(gè)筆誤或添加了一個(gè)忘記暫存的文件),那么之前的提交信息不必修改,你只需作出更改,暫存它們,然后通過以下命令避免不必要的編輯器環(huán)節(jié)即可:

$ git commit --amend --no-edit
修改多個(gè)提交信息
為了修改在提交歷史中較遠(yuǎn)的提交,必須使用更復(fù)雜的工具。Git沒有一個(gè)改變歷史工具,但是可以使用變基工具來變基一系列提交,基于它們?cè)瓉淼?HEAD 而不是將其移動(dòng)到另一個(gè)新的上面。通過 交互式變基工具,可以在任何想要修改的提交后停止,然后修改信息、添加文件或做任何想做的事情。

可以通過給 git rebase 增加 -i 選項(xiàng)來交互式地運(yùn)行變基。必須指定想要重寫多久遠(yuǎn)的歷史,這可以通過告訴命令將要變基到的提交來做 到。

例如,如果想要修改  最近三次提交信息,或者那組提交中的任意一個(gè)提交信息,將想要修改的最近一次提交的父提交 作為參數(shù)傳遞給 git rebase -i 命令,即 HEAD~2^ 或 HEAD~3。

$git rebase -i HEAD~3
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase
--continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
如果你希望指定 head 指針位置,那么你可以使用下面的命令

$git rebase -i commId
你需要修改腳本來讓它停留在你想修改的變更上。要達(dá)到這個(gè)目的,你只要將你想修改的每一次提交前面的 ‘pick’ 改為 ‘edit’。例如,只想修改第三次提交信息,可以像下面這樣修改文件:

edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
當(dāng)保存并退出編輯器時(shí),Git 將你帶回到列表中的最后一次提交,把你送回命令行并提示以下信息:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with
  git commit --amend
Once youre satisfied with your changes, run
  git rebase --continue
這些指令準(zhǔn)確地告訴你該做什么。輸入






$ git commit --amend
修改提交信息,然后退出編輯器。然后,運(yùn)行

$ git rebase --continue
這個(gè)命令將會(huì)自動(dòng)地應(yīng)用另外兩個(gè)提交,然后就完成了。如果需要將不止一處的 pick 改為 edit,需要在每一個(gè)

修改為 edit 的提交上重復(fù)這些步驟。每一次,Git 將會(huì)停止,讓你修正提交,然后繼續(xù)直到完成。

重新排序提交或完全移除提交
也可以使用交互式變基來重新排序或完全移除提交。

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
如果想要移除 added cat-file 提交然后修改另外兩個(gè)提,交引入的順序,可以將變基腳本改成這樣:

pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
當(dāng)保存并退出編輯器時(shí),Git 將你的分支帶回這些提交的父提交,應(yīng)用 310154e 然后應(yīng)用 f7f3f6d,最后停止。修改了那些提交的順序并完全地移除了 “added cat-file” 提交。

合并提交
通過交互式變基工具,也可以將一連串提交壓縮成一個(gè)單獨(dú)的提交。在變基信息中腳本給出了有用的指令:

# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase
--continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
如果,指定 “squash” 而不是 “pick” 或 “edit”,Git 將應(yīng)用兩者的修改并合并提交信息在一起。所以,如果想要這三次提交變?yōu)橐粋€(gè)提交,可以這樣修改腳本:

pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
當(dāng)保存并退出編輯器時(shí),Git 應(yīng)用所有的三次修改然后將你放到編輯器中來合并三次提交信息:

# This is a combination of 3 commits.
# The first commit's message is:
changed my name a bit
# This is the 2nd commit message:
updated README formatting and added blame
# This is the 3rd commit message:
added cat-file
當(dāng)你保存之后,你就擁有了一個(gè)包含前三次提交的全部變更的提交。

拆分提交
拆分一個(gè)提交會(huì)撤消這個(gè)提交,然后多次地部分地暫存與提交直到完成你所需次數(shù)的提交。

例如,假設(shè)想要拆分三次提交的中間那次提交。想要將它拆分為兩次提交:第一個(gè)“updated README formatting”,第二個(gè) “added blame” 來代替原來的“updated README formatting and added blame”。可以通過修改 rebase -i 的腳本來做到這點(diǎn),將要拆分的提交的指令修改為“edit”:

pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a8d added cat-file
然后,當(dāng)腳本帶你進(jìn)入到命令行時(shí),重置那個(gè)提交,拿到被重置的修改,從中創(chuàng)建幾次提交。當(dāng)保存并退出編輯器時(shí),Git帶你到列表中第一個(gè)提交的父提交,應(yīng)用第一個(gè)提交(f7f3f6d),應(yīng)用第二個(gè)提交( 310154e),然后讓你進(jìn)入命令行。那里,可以通過 git reset HEAD 做一次針對(duì)那個(gè)提交的混合重置,實(shí)際上將會(huì)撤消那次提交并將修改的文件取消暫存?,F(xiàn)在可以暫存并提交文件直到有幾個(gè)提交,然后當(dāng)完成時(shí)運(yùn)行 git rebase--continue:

$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase --continue
Git 在腳本中應(yīng)用最后一次提交(a5f4a0d),歷史記錄看起來像這樣:

$ git log -4 --pretty=format:"%h %s"
1c002dd added cat-file
9b29157 added blame
35cfb2b updated README formatting
f3cc40e changed my name a bit

核武器級(jí)選項(xiàng):filter-branch
從每一個(gè)提交中移除一個(gè)文件, filter-branch 是一個(gè)可能會(huì)用來擦洗整個(gè)提交歷史的工具。為了從整個(gè)提交歷史中移除一個(gè)叫做passwords.txt的文件,可以使用 --tree-filter 選項(xiàng)給  filter-branch

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
博文參考
《Pro Git》

作者:山河已無恙


歡迎關(guān)注微信公眾號(hào) :山河已無恙