這一節主要是幾個概念的解釋:HEAD
、master
以 及 Git 中非常重要的一個概念: branch
。
1.引用:commit 的快捷方式
首先,再看一次 log
:
git log
第一行的 commit
後面括號裏的 HEAD -> master, origin/master, origin/HEAD
,是幾個指 向這個 commit
的引用。在 Git 的使用中,經常 會需要對指定的 commit
進行操作。每一個 commit
都有一個它唯一的指定方式——它的 SHA-1
校驗和,也就是上圖中每個黃色的 commit
右邊的那一長串字符。兩個 SHA-1
值的 重複概率極低,所以你可以使用這個 SHA-1
值來 指代 commit
,也可以只使用它的前幾位來指代它 (例如第一個 78bb0ab7d541…16b77
,你使用 78bb0ab
甚至 78bb
來指代它通常也可以), 但畢竟這種沒有任何含義的字符串是很難記憶的, 所以 Git 提供了**「引用」的機制**:使用固定的字符在這裏插入代碼片
串作爲引用,指向某個 commit
,作爲操作 commit
時的快捷方式。
2.HEAD:當前 commit 的引用
上一段裏說到,圖中括號裏是指向這個 commit
的引用。其中這個括號裏的 HEAD 是引用中特 殊的一個:它是指向當前 commit
的引用。所謂當前 commit
這個概念很簡單,它指的就是 當前工作目錄所對應的 commit
。
例如上圖中的當前 commit
就是第一行中的那個 新的 commit
。每次當有新的 commit
的時 候,工作目錄自動與新的 commit
對應;而與 此同時, HEAD
也會轉而指向新的 commit
。 事實上,當使用 checkout
、 reset
等指令手動 指定改變當前 commit
的時候, HEAD
也會一起 跟過去。
總之,當前 commit
在哪裏, HEAD
就在哪裏, 這是一個永遠自動指向當前 commit
的引用,所 以你永遠可以用 HEAD
來操作當前 commit
。
3.branch
HEAD
是 Git 中一個獨特的引用,它是唯一的。而 除了 HEAD
之外,Git 還有一種引用,叫做 branch
(分支)。 HEAD
除了可以指向 commit
,還可以指向一個 branch
,當它指向某 個 branch
的時候,會通過這個 branch
來間接 地指向某個 commit
;另外,當 HEAD
在提交時自動向前移動的時候,它會像一個拖鉤一樣帶着它 所指向的 branch
一起移動。
例如上面的那張圖裏, HEAD -> master
中的 master
就是一個 branch
的名字,而它左邊的 箭頭 ->
表示 HEAD
正指向它(當然,也會間接 地指向它所指向的 commit
)。
如果我在這時創建一個 commit
,那麼 HEAD
會 帶着 master
一起移動到新的 commit
:
git commit
通過查看 log
,可以對這個邏輯進行驗證:
git log
從圖中可以看出,新的 commit
(提交信 息:"Add feature1"
)被創建後, HEAD
和 master
這兩個引用都指向了它,而在上面第一張 圖中的後兩個引用 origin/master
和 origin/HEAD
則依然停留在原先的位置。
4.master: 默認 branch
上面的這個 master
其實是一個特殊的branch
:它是 Git 的默認 branch
(俗稱主 branch
/ 主分支)。
所謂的「默認 branch
」,主要有兩個特點:
-
新創建的
repository
(倉庫)是沒有任何commit
的。但在它創建第一個commit
時,會把master
指向它,並把HEAD
指向master
。 -
當有人使用
git clone
時,除了從遠程倉 庫把.git
這個倉庫目錄下載到工作目錄 中,還會checkout (簽出) master
(checkout
的意思就是把某個commit
作爲當前commit
,把HEAD
移動過去,並把工作目錄的文件內容替換成 這個commit
所對應的內容)。
另外,需要說一下的是,大多數的開發團隊會規定 開發以master
爲核心,所有的分支都在一定程 度上圍繞着master
來開發。這個在事實上構成 了master
和其它分支在地位上的一個額外的區別。
5.branch 的通俗化理解
儘管在 Git 中,branch
只是一個指向 commit
的引用,但它有一個更通俗的理解:你還可以把一 個 branch 理解爲從初始 commit
到 branch
所指向的 commit
之間的所有 commit s
的一個 「串」。例如下面這張圖:
master
的本質是一個指向 3
的引用,但你也 可以把 master
理解爲是 1 2 3
三個 commit
的「串」,它的起點是 1
,終點是 3
。
這種理解方式比較符合 branch
這個名字的本意 (branch
的本意是樹枝,可以延伸爲事物的分 支),也是大多數人對 branch
的理解。不過如 果你選擇這樣理解 branch
,需要注意下面兩點:
-
所有的
branch
之間都是平等的。
例如上面這張圖,branch1
是1 2 5 6
的串,而不要理解爲2 5 6
或者5 6
。其實,起點在哪裏並不是 重要的,重要的是你要知道,所有branch
之間是平等的,master
除了上 面我說的那幾點之外,並不比其他branch
高級。這個認知的理解對於branch
的正確使用非常重要。
換個角度來說,上面這張圖我可以用別的畫 法來表達,它們的意思是一樣的:
通過這張動圖應該能夠對「平等」這個概念 更好地理解了吧? -
branch
包含了從初始commit
到它的所 有路徑,而不是一條路徑。並且,這些路徑 之間也是彼此平等的。
像上圖這樣,master
在合併了branch1
之後,從初始commit
到master
有了兩條路徑。這時,master
的串就包含了1 2 3 4 7
和1 2 5 6 7
這兩條路徑。而且,這兩 條路徑是平等的,1 2 3 4 7
這 條路徑並不會因爲它是「原生路徑」而擁有 任何的特別之處。
如果你喜歡用「樹枝」的概念來理解 Git 的 branch
,一定要注意上面說的這兩點,否則在今 後使用 branch
的時候就可能與出現理解偏差或 者使用方式不當的問題。事實上我本人並不喜歡用 這種方式來理解 branch
,因爲覺得它有點舍近求 遠的味道:我爲了「直觀」地思考,給它了一個形 象的比喻,但由於它的本質含義其實更加簡單,導 致我的這種比喻反而增加了思考它時的複雜度,未 免有點畫蛇添足。不過這是我自己的感受,怎麼理 解 branch
是個個人偏好的問題,這兩種理解方 式你選一個喜歡的就好。
6.branch 的創建、切換和刪除
6.1創建 branch
如果你想在某處創建 branch
,只需要輸入一行 git branch
名稱 。例如你現在在 master
上:
你想在這個 commit
處創建一個叫做 "feature1"
的 branch
,只要輸入:
git branch feature1
你的 branch
就創建好了:
6.2切換 branch
不過新建的 branch
並不會自動切換,你的 HEAD
在這時依然是指向 master
的。你需要用 checkout 來主動切換到你的新 branch
去:
git checkout feature1
然後 HEAD
就會指向新建的 branch
了:
除此之外,你還可以用 git checkout -b
名稱 來 把上面兩步操作合併執行。這行代碼可以幫你用指 定的名稱創建 branch
後,再直接切換過去。還 以 feature1
爲例的話,就是:
git checkout -b feature1
在切換到新的 branch
後,再次 commit
時 HEAD
就會帶着新的 branch
移動了:
... git commit
而這個時候,如果你再切換到 master
去 commit
,就會真正地出現分叉了:
git checkout master
...
git commit
6.3刪除 branch
刪除 branch
的方法非常簡單: git branch -d 名稱
。例如要刪除 feature1
這個 branch
:
git branch -d feature1
需要說明的有兩點:
HEAD
指向的branch
不能刪除。如果要 刪除HEAD
指向的branch
,需要先用checkout
把HEAD
指向其他地方。- 由於 Git 中的
branch
只是一個引用,所 以刪除branch
的操作也只會刪掉這個引 用,並不會刪除任何的commit
。(不過 如果一個commit
不在任何一個branch
的「路徑」上,或者換句話說,如果沒有任 何一個branch
可以回溯到這條
commit
(也許可以稱爲野生commit
?),那麼在一定時間後,它會被 Git 的回收機制刪除掉。) - 出於安全考慮,沒有被合併到
master
過 的branch
在刪除時會失敗(因爲怕你誤 刪掉「未完成」的branch
啊):
這種情況如果你確認是要刪除這個branch
(例如某個未完成的功能被團隊 確認永久斃掉了,不再做了),可以把d
改成-D
,小寫換成大寫,就能刪除 了。
7.「引用」的本質
所謂「引用」(reference),其實就是一個個的字 符串。這個字符串可以是一個 commit
的 SHA-1
碼(例: c08de9a4d8771144cd23986f9f76c4ed729e69b0
),
也可以是一個 branch
(例: ref: refs/heads/feature3
)。
Git 中的 HEAD
和每一個 branch
以及其他的引 用,都是以文本文件的形式存儲在本地倉庫 .git 目錄中,而 Git 在工作的時候,就是通過這些文本 文件的內容來判斷這些所謂的「引用」是指向誰 的。
8.小結
這一節介紹了 Git 中的一些「引用」: HEAD 、 master 、 branch
。這裏總結一下:
HEAD
是指向當前commit
的引用,它具有 唯一性,每個倉庫中只有一個HEAD
。在每 次提交時它都會自動向前移動到新的commit
。branch
是一類引用。HEAD
除了直接指向commit
,也可以通過指向某個branch
來 間接指向commit
。當HEAD
指向一個branch
時,commit
發生時,HEAD
會帶 着它所指向的branch
一起移動。master
是 Git 中的默認branch
,它和其 它branch
的區別在於:
i. 新建的倉庫中的第一個commit
會 被master
自動指向;
ii. 在git clone
時,會自動checkout
出master
。branch
的創建、切換和刪除:
i. 創建branch
的方式是git branch 名稱
或git checkout -b 名稱
(創建後自動切換);
ii. 切換的方式是git checkout 名 稱
;
iii. 刪除的方式是git branch -d 名 稱
。