傳統VCS的回滾操做git
對於版本控制系統VCS來講,回滾這個操做應該是個很普通也是很重要的需求。網絡
若是你是傳統VCS,好比SVN或者P4來講,revert是個最直觀,也是最直接的手段,固然前提是你的修改尚未被提交到遠程的中央倉庫。分佈式
若是你已經ci了你的code到了遠程中央倉庫,那revert恐怕也無能爲力,只能藉助其餘命令workaroud這個問題,好比:你用SVN的話,就得來個逆向merge操做,把全部的修改都merge回去。工具
但這樣作也有一些弊端:3d
此次merge會做爲一次全新的commit記錄記錄下來,也就是說它不能真正從你的歷史記錄裏面抹掉你那次不想要的修改。一般狀況下其實也沒啥大不了的,除非你我的潔癖就是不想看到之前的那次commit記錄或者你真的幹了啥不想讓別人知道的事情。版本控制
Git時代的回滾操做指針
但當發展到git時代,這種回滾操做的複雜度,已經隨着git模型自己的特色,變得不那麼簡單了。code
熟悉git的人都知道,爲了分佈式的需求,git將每個網絡節點做爲了一個完整的VCS,也就是每一個單臺的host在沒有網絡的前提下,都是一個不受任何影響能夠知足除了和其餘節點同步(好比:git pull/push這類)以外的幾乎全部操做。對象
爲了達到這種效果,git不只在本地有一個完整的local repository,並且將本來簡單的working tree(或者叫working directory)也切成了兩塊區域——working tree和index(也叫stage)。blog
這樣,光從本地修改的角度來看,你的修改就可能存在三塊區域中,working tree、index或者commit以後的歷史對象區域。下面咱們一個一個各個區域通常都怎麼回滾。
working tree內的回滾
這個屬於最簡單一種情形,本質上說也是和傳統VCS中revert直接對應的一種場景,只是這裏不叫revert了,而是git checkout,這種情形很簡單,這裏就不作截圖展現了。列出依稀經常使用的命令形式以下:
- git checkout file1 (回滾單個文件)
- git checkout file1 file2 ... fileN (一次回滾多個文件,中間用空格隔開便可)
- git checkout . (直接回滾當前目錄一下的全部working tree內的修改,會遞歸掃描當前目錄下的全部子目錄)
index內的回滾
這部分回滾也不復雜,由於這部分的回滾,只要你勤快點使用git status命令,命令的輸出上都會給你提示你須要幹啥。只是這個過程通常被分爲了兩步:
- 將index區域中修改過的文件移除index,也就是恢復到working tree中。這部用git reset來解決。
- 一旦文件從新回到working tree中,回滾操做就是上面提到的git checkout嘍。
這個看個截圖直觀點:
我working tree下的原始文件信息以下
我修改了a.txt和my_dir/b.txt,並將將他們加入了index區域,當前運行git status獲得以下輸出
這裏再執行git reset . 將當前目錄及子目錄內的全部修改移出index區域,再次運行git status命令
到這一步以後,就用上面提到git checkout就能夠解決問題了。
commit以後的回滾
這種情形是git本地回滾裏面最複雜,也是最容易讓人迷糊的了,由於針對不一樣的狀況,方法比較多,因此不是很好記。
- 修改最後一次commit的記錄:不少時候先要回滾僅僅是由於本身對最後一次的commit的漏掉(注意,這裏說的漏掉不只僅是你少提交了文件的修改,也包括你多提交了一下你不想要提交的東西)了一些東西,想要回滾此次commit以後再從新commit。若是是這樣的話,沒有必要真的非要先回滾再從新commit。只要在在本身已經滿意了本身全部的修改以後,直接執行git commit --amend,就能夠開啓上次提交的「補救」提交模式,而後把你對上次全部漏掉的東西加上去就行了。下面看個例子:我進行了一次錯誤的提交,修改的內容以下:
目前commit 記錄以下:
如今我想補救此次commit,至關於取消此次新加入的文件b.txt、取消對a.txt第三行的修改,而後加入我真正想要的修改:在my_dir下增長一個c.txt,而且修改a.txt的第三行爲另一句話。
再次經過git log查看commit記錄
請注意比較最新的一次commit的修改,其實已經被修改成另外一個SHA1的值了。這裏請注意,從某種意義上說(實際上這種替換在reflog中很容易追蹤到痕跡,只是在全部的commit逆向引用鏈條中,咱們已經找不到以前的那個fad4...),這種操做已經作到了無痕修改最後一次提交。這和SVN的逆向merge是本質不一樣的。
- 回滾中間的某次提交(固然也包括最後一次):好比我想要回滾上圖中倒數第二次提交,就是HEAD^那次,咱們先經過git show HEAD^看看那次提交都幹了啥?
而後再經過git revert HEAD^ 來回滾此次操做,而後咱們獲得了下面的提示:
杯具,衝突了。。。其實,只要你熟悉任何一種VCS工具,想一想這個場景,其實也是挺正常的。那就git status看看哪些個文件衝突了吧。
其實你只要仔細看看上面的說明信息,應該已經知道該怎麼解決這個衝突了。明顯,a.txt是衝突發生的文件:
打開這個文件,能夠看到標準的衝突標識文件。這裏正是以前咱們採用補救式提交方式修改的那句話。至於衝突怎麼解決很容易,看你究竟想要啥了,本身去編輯,去掉衝突範圍標識符號,保存文件便可。而後按照git正常的流程再次提交,編輯提交的信息便可。再次提交以後的log信息以下:
上面咱們基本上演示了一個標準的revert場景(包括了衝突解決),從這個過程能夠看出,git revert和SVN的逆向merge幾乎一模一樣,就是將你須要回滾的那次commit所作的全部操做,反向操做一次,而後從新作一一個單獨的commit對象進行提交。這個過程是否發生衝突,就取決於你的修改了。請注意,這個過程你雖然回滾了你不想要的修改內容,可是你無法抹掉那次commit在history中的信息,請注意上圖的第三行,他依舊堅挺的躺在那裏。這個也是git revert的特色。固然,git revert實際上也提供-n(--no-commit)參數,用來表示僅將revert的修改體如今當前的working tree,不自動進行提交。可是若是你真的想回滾那些修改的話,再次commit這個環節是逃不掉的。
- 回滾最後的N次提交(永遠從commit的history中抹掉這些記錄):這種場景就輪到git reset登場了。git reset的幫助文檔寫的很是清楚,在回滾commit的場景中,他的做用就是將當前的HEAD reset到你指定的那個分支。但這個過程當中最值得注意的就是你使用的參數,最經常使用的主要是--soft(我的推薦使用這個,他不會修改你目前index或者working tree中所作的任何修改)/--mixed(你在reset時不加任何參數時的默認行爲,會默默把你在index中的修改給滅了!)/--hard(這個是我絕的最危險的參數,會把你index和working tree中的全部修改毀滅的毛都不剩,使用以前請三思,這確實是你要的行爲!)這三種。由於我推薦使用--soft參數,下面主要演示回滾到3f412...那次的記錄(git reset --soft HEAD~2):
從上面能夠看出來,你的index區域突然多了不少未提交的修改,這些就是回滾回來的記錄,要怎麼處理他們,就看你的了。這時我再來看看log的記錄信息:
最新的提交已經變成咱們但願的那次了。其實從git reset的解釋中,咱們就能夠看出,git reset是一個「斬斷」式的回滾操做,由於你把當前的HEAD指針直接移動到了你須要回滾到的那次記錄。而git自己的commit鏈條是逆向回溯的,因此你在提交歷史裏面再也找不到當前HEAD指向的commit以後的記錄了。(不過若是你是git文藝青年的話,你固然知道,想找到那些表面上找不到的commit,經過reflog也是易如反掌)。
好了,到這裏,經常使用的git回滾操做和場景都介紹完了。但願對不熟悉git的TX能有所幫助。