天天看點

commit節點号 git_我說小夥子,你死記Git指令,不好使

commit節點号 git_我說小夥子,你死記Git指令,不好使

往期置頂資源整理

  • 資料結構和算法刷題筆記.pdf下載下傳
  • 找工作履歷模闆集(word格式)下載下傳
  • Java基礎核心知識大總結.pdf 下載下傳
  • 68道C/C++常見面試題(含答案)下載下傳
  • Java後端開發學習路線+知識點總結
  • 前端開發學習路線+知識點總結
  • 大資料開發學習路線+知識點總結
  • C/C++開發(背景)學習路線+知識點總結
  • 嵌入式開發學習路線+知識點總結

很多Git指令靠死記硬背很難徹底掌握,本文我們就從常用指令的工作原理角度入手,來徹底掌握這些指令。在開始本篇文章之前,我們可以先試着回答以下幾個問題:

  • 是否了解

    工作區

    暫存區

    倉庫

    之間的差別?
  • Git

    的常用指令在三大區域中是如何工作的?
  • 分支是如何合并的?原理是什麼?
  • 分支合并中

    rebase

    merge

    的差別?

如果有感到很模糊的,那麼建議還是往下仔細看看文章吧~

三大分區

我們首先用一張圖來了解工作區、暫存區和倉庫的位置:

commit節點号 git_我說小夥子,你死記Git指令,不好使

我們先看由下而上的路徑,首先工作區就是我們目前的檔案目錄,我們改完代碼,用

git add

指令把目前檔案加入暫存區,然後

git commit

把暫存區生成的快照送出到本地倉庫,最後再用

git push

指令把本地倉庫的送出複制到遠端倉庫,也就是

Github

之類的線上倉庫。

而由上到下的路徑其實也很好了解,

git pull

用來将遠端倉庫的最新送出拉取到本地倉庫,

git reset -- files

 用來撤銷最後一次

git add files

,也就是撤銷

commit

,這是我們前面提到的復原的一種辦法;

git checkout -- files

則是把檔案從暫存區複制到工作區,用來丢棄本地修改(也就是覆寫掉還未

add

到暫存區的改動)。

常用指令的工作原理

先來個開胃小菜:

diff

上一篇文章中我們講了

git diff

可以直覺的看到工作區和暫存區的差異,這裡我們畫圖示範下不同的

diff

是如何比較的:

commit節點号 git_我說小夥子,你死記Git指令,不好使
  • git diff

    ,不加任何參數,将工作區(未

    add

    的内容)和暫存區進行比較;
  • git diff HEAD

    ,将工作區與

    HEAD

    指針指向的

    commit

    進行比較,一般來說我們目前的改動就是在

    HEAD

    指向的

    commit

    的基礎上進行改動;
  • git diff --cached

    ,将暫存區與目前

    commit

    進行比較;
  • git diff dev

    ,将工作區與目标分支的最新

    commit

    進行比較;
  • git diff [commitId_1] [commitId_2]

    ,将兩個

    commit

    進行比較。

commit

前面我們說了,

commit

會在暫存區生成快照,然後推到本地倉庫,這裡我們考慮三種情況下的送出:

  • 目前

    HEAD

    指向末尾的

    commit

commit節點号 git_我說小夥子,你死記Git指令,不好使
  • 目前

    HEAD

    指向中間的

    commit

    ,此時送出就會再分離出一條新的路線,是以後續的分支合并就不可避免地要派上用場。
commit節點号 git_我說小夥子,你死記Git指令,不好使
  • 希望用新送出覆寫前一個送出:

    git commit --amend

commit節點号 git_我說小夥子,你死記Git指令,不好使

這個使用場景也非常廣泛,比如我們

git commit

後才發現漏改了點東西,這個時候如果再改再送出,就會導緻對一個錯誤的修改用了兩個

commit

,在

git log

上看将會非常醜,對于我們自己做小

demo

時可能無所謂,對于一些大項目或者開源項目,本來

commit

就很多,這樣胡亂地增加

commit

必然是不能接受的。

如上圖所示,我們新增的

commit

會代替原來的

commit

的位置,而舊

commit

則被抛棄掉。

checkout

當我們使用

git checkout [branch_name]

切換分支時,如下圖所示:

commit節點号 git_我說小夥子,你死記Git指令,不好使

dev

分支會把其中的内容複制到暫存區和工作區中,覆寫掉

master

的版本,而隻存在于

master

的檔案則會被删除。

reset

下圖展示了復原的情況,具體的三種情況請仔細看下方的描述:

commit節點号 git_我說小夥子,你死記Git指令,不好使
  • git reset [commitId] --sort

    ,這是最弱的復原方式,隻改變

    commit

    資訊,不影響暫存區和工作區;
  • git reset [commitId]

    ,不攜帶參數時,預設隻復原暫存區,也就是把

    dks8v

    所在的資訊複制到暫存區,但是不影響工作區;
  • git reset [commitId] --hard

    ,這種方式則能復原工作區和暫存區。

merge

Git

的合并有許多政策,預設情況下

Git

會幫助我們挑選合适的政策,當然如果我們需要手動指定,可以使用:

git merge -s [政策名稱]

,了解 

Git

 合并政策的原理可以使你對合并結果有一個準确的預期。

Fast-forward

Fast-forward

是最簡單的一種合并政策,如我們前面示例的圖所示,

dev

分支是

master

分支的祖先節點,那麼合并

git merge dev

的話,隻會将

dev

指向

master

目前位置,

Fast-forward

Git

合并兩個沒有分叉的分支時的預設行為。

Recursive

Recursive

Git

在合并兩個有分叉的分支時的預設行為,簡單的說,是遞歸的進行三路合并。

commit節點号 git_我說小夥子,你死記Git指令,不好使

這裡出現了一個新名詞——

三路合并(three-way merge)

,也是我們接下來講解的重點。我們先搞清楚合并的整體鍊路。

  • 首先

    dev

    分支的

    c5k8x

    HEAD

    指向的

    sf22x

    ,再加上它們的最近公共祖先

    a23c4

    先進行一次三路合并;
  • 然後将合并後的結果拷貝到暫存區和工作區;
  • 再然後産生一次新的送出,該送出的祖先為

    dev

    原master

分支合并的原理

首先,我們來看看兩個檔案如何合并:

下圖所示為

test.py

中某一行的代碼,如果我們要将

A/B

兩個版本合并,就需要确定是

A

修改了

B

,還是

B

修改了

A

,亦或者兩者都修改了,顯然這種情況下分辨不出來。

commit節點号 git_我說小夥子,你死記Git指令,不好使

是以,為了實作兩個檔案的合并,我們引入三路合并:

如下圖所示,很顯然

A

Base

版本相同,

B

版本的修改比

A

版本新,是以将

A/B

合并後,得到的就是

B

版本。

commit節點号 git_我說小夥子,你死記Git指令,不好使

聰明的讀者看完上面的例子,就會想到,要是

A/B

Base

都不一樣怎麼辦?這就是接下來要講的問題了。

沖突

當出現下圖這種情況時,一般就需要我們手動解決沖突了。

commit節點号 git_我說小夥子,你死記Git指令,不好使

也就是我們在合并代碼時往往會看到的一種情況:

對于新手而言,看到這個箭頭可能有點摸不着頭腦,到底哪個是哪個呢?其實分辨起來很簡單,中間的

=======

是分隔符,到最上方的

<<<<<<

之間的内容,是

HEAD

版本,也就是目前的

master

分支,而到最下方

>>>>>>

之間的内容,則是分支

B

的,我們隻需要删除箭頭,保留所需要的版本即可:

最終合并結果:

commit節點号 git_我說小夥子,你死記Git指令,不好使

遞歸三路合并

在實際的生産環境中,

Git

的分支往往非常繁雜,會導緻合并

A/B

時,能找到多個

A/B

的共同祖先,而所謂的遞歸三路合并就是,對它們的共同祖先繼續找共同祖先,直到找到唯一一個共同祖先為止,這樣可以減少沖突的機率。

commit節點号 git_我說小夥子,你死記Git指令,不好使

如上圖所示,我們要合并

5

6

,就需要先找到

5/6

的共同祖先——

2

3

,然後再繼續找共同祖先——

1

,當我們找到唯一祖先時,開始遞歸三路合并,先對

1、2、3

進行三路合并,得到臨時節點

2'/B

commit節點号 git_我說小夥子,你死記Git指令,不好使

接下來繼續對

2、5、6

進行三路合并,得到

7/C

commit節點号 git_我說小夥子,你死記Git指令,不好使

rebase

當我們處于

dev

分支,然後使用

git rebase master

時,可以了解為把

dev

分支上的部分在

master

分支後面重新送出了一遍(重演),具體看下圖:

commit節點号 git_我說小夥子,你死記Git指令,不好使

首先找到

dev

分支和

master

分支的祖先

a23c4

,然後從

a23c4

dev

所在路徑上的節點,都通過回放的方式插入到

master

之後,注意,這裡“複制”的過程中,

commitId

是會改變的。同時,

dev

舊分支上的節點因為沒有了引用則會被丢棄。

總結

回顧開頭的問題,相信仔細閱讀完本篇文章的你已經可以解答了。本篇文章更多聚焦在

Git

的工作原理上,但對于

底層原理

還未展開叙述,後續有機會我們會對

Git

底層到底是如何存儲檔案進行講解,敬請期待。

參考資料

圖解Git:https://marklodato.github.io/visual-git-guide/index-zh-cn.html

Pro Git:https://bingohuang.gitbooks.io/progit2/content/

往期資源整理 可自取

  • 資料結構和算法刷題筆記.pdf下載下傳
  • 找工作履歷模闆大分享.doc下載下傳
  • Java基礎核心知識大總結.pdf 下載下傳
  • 68道C/C++常見面試題(含答案)下載下傳
  • Java後端開發學習路線+知識點總結
  • 前端開發學習路線+知識點總結
  • 大資料開發學習路線+知識點總結
  • C/C++開發(背景)學習路線+知識點總結
  • 嵌入式開發學習路線+知識點總結