Git應用詳解第四講:版本回退的三種方式與stash

前言

前情提要:Git應用詳解第三講:本地分支的重要操做html

git做爲一款版本控制工具,其最核心的功能就是版本回退,沒有之一。熟悉git版本回退的操做可以讓你真真正正地放開手腳去開發,不用當心翼翼,怕一不當心刪除了不應刪除的文件。本節除了介紹版本回退的內容以外,還會介紹stash的使用。git

1、版本回退

git中永遠有後悔藥可吃,老是能夠回到版本庫的某一個時刻,這就叫作版本回退vim

image-20200406144058526

如上圖所示:當前master分支指針指向D,經過版本回退可使master指向CBA。進行版本回退的命令大致上有三種:resetrevertcheckout。下面就來一一講解:app

Ⅰ.git reset

1.參數

reset命令能夠添加不少參數,經常使用的有--mixed--soft--hard三種。下圖爲一次完整提交的四個階段:less

image-20200412192613526

三個參數大致上的區別爲:編輯器

  • --mixed:爲默認值,等同於git reset。做用爲:將文件回退到工做區,此時會保留工做區中的文件,但會丟棄暫存區中的文件;
  • --soft:做用爲:將文件回退到暫存區,此時會保留工做區暫存區中的文件;
  • --hard:做用爲:將文件回退到修改前,此時會丟棄工做區暫存區中的文件;

下面就來詳細地講解它們的使用方法:工具

首先在master分支進行四次提交,每次提交在test.txt中添加一行文本信息:測試

image-20200406164503683

--mixed

該參數爲默認值,做用爲:將文件回退到工做區中:以下圖所示,將test.txt文件回退一次提交:版本控制

image-20200412194811197

能夠看到第四次提交對test.txt的修改操做被回退到了工做區當中,而且保留了工做區中第四次提交對test.txt所作的修改,因此工做區中的test.txt文件內容與回退前一致。指針

--soft

該參數的做用爲:將文件回退到暫存區中:以下圖所示,將test.txt文件回退一次提交:

image-20200412195321082

能夠看到第四次提交對test.txt的修改操做被回退到了暫存區當中,而且保留了工做區和暫存區中第四次提交對test.txt所作的修改,因此,工做區中的文件內容與回退前一致。

--hard

該參數的做用爲:將文件回退到修改前:以下圖所示,將test.txt文件回退一次提交:

image-20200412205112201

能夠看到test.txt直接回到了進行第四次提交前,此時刪除了工做區和暫存區中第四次提交對test.txt所作的修改。因此,工做區變得乾淨了,test.txt文件內容回退到剛完成第三次提交時。

2.寫法

爲了方便演示reset的各類使用方法,下面的指令都採用--hard參數。

git reset --hard HEAD^

該命令的做用爲回退一次提交:

image-20200406164628192

回退後的狀態爲:

image-20200406164713774

能夠看到,該方法會同時改變了HEADmaster指針的指向;

git reset --hard HEAD^^

該命令的做用爲回退兩次提交:

image-20200406170323254

回退後的狀態爲:

image-20200406170352024

一樣,使用--hard參數回退,工做區是乾淨的;能夠看到,該方法也會同時改變HEADmaster指針的指向;

git reset --hard HEAD~n

該命令的做用爲回退n次提交:

image-20200406203027868

能夠看到使用了--hard參數,回退結果符合預期,而且該方法也會同步修改HEAD和分支master指針的指向。

注意:該方式只能向前回退,不能向後回退

上述命令中的HEAD能夠更換爲分支名,好比master

git reset --hard master~n

該命令表示將master分支回退n次提交。因爲HEAD始終指向當前分支,因此使用分支名和使用HEAD效果是同樣的。

git reset --hard commit_id

該指令的做用爲回退到指定的commit id的提交版本;因爲commit id是不會重複的,通常只須要寫前幾(6)位就能夠識別出來。經過commit id的回退方式既能夠向前回退,也能夠向後回退。以下所示,從1st commit日後回退到4th commit,其中4th commitcommit id = bdb373...

