Git 筆記系列(六)—— Git常用命令-Reset

時間 更新備注
2018-03-02 新建文章
2018-06-10 添加和revert&checkout的對比
2019-01-18 更新鏈接

目錄

引言

在提交層面上,reset將一個分支的末端指向另一個提交。這可以用來移除當前分支的一些提交。比如,下面這兩條命令讓 hotfix 分支向后回退了兩個提交。

git checkout hotfix
git reset HEAD~2

hotfix 分支末端的兩個提交現(xiàn)在變成了懸掛提交。也就是說,下次 Git 執(zhí)行垃圾回收的時候,這兩個提交會被刪除。換句話說,如果你想扔掉這兩個提交,你可以這么做。reset 操作如下圖所示:

image
image

如果你仔細研究reset命令本身就知道,它本身做的事情就是重置HEAD(當前分支的版本頂端)到另外一個commit

Reset解惑

讓我們跟著 reset 看看它都做了什么。它以一種簡單可預(yù)見的方式直接操縱這三棵樹。它做了三個基本操作。 第 1 步:移動 HEAD

reset 做的第一件事是移動 HEAD 的指向。這與改變 HEAD 自身不同(checkout 所做的); reset 移動 HEAD 指向的分支。這意味著如果 HEAD 設(shè)置為 master 分支(例如,你正在 master 分支上),運行 git reset 9e5e64a將會使master指向9e5e64a。

image

reset補充

總的來說,git reset命令是用來將當前branch重置到另外一個commit的,而這個動作可能會將index以及work tree同樣影響。比如如果你的master branch(當前checked out)是下面這個樣子:

- A - B - C (HEAD, master)

HEAD和master branch是在一起的,而你希望將master指向到B,而不是C,那么你執(zhí)行

git reset B以便移動master branch到``B那個commit:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

注意:git reset和checkout是不一樣的。如果你運行g(shù)it checkout B,那么你講得到:

- A - B (HEAD) - C (master)

這時HEAD和master branch就不在一個點上了,你進入detached HEAD State. HEAD,work tree,index都指向了B,但是master branch卻依然指向C。如果在這個點上,你執(zhí)行一個新的commit D,那么你講得到下面(當然這可能并不是你想要的,你可能想要的是創(chuàng)一個branch做bug fix):

- A - B - C (master)
       \
        D (HEAD)
image

記住git reset不會產(chǎn)生commits,它僅僅更新一個branch(branch本身就是一個指向一個commit的指針)指向另外一個commit(Head和branch Tip同時移動保持一致).其他的僅剩對于index和work tree(working directory)有什么影響。git checkout xxxCommit則只影響HEAD,如果xxxCommit和一個branch tip是一致的話,則HEAD和branch相匹配,如果xxxCommit并不和任何branch tip相一致,則git進入detached HEAD 狀態(tài)。

reset用法

重置命令(git reset)是Git最常用的命令之一,也是最危險,最容易誤用的命令。來看看git reset命令的用法。

用法一:git reset [-q] [<commit>] [--] <paths>...

用法二:git reset [--soft | --mixed | --hard | --merge | --keep] [-q] [<commit>]

上面列出了兩個用法,其中<commit>都是可選項,可以使用引用或者提交ID,如果省略 <commit>則相當于使用了HEAD的指向作為提交ID。
上面列出的兩種用法的區(qū)別在于,第一種用法在命令中包含路徑<paths>。為了避免路徑和引用(或者提交ID)同名而沖突,可以在<paths>前用兩個連續(xù)的短線(減號)作為分隔。

第一種用法(包含了路徑<paths>的用法)不會重置引用,更不會改變工作區(qū),而是用指定提交狀態(tài)(<commit>)下的文件(<paths>)替換掉暫存區(qū)中的文件。例如命令git reset HEAD <paths>相當于取消之前執(zhí)行的git add <paths>命令時改變的暫存區(qū)。

第二種用法(不使用路徑<paths>的用法)則會重置引用。根據(jù)不同的選項,可以對暫存區(qū)或者工作區(qū)進行重置。參照下面的版本庫模型圖,來看一看不同的參數(shù)對第二種重置語法的影響。

reset注意

當你傳入HEAD 以外的其他提交的時候要格小心,因為 reset 操作會重寫當前分支的歷史。正如 rebase 黃金法則所說的,在公共分支上這樣做可能會引起嚴重的后果

當執(zhí)行 “git reset HEAD” 命令時,暫存區(qū)的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區(qū)不受影響。

reset Parameters

  1. soft

reset 做的第一件事是移動 HEAD 的指向。
--soft參數(shù)告訴Git重置HEAD到另外一個commit,但也到此為止。如果你指定--soft參數(shù),Git將停止在那里而什么也不會根本變化。這意味著index,working copy都不會做任何變化,所有的在original HEAD和你重置到的那個commit之間的所有變更集都放在stage(index)區(qū)域中。

image
image
  1. mixed(default默認選項)

