git reset 和 git revert

吉他她他它發表於2019-03-04

今天恰巧遇到了需要回退分支的問題,我之前一直在用的都是git reset,自認為清楚明瞭,也就沒去看還有其他的回退方法。今天忽然被問到,特意查了查。

git reset

develop ----1      3-----
                /
branch a       a
複製程式碼

develop將a分支合併後,想要不留痕跡的撤回合併。這個時候用git reset就是很好的選擇了。

具體的操作步驟如下:

  1. 切換分支到develop
  2. git log 檢視當前分支日誌

例如我的日誌是這樣的:

commit 3
Merge 1 2
Author: admin <admin@163.com>
Date: Wed May 30 15:00:00 2018 +0800

    Merge branch `feature/a` into `develop`
    
    close a
    
    See merge request !20
    
commit 2
Author: admin <admin@163.com>
Date: Wed May 30 14:00:00 2018 +0800

    close a
    
commit 1
Author: admin <admin@163.com>
Date: Wed May 30 13:00:00 2018 +0800
    init project
複製程式碼

我要將develop回退到合併之前的狀態,那就是退到 commit 1這了,將commit號複製下來。退出編輯介面。

  1. 講一下git reset引數定義,具體見官網
    --soft 回退後a分支修改的程式碼被保留並標記為add的狀態(git status 是綠色的狀態)
    --mixed 重置索引,但不重置工作樹,更改後的檔案標記為未提交(add)的狀態。預設操作。
    --hard 重置索引和工作樹,並且a分支修改的所有檔案和中間的提交,沒提交的程式碼都被丟棄了。
    --merge 和--hard類似,只不過如果在執行reset命令之前你有改動一些檔案並且未提交,merge會保留你的這些修改,hard則不會。【注:如果你的這些修改add過或commit過,merge和hard都將刪除你的提交】
    --keep 和--hard類似,執行reset之前改動檔案如果是a分支修改了的,會提示你修改了相同的檔案,不能合併。如果不是a分支修改的檔案,會移除快取區。git status還是可以看到保持了這些修改。
    
    複製程式碼

a分支的程式碼我不需要了,以後應該也不需要了。因此:

git reset 1(貼上過來的commit號) --hard

a分支程式碼我還是想要的,只是這個提交我不想要了:
git reset 1

  1. git log檢視一下:
commit 1
Author: admin <admin@163.com>
Date: Wed May 30 13:00:00 2018 +0800
    init project
複製程式碼

是我想要的沒錯了,可以push到遠端去了。(這一步很危險,一定要確認好reset的結果確實是你想要的結果,否則,丟失了程式碼就不要怨我了)

git push origin develop

![rejected] develop -> develop (non-fast-forward)
error: 無法推送一些引用到 `git@github.cn:...`
提示:更新被拒絕,因為您當前分支的最新提交落後於其對應的遠端分支。
。。。
複製程式碼

好吧,我的分支確實落後於遠端的develop分支。我需要--force

git push origin develop --force

Total 0 (delta 0), reused 0 (delta 0)
To git@...
+ 83***...23***** develop -> develop (forced update)
複製程式碼

可以了,回到github檢視network。嗯,就是想要這樣的。

git revert

還是這個例子

develop ----1      3-----
                /
branch a       a
複製程式碼

還是之前的需求,不想要合併a,只想要沒合併a時的樣子。

操作步驟:

  1. 切分支到develop:git checkout develop
  2. 檢視日誌:git log,還是上面的日誌:
commit 3
Merge 1 2
Author: admin <admin@163.com>
Date: Wed May 30 15:00:00 2018 +0800

    Merge branch `feature/a` into `develop`
    
    close a
    
    See merge request !20
    
commit 2
Author: admin <admin@163.com>
Date: Wed May 30 14:00:00 2018 +0800

    close a
    
commit 1
Author: admin <admin@163.com>
Date: Wed May 30 13:00:00 2018 +0800
    init project
複製程式碼

這次和git reset 不同的是我不能複製 commit 1這個commit號了,我需要複製的是commit 2的commit號。因為revert後面跟的是具體需要哪個已經合併了的分支,而並不是需要會退到哪的commit號。

  1. 開始回退:git revert 2
Revert "close a"
This reverts commit 2
#.......
複製程式碼

這是相當於又新增了一個commit,把a分支上的所有修改又改了回去。

  1. Ctrl+X離開編輯commit資訊頁面。

