代碼回滾:Reset、Checkout、Revert的選擇(轉)

BY 童仲毅(geeeeeeeeek@github)git

這是一篇在原文(BY atlassian)基礎上演繹的譯文。除非另行註明,頁面上全部內容採用知識共享-署名(CC BY 2.5 AU)協議共享。github

git resetgit checkoutgit revert是你的Git工具箱中最有用的一些命令。它們都用來撤銷代碼倉庫中的某些更改,而前兩個命令不只能夠做用於提交,還能夠做用於特定文件。緩存

由於它們很是類似,因此咱們常常會搞混,不知道什麼場景下該用哪一個命令。在這篇文章中,咱們會比較git resetgit checkoutgit revert最多見的用法。但願你在看完後能遊刃有餘地使用這些命令來管理你的倉庫。安全

Git repo的主要組成

Git倉庫有三個主要組成——工做目錄,緩存區和提交歷史。這張圖有助於理解每一個命令到底產生了哪些影響。當你閱讀的時候,牢記這張圖。工具

提交層面的操做

你傳給git resetgit checkout的參數決定了它們的做用域。若是你沒有包含文件路徑,這些操做對全部提交生效。咱們這一節要探討的就是提交層面的操做。注意,git revert沒有文件層面的操做。spa

Reset

在提交層面上,reset將一個分支的末端指向另外一個提交。這能夠用來移除當前分支的一些提交。好比,下面這兩條命令讓hotfix分支向後回退了兩個提交。code

git checkout hotfix
git reset HEAD~2

hotfix分支末端的兩個提交如今變成了懸掛提交。也就是說,下次Git執行垃圾回收的時候,這兩個提交會被刪除。換句話說,若是你想扔掉這兩個提交,你能夠這麼作。reset操做以下圖所示:ip

把hotfix分支reset到HEAD~2

若是你的更改尚未共享給別人,git reset是撤銷這些更改的簡單方法。當你開發一個功能的時候發現『糟糕,我作了什麼?我應該從新來過!』時,reset就像是go-to命令同樣。ci

除了在當前分支上操做,你還能夠經過傳入這些標記來修改你的緩存區或工做目錄:作用域

  • --soft – 緩存區和工做目錄都不會被改變
  • --mixed – 默認選項。緩存區和你指定的提交同步,但工做目錄不受影響
  • --hard – 緩存區和工做目錄都同步到你指定的提交

把這些標記想成定義git reset操做的做用域就容易理解多了。

git rese的定義域

這些標記每每和HEAD做爲參數一塊兒使用。好比,git reset --mixed HEAD 將你當前的改動從緩存區中移除,可是這些改動還留在工做目錄中。另外一方面,若是你想徹底捨棄你沒有提交的改動,你可使用git reset --hard HEAD。這是git reset最經常使用的兩種用法。

當你傳入HEAD之外的其餘提交的時候要格外當心,由於reset操做會重寫當前分支的歷史。正如Rebase黃金法則所說的,在公共分支上這樣作可能會引發嚴重的後果。

Checkout

你應該已經很是熟悉提交層面的git checkout。當傳入分支名時,能夠切換到那個分支。

git checkout hotfix

上面這個命令作的不過是將HEAD移到一個新的分支,而後更新工做目錄。由於這可能會覆蓋本地的修改,Git強制你提交或者緩存工做目錄中的全部更改,否則在checkout的時候這些更改都會丟失。和git reset不同的是,git checkout沒有移動這些分支。

將 HEAD 從 master 移到 hotfix

除了分支以外,你還能夠傳入提交的引用來checkout到任意的提交。這和checkout到另外一個分支是徹底同樣的:把HEAD移動到特定的提交。好比,下面這個命令會checkout到當前提交的祖父提交。

git checkout HEAD~2

將HEAD移動到任意commit

這對於快速查看項目舊版原本說很是有用。但若是你當前的HEAD沒有任何分支引用,那麼這會形成HEAD分離。這是很是危險的,若是你接着添加新的提交,而後切換到別的分支以後就沒辦法回到以前添加的這些提交。所以,在爲分離的HEAD添加新的提交的時候你應該建立一個新的分支。

Revert

Revert撤銷一個提交的同時會建立一個新的提交。這是一個安全的方法,由於它不會重寫提交歷史。好比,下面的命令會找出倒數第二個提交,而後建立一個新的提交來撤銷這些更改,而後把這個提交加入項目中。

git checkout hotfix
git revert HEAD~2

以下圖所示:

revert到倒數第二個commit

相比git reset,它不會改變如今的提交歷史。所以,git revert能夠用在公共分支上,git reset應該用在私有分支上。

你也能夠把git revert看成撤銷已經提交的更改,而git reset HEAD用來撤銷沒有提交的更改。

就像git checkout 同樣,git revert 也有可能會重寫文件。因此,Git會在你執行revert以前要求你提交或者緩存你工做目錄中的更改。

文件層面的操做

git resetgit checkout 命令也接受文件路徑做爲參數。這時它的行爲就大爲不一樣了。它不會做用於整份提交,參數將它限制於特定文件。

Reset

當檢測到文件路徑時,git reset 將緩存區同步到你指定的那個提交。好比,下面這個命令會將倒數第二個提交中的foo.py加入到緩存區中,供下一個提交使用。

git reset HEAD~2 foo.py

和提交層面的git reset同樣,一般咱們使用HEAD而不是某個特定的提交。運行git reset HEAD foo.py 會將當前的foo.py從緩存區中移除出去,而不會影響工做目錄中對foo.py的更改。

將一個文件從commit歷史中移動到stage緩存中

--soft、--mixed和--hard對文件層面的git reset毫無做用,由於緩存區中的文件必定會變化,而工做目錄中的文件必定不變。

Checkout

Checkout一個文件和帶文件路徑git reset 很是像,除了它更改的是工做目錄而不是緩存區。不像提交層面的checkout命令,它不會移動HEAD引用,也就是你不會切換到別的分支上去。

將文件從提交歷史移動到工做目錄中

好比,下面這個命令將工做目錄中的foo.py同步到了倒數第二個提交中的foo.py。

git checkout HEAD~2 foo.py

和提交層面相同的是,它能夠用來檢查項目的舊版本,但做用域被限制到了特定文件。

若是你緩存而且提交了checkout的文件,它具有將某個文件回撤到以前版本的效果。注意它撤銷了這個文件後面全部的更改,而git revert 命令只撤銷某個特定提交的更改。

git reset 同樣,這個命令一般和HEAD一塊兒使用。好比git checkout HEAD foo.py等同於捨棄foo.py沒有緩存的更改。這個行爲和git reset HEAD --hard很像,但隻影響特定文件。

總結

你如今已經掌握了Git倉庫中撤銷更改的全部工具。git resetgit checkout、和 git revert命令比較容易混淆,但當你想起它們對工做目錄、緩存區和提交歷史的不一樣影響,就會容易判斷如今應該用哪一個命令。

下面這個表格總結了這些命令最經常使用的使用場景。記得常常對照這個表格,由於你使用Git時必定會常常用到。

命令 做用域 經常使用情景
git reset 提交層面 在私有分支上舍棄一些沒有提交的更改
git reset 文件層面 將文件從緩存區中移除
git checkout 提交層面 切換分支或查看舊版本
git checkout 文件層面 捨棄工做目錄中的更改
git revert 提交層面 在公共分支上回滾更改
git revert 文件層面 (然而並無)
相關文章
相關標籤/搜索