轉自http://git-scm.com/book/zh/Git-%E5%B7%A5%E5%85%B7-%E9%87%8D%E5%86%99%E5%8E%86%E5%8F%B2git
重寫歷史服務器
不少時候,在 Git 上工做的時候,你也許會因爲某種緣由想要修訂你的提交歷史。Git 的一個卓越之處就是它容許你在最後可能的時刻再做決定。你能夠在你即將提交暫存區時決定什麼文件納入哪一次提交,你可使用 stash 命令來決定你暫時擱置的工做,你能夠重寫已經發生的提交以使它們看起來是另一種樣子。這個包括改變提交的次序、改變說明或者修改提交中包含的文件,將提交歸併、拆分或者徹底刪除——這一切在你還沒有開始將你的工做和別人共享前都是能夠的。編輯器
在這一節中,你會學到如何完成這些頗有用的任務以使你的提交歷史在你將其共享給別人以前變成你想要的樣子。工具
改變最近一次提交學習
改變最近一次提交也許是最多見的重寫歷史的行爲。對於你的最近一次提交,你常常想作兩件基本事情:改變提交說明,或者改變你剛剛經過增長,改變,刪除而記錄的快照。測試
若是你只想修改最近一次提交說明,這很是簡單:spa
$ git commit --amend
這會把你帶入文本編輯器,裏面包含了你最近一次提交說明,供你修改。當你保存並退出編輯器,這個編輯器會寫入一個新的提交,裏面包含了那個說明,而且讓它成爲你的新的最近一次提交。命令行
若是你完成提交後又想修改被提交的快照,增長或者修改其中的文件,可能由於你最初提交時,忘了添加一個新建的文件,這個過程基本上同樣。你經過修改文件而後對其運行git add或對一個已被記錄的文件運行git rm,隨後的git commit --amend會獲取你當前的暫存區並將它做爲新提交對應的快照。指針
使用這項技術的時候你必須當心,由於修正會改變提交的SHA-1值。這個很像是一次很是小的rebase——不要在你最近一次提交被推送後還去修正它。code
修改多個提交說明
要修改歷史中更早的提交,你必須採用更復雜的工具。Git沒有一個修改歷史的工具,可是你可使用rebase工具來衍合一系列的提交到它們原來所在的HEAD上而不是移到新的上。依靠這個交互式的rebase工具,你就能夠停留在每一次提交後,若是你想修改或改變說明、增長文件或任何其餘事情。你能夠經過給git rebase增長-i選項來以交互方式地運行rebase。你必須經過告訴命令衍合到哪次提交,來指明你須要重寫的提交的回溯深度。
例如,你想修改最近三次的提交說明,或者其中任意一次,你必須給git rebase -i提供一個參數,指明你想要修改的提交的父提交,例如HEAD~2或者HEAD~3。可能記住~3更加容易,由於你想修改最近三次提交;可是請記住你事實上所指的是四次提交以前,即你想修改的提交的父提交。
$ git rebase -i HEAD~3
再次提醒這是一個衍合命令——HEAD~3..HEAD範圍內的每一次提交都會被重寫,不管你是否修改說明。不要涵蓋你已經推送到中心服務器的提交——這麼作會使其餘開發者產生混亂,由於你提供了一樣變動的不一樣版本。
運行這個命令會爲你的文本編輯器提供一個提交列表,看起來像下面這樣
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 = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
很重要的一點是你得注意這些提交的順序與你一般經過log命令看到的是相反的。若是你運行log,你會看到下面這樣的結果:
$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit
請注意這裏的倒序。交互式的rebase給了你一個即將運行的腳本。它會從你在命令行上指明的提交開始(HEAD~3)而後自上至下重播每次提交裏引入的變動。它將最先的列在頂上而不是最近的,由於這是第一個須要重播的。
你須要修改這個腳原本讓它停留在你想修改的變動上。要作到這一點,你只要將你想修改的每一次提交前面的pick改成edit。例如,只想修改第三次提交說明的話,你就像下面這樣修改文件:
edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
當你保存並退出編輯器,Git會倒回至列表中的最後一次提交,而後把你送到命令行中,同時顯示如下信息:
$ git rebase -i HEAD~3
Stopped at 7482e0d... updated the gemspec to hopefully work better
You can amend the commit now, with
git commit --amend
Once you’re satisfied with your changes, run
git rebase --continue
這些指示很明確地告訴了你該幹什麼。輸入
$ git commit --amend
修改提交說明,退出編輯器。而後,運行
$ git rebase --continue
這個命令會自動應用其餘兩次提交,你就完成任務了。若是你將更多行的 pick 改成 edit ,你就能對你想修改的提交重複這些步驟。Git每次都會停下,讓你修正提交,完成後繼續運行。
重排提交
你也可使用交互式的衍合來完全重排或刪除提交。若是你想刪除"added cat-file"這個提交而且修改其餘兩次提交引入的順序,你將rebase腳本從這個
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
改成這個:
pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
當你保存並退出編輯器,Git 將分支倒回至這些提交的父提交,應用310154e,而後f7f3f6d,接着中止。你有效地修改了這些提交的順序而且完全刪除了"added cat-file"此次提交。
壓制(Squashing)提交
交互式的衍合工具還能夠將一系列提交壓制爲單一提交。腳本在 rebase 的信息裏放了一些有用的指示:
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#
若是不用"pick"或者"edit",而是指定"squash",Git 會同時應用那個變動和它以前的變動並將提交說明歸併。所以,若是你想將這三個提交合併爲單一提交,你能夠將腳本修改爲這樣:
pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
當你保存並退出編輯器,Git 會應用所有三次變動而後將你送回編輯器來歸併三次提交說明。
# 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
當你保存以後,你就擁有了一個包含前三次提交的所有變動的單一提交。
拆分提交
拆分提交就是撤銷一次提交,而後屢次部分地暫存或提交直到結束。例如,假設你想將三次提交中的中間一次拆分。將"updated README formatting and added blame"拆分紅兩次提交:第一次爲"updated README formatting",第二次爲"added blame"。你能夠在rebase -i腳本中修改你想拆分的提交前的指令爲"edit":
pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
而後,這個腳本就將你帶入命令行,你重置那次提交,提取被重置的變動,從中建立屢次提交。當你保存並退出編輯器,Git 倒回到列表中第一次提交的父提交,應用第一次提交(f7f3f6d),應用第二次提交(310154e),而後將你帶到控制檯。那裏你能夠用git reset HEAD^對那次提交進行一次混合的重置,這將撤銷那次提交而且將修改的文件撤回。此時你能夠暫存並提交文件,直到你擁有屢次提交,結束後,運行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在腳本中應用了最後一次提交(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
再次提醒,這會修改你列表中的提交的 SHA 值,因此請確保這個列表裏不包含你已經推送到共享倉庫的提交。
核彈級選項: filter-branch
若是你想用腳本的方式修改大量的提交,還有一個重寫歷史的選項能夠用——例如,全局性地修改電子郵件地址或者將一個文件從全部提交中刪除。這個命令是filter-branch,這個會大面積地修改你的歷史,因此你頗有可能不應去用它,除非你的項目還沒有公開,沒有其餘人在你準備修改的提交的基礎上工做。儘管如此,這個能夠很是有用。你會學習一些常見用法,藉此對它的能力有所認識。
從全部提交中刪除一個文件
這個常常發生。有些人不經思考使用git add .,意外地提交了一個巨大的二進制文件,你想將它從全部地方刪除。也許你不當心提交了一個包含密碼的文件,而你想讓你的項目開源。filter-branch大概會是你用來清理整個歷史的工具。要從整個歷史中刪除一個名叫password.txt的文件,你能夠在filter-branch上使用--tree-filter選項:
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
--tree-filter選項會在每次檢出項目時先執行指定的命令而後從新提交結果。在這個例子中,你會在全部快照中刪除一個名叫 password.txt 的文件,不管它是否存在。若是你想刪除全部不當心提交上去的編輯器備份文件,你能夠運行相似git filter-branch --tree-filter 'rm -f *~' HEAD的命令。
你能夠觀察到 Git 重寫目錄樹而且提交,而後將分支指針移到末尾。一個比較好的辦法是在一個測試分支上作這些而後在你肯定產物真的是你所要的以後,再 hard-reset 你的主分支。要在你全部的分支上運行filter-branch的話,你能夠傳遞一個--all給命令。
將一個子目錄設置爲新的根目錄
假設你完成了從另一個代碼控制系統的導入工做,獲得了一些沒有意義的子目錄(trunk, tags等等)。若是你想讓trunk子目錄成爲每一次提交的新的項目根目錄,filter-branch也能夠幫你作到:
$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten
如今你的項目根目錄就是trunk子目錄了。Git 會自動地刪除不對這個子目錄產生影響的提交。
全局性地更換電子郵件地址
另外一個常見的案例是你在開始時忘了運行git config來設置你的姓名和電子郵件地址,也許你想開源一個項目,把你全部的工做電子郵件地址修改成我的地址。不管哪一種狀況你均可以用filter-branch來更換屢次提交裏的電子郵件地址。你必須當心一些,只改變屬於你的電子郵件地址,因此你使用--commit-filter:
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
這個會遍歷並重寫全部提交使之擁有你的新地址。由於提交裏包含了它們的父提交的SHA-1值,這個命令會修改你的歷史中的全部提交,而不只僅是包含了匹配的電子郵件地址的那些。