爲了熟悉該指令,咱們分兩種方式進行回退:使用--hard參數與使用默認參數。

  • 使用--hard參數

    image-20200406193422130

    從圖中能夠看出:經過第四次提交的commit_id: bdb373順利地從第一次提交向後回退到了第四次提交,而且工做區乾淨。該方法也同時修改了HEAD和分支master的指向,具體過程爲:

    image-20200414171228274

  • 使用默認參數

    image-20200406193005200

    能夠看到切換回了4th commit,可是工做區的test.txt文件並無變化;這是由於,在4th -> 1st的過程當中,須要在工做區中刪除test.txt文件中的2nd line、3rd line、4th line。經過默認參數--mixed,將4th commit對文件的修改回退到了工做區當中,以下圖所示:

    image-20200406202451310

    這個過程丟棄了暫存區中對文件的刪除操做,可是保留了工做區中對文件的刪除操做。因此,工做區中的test.txt文件仍然處於刪除了三行內容的狀態。

    此時只須要將修改操做從階段1移動到修改前的階段0,便可將文件恢復到修改前的狀態,並清空工做區。能夠採用git restore test.txt實現:

    image-20200406202716247

Ⅱ.git revert

revert是回滾,重作的意思。不一樣於reset直接經過改變分支指向來進行版本回退,而且不產生新的提交;revert是經過額外建立一次提交,來取消分支上指定的某次提交的方式,來實現版本回退的。以下圖所示,假如想要重作提交B,重作前與重作後的狀態爲:

image-20200413234440432

所謂重作提交B,指的是在新建的提交B'中取消提交B中所作的一切操做。也就是說revert的思想爲:經過建立一個新提交來取消不要的提交。因此,提交數會增長。

1.參數

git一樣爲revert提供了許多參數,經常使用的有如下三種。爲了演示它們的做用,首先須要設置對應的測試環境:在dev分支上進行四次提交,每次提交都爲test.txt添加一行內容:

image-20200414000404304

-e

-e參數是--edit的縮寫,爲revert指令的默認參數,即git revert -e等同於git revert。該參數的做用爲在重作過程當中,新建一次提交的同時編輯提交信息。好比經過如下命令重作上述的dev2提交:

git revert f4a95

執行該指令後會建立一次新的提交來取消提交dev2所作的一切操做,而且會進入vim編輯器,編輯新提交的提交註釋:

image-20200414115052089

以下圖所示,提交dev2爲文件test.txt添加的dev2文本被取消了,而且dev分支上多了一次提交:

image-20200414114945783

--no-edit

該參數的做用爲不編輯因爲revert重作,所新增提交的註釋信息。以下圖所示,經過:

git revert --no-edit f4a95b

重作提交dev2的過程當中,並不會進入vim編輯器編輯新增提交的註釋信息,而是採用默認的註釋信息:Revert "dev2"

image-20200414114748865

-n

-n參數是--no-commit的簡寫形式,做用爲對revert重作某次提交時所產生的修改,不進行提交,也就是不會新增一次提交;

以下圖所示,這是revert指令經過新建提交B'來取消提交B的過程,分爲0~4個階段。不添加-n參數時,revert指令會產生一次額外提交B',此時處於下圖中的第3階段。而使用-n參數時,雖然revert指令也會經過新建提交B'來重作提交B。可是,此時還處於生成提交B'的過程,尚未徹底生成提交B',也就是處於下圖中的第2階段。

image-20200414002942670

這種作法的好處是,容許咱們干涉revert重作過程,手動進行提交。以下圖所示,經過:

git revert -n f4a95

重作提交dev2的過程當中,手動暫停了重作過程。雖然提交dev2test.txt所作的修改已被撤銷,可是這一重作操做還未進行提交:

image-20200414120436217

這樣咱們既能夠修改重作過程當中不滿意的地方,也能夠隨意添加註釋。修改完後,經過手動提交的方式,完成重作(REVERTING)操做:

image-20200414121147251

2.寫法

revert指令也有多種寫法,下面介紹主要的幾種。爲了方便演示,下列指令都採用默認參數-e手動編輯每次新增提交的註釋信息。

git revert commit_id

