【轉】如何在Git中撤銷一切

翻譯:李偉 
審校:張帆
譯自:Githubgit

任何一個版本控制系統中,最有用的特性之一莫過於 「撤銷(undo)」操做。在Git中,「撤銷」有不少種含義。github

當你完成了一次新的提交(commit),Git會及時存儲當前時刻倉庫(repository)的快照(snapshot);你可以使用Git將項目回退到任何以前的版本。安全

下文中,我將列舉幾個常見的、須要「撤銷」的場景,而且展現如何使用Git來完成這些操做。app

1、撤銷一個公共修改 Undo a "public" change編輯器

場景:你剛剛用git push將本地修改推送到了GitHub,這時你意識到在提交中有一個錯誤。你想撤銷此次提交。oop

使用撤銷命令:git revert學習

發生了什麼:git revert將根據給定SHA的相反值,建立一個新的提交。若是舊提交是「matter」,那麼新的提交就是「anti-matter」——舊提交中全部已移除的東西將會被添加進到新提交中,舊提交中增長的東西將在新提交中移除。翻譯

這是Git最安全、也是最簡單的「撤銷」場景,由於這樣不會修改歷史記錄——你如今能夠git push下剛剛revert以後的提交來糾正錯誤了。3d

2、修改最近一次的提交信息 Fix the last commit message版本控制

場景:你只是在最後的提交信息中敲錯了字,好比你敲了git commit -m "Fxies bug #42",而在執行git push以前你已經意識到你應該敲"Fixes bug #42"。

使用撤銷命令:git commit –amend或git commit --amend -m "Fixes bug #42"

發生了什麼:git commit –amend將使用一個包含了剛剛錯誤提交全部變動的新提交,來更新並替換這個錯誤提交。因爲沒有staged的提交,因此實際上這個提交只是重寫了先前的提交信息。

3、撤銷本地更改 Undo "local" changes

場景:當你的貓爬過鍵盤時,你正在編輯的文件剛好被保存了,你的編輯器也恰在此時崩潰了。此時你並無提交過代碼。你指望撤銷這個文件中的全部修改——將這個文件回退到上次提交的狀態。

使用撤銷命令:git checkout --

發生了什麼:git checkout將工做目錄(working directory)裏的文件修改爲先前Git已知的狀態。你能夠提供一個期待回退分支的名字或者一個確切的SHA碼,Git也會默認檢出HEAD——即:當前分支的上一次提交。

注意:用這種方法「撤銷」的修改都將真正的消失。它們永遠不會被提交。所以Git不能恢復它們。此時,必定要明確本身在作什麼!(或許能夠用git diff來肯定)

4、重置本地修改 Reset "local" changes

場景:你已經在本地作了一些提交(還沒push),但全部的東西都糟糕透了,你想撤銷最近的三次提交——就像它們從沒發生過同樣。

使用撤銷命令:git reset或git reset --hard

發生了什麼:git reset將你的倉庫紀錄一直回退到指定的最後一個SHA表明的提交,那些提交就像從未發生過同樣。默認狀況下,git reset會保留工做目錄(working directory)。這些提交雖然消失了,可是內容還在磁盤上。這是最安全的作法,但一般狀況是:你想使用一個命令來「撤銷」全部提交和本地修改——那麼請使用--hard參數吧。

5、撤銷本地後重作 Redo after undo "local"

場景:你已經提交了一些內容,並使用git reset –hard撤銷了這些更改(見上面),忽然意識到:你想還原這些修改!

使用撤銷命令:git reflog和git reset, 或者git checkout

發生了什麼:git reflog是一個用來恢復項目歷史記錄的好辦法。你能夠經過git reflog恢復幾乎任何已提交的內容。

你或許對git log命令比較熟悉,它能顯示提交列表。git reflog與之相似,只不過git reflog顯示的是HEAD變動次數的列表。

一些說明:

1. 只有HEAD會改變。當你切換分支時,用git commit提交變動時,或是用git reset撤銷提交時,HEAD都會改變。但當你用git checkout --時, HEAD不會發生改變。(就像上文提到的情形,那些更改根本就沒有提交,所以reflog就不能幫助咱們進行恢復了)

2.  git reflog不會永遠存在。Git將會按期清理那些「不可達(unreachable)」的對象。不要指望可以在reflog裏找到數月前的提交記錄。

3.  reflog只是你我的的。你不能用你的reflog來恢復其餘開發者未push的提交。

blob.png

所以,怎樣合理使用reflog來找回以前「未完成」的提交呢?這要看你究竟要作什麼:

1. 若是你想恢復項目歷史到某次提交,那請使用git reset --hard

2. 若是你想在工做目錄(working direcotry)中恢復某次提交中的一個或多個文件,而且不改變提交歷史,那請使用git checkout--

3. 若是你想確切的回滾到某次提交,那麼請使用git cherry-pick。

6、與分支有關的那些事 Once more, with branching

場景:你提交了一些變動,而後你意識到你正在master分支上,但你指望的是在feature分支上執行這些提交。

使用撤銷命令:git branch feature, git reset --hard origin/master, 和 git checkout feature

發生了什麼:你可能用的是git checkout -b來創建新的分支,這是建立和檢出分支的便捷方法——但實際你並不想馬上切換分支。git branch feature會創建一個叫feature的分支,這個分支指向你最近的提交,可是你還停留在master分支上。

git reset --hard將master回退至origin/master,並忽略全部新提交。別擔憂,那些提交都還保留在feature上。

最後,git checkout將分支切換到feature,這個分支原封不動的保留了你最近的全部工做。

7、事半功倍處理分支 Branch in time saves nine

