有關 Git 中 commit 的原理 理解 及 reset、checkout 命令詳解

對 Git 的學習一直處於學了忘,忘了學的狀態。主要是第一次學會的時候沒有好好進行運用,當想要使用的時,發現,大半都已經忘記了。從新拾起來的時候,又是發現了大量問題。而後再次學習→忘記→學習→忘記,死循環。因此,我仍是將本身一直陷進去的坑給填好吧,這樣,下次忘記的時候容易→學習→忘記→學習。html

更好的閱讀體驗課移至此處
https://halfmoonvic.github.io...git

一、 三個區

看了許多關於 Git 的教程,大可能是先說工做目錄、暫存目錄(索引)和版本庫(倉庫或者HEAD)。都會配些圖片,看來看去,我以爲在Reset, Checkout, and Revert一文中就三個區的概念最爲恰當。一圖以蔽之github

  • Working Directory —— 工做目錄

視力所及處皆是 Working Directoryide

  • Staged Snapshot

有的將這裏稱爲暫存目錄,有的稱爲索引區。而在Reset, Checkout, and Revert一文中就是簡單稱之爲「快照區」,在下實在是認同。緣由如下講。svg

  • Commit History

其它文章看到過有將其爲版本庫或是倉庫的,這個讓人糊塗。由於 Git 自己就是一個版本控制系統,私覺得這三者聯合起來才能稱之爲版本庫。而至於 HEAD,說的仍是比較貼切的。HEAD 只能在這裏遊動,跑不了。wordpress

而將其稱爲 Commit Histroy 最爲貼切處是由於它很好的描述了這個區域的做用 —— 保存 commit 歷史。

二、commit

初學 Git 時,只是簡單的學習 add commit 等等基本操做命名,進而去學習各類遠程庫的推送等等操做,覺得這些好複雜。可等到學完的時候,才發現,本身仍是不怎麼懂最爲基本的 commit 概念。我本身在學習的過程當中逐漸認識到 commit 的厲害之處,對它的認識也是一直處於變化當中。到如今根本不在意它的中文意思應該是什麼,而只是簡單稱它爲 commit ,動詞性的或者名詞性質的,通通稱之爲 commit學習

而我就 Git 的我的理解,我認爲其思想就是基於 commit 的各類控制操做ui

2.一、 基於 commit 的版本操做控制思想
  1. 最開始對 commit 的理解就是簡單的認爲其爲中文釋義——提交。

在咱們修改了文件以後進行git add <file_path>操做,進而進行git commit <file_path>。從 Staged SnapshotCommit History 的操做,這看起來很像一個提交。spa

  1. 繼續學習當中,即涉及到第三個區 Commit History 中的 commit 時,讓人沉醉 —— 快照。

咱們能夠經過 checkout 命名輕鬆的移動HEAD到不一樣的 <commitID> 上,在上一處對 commit 的理解仍是一個動詞性的(提交)。這裏 commit 卻好像是變成了一個名詞——快照(我的跟樂意用<commitID>來表示)。翻譯

  1. 懷疑階段——我究竟在 提交(git commit <file_path>)什麼?

git commit <file_path>操做,開始會認爲是提交了你在 Working Diradd的文件。這個很是對,沒有半點錯誤。但是,你commit操做是 Staged SnapshotCommit History 二者之間的操做。這個叉跨的太大!

  1. 釋疑階段 —— 合體啊~

commit既是提交又是快照
這裏所謂的既是提交又是快照指的僅僅是git commit <file_path>一處操做。什麼意思?
我在將文件git add <file_path>操做後,我在進行git commit <file_path>操做時,事實上是先對addStaged Snapshot 來的文件進行拍照(快照),而後提交給 Commit History

  1. 到處皆是 commit