這是最經常使用的寫法,經過commit_id精準地選擇想要重作的提交。分兩種狀況:

  • 狀況一:重作最新一次提交,不會發生衝突。

    例如:經過如下指令,重作dev分支上最新的一次提交dev2

    git revert f4a95b

    首先進入vim編輯器編輯新增提交的註釋信息:

    image-20200414135326937

    隨後完成重作操做,以下圖所示;可見提交dev2test.txt添加的dev2內容被刪除了,而且多出一次提交,說明重作成功:

    image-20200414140040443

  • 狀況二:重作非最新一次提交,會發生衝突。

    例如:經過如下指令,重作dev分支上的第三次提交dev1

    git revert dbde45

    會出現合併衝突:

    image-20200414140502098

    使用git mergetool指令,經過vim編輯器的工具vimdiff顯示衝突文件test.txt

    image-20200414140645448

    回車進入vim編輯器界面,解決衝突:

    image-20200414141354304

    解決衝突以後,手動進行一次提交,完成revert過程:

    image-20200414142323103

  • 爲何會出現衝突?

    經過上面的例子不難看出,revert操做生成的新提交實際上是經過兩次提交合並而成的。以下圖所示:

    image-20200414143430837

    • 首先,將被重作的提交dev1的前一次提交2nd複製一份,即圖中的2nd'
    • 而後,將它與當前分支的最新提交dev2進行合併,由今生成revert操做新增的提交;

    知道了revert操做新增的提交的由來後,就不難解釋爲何會出現合併衝突了,以下圖所示:

    image-20200414144109389

    合併的兩次提交中,文件test.txt的內容不同。git不知道以哪一個版本爲準,天然會致使自動合併失敗,須要手動合併。

git revert HEAD

該指令的做用爲重作所在分支的最新一次提交,而且不會發生衝突:

image-20200414150640086

git revert HEAD^

該指令的做用爲重作所在分支的倒數第二次提交,會發生衝突,須要手動合併,完成重作操做:

image-20200414151002143

git revert HEAD^^

該指令的做用爲重作所在分支的倒數第三次提交,會發生衝突,須要手動合併,完成重作操做:

image-20200414180953703

git revert HEAD~n

該指令的做用爲重作所在分支的倒數第n+1次提交,會發生衝突,須要手動合併,完成重作操做。過程與上述一致,這裏就再也不贅述了。

總結:經常使用git revert commit_id這種方式。

3.撤銷revert操做

思路很簡單,再次經過revert操做取消上一次的revert操做(即所謂"負負得正")。

操做前,dev分支上的提交記錄和test.txt文件內容以下:

image-20200414153206034

經過:git revert --no-edit f4a95重作提交dev2--no-edit表示不修改新增提交的註釋):

image-20200414153456451

重作後,多了一次提交,而且test.txt文件中刪除了dev2這一行內容。此時,能夠經過:

git revert --no-edit 582d127

重作上一次重作操做,以此達到取消上一次重作操做的目的:

image-20200414153724455

如上圖所示,雖然多出了一次提交,可是test.txt文件中被刪除的dev2內容被恢復了,這樣就撤銷了revert操做。

Ⅲ.git checkout

1.git checkout commit_id

使用checkout能夠進行版本回退,如直接使用:

git checkout cb214

回退到提交3rd,此時會出現以下提示:

image-20200311111540863

注意到,切換後HEAD指向的再也不是master分支,而是cb214...即第三次提交,查看歷史提交記錄:

image-20200311111719389

可看到只有3次提交,什麼意思呢?以下圖所示:

image-20200412001646768

image-20200311112656834

經過git checkoutHEAD指針指向了第3次提交,能夠將它想象爲一個新的分支。可是卻沒有實際建立分支,即此時head指向的由提交1~3組成的commit對象鏈條處於遊離狀態;

接着,在HEAD還指向遊離的提交節點3的基礎上對文件作出新的修改:

image-20200311113237150

  • 此時若是咱們切換回master分支,會出現下列錯誤

image-20200311113209483

提示顯示:若是沒有保存就從遊離的提交上切換到master分支,這一修改就會被checkout命令覆蓋。咱們能夠在切換前進行一次提交操做:

image-20200311113625297

此時的狀態爲:

image-20200412002213790

  • 在遊離的Commit對象鏈中進行了一次提交以後,再次經過:git checkout master切換到master分支:

image-20200311114055018

提示大意爲:若是沒有任何分支指向剛纔在遊離的Commit對象鏈中進行的提交,那麼該提交就會被忽略。此時的狀態以下圖所示:

image-20200412002655921

若是想要建立一個分支保存(指向)這條遊離的Commit對象鏈,如今就是很好的時機。根據上述提示的命令:

git branch mycommit  c4d5cc3

建立指向commit_idc4d5cc3的提交(即上述的提交節點5)的分支mycommit

image-20200311115117279

由此遊離的commit對象鏈得以被新分支所指向,並獲得了保存,此時的狀態以下圖所示:

image-20200412004042471

總結:

  • 經過checkout進行版本回退會形成遊離的提交對象鏈,須要額外建立一個分支進行保存;

  • 所以,使用checkout進行版本回退的思路爲,先切換到想要回退的提交版本,再刪除進行版本回退的分支dev。最後,建立一個新的dev分支指向遊離的提交對象鏈,完成分支dev的版本回退,簡稱"偷天換日";

  • 只要有分支指向,提交就不會被丟棄。

Ⅳ.revertreset的選擇

因爲checkout會形成遊離的提交對象鏈,因此,通常不使用checkout而是使用resetrevert進行版本回退:

  • revert經過建立一個新提交的方式來撤銷某次操做,該操做以前和以後的提交記錄都會被保留,而且會將該撤銷操做做爲最新的提交;

  • reset是經過改變HEAD和分支指針指向的方式,進行版本回退,該操做以後的提交記錄不會被保留,而且不會建立新的提交;

在我的開發上,建議使用reset;可是在團隊開發中建議使用revert,特別是公共的分支(好比master),這樣可以完整保留提交歷史,方便回溯。

Ⅴ.回退方法彙總

版本回退主要有三大方式resetrevertcheckout,各方式的比較以下:

方法 效果 向前回退 向後回退 同步修改HEAD與分支指向
git reset --hard HEAD^ 往前回退1次提交
git reset --hard HEAD^^ 往前回退2次提交
git reset --hard HEAD~n 往前回退n次提交
git reset --hard <commit_id> 回退到指定commit id的提交
git revert HEAD 重作最新一次提交
git revert HEAD^ 重作倒數第二次提交
git revert HEAD^^ 重作倒數第三次提交
git revert HEAD~n 重作倒數第n+1次提交
git revert commit_id 重作指定commit_id的提交
git checkout commit_id 回退到指定commit id的提交

從上表可知,只有下列三種方式能夠自由地向前向後回退:

git reset --hard commit_id
git revert commit_id
git checkout commit_id

可是,使用checkout進行回退會出現遊離的提交,須要建立一個新分支進行保存,因此不經常使用。

2、git stash

1.git stash的做用

git stash指令的做用爲:對沒有提交到版本庫的,位於工做區或暫存區中游離的修改進行保存,在須要時可進行恢復。具體應用場景以下:

master分支進行兩次提交:1st2nd,隨後建立並切換到dev分支。在dev分支上進行一次提交(dev1),此時兩分支的狀態爲:

image-20200412235844426

隨後在dev分支上給文件test.txt添加一行dev2,可是不提交到暫存區,直接切換到master分支,會出現以下錯誤:

image-20200413001632846

圖中顯示的錯誤大意爲:在dev分支上的修改會被checkout操做覆蓋。下面咱們來看看,將dev分支上的這一修改操做添加到暫存區後,再切換分支,是否還會出現一樣的問題:

image-20200413001752227

可見仍是會出現該錯誤,這初步驗證了位於工做區和暫存區中的修改都會被checkout操做覆蓋的結論。緣由以下圖所示:

image-20200413001917190

雖然在dev分支上修改了文件,可是沒有將這一修改操做進行提交。這樣就不會產生提交節點,就如上圖所示,修改dev2是遊離的,在切換分支的時候會被丟棄。

這種狀況在平常開發中很常見,當在develop分支上開發新功能的時候,master分支出現緊急狀況須要切換回去進行修復。可是,當前分支的新功能還沒開發徹底,貿然切換分支,原來開發的內容就會因被覆蓋而丟失,怎麼辦呢?

