天天看點

圖解 Git,一目了然!

圖解 Git,一目了然!

上面的四條指令在工作目錄、暫存目錄(也叫做索引)和倉庫之間複制檔案。

git add *files* 把目前檔案放入暫存區域。

git commit 給暫存區域生成快照并送出。

git reset -- *files* 用來撤銷最後一次git add *files*,你也可以用git reset 撤銷所有暫存區域檔案。

git checkout -- *files* 把檔案從暫存區域複制到工作目錄,用來丢棄本地修改。

你可以用 git reset -p, git checkout -p, or git add -p進入互動模式。

也可以跳過暫存區域直接從倉庫取出檔案或者直接送出代碼。

圖解 Git,一目了然!

git commit -a相當于運作 git add 把所有目前目錄下的檔案加入暫存區域再運作。git commit.

git commit *files* 進行一次包含最後一次送出加上工作目錄中檔案快照的送出。并且檔案被添加到暫存區域。

git checkout HEAD -- *files* 復原到複制最後一次送出。

約定

後文中以下面的形式使用圖檔。

圖解 Git,一目了然!

綠色的5位字元表示送出的ID,分别指向父節點。分支用橘色顯示,分别指向特定的送出。目前分支由附在其上的HEAD辨別。 這張圖檔裡顯示最後5次送出,ed489是最新送出。 main分支指向此次送出,另一個stable分支指向祖父送出節點。

指令詳解

Diff

有許多種方法檢視兩次送出之間的變動。下面是一些示例。

圖解 Git,一目了然!

Commit

送出時,git用暫存區域的檔案建立一個新的送出,并把此時的節點設為父節點。然後把目前分支指向新的送出節點。下圖中,目前分支是main。 在運作指令之前,main指向ed489,送出後,main指向新的節點f0cec并以ed489作為父節點。

圖解 Git,一目了然!

即便目前分支是某次送出的祖父節點,git會同樣操作。下圖中,在main分支的祖父節點stable分支進行一次送出,生成了1800b。 這樣,stable分支就不再是main分支的祖父節點。此時,合并 (或者 衍合) 是必須的。

圖解 Git,一目了然!

如果想更改一次送出,使用

git commit --amend

。git會使用與目前送出相同的父節點進行一次新送出,舊的送出會被取消。

圖解 Git,一目了然!

另一個例子是分離HEAD送出,後文講。

Checkout

checkout指令用于從曆史送出(或者暫存區域)中拷貝檔案到工作目錄,也可用于切換分支。

當給定某個檔案名(或者打開-p選項,或者檔案名和-p選項同時打開)時,git會從指定的送出中拷貝檔案到暫存區域和工作目錄。比如,git checkout HEAD~ foo.c會将送出節點HEAD~(即目前送出節點的父節點)中的foo.c複制到工作目錄并且加到暫存區域中。(如果指令中沒有指定送出節點,則會從暫存區域中拷貝内容。)注意目前分支不會發生變化。

圖解 Git,一目了然!

當不指定檔案名,而是給出一個(本地)分支時,那麼HEAD辨別會移動到那個分支(也就是說,我們“切換”到那個分支了),然後暫存區域和工作目錄中的内容會和HEAD對應的送出節點一緻。新送出節點(下圖中的a47c3)中的所有檔案都會被複制(到暫存區域和工作目錄中);隻存在于老的送出節點(ed489)中的檔案會被删除;不屬于上述兩者的檔案會被忽略,不受影響。

圖解 Git,一目了然!

如果既沒有指定檔案名,也沒有指定分支名,而是一個标簽、遠端分支、SHA-1值或者是像main~3類似的東西,就得到一個匿名分支,稱作detached HEAD(被分離的HEAD辨別)。這樣可以很友善地在曆史版本之間互相切換。比如說你想要編譯1.6.6.1版本的git,你可以運作git checkout v1.6.6.1(這是一個标簽,而非分支名),編譯,安裝,然後切換回另一個分支,比如說git checkout main。然而,當送出操作涉及到“分離的HEAD”時,其行為會略有不同,詳情見在下面。

圖解 Git,一目了然!

HEAD辨別處于分離狀态時的送出操作

當HEAD處于分離狀态(不依附于任一分支)時,送出操作可以正常進行,但是不會更新任何已命名的分支。(你可以認為這是在更新一個匿名分支。)

圖解 Git,一目了然!

一旦此後你切換到别的分支,比如說main,那麼這個送出節點(可能)再也不會被引用到,然後就會被丢棄掉了。注意這個指令之後就不會有東西引用2eecb。

圖解 Git,一目了然!

但是,如果你想儲存這個狀态,可以用指令

git checkout -b *name*

來建立一個新的分支。

圖解 Git,一目了然!

Reset

reset指令把目前分支指向另一個位置,并且有選擇的變動工作目錄和索引。也用來在從曆史倉庫中複制檔案到索引,而不動工作目錄。

如果不給選項,那麼目前分支指向到那個送出。如果用

--hard

選項,那麼工作目錄也更新,如果用

--soft

選項,那麼都不變。

圖解 Git,一目了然!

如果沒有給出送出點的版本号,那麼預設用HEAD。這樣,分支指向不變,但是索引會復原到最後一次送出,如果用

--hard

選項,工作目錄也同樣。

圖解 Git,一目了然!

如果給了檔案名(或者

-p

選項), 那麼工作效果和帶檔案名的checkout差不多,除了索引被更新。

圖解 Git,一目了然!

Merge

merge 指令把不同分支合并起來。合并前,索引必須和目前送出相同。如果另一個分支是目前送出的祖父節點,那麼合并指令将什麼也不做。 另一種情況是如果目前送出是另一個分支的祖父節點,就導緻fast-forward合并。指向隻是簡單的移動,并生成一個新的送出。

圖解 Git,一目了然!

否則就是一次真正的合并。預設把目前送出(ed489 如下所示)和另一個送出(33104)以及他們的共同祖父節點(b325c)進行一次三方合并。結果是先儲存目前目錄和索引,然後和父節點33104一起做一次新送出。

圖解 Git,一目了然!

Cherry Pick

cherry-pick指令"複制"一個送出節點并在目前分支做一次完全一樣的新送出。

圖解 Git,一目了然!

Rebase

衍合是合并指令的另一種選擇。合并把兩個父分支合并進行一次送出,送出曆史不是線性的。衍合在目前分支上重演另一個分支的曆史,送出曆史是線性的。 本質上,這是線性化的自動的 cherry-pick

圖解 Git,一目了然!

上面的指令都在topic分支中進行,而不是main分支,在main分支上重演,并且把分支指向新的節點。注意舊送出沒有被引用,将被回收。

要限制復原範圍,使用

--onto

選項。下面的指令在main分支上重演目前分支從169a6以來的最近幾個送出,即2c33a。

圖解 Git,一目了然!

同樣有git rebase --interactive讓你更友善的完成一些複雜操作,比如丢棄、重排、修改、合并送出。

技術說明

檔案内容并沒有真正存儲在索引(.git/index)或者送出對象中,而是以blob的形式分别存儲在資料庫中(.git/objects),并用SHA-1值來校驗。 索引檔案用識别碼列出相關的blob檔案以及别的資料。對于送出來說,以樹(tree)的形式存儲,同樣用對于的哈希值識别。樹對應着工作目錄中的檔案夾,樹中包含的 樹或者blob對象對應着相應的子目錄和檔案。每次送出都存儲下它的上一級樹的識别碼。

如果用detached HEAD送出,那麼最後一次送出會被the reflog for HEAD引用。但是過一段時間就失效,最終被回收,與git commit --amend或者git rebase很像。