接下來,reset 會用 HEAD 指向的當前快照的內(nèi)容來更新索引。
--mixed是reset的默認參數(shù),也就是當你不指定任何參數(shù)時的參數(shù)。它將重置HEAD到另外一個commit,并且重置index以便和HEAD相匹配,但是也到此為止。working copy不會被更改。所有該branch上從original HEAD(commit)到你重置到的那個commit之間的所有變更將作為local modifications保存在working area中,(被標示為local modification or untracked via git status),但是并未staged的狀態(tài),你可以重新檢視然后再做修改和commit

image
image
  1. hard

reset 要做的的第三件事情就是讓工作目錄看起來像索引。如果使用 --hard 選項,它將會繼續(xù)這一步。

Git reflog

image

--hard參數(shù)將會blow out everything.它將重置HEAD返回到另外一個commit(取決于~12的參數(shù)),重置index以便反映HEAD的變化,并且重置working copy也使得其完全匹配起來。這是一個比較危險的動作,具有破壞性,數(shù)據(jù)因此可能會丟失!如果真是發(fā)生了數(shù)據(jù)丟失又希望找回來,那么只有使用:git reflog命令了。makes everything match the commit you have reset to.你的所有本地修改將丟失。如果我們希望徹底丟掉本地修改但是又不希望更改branch所指向的commit,則執(zhí)行git reset --hard = git reset --hard HEAD. i.e. don't change the branch but get rid of all local changes.另外一個場景是簡單地移動branch從一個到另一個commit而保持index/work區(qū)域同步。這將確實令你丟失你的工作,因為它將修改你的work tree!

image
image
working index HEAD target         working index HEAD
----------------------------------------------------
  A       B     C    D     --soft   A       B     D
                           --mixed  A       D     D
                           --hard   D       D     D
                           --merge (disallowed)

working index HEAD target         working index HEAD
----------------------------------------------------
  A       B     C    C     --soft   A       B     C
                           --mixed  A       C     C
                           --hard   C       C     C
                           --merge (disallowed)

限定reset重置范圍

前面講述了 reset 基本形式的行為,不過你還可以給它提供一個作用路徑。若指定了一個路徑,reset 將會跳 過第 1 步,并且將它的作用范圍限定為指定的文件或文件集合。這樣做自然有它的道理,因為 HEAD 只是一個指 針,你無法讓它同時指向兩個提交中各自的一部分。不過索引和工作目錄 可以部分更新,所以重置會繼續(xù)進行 第2、3步。

現(xiàn)在,假如我們運行git reset file.txt(這其實是git reset --mixed HEAD file.txt的簡寫形 式,因為你既沒有指定一個提交的 SHA-1 或分支,也沒有指定 --soft 或 --hard),它會:

  1. 移動 HEAD 分支的指向 (已跳過)
  2. 讓索引看起來像 HEAD (到此處停止)

所以它本質(zhì)上只是將 file.txt 從 HEAD 復(fù)制到索引中。

image

更進一步,我們可以不讓 Git 從 HEAD 拉取數(shù)據(jù),而是通過具體指定一個提交來拉取該文件的對應(yīng)版本。我們只需運行類似 于git reset eb43bf file.txt的命令即可。

image

壓縮提交

我們來看看如何利用這種新的功能來做一些有趣的事情 - 壓縮提交。

假設(shè)你的一系列提交信息中有 “oops.”、“WIP” 和 “forgot this file”,聰明的你就能使用 reset 來輕松快 速地將它們壓縮成單個提交,也顯出你的聰明。(壓縮提交 展示了另一種方式,不過在本例中用 reset 更簡 單。)

假設(shè)你有一個項目,第一次提交中有一個文件,第二次提交增加了一個新的文件并修改了第一個文件,第三次提

交再次修改了第一個文件。由于第二次提交是一個未完成的工作,因此你想要壓縮它。

image

那么可以運行git reset --soft HEAD~2來將HEAD分支移動到一個舊一點的提交上(即你想要保留的第 一個提交):

image

注意,這時候因為是在上次提交的后,的Index暫存區(qū)和Working Directory是保持一致的,所以可以直接提交。

然后只需再次運行git commit:

image

現(xiàn)在你可以查看可到達的歷史,即將會推送的歷史,現(xiàn)在看起來有個 v1 版 file-a.txt 的提交,接著第二個提 交將 file-a.txt 修改成了 v3 版并增加了 file-b.txt。包含 v2 版本的文件已經(jīng)不在歷史中了。

reset和checkout

checkout這個命令做的不過是將HEAD移到一個新的分支,然后更新工作目錄。因為這可能會覆蓋本地的修改,Git 強制你提交或者緩存工作目錄中的所有更改,不然在 checkout 的時候這些更改都會丟失。和 git reset 不一樣的是,git checkout 沒有移動這些分支。這對于快速查看項目舊版本來說非常有用。

checkout對工作目錄是安全的,它會通過檢查來確保不會將已更改的文件吹走。

  • checkout不會去修改你在Working Directory里修改過的文件
  • checkout則把HEAD移動到另一個分支
  • reset會不做檢查把working directory里的所有內(nèi)容都更新掉
  • reset把branch移動到HEAD指向的地方
image