私覺得,快照這個動做是時時進行的。如你在 Working Dir 中對文件的add操做,是將 Working Dir 拍照了一下,傳給了 Staged Snapshot 了。而後 Staged Snapshot 在將傳過來的快照傳給 Commit History。這樣,從工做目錄開始就是快照級別的操做。這些命令操做當中也就不用糾結這個文件究竟是怎樣的一副狀態了。由於它遠在你add的時候就已經被固定爲快照了。

2.二、 根據顏色辨別狀態

git status時,那個 modified 是顯示紅色仍是綠色,是依據於 commit 所處狀態來決定的。

  1. 顯示 紅色 就是 「Woring Dir 區的 commit」 與 「Staged Snapshot 的 commit / Commit History 的 commit」 有區別。

紅色 modified 即爲「Woring Dir 區的 commit」中內容還未被 add。

  1. 顯示 綠色 是 「Woring Dir 區的 commit / Staged Snapshot 的 commit」 與 「Commit History 的 commit」 有區別。

綠色 modified 即爲「Staged Snapshot 的 commit」還未被 commit。

印象加深圖:
modified

2.三、 總結 commit

到如今,我已經不在意 commit 究竟是什麼意思了,只是簡單稱爲 commit,若是必定讓我翻譯一下。我更爲樂意稱之爲「快照」。
至於那種 commit 的傳遞,我將其認爲是 Git 本身內部命令的結果。
而涉及到「提交」一律念大概只有git commit <file_path>中的commit

三、reset 命令

checkout命令相比,我認爲reset簡單的多,但此命令有危險。除非,你知道本身在作什麼。

reset 許多命令模式會變動 master(或者其它分支名,本文只以 master 分支舉例)分支位置

3.一、 reset 的三種命令模式:
模式名稱 master的位置 暫存區 工做目錄
狀況1.1 --mixed<file_path> 操控 Commit History 和 Staged Snapshot 修改 修改 不修改
狀況1.2 --mixed<file_path> 操控 Staged Snapshot 不修改 修改 不修改
狀況 2 --hard 操控 Commit History、Staged Snapshot、Working Directory 修改 修改 修改
狀況 3 --soft 操控 Commit History 修改 不修改 不修改

示意以下三圖,從左至右依次爲:mixed(操做對象爲HEAD、狀況1.1)、hard、soft三種模式。

  • mixed模式的時候操做對象便可是 HEAD 也能夠是 Staged Snapshop
  • soft hard模式針對的操做對象均是 HEAD

若是強行添加<file_path>,則會報錯fatal: Cannot do (hard, soft) reset with paths

  • 三種模式操做HEAD時,都可以移動HEAD及其HEAD所處的當前<branch>mixed模式帶有<file_path>時則不能夠,其操做控制影響的 是 Staged Snapshop 區。



ᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂ 分割線 ᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂᶂ

關於本人所作圖片有兩處須要闡述,請看下大圖:
高清無碼大圖!!!

  • Working DirStaged Snapshot 兩區,我依然畫出了歷史 commit。不過,未處於激活狀態時,是以灰濛狀態般顯示的。這個灰濛狀態實際意義上就是不存在的意思,此處是爲理解上文中「到處皆 commit」之意而作。真正有多個 commit 的只有 Commit History區。
  • 在我多方多查看參考資料時,發現大可能是以箭頭形式來指向,如 Commit HistoryStaged Snapshot。箭頭性的示意圖給了我一個誤會,那就是 Commit History 控制着 Staged Snapshot 區,Staged Snapshot 區控制着 Working Dir 區。

    而事實上,我更樂意用<font style="color: red;">同步</font>一詞來作比喻。

    • reset hard的時候,是三區的 commit 相同步的;
    • reset mixed的時候則是 Commit HistoryStaged Snapshot 之間的同步;
    • reset soft就只有 Commit History 本身玩耍了。