場景:你基於master新建了一個feature分支,可是master分支遠遠落後與origin/master。如今master分支與origin/master同步了,你指望此刻能在feature下馬上commit代碼,而且不是在遠遠落後master的狀況下。

使用撤銷命令:git checkout feature和git rebase master

發生了什麼:你也許已經敲了命令:git reset(可是沒用--hard,有意在磁盤上保存這些提交內容),而後敲了git checkout -b,以後從新提交更改,可是那樣的話,你將失去本地的提交記錄。不過,一個更好的方法:

使用git rebase master能夠作到一些事情:

1.首先,它定位你當前檢出分支和master之間的共同祖先節點(common ancestor)。

2.而後,它將當前檢出的分支重置到祖先節點(ancestor),並將後來全部的提交都暫存起來。

3.最後,它將當前檢出分支推動至master末尾,同時在master最後一次提交以後,再次提交那些在暫存區的變動。

8、批量撤銷/找回 Mass undo/redo

場景:你開始朝一個既定目標開發功能,可是中途你感受用另外一個方法更好。你已經有十幾個提交,可是你只想要其中的某幾個,其餘的均可以刪除不要。

使用撤銷命令:git rebase -i

發生了什麼:-i將rebases設置爲「交互模式(interactive mode)」。rebase開始執行的操做就像上文討論的同樣,可是在從新執行某個提交時,它會暫停下來,讓你修改每一次提交。

rebase –i將會打開你的默認文本編輯器,而後列出正在執行的提交,就像這樣:

blob.png

前兩列最關鍵:第一列是選擇命令,它會根據第二列中的SHA碼選擇相應的提交。默認狀況下,rebase –i會認爲每一個更改都正經過pick命令被提交。

要撤銷一個提交,直接在編輯器刪除對應的行就能夠了。若是在你的項目再也不須要這些錯誤的提交,你能夠直接刪除上圖中的第1行和3-4行。

若是你想保留提交但修改提交信息,你可使用reword命令。即,將命令關鍵字pick換成reword(或者r)。你如今可能想馬上修改提交消息,但這麼作不會生效——rebase –i將忽略SHA列後的全部東西。現有的提交信息會幫助咱們記住0835fe2表明什麼。當你敲完rebase –i命令後,Git纔開始提示你重寫那些新提交消息。

若是你須要將2個提交合並,你能夠用squash或者fixup命令,以下圖:

blob.png

squash和fixup都是「向上」結合的——那些用了這些合併命令(編者按:指squash、fixup)的提交,將會和它以前的提交合並:上圖中,0835fe2和6943e85將會合併成一個提交,而38f5e4e和af67f82將會合併成另外一個提交。

當你用squash時,Git將會提示是否填寫新的提交消息;fixup則會給出列表中第一個提交的提交信息。在上圖中,af67f82是一個「Ooops」信息,由於這個提交信息已經同38f5e4e同樣了。可是你能夠爲0835fe2和6943e85合併的新提交編寫提交信息。

當你保存並退出編輯器時,Git將會按照從上到下的順序執行你的提交。你能夠在保存這些提交以前,修改提交的執行順序。若是有須要,你能夠將af67f82和0835fe2合併,而且能夠這樣排序:

blob.png

9、修復早先的提交 Fix an earlier commit

場景:以前的提交裏落下了一個文件,若是先前的提交能有你留下的東西就行了。你尚未push,而且這個提交也不是最近的提交,所以你不能用commit –amend。

使用撤銷命令:git commit --squash和git rebase --autosquash -i

發生了什麼:git commit –squash將會建立一個新的提交,該提交信息可能像這樣「squash! Earlier commit」。(你也能夠手寫這些提交信息,commit –squash只是免得讓你打字了)。

若是你不想爲合併的提交編寫信息,也能夠考慮使用命令git commit --fixup。這種狀況下,你可能會使用commit --fixup,由於你僅但願在rebase中使用以前的提交信息。

rebase --autosquash –i將會啓動rebase交互編輯器,編輯器會列出任何已完成的squash!和fixup!提交,以下圖:

blob.png

當使用--squash和–fixup時,你或許記不清你想修復的某個提交的SHA碼——只知道它可能在一個或五個提交以前。你或許可使用Git的^和~操做符手動找回。HEAD^表示HEAD的前一次提交。HEAD~4表示HEAD前的4次提交,加起來總共是前5次提交。

10、中止跟蹤一個已被跟蹤的文件 Stop tracking a tracked file

場景:你意外將application.log添加到倉庫中,如今你每次運行程序,Git都提示application.log中有unstaged的提交。你在.gitignore中寫上」*.log」,但仍舊沒用——怎樣告訴Git「撤銷」跟蹤這個文件的變化呢?

使用撤銷命令: git rm --cached application.log

發生了什麼:儘管.gitignore阻止Git跟蹤文件的變化,甚至是以前沒被跟蹤的文件是否存在,可是,一旦文件被add或者commit,Git會開始持續跟蹤這個文件的變化。相似的,若是你用git add –f來「強制」add,或者覆蓋.gitignore,Git仍是會繼續監視變化。因此之後最好不要使用–f來add .gitignore文件。

若是你但願移除那些應當被忽略的文件,git rm –cached能夠幫助你,並將這些文件保留在磁盤上。由於這個文件如今被忽略了,你將不會在git status中看到它,也不會再把這個文件commit了。

以上就是如何在Git上撤銷的方法。若是你想學習更多Git命令用法,能夠移步下面相關的文檔:

原文地址:Github

譯文地址:http://www.jointforce.com/jfperiodical/article/show/796?m=d03

相關文章
相關標籤/搜索