reset和revert

  • git revert可以用在公共分支上,git reset應(yīng)該用在私有分支上.

git revert用于記錄一些新的提交以反轉(zhuǎn)一些早期提交的影響(通常只是一個錯誤的提交)。如果你想扔掉工作目錄中所有未提交的更改,你應(yīng)該看到git-reset,特別是--hard選項。如果你想提取特定文件,就像在另一個提交中那樣,你應(yīng)該看到git-checkout,特別是git checkout <commit> -- <filename>語法。請謹慎使用這些替代方法,因為它們都會丟棄工作目錄中的未提交更改。

reset是用來修改提交歷史的,想象這種情況,如果你在2天前提交了一個東西,突然發(fā)現(xiàn)這次提交是有問題的。

這個時候你有兩個選擇,要么使用git revert(推薦),要么使用git reset。

image

上圖可以看到git reset是會修改版本歷史的,他會丟棄掉一些版本歷史。

git revert是根據(jù)那個commit逆向生成一個新的commit,版本歷史是不會被破壞的。
相比git reset,它不會改變現(xiàn)在的提交歷史。因此,git revert可以用在公共分支上,git reset應(yīng)該用在私有分支上。

你也可以把git revert當作撤銷已經(jīng)提交的更改,而git reset HEAD用來撤銷沒有提交的更改。

就像git checkout 一樣,git revert 也有可能會重寫文件。所以,Git會在你執(zhí)行revert之前要求你提交或者緩存你工作目錄中的更改。

如果你的更改還沒有共享給別人,git reset 是撤銷這些更改的簡單方法。當你開發(fā)一個功能的時候發(fā)現(xiàn)「糟糕,我做了什么?我應(yīng)該重新來過!」時,reset 就像是 go-to 命令一樣。

除了在當前分支上操作,你還可以通過傳入這些標記來修改你的緩存區(qū)或工作目錄:

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

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

相比 git reset,它不會改變現(xiàn)在的提交歷史。因此,git revert 可以用在公共分支上,git reset 應(yīng)該用在私有分支上。

總結(jié)

簡單總結(jié)一下,其實就是--soft 、--mixed以及--hard是指代三個不同的恢復(fù)等級。使用--soft就僅僅將Head頭指針恢復(fù),已經(jīng)add的緩存以及工作空間的所有東西都不變。如果使用--mixed,就將Head頭指針恢復(fù)掉,已經(jīng)add的緩存也會丟失掉,工作空間的代碼什么的是不變的。如果使用--hard,那么一切就全都恢復(fù)了,Head頭指針變,add的緩存消失,本地工作區(qū)的代碼的也恢復(fù)到指定之前版本的狀態(tài)。

命令 作用域 常用情景
git reset 提交層面 在私有分支上舍棄一些沒有提交的更改
git reset 文件層面 將文件從緩存區(qū)中移除
git checkout 提交層面 切換分支或查看舊版本
git checkout 文件層面 舍棄工作目錄中的更改
git revert 提交層面 在公共分支上回滾更改
git revert 文件層面 (然而并沒有)
                         head    index   work dir  wd safe
Commit Level
reset --soft [commit]    REF     NO      NO        YES
reset [commit]           REF     YES     NO        YES
reset --hard [commit]    REF     YES     YES       NO
checkout [commit]        HEAD    YES     YES       YES

File Level
reset (commit) [file]    NO      YES     NO        YES
checkout (commit) [file] NO      YES     YES       NO

參考

  1. git reset soft,hard,mixed之區(qū)別深解
  2. Git - git-reset Documentation
  3. 代碼回滾:git reset、git checkout和git revert區(qū)別和聯(lián)系 - houpy - 博客園
  4. Pro Git(中文版)

Git Revert

  1. Git Revert | Atlassian Git Tutorial
  2. Learn how to undo changes in Git using Bitbucket Cloud
  3. 5.2 代碼回滾:Reset、Checkout、Revert 的選擇 · geeeeeeeeek/git-recipes Wiki
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1,查看所有遠程分支:%git branch -r 2, 拉取遠程分支并創(chuàng)建本地分支git checkout -...
    will666閱讀 2,186評論 0 18
  • git branch 查看本地所有分支 git status 查看當前狀態(tài) git commit 提交 git b...
    猿萬閱讀 5,384評論 1 45
  • 我們常常期待能遇到一個何以琛,卻忘了自己根本不是趙默笙的事實。在錦瑟流年中,在懷揣著少女夢的同時,我們都慢慢成為了...
    菩提有樹閱讀 806評論 2 3
  • 談起跑步,已經(jīng)有一年有余。2015年的夏天因為大學(xué)同學(xué)的遠程帶領(lǐng)以及工作一年后壓抑的情緒,我開始跑步了。 ...
    陳佳熠閱讀 459評論 3 2
  • 百無聊賴的日子慢到不能再慢,送完蕭曉曉回家,我沒打車準備自己走路溜達回家,自從我稀里糊涂的成了蕭曉曉的男友后,開始...
    憶如素閱讀 3,001評論 7 10

友情鏈接更多精彩內(nèi)容