git log檢視一下是不是我想的那樣

commit 4
Author: admin <admin@163.com>
Date: Wed May 30 17:00:00 2018 +0800
    Revert "close a"
    This reverts commit 2
commit 3
Merge 1 2
Author: admin <admin@163.com>
Date: Wed May 30 15:00:00 2018 +0800

    Merge branch `feature/a` into `develop`
    
    close a
    
    See merge request !20
    
commit 2
....
複製程式碼

確實是新增加了一個commit,檢視程式碼發現a分支的修改都不存在了,也達到了我想要的效果。

  1. push的遠端伺服器上git push origin develop

檢視network,是這樣的:

develop ----1      3-----revert a------
                /
branch a       a
複製程式碼

如此看來,git resetgit revert都能實現我現在的需求,那這兩個到底有什麼區別呢,在網上查了這個問題,我覺得說的有些抽象,看的不是很明白,於是自己實踐了之後才明白。

區別

  1. git revert是用一次新的commit來回滾之前的commit,git reset是直接刪除指定的commit。

    這個很好理解,在剛才的操作中我們看日誌已經可以看到這個現象。

    git reset操作之後,我們檢視上面例子的network已經可以看到network中只有commit 1,分支a合併分支後的commit 3都消失了;

    git revert操作之後,network中還是可以看到a分支和合並a分支的操作,只不過在其基礎上又增加了一個revert的commit而已。

  2. git reset 是把HEAD向後移動了一下,而git revert是HEAD繼續前進,只是新的commit的內容和要revert的內容正好相反,能夠抵消要被revert的內容。

    這個也是可以清晰明瞭的看到,我就不做過多的解釋了

  3. 在回滾這一操作上看,效果差不多。但是在日後繼續merge以前的老版本時有區別。因為git revert是用一次逆向的commit“中和”之前的提交,因此日後合併老的branch時,導致這部分改變不會再次出現,但是git reset是之間把某些commit在某個branch上刪除,因而和老的branch再次merge時,這些被回滾的commit應該還會被引入。

    git revert

    現在的需求是我之前已經把a分支revert了,但是我現在又需要a分支的程式碼了,我之前都寫過一遍總不能再重新寫一遍了。我首先想到的方法,把a分支再merge到develop不就好了。

    git merge a
    複製程式碼

    結果

    Already up-to-date
    複製程式碼

    啥?因為我們之前提交合並的a分支的程式碼還在,因此我們並不能在重新合併a分支。

    解決辦法:使用revert之前revert的commit號。在上面的例子中就是git revert 4。於是又新增了一個commit,把之前revert的程式碼又重新revert回來了。具體參考

    git reset

    依舊是上面的需求。

    develop -----1-----4-------5------6
                     /      /     /
    feature         b      c       d
    複製程式碼

    現在我將develop reset到commit 4這裡,繼續提交我以後的程式碼

    develop -----1-----4-------7------8
                     / \     /
    feature         b      e
                            ------5----d
                              -c-/
    複製程式碼

    我現在想重新合併d分支的程式碼

     develop -----1-----4-------7------8------9
                     / \     /             /
    feature         b      e              /
                            ------5----d/
                              -c-/
    複製程式碼

    合併過來大概是這個樣子的。也就是說d分支之前已經合併過c分支,因此如果還想要合併d分支的話,c分支的程式碼就會同步的帶過來。

應用場景

講到這裡大部分常用的應用場景也就應該是比較清晰的了。

  1. 如果回退分支的程式碼以後還需要的話用git revert就再好不過了;
    如果分支我就是提錯了沒用了還不想讓別人發現我錯的程式碼,那就git reset
  2. 例如:develop分支已經合併了a、b、c、d四個分支,我忽然發現b分支沒用啊,程式碼也沒必要,這個時候就不能用reset了,因為使用reset之後c和d的分支也同樣消失了。這時候只能用git revert b分支commit號,這樣c和d的程式碼依然還在。

總結

我能想到的大概就是這些了,當然在具體專案上我們會遇到很多沒遇到過的情形。我的建議是不要盲目的查詢類似問題後就去修改,因為他的問題並不一定就等同於你的問題。我建議去建立測試的分支,等真正整明白了,再去整線上的分支也不遲。

第一次寫了這麼詳細的一篇文章,主要也是因為網上查到的知識,對git知識體系一般的人來說,看的比較費解。

相關文章