有人可能會說進行一次commit不就能夠了嗎?確實能夠。可是,這樣不符合提交的代碼就是正確代碼的原則。更好的解決方法爲使用git stash,以下圖所示:

image-20200413002115302

可見git stash能夠將當前dev分支上,位於在工做區或暫存區中的修改,在未提交的狀況下進行了保存;而且將分支回退到修改前的狀態,保存事後,就能夠很順暢地切換回master分支了。

圖中的WIPworking in progress)表示的是正在進行的工做;

當咱們在master分支上完成了工做,再次切換回dev分支時,查看test.txt文件:

image-20200413002256321

發現切換分支前所作的修改dev2消失了,這是爲何呢?

  • 其實,上面經過git stashdev分支上工做區或暫存區中的修改,提交到了stash區域進行保存,並將dev分支回退到修改前的狀態。以下圖所示:

    image-20200413003349365

  • 切換到master分支時test分支上的修改依舊會被覆蓋。因此,再次回到dev分支時須要從stash區域中恢復切換分支前保存的修改;

怎樣恢復經過git stash保存到stash中的修改呢?能夠經過:

git stash list

查看該分支上被stash保存的修改:

image-20200413224408623

繼續給test.txt文件添加內容:dev3,並經過如下指令保存修改的同時添加註釋:

git stash save '註釋'

image-20200413225024618

  • 首先,經過上述命令能夠修改stash中存儲修改的備註信息;
  • 其次,雖然在test分支上進行了兩次修改,可是使用git stash保存修改後,文件test.txt並無實際被修改;

2.恢復stash存儲的修改

方法有不少,主要有如下三種:

git stash pop

image-20200413225140030

如圖所示,經過上述命令將stash中存儲的最新一次修改恢復了。相信你已經發現了,stash很是相似:先保存的修改,排在最後,序號最大;後保存的修改,排在最前,序號最小;

恢復了最新一次修改後,再次查看stash

image-20200413225221071

能夠看到存儲的修改只剩下一條了,由此可推斷出git stash pop做用爲:

  • 第一:恢復stash中存儲的最新一次修改;
  • 第二:將該修改從stash中刪除;
git stash apply

image-20200413225457480

如上圖所示,使用該指令時發生了合併衝突。這是由於,stash中保存的每一次修改表明的都是一個版本。

image-20200413231349820

  • 如上圖所示,在test分支上,進行第一次修改後,經過git stash將該修改做爲修改0保存到stash中,此時分支中的文件並無發生改變;

  • 進行第二次修改後,經過git stash將修改做爲修改1保存到stash中,分支中的文件依舊沒有發生改變;此時的stash中至關於保存着同一分支上兩個修改後的版本;

  • 此時經過git stash pop取出修改0,與test分支進行合併;再經過git stash pop取出修改1,再次與test分支進行合併,兩個版本合併天然會產生衝突。

手動解決衝突後,要進行一次提交纔算完成了手動合併;隨後查看stash

image-20200413230750201

修改0仍然存在,說明git stash apply的做用爲取出stash中最新(前面)的修改並與分支進行合併。可是,stash中存儲的該修改並不會被刪除;

git stash apply stash@{n}

這是最經常使用的方法,做用爲從stash中恢復特定的修改,而且不刪除stash中的該修改。

test.txt的兩次修改經過git stash存儲到stash中,以下圖所示:

image-20200413232024080

經過git stash apply stash@{1}恢復stash中存儲的修改1

image-20200413232309330

如上圖所示,成功地恢復了stash中的修改1,而且stash中的修改1並無被刪除;

總結:

  • git stash pop:恢復並刪除stash中存儲的最新修改;
  • git stash apply:恢復但不刪除stash中存儲的最新修改;
  • git stash apply stash@{0}:恢復但不刪除stash中存儲的特定提交;

以上就是這一節的所有內容了,相信看到這裏的你已經可以熟練地使用Git進行版本回退了。下一節將會介紹大名鼎鼎的GithubGit的圖形化操做界面。期待與你再次相見!

相關文章
相關標籤/搜索