但是,光是這樣還不夠。如git reset hard HEAD^^模式的時候,是將HEAD^^所在的 <commitID>同步至 Staged SnapshotStaged Snapshot 區。看起來仍是用箭頭來指示比較合適。
這裏箭頭容易形成的誤導是這看起來像是 Commit History 在發號施令,故放棄使用箭頭形式。那麼是誰在發號施令呢?答:是 reset 命令(亦或者說是 reset 在使三者相同步)。
施術者不一樣,職務不一樣,採用的形式不一樣。而我,我更偏向於這種同步性的符號,而非箭頭這種指示性的符號。

  • 三副動圖中,在 Commit History 區變化的是 master 對不一樣的 commit 的指向。三個區的所依據的同步狀態既是 HEAD 所處的位置。亦能夠說是,HEAD指向哪裏,master 跟着到哪裏。主導的能夠說是經過 master 分支 指向 commitHEAD
3.二、 狀況1.1 狀況1.2 --mixed模式

當爲註明reset採用何種模式的時候,默認是採用--mixed模式
此命令經常被用於撤銷 Staged Snapshot 中的 add 進來的文件,一些教程如是說。可事實上,這個命令從始至終都沒 Working Dir 啥事。

1. git reset <commitID>

此種狀況會移動master位置。
以下圖所示,其做用是將 Commit HistoryHEAD^^處的 <commitID> 同步至 Staged Snapshot 區。

2. git reset <commitID> <file_path>

以下圖所示,HEAD位置是不變的,天然HEAD所指向的分支位置也是不變的。變化的只有 Staged Snapshot 區。

如此,當你進行的add操做時,通常狀況下,Working Dircommit 是會超前於 Commit History 區 的 commit
當進行 git reset <file_path>(當未註明 <commitID> 時,默認採用 HEAD) 操做時,Staged Snapshot 區的 commit 恢復到上個狀態。
這看起來像是撤銷操做,實質上是覆蓋操做。便是如上圖中的6跑到了如今的7'位置上

3. 其它無關雜項
  1. 事實上, HEAD <file_path> 都可省略書寫,

    • git reset <file_path> 等價於 git reset HEAD <file_path>
    • git reset 等價於 git reset HEAD
  2. git reset <commitID> . 並不等價於 git reset <commitID>

git reset HEAD^^ . 也不會移動 HEAD,由於 . 就是表明了全部文件。這樣,reset 的操做對象又變成了 Staged Snapshot 區。

3.3 狀況2 hard —— git reset --hard <commitID>

此命令用於你作錯了事情,想要完全抹除蹤影。
採用hard模式的時候,意味着你想要丟棄在 Working Dir 區中的修改。以後繼續進行你的工做。詳細的 commit 步驟不講了,對照第一條看便可。放張圖給你。

  1. git reset --hard HEAD^^命令使得 master 分支狀況發生改變,版本回退了兩個,這裏就有危險要發生了。

    假如你已經將最新的「6commit」推送到遠程庫。這個時候,你經過hard回退了兩個版本,「5commit」「6commit」已被在本地 Commit History 區 刪除掉(我亦用了灰濛狀態來表示)。這個時候,你再次進行git commit <file_path>操做時,新的 commit 將是「55commit」。至此,你已與遠程庫的 Commit History 歷史相沖突了。反正,我能想到的解決辦法只有將遠程庫刪除掉,從新推送本地庫,除此以外沒有其它招了。

  2. git reset --hard HEAD命令

git reset --hard HEAD^^命令相比,git reset --hard HEAD命令要柔和的多。以下圖所示:

這個是恢復當前 HEAD 所在的 commit 到如下兩個區。也就不存在與遠程庫相沖突的問題了。

3.4 狀況3 soft —— git reset --soft HEAD^^

soft 僅僅是移動 Commit History 區的 master 就不一樣的 commit 的指向。沒什麼好說的了。

四、checkout 命令

  • 狀況1.1: 切換HEAD到具體分支「如 git checkout dev
  • 狀況1.2: 切換HEAD到匿名分支「如 git checkout <commitID>
  • 狀況2: Staged Snapshop 區 <commitID>的同步操做,未有分支切換「如 git checkout <file_path>
  • 狀況3: Commit History 區 <commitID>同步操做,未有分支切換 如「git checkout <commitID> <file_path>
