1. 程式人生 > >git的reset和checkout的區別

git的reset和checkout的區別

Git裡有三個區域很重要

  1. HEAD 指向最近一次commit裡的所有snapshot
  2. Index 快取區域,只有Index區域裡的東西才可以被commit
  3. Working Directory 使用者操作區域

下圖解釋了這三個區域的狀態的變化過程:

初始狀態

當你checkout分支的時候,git做了這麼三件事情

  1. 將HEAD指向那個分支的最後一次commit
  2. 將HEAD指向的commit裡所有檔案的snapshot替換掉Index區域裡原來的內容
  3. 將Index區域裡的內容填充到Working Directory裡

所以你可以發現,HEAD、Index、Working Directory這個時候裡的內容都是一模一樣的。

注意:一般會誤解為,Index中的內容是空的,只有git add後才會有東西。實際上不是,Index裡一直是有東西的。

所以,Git的所有操作就是對這三個區域的狀態(或內容)的操作。

Changed

如果你在Working Directory裡修改了檔案,git會發現Working Directory裡的內容和Index區域裡的內容不一致了。

這個時候git status的結果是:

# Changes not staged for commit:

Staged

一個檔案僅僅changed是不能被commit的,Git要求只能提交Index裡的東西。

所以需要git add。這個命令的意思是,把Changed的檔案的內容同步到Index區域裡。這樣Working Directory和Index區域的內容就一致了。這個過程被稱之為stage

這個時候git status的結果是:

# Changes to be committed:

Committed

最後,你就可以提交了

git commit

這樣,就把HEAD的狀態和Index以及Working Directory形成一致了。

reset

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

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

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

而git revert是根據那個commit逆向生成一個新的commit,版本歷史是不會被破壞的。

已經push到遠端倉庫的commit不允許reset

上面已經講了,git reset是會丟棄掉commit的。

如果commit已經被push到遠端倉庫上了,也就意味著其他開發人員就可能基於這個commit形成了新的commit,這時你去reset,就會造成其他開發人員的提交歷史莫名其妙的丟失,或者其他災難性的後果。

因此,一旦commit已經被push到遠端倉庫,那麼是堅決不允許去reset它的。

不帶檔案引數的reset

前面章節已經說道Git有三個區域,Git的所有操作實際上是在操作這三個區域的狀態(或內容)。

git reset配合不同的引數,對這三個區域會產生不同的影響。

reset實際上有3個步驟,根據不同的引數可以決定執行到哪個步驟(--soft--mixed--hard)。

  1. 改變HEAD所指向的commit(--soft)
  2. 執行第1步,將Index區域更新為HEAD所指向的commit裡包含的內容(--mixed)
  3. 執行第1、2步,將Working Directory區域更新為HEAD所指向的commit裡包含的內容(--hard)

注意

–mixed是預設引數,也就是說執行reset的時候不給就認為是--mixed

下表說明了三種形式的git reset所產生的不同效果。

target代表想要將git指向到哪個commit

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

上面講到的git reset實際上不帶引數的,如果帶上檔案引數,那麼效果會是怎樣的?

  1. HEAD不會動
  2. 將那個commit的snapshot裡的那個檔案放到Index區域中

需要注意的是帶檔案引數的git reset沒有--hard, --soft這兩個引數。只有--mixed引數。

unstage

下面這兩個命令是一樣的,都是reset到HEAD上。

git reset file.txt
git reset --mixed HEAD file.txt

這個例子的意義在於,unstage file,仔細想一想是不是這樣?當你把一個檔案stage到Index區域裡後後悔了,那麼只需要把Index區域裡的這個檔案恢復到最近一次commit的狀態(也就是HEAD),那就相當於unstage了。

恢復到歷史版本

下面這個命令就是將某個檔案恢復到歷史版本上。

reset eb43bf file.txt

這個例子的意思在於,把某個檔案恢復到Index區域裡,然後直接commit,這樣就等於把這個檔案恢復到歷史版本了,這樣依賴你都不需要去改動Working Directory了。

checkout

前面講到checkout是會修改HEAD的指向,變更Index區域裡的內容,修改Working Directory裡的內容。

這看上去很像reset --hard,但和reset --hard相比有兩個重要的差別

  1. reset會把working directory裡的所有內容都更新掉
  2. checkout不會去修改你在Working Directory裡修改過的檔案
  3. reset把branch移動到HEAD指向的地方
  4. checkout則把HEAD移動到另一個分支

第二個區別可能有點難以理解,舉例來說:假設你有兩個分支master和develop,這兩個分支指向不一樣的commit,我們現在在develop分支上(HEAD指向的地方)

如果我們git reset master,那麼develop就會指向master所指向的那個commit。

如果我們git checkout master,那麼develop不會動,只有HEAD會移動。HEAD會指向master。看圖:

帶檔案引數

當執行git checkout [branch] file時,checkout幹了這件事情:

  1. 更新了index區域裡file檔案的內容
  2. 更新了working directory裡file檔案的內容

總結reset和checkout

                         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

“head”一列中的“REF”表示該命令移動了HEAD指向的分支引用,而“HEAD”則表示只移動了HEAD自身。 特別注意 “wd safe?” 一列,YES表示不會懂你在work dir的修改,NO代表會動你在work dir的修改。