4.一、 checkout 做用對象是誰?答:看你寫誰
  1. checkout 用於「狀況1.1」,或者「狀況1.2」時,那麼其做用對象就是 Commit History 中的 HEAD

其做用是讓 HEAD 指向某一個 <commitID> 並將 此 <commitID> 的內容同步至 Staged SnapshotWorking Dir

  1. 而當 checkout 用於「狀況2」,其做用對象爲當前 Staged Snapshot 區的 commitID

其做用是讓 當前 Staged Snapshop 區的 <commitID> 同步進 當前 Woring Dir

  1. 而當 checkout 用於「狀況3」,其做用對象爲 Commit History 中 的 <commitID>,而非 HEAD

其做用是 同步Commit History上某一個<commitID>的內容至當下的Staged SnapshopWoring Dir

4.二、 狀況1.1 && 1.2

HEAD基本上是一直處於指向某一分支的狀況的(HEAD指針指向發生過變更),如 HEAD → master、HEAD → dev 亦或是 HEAD → 匿名分支

  1. git checkout dev

咱們可使用諸如git branch dev般的命令達到切換分支的效果。這裏能夠注意一下,所謂的分支切換,其實質只不過是更改 HEAD 的指向。切換分支的時候就是在更改 HEAD 指向哪一個分支

  1. git checkout <commitID>

當咱們不寫明具體的分支名稱的時,而倒是寫了一個 Commit History 中的一個 <commitID>。那麼,HEAD 將被直接切換指向到這個 <commitID>。這個時候咱們能夠稱其爲匿名分支
修改 bug 的時候會經常這麼作吧。
以下圖,此時,你能夠經過git branch fix_bug命令新建分支,以後在merge到主分支。

4.三、 狀況2: git checkout <file_path>

HEAD 未發生過變更,一直處於當前分支。

git checkout <file_path>命令經常被用於丟棄本地修改。
<span style="color:red">實質上這是一個 Staged Snapshot 區 與 Working Dir 區 兩區相同步的一個操做。同步的方向是由 Staged Snapshot 區 → Working Dir 區。</span>

其做用效果與git reset --hard test.txt差很少,只是在git reset --hard test.txt中,Staged Snapshot 區的 commit 被更新了一次,但它和原來同樣。

4.四、 狀況3: 命令組合git checkout <commitID> <file_path>

HEAD 未發生過變更,一直處於當前分支。

<span style="color:red">git checkout HEAD^^ <file_path>命令是將 Commit History 中 的 commit 同步到 Staged Snapshot 和 Working Dir 區。而 Commit History 中的 HEAD 並未發生移動。</span>

git checkout HEAD^^ <file_path>命令形成的結果是 Staged Snapshot 區的 commit 與 Working Dir 區的 commit 保持一致,而與 Commit History 當前的 commit 不一樣(HEAD未移動的緣由)
以下圖,即 4' = 4'' ≠ 6

同時,咱們能夠經過 git checkout HEAD^^ . 命令將過去的某一版本下的全部文件放置當前的文件目錄下面,而不更改 HEAD 指針

4.5 總結

狀況1.1,狀況1.2,未有指定 <file_path> 這時,會發生 HEAD 的變更,其指向天然也是由 Commit History 指向 Staged SnapshotWoring Dir
狀況2,狀況3 指定了 <file_path>時(針對操做是特定文件),若是沒有指定 Commit Id,則默認從 Staged SnapshotWoring Dir
若是指定了 <commitID>, 則是 在 <commitID>Staged Snapshot<commitID>Woring Dir

結語

我的對git的基本命令的理解就是這些。日常使用 Git 過程中,大體上能夠爲所欲爲的控制各個 commit 版本了。

參考資料:

圖解Git

細說git reset和git checkout的不一樣之處

Reset, Checkout, and Revert

Why git can't do hard/soft resets by path?

相關文章
相關標籤/搜索