設定與配置
取得與建立專案
基本快照
分支與合併
分享與更新專案
檢查與比較
修補
除錯
電子郵件
外部系統
伺服器管理
指南
管理
底層指令
- 2.46.1 → 2.47.0 無變更
-
2.46.0
07/29/24
- 2.45.1 → 2.45.2 無變更
-
2.45.0
04/29/24
- 2.44.1 → 2.44.2 無變更
-
2.44.0
02/23/24
- 2.43.2 → 2.43.5 無變更
-
2.43.1
02/09/24
-
2.43.0
11/20/23
- 2.42.1 → 2.42.3 無變更
-
2.42.0
08/21/23
- 2.39.1 → 2.41.2 無變更
-
2.39.0
12/12/22
- 2.38.1 → 2.38.5 無變更
-
2.38.0
10/02/22
- 2.36.1 → 2.37.7 無變更
-
2.36.0
04/18/22
- 2.34.1 → 2.35.8 無變更
-
2.34.0
11/15/21
- 2.33.1 → 2.33.8 無變更
-
2.33.0
08/16/21
- 2.32.1 → 2.32.7 無變更
-
2.32.0
06/06/21
- 2.30.1 → 2.31.8 無變更
-
2.30.0
12/27/20
- 2.28.1 → 2.29.3 無變更
-
2.28.0
07/27/20
- 2.25.1 → 2.27.1 無變更
-
2.25.0
01/13/20
- 2.24.1 → 2.24.4 無變更
-
2.24.0
11/04/19
- 2.23.1 → 2.23.4 無變更
-
2.23.0
08/16/19
- 2.22.1 → 2.22.5 無變更
-
2.22.0
06/07/19
- 2.21.1 → 2.21.4 無變更
-
2.21.0
02/24/19
- 2.19.1 → 2.20.5 無變更
-
2.19.0
09/10/18
- 2.18.1 → 2.18.5 無變更
-
2.18.0
06/21/18
- 2.17.0 → 2.17.6 無變更
-
2.16.6
12/06/19
-
2.15.4
12/06/19
-
2.14.6
12/06/19
-
2.13.7
05/22/18
-
2.12.5
09/22/17
- 2.9.5 → 2.11.4 無變更
-
2.8.6
07/30/17
-
2.7.6
07/30/17
-
2.6.7
05/05/17
- 2.5.6 無變更
-
2.4.12
05/05/17
- 2.3.10 無變更
-
2.2.3
09/04/15
-
2.1.4
12/17/14
-
2.0.5
12/17/14
簡介
Git 是一個快速的分散式版本控制系統。
本手冊旨在讓具備基本 UNIX 命令列技能,但沒有 Git 先前知識的人閱讀。
後續章節涵蓋更專業的主題。
可透過 man 頁面或 git-help[1] 指令取得完整的參考文件。例如,對於指令 git clone <repo>
,您可以使用
$ man git-clone
或
$ git help clone
後者,您可以使用您選擇的手冊檢視器;請參閱 git-help[1] 取得更多資訊。
另請參閱Git 快速參考,以簡短概述 Git 指令,沒有任何說明。
最後,請參閱本手冊的附註和待辦事項清單,以了解您可以協助使本手冊更完整的方式。
儲存庫與分支
如何取得 Git 儲存庫
在您閱讀本手冊時,擁有一個 Git 儲存庫來實驗會很有用。
取得儲存庫的最佳方式是使用 git-clone[1] 指令下載現有儲存庫的副本。如果您還沒有想到任何專案,以下是一些有趣的範例
# Git itself (approx. 40MB download): $ git clone git://git.kernel.org/pub/scm/git/git.git # the Linux kernel (approx. 640MB download): $ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
對於大型專案,初始複製可能很耗時,但您只需要複製一次。
clone 指令會建立一個以專案名稱 (以上範例中為 git
或 linux
) 命名的目錄。在您 cd 進入此目錄後,您會看到它包含專案檔案的副本,稱為工作樹,以及一個名為 .git
的特殊頂層目錄,其中包含專案歷程記錄的所有資訊。
如何簽出專案的不同版本
Git 最適合被視為儲存檔案集合歷程記錄的工具。它將歷程記錄儲存為專案內容的相關快照的壓縮集合。在 Git 中,每個此類版本都稱為提交。
這些快照不一定都以從舊到新的單一直線排列;相反地,工作可能會沿著稱為分支的平行開發線同時進行,這些分支可能會合併和分歧。
單一 Git 儲存庫可以追蹤多個分支上的開發。它透過保留參考每個分支上最新提交的頭部清單來執行此操作; git-branch[1] 指令會向您顯示分支頭部的清單
$ git branch * master
剛複製的儲存庫包含單一分支頭部,預設名稱為 "master",工作目錄會初始化為該分支頭部所參考的專案狀態。
大多數專案也使用標籤。標籤和頭部一樣,是專案歷程記錄的參考,可以使用 git-tag[1] 指令列出
$ git tag -l v2.6.11 v2.6.11-tree v2.6.12 v2.6.12-rc2 v2.6.12-rc3 v2.6.12-rc4 v2.6.12-rc5 v2.6.12-rc6 v2.6.13 ...
標籤預期總是會指向專案的相同版本,而頭部預期會隨著開發的進展而推進。
建立一個指向其中一個版本的新分支頭部,並使用 git-switch[1] 將其簽出
$ git switch -c new v2.6.13
工作目錄接著會反映專案在標記 v2.6.13 時所具有的內容,而 git-branch[1] 顯示兩個分支,星號標記目前簽出的分支
$ git branch master * new
如果您決定想要檢視版本 2.6.17,您可以使用以下指令修改目前的分支以指向 v2.6.17
$ git reset --hard v2.6.17
請注意,如果目前的分支頭部是您對歷程記錄中特定點的唯一參考,則重設該分支可能會讓您無法找到它以前指向的歷程記錄;因此請小心使用此指令。
了解歷程記錄:提交
專案歷程記錄中的每個變更都由提交表示。 git-show[1] 指令會顯示目前分支上最新的提交
$ git show commit 17cf781661e6d38f737f15f53ab552f1e95960d7 Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)> Date: Tue Apr 19 14:11:06 2005 -0700 Remove duplicate getenv(DB_ENVIRONMENT) call Noted by Tony Luck. diff --git a/init-db.c b/init-db.c index 65898fa..b002dc6 100644 --- a/init-db.c +++ b/init-db.c @@ -7,7 +7,7 @@ int main(int argc, char **argv) { - char *sha1_dir = getenv(DB_ENVIRONMENT), *path; + char *sha1_dir, *path; int len, i; if (mkdir(".git", 0755) < 0) {
如您所見,提交會顯示誰進行了最新的變更、他們做了什麼以及原因。
每個提交都有一個 40 個十六進位數字的 id,有時稱為「物件名稱」或「SHA-1 id」,顯示在 git show
輸出的第一行。您通常可以使用較短的名稱 (例如標籤或分支名稱) 來參考提交,但這個較長的名稱也可能很有用。最重要的是,它是此提交的全域唯一名稱:因此,如果您告訴其他人物件名稱 (例如在電子郵件中),則您保證該名稱在他們的儲存庫中會參考與您儲存庫中相同的提交 (假設他們的儲存庫有該提交)。由於物件名稱是根據提交的內容計算的雜湊,因此您保證提交永遠不會在不變更其名稱的情況下變更。
事實上,在Git 概念中,我們將看到儲存在 Git 歷程記錄中的所有內容,包括檔案資料和目錄內容,都儲存在一個物件中,該物件的名稱是其內容的雜湊。
了解歷程記錄:提交、父提交和可達性
每個提交 (除了專案中的第一個提交) 也都有一個父提交,顯示在此提交之前發生的情況。遵循父提交的鏈結最終會將您帶回專案的開頭。
然而,提交並未形成簡單的清單;Git 允許開發線分歧,然後重新收斂,而兩條開發線重新收斂的點稱為「合併」。因此,代表合併的提交可能有多個父提交,每個父提交代表導致該點的一條開發線上的最新提交。
查看其運作方式的最佳方法是使用 gitk[1] 指令;現在在 Git 儲存庫上執行 gitk 並尋找合併提交將有助於了解 Git 如何組織歷程記錄。
在以下內容中,如果提交 X 是提交 Y 的祖先,我們說提交 X 可從提交 Y「存取」。或者,您可以說 Y 是 X 的子系,或者存在從提交 Y 到提交 X 的父鏈。
操作分支
建立、刪除和修改分支都快速又容易;以下是指令的摘要:
-
git branch
-
列出所有分支。
-
git branch <branch>
-
建立一個名為
<branch>
的新分支,其參考與目前分支相同的歷史點。 -
git branch <branch> <start-point>
-
建立一個名為
<branch>
的新分支,其參考<start-point>
,<start-point>
可以用任何你喜歡的方式指定,包括使用分支名稱或標籤名稱。 -
git branch -d <branch>
-
刪除分支
<branch>
;如果該分支尚未完全合併到其上游分支或包含在目前分支中,此指令將會失敗並顯示警告。 -
git branch -D <branch>
-
刪除分支
<branch>
,無論其合併狀態如何。 -
git switch <branch>
-
將目前分支設為
<branch>
,並更新工作目錄以反映<branch>
所參考的版本。 -
git switch -c <new> <start-point>
-
建立一個參考
<start-point>
的新分支<new>
,並將其切換為目前分支。
特殊符號「HEAD」始終可以用來指代目前分支。事實上,Git 在 .git
目錄中使用一個名為 HEAD
的檔案來記住哪個分支是目前的。
$ cat .git/HEAD ref: refs/heads/master
檢視舊版本而不建立新分支
git switch
指令通常預期一個分支頭,但當使用 --detach 呼叫時,它也會接受任意提交;例如,您可以檢視標籤所參考的提交。
$ git switch --detach v2.6.17 Note: checking out 'v2.6.17'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another switch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command again. Example: git switch -c new_branch_name HEAD is now at 427abfa Linux v2.6.17
然後,HEAD 會指向提交的 SHA-1 值,而不是分支,而 git branch 會顯示您不再處於任何分支上。
$ cat .git/HEAD 427abfa28afedffadfca9dd8b067eb6d36bac53f $ git branch * (detached from v2.6.17) master
在這種情況下,我們說 HEAD 處於「分離 (detached)」狀態。
這是一種輕鬆檢視特定版本的方法,而無需為新分支建立名稱。如果您之後決定要建立這個版本的新分支 (或標籤),仍然可以這麼做。
檢視遠端儲存庫的分支
您複製時建立的「master」分支是您所複製的儲存庫中 HEAD 的副本。然而,該儲存庫可能也有其他分支,而您的本地儲存庫會保留追蹤每個遠端分支的分支,稱為遠端追蹤分支,您可以使用 git-branch[1] 的 -r
選項來檢視它們。
$ git branch -r origin/HEAD origin/html origin/maint origin/man origin/master origin/next origin/seen origin/todo
在這個範例中,「origin」被稱為遠端儲存庫,或簡稱「遠端」。從我們的角度來看,此儲存庫的分支稱為「遠端分支」。上面列出的遠端追蹤分支是在複製時根據遠端分支建立的,並將由 git fetch
(因此 git pull
) 和 git push
更新。請參閱 使用 git fetch 更新儲存庫 以了解詳細資訊。
您可能想在您自己的分支上建立其中一個遠端追蹤分支,就像您對標籤所做的一樣。
$ git switch -c my-todo-copy origin/todo
您也可以直接檢視 origin/todo
以檢視它或編寫一次性的修補程式。請參閱 分離的 HEAD。
請注意,「origin」這個名稱只是 Git 預設用來指稱您複製來源儲存庫的名稱。
命名分支、標籤和其他參考
分支、遠端追蹤分支和標籤都是對提交的參考。所有參考都以斜線分隔的路徑名稱命名,並以 refs
開頭;到目前為止我們使用的名稱實際上是簡寫。
-
分支
test
是refs/heads/test
的簡寫。 -
標籤
v2.6.18
是refs/tags/v2.6.18
的簡寫。 -
origin/master
是refs/remotes/origin/master
的簡寫。
如果存在同名的標籤和分支,則完整的名稱有時會很有用。
(新建立的參考實際上儲存在 .git/refs
目錄中,位於其名稱給定的路徑下。然而,為了提高效率,它們也可以打包在單一檔案中;請參閱 git-pack-refs[1])。
另一個有用的快捷方式是,可以使用儲存庫的名稱來指稱該儲存庫的「HEAD」。因此,例如,「origin」通常是儲存庫「origin」中 HEAD 分支的簡寫。
有關 Git 檢查參考的完整路徑列表,以及當存在多個具有相同簡寫名稱的參考時,它用來決定選擇哪個參考的順序,請參閱 gitrevisions[7] 的「SPECIFYING REVISIONS」章節。
使用 git fetch 更新儲存庫
在您複製儲存庫並提交一些自己的變更後,您可能想要檢查原始儲存庫是否有更新。
git-fetch
指令,若不帶任何引數,將會把所有遠端追蹤分支更新為在原始儲存庫中找到的最新版本。它不會觸及您的任何分支,甚至不會觸及複製時為您建立的「master」分支。
從其他儲存庫擷取分支
您也可以使用 git-remote[1] 來追蹤來自您複製來源以外之儲存庫的分支。
$ git remote add staging git://git.kernel.org/.../gregkh/staging.git $ git fetch staging ... From git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging * [new branch] master -> staging/master * [new branch] staging-linus -> staging/staging-linus * [new branch] staging-next -> staging/staging-next
新的遠端追蹤分支將儲存在您給 git remote add
的簡寫名稱下,在本例中為 staging
。
$ git branch -r origin/HEAD -> origin/master origin/master staging/master staging/staging-linus staging/staging-next
如果您稍後執行 git fetch <remote>
,則會更新指定 <remote>
的遠端追蹤分支。
如果您檢視檔案 .git/config
,您會看到 Git 已新增一個新的段落。
$ cat .git/config ... [remote "staging"] url = git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/staging.git fetch = +refs/heads/*:refs/remotes/staging/* ...
這就是 Git 追蹤遠端分支的原因;您可以透過使用文字編輯器編輯 .git/config
來修改或刪除這些組態選項。(請參閱 git-config[1] 的「CONFIGURATION FILE」章節以了解詳細資訊。)
探索 Git 歷史記錄
Git 最好被認為是儲存檔案集合歷史記錄的工具。它透過儲存檔案階層內容的壓縮快照,以及顯示這些快照之間關係的「提交」來執行此操作。
Git 提供了極其靈活且快速的工具來探索專案的歷史記錄。
我們先從一個專門的工具開始,它可用於尋找將錯誤引入專案的提交。
如何使用 bisect 尋找回歸
假設您專案的 2.6.18 版可以正常運作,但「master」的版本會崩潰。有時,尋找此類回歸原因的最佳方法是,對專案的歷史記錄執行暴力搜尋,以找出導致問題的特定提交。git-bisect[1]
指令可以幫助您做到這一點。
$ git bisect start $ git bisect good v2.6.18 $ git bisect bad master Bisecting: 3537 revisions left to test after this [65934a9a028b88e83e2b0f8b36618fe503349f8e] BLOCK: Make USB storage depend on SCSI rather than selecting it [try #6]
如果您在此時執行 git branch
,您會看到 Git 已暫時將您移至「(no branch)」中。HEAD 現在已與任何分支分離,並直接指向一個可從「master」訪問但無法從 v2.6.18 訪問的提交 (提交 ID 為 65934)。編譯並測試它,看看它是否崩潰。假設它確實崩潰。然後
$ git bisect bad Bisecting: 1769 revisions left to test after this [7eff82c8b1511017ae605f0c99ac275a7e21b867] i2c-core: Drop useless bitmaskings
會檢視舊版本。像這樣繼續,在每個階段告訴 Git 它給您的版本是好還是壞,並注意要測試的修訂版本數量每次都會減半。
大約經過 13 次測試 (在本例中) 之後,它將會輸出有罪提交的提交 ID。然後,您可以使用 git-show[1] 檢查提交,找出是誰編寫的,並將包含提交 ID 的錯誤報告郵寄給他們。最後,執行
$ git bisect reset
回到您之前的分支。
請注意,git bisect
在每個階段為您檢視的版本只是一個建議,如果您認為這是一個好主意,您可以自由地嘗試不同的版本。例如,有時您可能會遇到一個破壞了無關事物的提交;執行
$ git bisect visualize
這將會執行 gitk 並用一個標示為「bisect」的標記來標記它選擇的提交。選擇附近看起來安全的提交,記下它的提交 ID,並使用以下命令檢視它:
$ git reset --hard fb47ddb2db
然後測試,視情況執行 bisect good
或 bisect bad
,然後繼續。
您可能只想告訴 Git 您想要略過目前的提交,而不是使用 git bisect visualize
,然後使用 git reset --hard fb47ddb2db
。
$ git bisect skip
然而,在這種情況下,Git 可能最終無法分辨一些最早略過的提交和稍後的不良提交之間的第一個不良提交。
如果您有測試腳本可以分辨好與壞的提交,也有一些方法可以自動化 bisect 流程。請參閱 git-bisect[1] 以了解更多關於這和其他 git bisect
功能的資訊。
命名提交
我們已經看到了幾種命名提交的方法:
-
40 個十六進位數字的物件名稱
-
分支名稱:指的是給定分支頭部的提交
-
標籤名稱:指的是給定標籤指向的提交 (我們已經看到分支和標籤是參考的特殊情況)。
-
HEAD:指的是目前分支的頭部
還有更多;請參閱 gitrevisions[7] 手冊頁的「SPECIFYING REVISIONS」章節,以取得命名修訂版本的完整方法列表。以下是一些範例:
$ git show fb47ddb2 # the first few characters of the object name # are usually enough to specify it uniquely $ git show HEAD^ # the parent of the HEAD commit $ git show HEAD^^ # the grandparent $ git show HEAD~4 # the great-great-grandparent
請記住,合併提交可能有多個父提交;預設情況下,^
和 ~
會跟隨提交中列出的第一個父提交,但您也可以選擇:
$ git show HEAD^1 # show the first parent of HEAD $ git show HEAD^2 # show the second parent of HEAD
除了 HEAD 之外,還有其他幾個用於提交的特殊名稱:
合併 (稍後討論),以及諸如 git reset
之類的作業 (會變更目前檢視的提交),通常會將 ORIG_HEAD 設定為目前作業之前 HEAD 的值。
git fetch
作業始終將最後擷取的分支頭儲存在 FETCH_HEAD 中。例如,如果您執行 git fetch
而未將本機分支指定為作業的目標
$ git fetch git://example.com/proj.git theirbranch
仍然可以從 FETCH_HEAD 取得擷取的提交。
當我們討論合併時,我們也會看到特殊名稱 MERGE_HEAD,它指的是我們正在合併到目前分支中的另一個分支。
git-rev-parse[1]
指令是一個低階指令,有時可用於將提交的某些名稱轉換為該提交的物件名稱。
$ git rev-parse origin e05db0fd4f31dde7005f075a84f96b360d05984b
建立標籤
我們也可以建立標籤來參考特定的提交;在執行以下命令後:
$ git tag stable-1 1b2e1d63ff
您可以使用 stable-1
來參考提交 1b2e1d63ff。
這會建立一個「輕量」標籤。如果您還想在標籤中包含註解,並可能對其進行加密簽署,那麼您應該建立一個標籤物件;請參閱 git-tag[1] 手冊頁以了解詳細資訊。
瀏覽修訂版本
git-log[1]
命令可以顯示提交的清單。單獨使用時,它會顯示所有可從父提交存取到的提交;但您也可以發出更具體的請求。
$ git log v2.5.. # commits since (not reachable from) v2.5 $ git log test..master # commits reachable from master but not test $ git log master..test # ...reachable from test but not master $ git log master...test # ...reachable from either test or master, # but not both $ git log --since="2 weeks ago" # commits from the last 2 weeks $ git log Makefile # commits which modify Makefile $ git log fs/ # ... which modify any file under fs/ $ git log -S'foo()' # commits which add or remove any file data # matching the string 'foo()'
當然,您也可以組合所有這些條件;以下指令會找出自 v2.5 以來,有變動到 Makefile
或 fs
下任何檔案的提交。
$ git log v2.5.. Makefile fs/
您也可以要求 git log 顯示補丁。
$ git log -p
請參閱 git-log[1]
手冊頁中的 --pretty
選項,以取得更多顯示選項。
請注意,git log 從最新的提交開始,並向後追溯父提交;然而,由於 Git 歷史記錄可能包含多條獨立的開發線,因此提交的特定列出順序可能有些隨意。
產生差異
您可以使用 git-diff[1]
在任何兩個版本之間產生差異。
$ git diff master..test
這將產生兩個分支頂端之間的差異。如果您希望找出它們共同祖先的差異來測試,您可以使用三個點而不是兩個點。
$ git diff master...test
有時您想要的可能是一組補丁;為此,您可以使用 git-format-patch[1]
。
$ git format-patch master..test
這將為每個可從 test 存取但不能從 master 存取的提交產生一個帶有補丁的文件。
檢視舊檔案版本
您可以隨時先檢出正確的修訂版本來檢視舊版本的檔案。但有時,能夠在不檢出任何內容的情況下檢視單一檔案的舊版本會更方便;這個命令可以做到這一點。
$ git show v2.5:fs/locks.c
冒號前面可以是任何命名提交的內容,而冒號後面可以是任何 Git 追蹤的檔案路徑。
範例
計算分支上的提交數量
假設您想知道自從 mybranch
從 origin
分歧以來,您在 mybranch
上進行了多少次提交。
$ git log --pretty=oneline origin..mybranch | wc -l
或者,您可能會經常看到使用較低階的命令 git-rev-list[1]
來完成這種類似的事情,它只是列出所有給定提交的 SHA-1 值。
$ git rev-list origin..mybranch | wc -l
檢查兩個分支是否指向相同的歷史記錄
假設您想檢查兩個分支是否指向歷史記錄中的同一個點。
$ git diff origin..master
這會告訴您專案的內容在兩個分支上是否相同;然而,理論上,相同的專案內容可能透過兩個不同的歷史路徑到達。您可以比較物件名稱。
$ git rev-list origin e05db0fd4f31dde7005f075a84f96b360d05984b $ git rev-list master e05db0fd4f31dde7005f075a84f96b360d05984b
或者您可以回想一下,...
運算符選擇可以從一個或另一個參考存取但不能同時從兩者存取的所有提交;所以
$ git log origin...master
當兩個分支相等時,將不會返回任何提交。
尋找包含特定修復的首次標記版本
假設您知道提交 e05db0fd 修復了某個問題。您想找到包含該修復的最早標記版本。
當然,可能有多個答案——如果歷史記錄在提交 e05db0fd 之後分支,則可能有多個「最早」標記版本。
您可以直接目視檢查 e05db0fd 以來的提交。
$ gitk e05db0fd..
或者您可以使用 git-name-rev[1]
,它將根據找到的任何指向提交後代的標籤來命名提交。
$ git name-rev --tags e05db0fd e05db0fd tags/v1.5.0-rc1^0~23
git-describe[1]
命令的作用相反,它使用給定提交所基於的標籤來命名修訂版本。
$ git describe e05db0fd v1.5.0-rc0-260-ge05db0f
但有時這可以幫助您猜測哪些標籤可能在給定提交之後。
如果您只想驗證給定的標記版本是否包含給定的提交,可以使用 git-merge-base[1]
。
$ git merge-base e05db0fd v1.5.0-rc1 e05db0fd4f31dde7005f075a84f96b360d05984b
merge-base 命令會找出給定提交的共同祖先,並且在其中一個是另一個的後代的情況下,始終會返回其中一個;因此,上面的輸出顯示 e05db0fd 實際上是 v1.5.0-rc1 的祖先。
或者,請注意
$ git log v1.5.0-rc1..e05db0fd
當且僅當 v1.5.0-rc1 包含 e05db0fd 時,才會產生空的輸出,因為它只輸出無法從 v1.5.0-rc1 存取的提交。
作為另一種替代方法,git-show-branch[1]
命令會列出可從其引數存取的提交,並在左側顯示指示該提交可從哪些引數存取的內容。因此,如果您執行類似這樣的命令:
$ git show-branch e05db0fd v1.5.0-rc0 v1.5.0-rc1 v1.5.0-rc2 ! [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if available ! [v1.5.0-rc0] GIT v1.5.0 preview ! [v1.5.0-rc1] GIT v1.5.0-rc1 ! [v1.5.0-rc2] GIT v1.5.0-rc2 ...
那麼像這樣的行
+ ++ [e05db0fd] Fix warnings in sha1_file.c - use C99 printf format if available
表示 e05db0fd 可從其自身、v1.5.0-rc1 和 v1.5.0-rc2 存取,但不能從 v1.5.0-rc0 存取。
顯示某個分支獨有的提交
假設您想查看可從名為 master
的分支頭存取,但不能從儲存庫中任何其他頭存取的所有提交。
我們可以使用 git-show-ref[1]
列出此儲存庫中的所有頭。
$ git show-ref --heads bf62196b5e363d73353a9dcf094c59595f3153b7 refs/heads/core-tutorial db768d5504c1bb46f63ee9d6e1772bd047e05bf9 refs/heads/maint a07157ac624b2524a059a3414e99f6f44bebc1e7 refs/heads/master 24dbc180ea14dc1aebe09f14c8ecf32010690627 refs/heads/tutorial-2 1e87486ae06626c2f31eaa63d26fc0fd646c8af2 refs/heads/tutorial-fixes
我們可以僅取得分支頭名稱,並在標準工具 cut 和 grep 的幫助下移除 master
。
$ git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master' refs/heads/core-tutorial refs/heads/maint refs/heads/tutorial-2 refs/heads/tutorial-fixes
然後,我們可以要求查看所有可從 master 存取但不能從其他頭存取的提交。
$ gitk master --not $( git show-ref --heads | cut -d' ' -f2 | grep -v '^refs/heads/master' )
顯然,無窮無盡的變化是可能的;例如,要查看可從某個頭存取但不能從儲存庫中任何標籤存取的所有提交。
$ gitk $( git show-ref --heads ) --not $( git show-ref --tags )
(請參閱 gitrevisions[7]
以取得有關提交選擇語法的說明,例如 --not
。)
建立軟體版本的變更日誌和 tarball
git-archive[1]
命令可以從專案的任何版本建立 tar 或 zip 壓縮檔;例如
$ git archive -o latest.tar.gz --prefix=project/ HEAD
將使用 HEAD 產生一個 gzipped tar 壓縮檔,其中每個檔名都以 project/
作為前綴。如果可能,輸出檔格式會從輸出檔案副檔名推斷出來,請參閱 git-archive[1]
以取得詳細資訊。
早於 1.7.7 的 Git 版本不知道 tar.gz
格式,您需要明確使用 gzip。
$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz
如果您要發佈新版本的軟體專案,您可能希望同時建立一個變更日誌,以包含在發佈公告中。
例如,Linus Torvalds 會透過標記新核心版本來建立,然後執行
$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7
其中 release-script 是一個看起來像這樣的 shell 腳本:
#!/bin/sh stable="$1" last="$2" new="$3" echo "# git tag v$new" echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz" echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz" echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new" echo "git shortlog --no-merges v$new ^v$last > ../ShortLog" echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"
然後,他只是在驗證輸出命令看起來沒問題之後,複製貼上它們。
尋找引用具有給定內容的檔案的提交
有人給你一份檔案的副本,並詢問哪些提交修改了檔案,使其在提交之前或之後包含給定的內容。您可以透過以下方式找出答案:
$ git log --raw --abbrev=40 --pretty=oneline | grep -B 1 `git hash-object filename`
找出這如何運作的細節留給(進階)學生作為練習。git-log[1]
、git-diff-tree[1]
和 git-hash-object[1]
手冊頁可能會有所幫助。
使用 Git 開發
告訴 Git 您的姓名
在建立任何提交之前,您應該先向 Git 介紹自己。最簡單的方法是使用 git-config[1]
。
$ git config --global user.name 'Your Name Comes Here' $ git config --global user.email 'you@yourdomain.example.com'
這會將以下內容新增到您主目錄中名為 .gitconfig
的檔案中。
[user] name = Your Name Comes Here email = you@yourdomain.example.com
請參閱 git-config[1]
的「組態檔」章節,以取得有關組態檔的詳細資訊。該檔案是純文字,因此您也可以使用您最喜歡的編輯器進行編輯。
建立新的儲存庫
從頭開始建立新的儲存庫非常容易。
$ mkdir project $ cd project $ git init
如果您有一些初始內容(例如,一個 tarball)。
$ tar xzvf project.tar.gz $ cd project $ git init $ git add . # include everything below ./ in the first commit: $ git commit
如何建立提交
建立新的提交需要三個步驟。
-
使用您最喜歡的編輯器對工作目錄進行一些變更。
-
告訴 Git 您的變更。
-
使用您在步驟 2 中告知 Git 的內容來建立提交。
實際上,您可以根據需要多次交錯和重複步驟 1 和 2:為了追蹤您希望在步驟 3 中提交的內容,Git 會在一個名為「索引」的特殊暫存區中維護樹狀結構內容的快照。
一開始,索引的內容將與 HEAD 的內容相同。因此,顯示 HEAD 與索引之間差異的命令 git diff --cached
應該在此時產生沒有輸出。
修改索引很容易。
要使用新的或修改過的檔案的內容來更新索引,請使用
$ git add path/to/file
要從索引和工作樹中移除檔案,請使用
$ git rm path/to/file
在每個步驟之後,您可以驗證
$ git diff --cached
始終顯示 HEAD 和索引檔案之間的差異——這就是您現在建立提交時要提交的內容——並且
$ git diff
顯示工作樹和索引檔案之間的差異。
請注意,git add
始終只將檔案的目前內容新增到索引中;除非您再次對該檔案執行 git add
,否則對同一個檔案的進一步變更將會被忽略。
當您準備好時,只需執行
$ git commit
並且 Git 將提示您輸入提交訊息,然後建立新的提交。請檢查以確保它看起來與您預期的一樣。
$ git show
作為一個特殊的快捷方式,
$ git commit -a
將使用您已修改或刪除的任何檔案來更新索引並建立提交,所有步驟都在一步完成。
許多命令對於追蹤您即將提交的內容很有用。
$ git diff --cached # difference between HEAD and the index; what # would be committed if you ran "commit" now. $ git diff # difference between the index file and your # working directory; changes that would not # be included if you ran "commit" now. $ git diff HEAD # difference between HEAD and working tree; what # would be committed if you ran "commit -a" now. $ git status # a brief per-file summary of the above.
您也可以使用 git-gui[1]
來建立提交、檢視索引和工作樹檔案中的變更,並單獨選擇差異區塊以包含在索引中(透過右鍵點擊差異區塊並選擇「為提交暫存區塊」)。
建立良好的提交訊息
雖然不是必需的,但最好以一行簡短(不超過 50 個字元)的變更摘要開始提交訊息,然後是空行,再是更完整的描述。提交訊息中第一個空行之前的文字會被視為提交標題,並且該標題會在整個 Git 中使用。例如,git-format-patch[1]
會將提交轉換為電子郵件,並且在「主旨」行中使用標題,並在主體中使用提交的其餘部分。
忽略檔案
專案通常會產生您不想使用 Git 追蹤的檔案。這通常包括建置過程產生的檔案或編輯器建立的臨時備份檔案。當然,不使用 Git 追蹤檔案只是不對它們呼叫 git add
的問題。但是,讓這些未追蹤的檔案散落在周圍很快就會變得惱人;例如,它們使得 git add .
實際上毫無用處,並且它們會不斷出現在 git status
的輸出中。
您可以透過在工作目錄的最上層建立一個名為 .gitignore
的檔案,並包含以下內容來告知 Git 忽略某些檔案。
# Lines starting with '#' are considered comments. # Ignore any file named foo.txt. foo.txt # Ignore (generated) html files, *.html # except foo.html which is maintained by hand. !foo.html # Ignore objects and archives. *.[oa]
請參閱 gitignore[5]
以取得有關語法的詳細說明。您也可以將 .gitignore 檔案放在工作樹中的其他目錄中,它們將適用於這些目錄及其子目錄。.gitignore
檔案可以像任何其他檔案一樣新增到您的儲存庫中(只需像往常一樣執行 git add .gitignore
和 git commit
),這在排除模式(例如,與建置輸出檔案匹配的模式)對於克隆您儲存庫的其他使用者也很有意義時,非常方便。
如果您希望排除模式僅影響某些儲存庫(而不是給定專案的每個儲存庫),您可以將它們放在您儲存庫中名為 .git/info/exclude
的檔案中,或放在 core.excludesFile
組態變數指定的任何檔案中。某些 Git 命令也可以直接在命令列上取得排除模式。請參閱 gitignore[5]
以取得詳細資訊。
如何合併
您可以使用 git-merge[1]
來重新加入兩個分歧的開發分支。
$ git merge branchname
將 branchname
分支的開發內容合併到目前分支。
合併是透過結合 branchname
分支的變更以及目前分支自歷史分叉以來,直到最新提交的變更來完成。當此結合順利完成時,工作目錄會被合併結果覆寫;當此結合導致衝突時,則會被半合併的結果覆寫。因此,如果您有未提交的變更,且這些變更影響到與合併相關的檔案,Git 將會拒絕繼續執行。大多數情況下,您會希望在合併前先提交變更。如果您不這麼做,那麼 git-stash[1] 可以暫時移除這些變更,讓您在執行合併時不受影響,並在之後重新套用。
如果變更彼此獨立,Git 會自動完成合併並提交結果(或者在快轉的情況下,重複使用現有的提交,請見下文)。另一方面,如果發生衝突——例如,如果同一個檔案在遠端分支和本地分支以兩種不同的方式修改——那麼您會收到警告;輸出可能如下所示:
$ git merge next 100% (4/4) done Auto-merged file.txt CONFLICT (content): Merge conflict in file.txt Automatic merge failed; fix conflicts and then commit the result.
衝突標記會留在有問題的檔案中,在您手動解決衝突後,您可以像建立新檔案一樣,使用內容更新索引並執行 Git 提交。
如果您使用 gitk 檢查產生的提交,您會看到它有兩個父節點,一個指向目前分支的頂端,另一個指向其他分支的頂端。
解決合併衝突
當合併無法自動解決時,Git 會將索引和工作目錄置於一種特殊的狀態,讓您可以取得所有需要用來協助解決合併衝突的資訊。
有衝突的檔案會在索引中被特別標記,因此在您解決問題並更新索引之前,git-commit[1] 會失敗。
$ git commit file.txt: needs merge
此外,git-status[1] 會將這些檔案列為「未合併」,且有衝突的檔案會加入衝突標記,如下所示:
<<<<<<< HEAD:file.txt Hello world ======= Goodbye >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
您只需要編輯檔案以解決衝突,然後執行:
$ git add file.txt $ git commit
請注意,提交訊息將會自動填入一些關於合併的資訊。通常,您可以直接使用這個預設訊息,無需修改,但如果需要,您也可以加入您自己的額外評論。
以上是您解決簡單合併衝突所需知道的所有資訊。但 Git 也提供更多資訊來協助解決衝突。
在合併期間取得衝突解決的協助
Git 能夠自動合併的所有變更都已加入索引檔案,因此 git-diff[1] 只會顯示衝突。它使用一種不尋常的語法:
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,5 @@@ ++<<<<<<< HEAD:file.txt +Hello world ++======= + Goodbye ++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
回想一下,在我們解決此衝突後將提交的提交,將會有兩個父節點,而不是通常的一個:一個父節點將會是 HEAD,即目前分支的頂端;另一個父節點將會是另一個分支的頂端,暫時儲存在 MERGE_HEAD 中。
在合併期間,索引會保存每個檔案的三個版本。這三個「檔案階段」中的每一個都代表檔案的不同版本。
$ git show :1:file.txt # the file in a common ancestor of both branches $ git show :2:file.txt # the version from HEAD. $ git show :3:file.txt # the version from MERGE_HEAD.
當您要求 git-diff[1] 顯示衝突時,它會執行工作目錄中衝突合併結果與第 2 階段和第 3 階段之間的三向差異比較,只顯示內容來自雙方的區塊(換句話說,當區塊的合併結果僅來自第 2 階段時,該部分不會發生衝突,也不會顯示。第 3 階段也是如此)。
上述差異比較顯示 file.txt 的工作目錄版本與第 2 階段和第 3 階段版本之間的差異。因此,它現在使用兩列,而不是在每一行前面加上單個 +
或 -
:第一列用於表示第一個父節點和工作目錄副本之間的差異,第二列用於表示第二個父節點和工作目錄副本之間的差異。(有關格式的詳細資訊,請參閱 git-diff-files[1] 的「合併差異格式」章節。)
在以顯而易見的方式解決衝突(但在更新索引之前)之後,差異比較看起來會像這樣:
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,1 @@@ - Hello world -Goodbye ++Goodbye world
這表示我們解決的版本從第一個父節點刪除了 "Hello world",從第二個父節點刪除了 "Goodbye",並新增了之前兩者都沒有的 "Goodbye world"。
一些特殊的差異比較選項允許將工作目錄與這些階段的任何一個進行差異比較。
$ git diff -1 file.txt # diff against stage 1 $ git diff --base file.txt # same as the above $ git diff -2 file.txt # diff against stage 2 $ git diff --ours file.txt # same as the above $ git diff -3 file.txt # diff against stage 3 $ git diff --theirs file.txt # same as the above.
當使用 *ort* 合併策略(預設值)時,在以合併結果更新工作目錄之前,Git 會寫入一個名為 AUTO_MERGE 的 ref,以反映它即將寫入的樹狀結構狀態。具有無法自動合併的文字衝突的衝突路徑會使用衝突標記寫入此樹狀結構,就像在工作目錄中一樣。因此,AUTO_MERGE 可以與 git-diff[1] 一起使用,以顯示您目前為止為解決衝突所做的變更。使用與上述相同的範例,在解決衝突後,我們會得到:
$ git diff AUTO_MERGE diff --git a/file.txt b/file.txt index cd10406..8bf5ae7 100644 --- a/file.txt +++ b/file.txt @@ -1,5 +1 @@ -<<<<<<< HEAD:file.txt -Hello world -======= -Goodbye ->>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt +Goodbye world
請注意,差異比較顯示我們刪除了衝突標記和內容行的兩個版本,並改為寫入 "Goodbye world"。
git-log[1] 和 gitk[1] 命令也為合併提供特殊協助:
$ git log --merge $ gitk --merge
這些命令會顯示僅存在於 HEAD 或 MERGE_HEAD 上,並且會影響未合併檔案的所有提交。
您也可以使用 git-mergetool[1],它可讓您使用 Emacs 或 kdiff3 等外部工具合併未合併的檔案。
每次您解決檔案中的衝突並更新索引時,
$ git add file.txt
該檔案的不同階段將會被「摺疊」,之後 git diff
將(預設情況下)不再顯示該檔案的差異比較。
還原合併
如果您遇到困難,並決定放棄並將所有混亂都拋棄,您可以隨時使用下列命令回到合併前的狀態:
$ git merge --abort
或者,如果您已經提交了想要拋棄的合併,則可以使用:
$ git reset --hard ORIG_HEAD
但是,在某些情況下,最後這個命令可能會有危險——如果已經提交的提交本身可能已合併到另一個分支,請永遠不要拋棄已提交的提交,因為這樣做可能會使後續的合併更加混亂。
快轉合併
上面沒有提到一個特殊情況,會被以不同的方式處理。通常,合併會產生一個合併提交,有兩個父節點,每個父節點指向合併的兩個開發線條之一。
但是,如果目前分支是另一個分支的祖先——因此目前分支中存在的所有提交都已包含在另一個分支中——那麼 Git 只會執行「快轉」;目前分支的頭部會向前移動以指向合併進來的分支的頭部,而不會建立任何新的提交。
修正錯誤
如果您搞砸了工作目錄,但尚未提交您的錯誤,您可以使用下列命令將整個工作目錄返回到上次提交的狀態:
$ git restore --staged --worktree :/
如果您進行了一次後來希望自己沒有進行的提交,則有兩種根本不同的方法來解決問題:
-
您可以建立一個新的提交,以還原舊提交所做的任何動作。如果您的錯誤已經公開,那麼這是正確的做法。
-
您可以返回並修改舊提交。如果您已經將歷史記錄公開,則絕對不應這樣做;Git 通常不希望專案的「歷史記錄」發生變更,而且無法正確地從其歷史記錄已變更的分支執行重複的合併。
使用新的提交修正錯誤
建立一個新的提交來還原較早的變更非常容易;只需將 git-revert[1] 命令傳遞給錯誤提交的參考即可;例如,還原最近的提交:
$ git revert HEAD
這將會建立一個新的提交,以還原 HEAD 中的變更。您將有機會編輯新提交的提交訊息。
您也可以還原較早的變更,例如,倒數第二個:
$ git revert HEAD^
在這種情況下,Git 將嘗試還原舊變更,同時保持自那時起所做的任何變更。如果最近的變更與要還原的變更重疊,那麼系統會要求您手動修正衝突,就像解決合併衝突的情況一樣。
透過重寫歷史記錄來修正錯誤
如果問題提交是最近的提交,而且您尚未公開該提交,那麼您可以直接使用 git reset
將其銷毀。
或者,您可以編輯工作目錄並更新索引來修正您的錯誤,就像您要建立新的提交一樣,然後執行:
$ git commit --amend
這將會以包含您的變更的新提交來取代舊提交,讓您有機會先編輯舊提交訊息。
再次強調,您永遠不應對可能已經合併到另一個分支的提交執行此操作;在這種情況下,請改用 git-revert[1]。
也可以取代歷史記錄中更早的提交,但這是一個進階主題,將留給另一個章節討論。
檢查檔案的舊版本
在還原先前的錯誤變更的過程中,您可能會發現使用 git-restore[1] 檢查特定檔案的舊版本會很有用。以下命令:
$ git restore --source=HEAD^ path/to/file
會以 HEAD^ 中所具有的內容取代 path/to/file,並且也會更新索引以進行比對。它不會變更分支。
如果您只想查看檔案的舊版本,而不想修改工作目錄,您可以使用 git-show[1] 執行此操作:
$ git show HEAD^:path/to/file
這將會顯示檔案的指定版本。
暫時擱置進行中的工作
當您正在處理複雜的事情時,您發現一個不相關但明顯且微不足道的錯誤。您可能想在繼續之前修復它。您可以使用 git-stash[1] 來儲存您目前的工作狀態,並且在修復錯誤之後(或選擇在不同的分支上修復後再回來),取消儲存正在進行中的變更。
$ git stash push -m "work in progress for foo feature"
此命令會將您的變更儲存到 stash
中,並將您的工作目錄和索引重置為與目前分支的頂端相符。然後您可以像平常一樣進行修復。
... edit and test ... $ git commit -a -m "blorpl: typofix"
在那之後,您可以使用 git stash pop
回到您正在處理的工作。
$ git stash pop
確保良好效能
在大型儲存庫中,Git 依賴壓縮來防止歷史資訊佔用過多磁碟或記憶體空間。某些 Git 命令可能會自動執行 git-gc[1],因此您不必擔心手動執行它。然而,壓縮大型儲存庫可能需要一些時間,因此您可能需要明確呼叫 gc
以避免在不方便時觸發自動壓縮。
確保可靠性
檢查儲存庫是否損壞
git-fsck[1] 命令會對儲存庫執行一些自我一致性檢查,並報告任何問題。這可能需要一些時間。
$ git fsck dangling commit 7281251ddd2a61e38657c827739c57015671a6b3 dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63 dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5 dangling blob 218761f9d90712d37a9c5e36f406f92202db07eb dangling commit bf093535a34a4d35731aa2bd90fe6b176302f14f dangling commit 8e4bec7f2ddaa268bef999853c25755452100f8e dangling tree d50bb86186bf27b681d25af89d3b5b68382e4085 dangling tree b24c2473f1fd3d91352a624795be026d64c8841f ...
您會看到關於懸掛物件的資訊訊息。它們是仍然存在於儲存庫中,但不再被任何分支引用的物件,並且會在一段時間後透過 gc
移除。您可以執行 git fsck --no-dangling
來隱藏這些訊息,並仍然檢視真正的錯誤。
復原遺失的變更
Reflogs
假設您使用 git reset --hard
修改了一個分支,然後才意識到該分支是您指向歷史記錄中該點的唯一參考。
幸運的是,Git 也會保留一個日誌,稱為「reflog」,記錄每個分支的所有先前值。因此,在這種情況下,您仍然可以使用例如以下方式找到舊的歷史記錄:
$ git log master@{1}
這會列出可從 master
分支頭的先前版本存取的提交。此語法可以與任何接受提交的 Git 命令一起使用,而不僅限於 git log
。其他一些範例
$ git show master@{2} # See where the branch pointed 2, $ git show master@{3} # 3, ... changes ago. $ gitk master@{yesterday} # See where it pointed yesterday, $ gitk master@{"1 week ago"} # ... or last week $ git log --walk-reflogs master # show reflog entries for master
HEAD 會保留一個單獨的 reflog,因此
$ git show HEAD@{"1 week ago"}
將顯示 HEAD 一週前指向的位置,而不是目前分支一週前指向的位置。這可讓您查看您簽出的歷史記錄。
預設情況下,reflog 會保留 30 天,之後可能會被修剪。請參閱 git-reflog[1] 和 git-gc[1] 以了解如何控制此修剪,並參閱 gitrevisions[7] 的「SPECIFYING REVISIONS」部分以了解詳細資訊。
請注意,reflog 歷史記錄與一般的 Git 歷史記錄非常不同。雖然一般的歷史記錄由在同一個專案上工作的每個儲存庫共享,但 reflog 歷史記錄不會共享:它只會告訴您本地儲存庫中的分支如何隨著時間推移而變更。
檢查懸掛物件
在某些情況下,reflog 可能無法拯救您。例如,假設您刪除一個分支,然後才意識到您需要它包含的歷史記錄。reflog 也會被刪除;但是,如果您尚未修剪儲存庫,那麼您可能仍然可以在 git fsck
報告的懸掛物件中找到遺失的提交。請參閱 懸掛物件 以了解詳細資訊。
$ git fsck dangling commit 7281251ddd2a61e38657c827739c57015671a6b3 dangling commit 2706a059f258c6b245f298dc4ff2ccd30ec21a63 dangling commit 13472b7c4b80851a1bc551779171dcb03655e9b5 ...
您可以使用例如以下方式來檢查其中一個懸掛提交:
$ gitk 7281251ddd --not --all
它會執行聽起來像它的動作:它表示您想要查看懸掛提交所描述的提交歷史記錄,而不是您所有現有分支和標籤所描述的歷史記錄。因此,您會得到完全從該遺失的提交可存取的歷史記錄。(請注意,它可能不只是一個提交:我們只會報告「該行的頂端」為懸掛,但可能有一個完整、深入且複雜的提交歷史記錄被刪除。)
如果您決定要恢復歷史記錄,您可以隨時建立指向它的新參考,例如,一個新的分支
$ git branch recovered-branch 7281251ddd
其他類型的懸掛物件(blob 和樹狀結構)也是有可能的,並且懸掛物件可能會在其他情況下出現。
與他人共享開發
使用 git pull 取得更新
在您複製儲存庫並提交一些自己的變更後,您可能會希望檢查原始儲存庫是否有更新,並將它們合併到您自己的工作中。
我們已經了解 如何使用 git fetch[1] 讓遠端追蹤分支保持最新,以及如何合併兩個分支。因此,您可以使用以下方式從原始儲存庫的 master 分支合併變更:
$ git fetch $ git merge origin/master
但是,git-pull[1] 命令提供了一種一步完成此操作的方法
$ git pull origin master
事實上,如果您簽出了 master
,則此分支已由 git clone
配置為從 origin 儲存庫的 HEAD 分支取得變更。因此,您通常只需使用簡單的
$ git pull
這個命令會從遠端分支抓取變更到您的遠端追蹤分支 origin/*
,並將預設分支合併到目前分支中。
更一般地說,從遠端追蹤分支建立的分支預設會從該分支提取。請參閱 git-config[1] 中 branch.<name>.remote
和 branch.<name>.merge
選項的描述,以及 git-checkout[1] 中 --track
選項的討論,以了解如何控制這些預設值。
除了節省您的按鍵次數之外,git pull
還會透過產生一個預設的提交訊息來協助您,該訊息會記錄您從哪個分支和儲存庫提取。
(但請注意,在 快轉 的情況下,不會建立這樣的提交;相反,您的分支只會更新為指向上游分支的最新提交。)
git pull
命令也可以使用 .
作為「遠端」儲存庫,在這種情況下,它只會合併目前儲存庫中的分支;因此,以下命令
$ git pull . branch $ git merge branch
大致相等。
將修補程式提交到專案
如果您只有一些變更,最簡單的提交方式可能只是將它們以電子郵件中的修補程式形式發送
首先,使用 git-format-patch[1];例如
$ git format-patch origin
會在目前目錄中產生一系列編號的檔案,每個檔案對應目前分支中的一個修補程式,但不包含在 origin/HEAD
中。
git format-patch
可以包含一個初始的「封面信」。您可以在 format-patch
在提交訊息之後但在修補程式本身之前放置的三個破折號行之後插入對個別修補程式的評論。如果您使用 git notes
來追蹤您的封面信件內容,git format-patch --notes
將以類似的方式包含提交的註解。
然後您可以將這些匯入您的郵件用戶端並手動傳送它們。但是,如果您一次要傳送大量內容,您可能更喜歡使用 git-send-email[1] 腳本來自動化該過程。請先查閱專案的郵件列表,以確定他們提交修補程式的要求。
將修補程式匯入專案
Git 還提供了一個名為 git-am[1] 的工具(am 代表「apply mailbox」),用於匯入這種以電子郵件傳送的一系列修補程式。只需將所有包含修補程式的訊息按順序儲存到一個單一的郵箱檔案中,例如 patches.mbox
,然後執行
$ git am -3 patches.mbox
Git 會按順序套用每個修補程式;如果發現任何衝突,它會停止,您可以按照「解決合併衝突」中的描述來修正衝突。(-3
選項會告訴 Git 執行合併;如果您希望它只是中止並保持您的樹狀結構和索引不變,則可以省略該選項。)
一旦索引使用衝突解決的結果更新後,請勿建立新的提交,只需執行
$ git am --continue
Git 就會為您建立提交並繼續套用郵箱中其餘的修補程式。
最終結果將是一系列提交,每個提交對應原始郵箱中的一個修補程式,作者和提交日誌訊息均取自包含每個修補程式的訊息。
公用 Git 儲存庫
將變更提交到專案的另一種方法是告知該專案的維護者使用 git-pull[1] 從您的儲存庫提取變更。在「使用 git pull
取得更新」一節中,我們將其描述為從「主要」儲存庫取得更新的方法,但在另一個方向上也同樣有效。
如果您和維護者在同一台機器上都有帳戶,則可以直接從彼此的儲存庫中提取變更;接受儲存庫 URL 作為參數的命令也接受本機目錄名稱
$ git clone /path/to/repository $ git pull /path/to/other/repository
或 ssh URL
$ git clone ssh://yourhost/~you/repository
對於開發人員較少的專案,或用於同步一些私有儲存庫,這可能就是您所需要的全部。
但是,更常見的做法是維護一個單獨的公用儲存庫(通常在不同的主機上),供其他人從中提取變更。這通常更方便,並且允許您將正在進行的私人工作與公開可見的工作清楚地分開。
您將繼續在您的個人儲存庫中執行您的日常工作,但會定期將您個人儲存庫中的變更「推送」到您的公用儲存庫中,允許其他開發人員從該儲存庫中提取。因此,在有一個具有公用儲存庫的其他開發人員的情況下,變更的流程如下所示
you push your personal repo ------------------> your public repo ^ | | | | you pull | they pull | | | | | they push V their public repo <------------------- their repo
我們將在以下章節中說明如何執行此操作。
設定公用儲存庫
假設您的個人儲存庫位於 ~/proj
目錄中。我們首先建立儲存庫的新複製品,並告知 git daemon
它是要公開的
$ git clone --bare ~/proj proj.git $ touch proj.git/git-daemon-export-ok
產生的目錄 proj.git 包含一個「裸」git 儲存庫 — 它只是 .git
目錄的內容,沒有在其周圍簽出任何檔案。
接下來,將 proj.git
複製到您計劃託管公用儲存庫的伺服器。您可以使用 scp、rsync 或任何最方便的方式。
透過 Git 協定匯出 Git 儲存庫
這是首選方法。
如果其他人管理伺服器,他們應該告訴您將儲存庫放置在哪個目錄中,以及它將顯示在哪個 git://
URL 上。然後您可以跳到下面的「將變更推送至公用儲存庫」一節。
否則,您所需要做的就是啟動 git-daemon[1];它將在連接埠 9418 上監聽。預設情況下,它將允許存取任何看起來像 Git 目錄且包含魔術檔案 git-daemon-export-ok 的目錄。將一些目錄路徑作為 git daemon
參數傳遞將進一步將匯出限制為這些路徑。
您也可以將 git daemon
作為 inetd 服務執行;請參閱 git-daemon[1] 手冊頁以了解詳細資訊。(尤其請參閱範例章節。)
透過 HTTP 匯出 git 儲存庫
Git 協定提供更好的效能和可靠性,但在設定有 Web 伺服器的主機上,HTTP 匯出可能更容易設定。
您所需要做的就是將新建立的裸 Git 儲存庫放置在 Web 伺服器匯出的目錄中,並進行一些調整,以便為 Web 用戶端提供它們需要的一些額外資訊
$ mv proj.git /home/you/public_html/proj.git $ cd proj.git $ git --bare update-server-info $ mv hooks/post-update.sample hooks/post-update
(有關最後兩行的說明,請參閱 git-update-server-info[1] 和 githooks[5]。)
宣傳 proj.git
的 URL。然後其他任何人都可以從該 URL 複製或提取,例如使用如下的命令行:
$ git clone http://yourserver.com/~you/proj.git
(另請參閱setup-git-server-over-http,其中介紹了使用 WebDAV 的稍微複雜的設定,該設定也允許透過 HTTP 推送變更。)
推送變更到公開儲存庫
最簡單的方法是使用git-push[1]和 ssh;要使用您名為 master
的分支的最新狀態更新名為 master
的遠端分支,請執行
$ git push ssh://yourserver.com/~you/proj.git master:master
或者直接執行
$ git push ssh://yourserver.com/~you/proj.git master
與 git fetch
一樣,如果這不會導致快轉,git push
將會抱怨;請參閱以下章節,以了解有關處理此情況的詳細資訊。
請注意,push
的目標通常是裸儲存庫。您也可以推送至具有已檢出工作樹的儲存庫,但預設情況下,會拒絕推送更新目前已檢出的分支,以防止混淆。有關詳細資訊,請參閱git-config[1]中 receive.denyCurrentBranch 選項的說明。
與 git fetch
一樣,您也可以設定組態選項以節省輸入;例如
$ git remote add public-repo ssh://yourserver.com/~you/proj.git
將以下內容新增至 .git/config
[remote "public-repo"] url = yourserver.com:proj.git fetch = +refs/heads/*:refs/remotes/example/*
這讓您只需使用以下指令即可執行相同的推送
$ git push public-repo master
有關詳細資訊,請參閱 git-config[1] 中 remote.<name>.url
、branch.<name>.remote
和 remote.<name>.push
選項的說明。
推送失敗時該怎麼辦
如果推送不會導致遠端分支的快轉,則推送會失敗並出現類似以下的錯誤
! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '...' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
例如,如果您
-
使用
git reset --hard
移除已發佈的提交,或 -
使用
git commit --amend
取代已發佈的提交(如透過重寫歷史記錄修正錯誤中所述),或 -
使用
git rebase
將任何已發佈的提交進行變基(如使用 git rebase 保持修補程式系列更新中所述)。
您可以在分支名稱前面加上加號,以強制 git push
執行更新
$ git push ssh://yourserver.com/~you/proj.git +master
請注意新增的 +
符號。或者,您可以使用 -f
旗標強制執行遠端更新,如下所示
$ git push -f ssh://yourserver.com/~you/proj.git master
通常,每當修改公開儲存庫中的分支頭時,都會修改為指向它之前指向的提交的子代。在這種情況下強制推送,您會打破該慣例。(請參閱重寫歷史記錄的問題。)
儘管如此,對於需要簡單方法來發佈正在進行中的修補程式系列的人來說,這是一種常見的做法,而且只要您警告其他開發人員您打算如何管理分支,這就是一個可接受的折衷方案。
當其他人有權推送至同一個儲存庫時,推送也可能會以這種方式失敗。在這種情況下,正確的解決方案是在更新您的工作後重試推送:透過拉取,或透過提取後變基;請參閱下一節和gitcvs-migration[7]以取得更多資訊。
設定共用儲存庫
另一種協作方式是使用類似於 CVS 中常用的模型,其中多個具有特殊權限的開發人員都從單一共用儲存庫推送和提取。有關如何設定此功能的說明,請參閱gitcvs-migration[7]。
但是,雖然 Git 對共用儲存庫的支援沒有任何問題,但通常不建議使用這種操作模式,原因很簡單,因為 Git 支援的協作模式(透過交換修補程式和從公開儲存庫提取)相較於中央共用儲存庫,有許多優勢
-
Git 快速匯入和合併修補程式的功能讓單一維護者即使在非常高的速率下也能處理傳入的變更。而且,當變更太多時,
git pull
提供了一種簡單的方法,讓維護者將這項工作委派給其他維護者,同時仍然允許選擇性地檢查傳入的變更。 -
由於每個開發人員的儲存庫都具有專案歷史記錄的相同完整副本,因此沒有哪個儲存庫是特殊的,而且另一個開發人員可以輕鬆接管專案的維護,無論是透過相互協議,還是因為維護者變得沒有回應或難以合作。
-
缺少中央「提交者」群組意味著較不需要就誰「在」和誰「出」做出正式決定。
允許網頁瀏覽儲存庫
gitweb cgi 腳本為使用者提供了一種簡單的方法,無需安裝 Git 即可瀏覽專案的修訂、檔案內容和記錄。可以選擇性地啟用 RSS/Atom 摘要和 blame/註解詳細資訊等功能。
git-instaweb[1] 命令提供了一種使用 gitweb 開始瀏覽儲存庫的簡單方法。使用 instaweb 時的預設伺服器為 lighttpd。
有關使用 CGI 或具備 Perl 功能的伺服器設定永久安裝的詳細說明,請參閱 Git 原始碼樹中的檔案 gitweb/INSTALL 和 gitweb[1]。
如何取得具有最少歷史記錄的 Git 儲存庫
淺層複製及其截斷的歷史記錄,當一個人只對專案的近期歷史記錄感興趣,而從上游取得完整歷史記錄的成本很高時很有用。
淺層複製是透過指定 git-clone[1] --depth
切換來建立的。之後可以使用 git-fetch[1] --depth
切換來變更深度,或使用 --unshallow
還原完整歷史記錄。
只要合併基準位於近期歷史記錄中,在淺層複製內合併即可運作。否則,它會像合併不相關的歷史記錄一樣,並且可能導致巨大的衝突。此限制可能會使此儲存庫不適合在以合併為基礎的工作流程中使用。
範例
為 Linux 子系統維護者維護主題分支
這說明了 Tony Luck 如何在其擔任 Linux 核心 IA64 架構維護者的角色時使用 Git。
他使用了兩個公開分支
-
「測試」樹,最初將修補程式放置到其中,以便在與其他正在進行的開發整合時可以接觸到一些修補程式。只要 Andrew 想要,就可以將此樹提取到 -mm 中。
-
「發佈」樹,經過測試的修補程式會移入其中進行最終健全性檢查,並作為向上游發送給 Linus 的媒介(透過向他發送「請提取」請求。)
他還使用一組暫時分支(「主題分支」),每個分支都包含一個邏輯修補程式分組。
要設定此功能,請先透過複製 Linus 的公開樹來建立您的工作樹
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work $ cd work
Linus 的樹將儲存在名為 origin/master 的遠端追蹤分支中,並且可以使用git-fetch[1]進行更新;您可以使用git-remote[1]設定「遠端」來追蹤其他公開樹,並使用git-fetch[1]使其保持最新狀態;請參閱儲存庫和分支。
現在建立您將在其中工作的分支;這些分支從 origin/master 分支的目前頂端開始,並且應該設定為(使用 git-branch[1] 的 --track
選項)預設從 Linus 合併變更。
$ git branch --track test origin/master $ git branch --track release origin/master
這些可以使用 git-pull[1] 輕鬆保持最新狀態。
$ git switch test && git pull $ git switch release && git pull
重要提示!如果這些分支中有任何本機變更,則此合併會在歷史記錄中建立提交物件(如果沒有本機變更,Git 只會執行「快轉」合併)。許多人不喜歡這在 Linux 歷史記錄中建立的「雜訊」,因此您應該避免在 release
分支中隨意執行此操作,因為當您要求 Linus 從發佈分支提取時,這些雜訊提交將成為永久歷史記錄的一部分。
一些組態變數(請參閱 git-config[1])可以輕鬆地將兩個分支都推送至您的公開樹。(請參閱設定公開儲存庫。)
$ cat >> .git/config <<EOF [remote "mytree"] url = master.kernel.org:/pub/scm/linux/kernel/git/aegl/linux.git push = release push = test EOF
然後您可以使用 git-push[1] 推送測試樹和發佈樹
$ git push mytree
或者只使用以下指令推送測試和發佈分支之一
$ git push mytree test
或
$ git push mytree release
現在要套用社群中的一些修補程式。為此修補程式(或相關的修補程式群組)考慮一個簡短而精采的名稱,並從 Linus 分支的近期穩定標籤建立一個新分支。為您的分支選擇一個穩定的基礎將:1) 幫助您:透過避免包含不相關且可能經過輕微測試的變更 2) 幫助未來使用 git bisect
來尋找問題的錯誤獵人
$ git switch -c speed-up-spinlocks v2.6.35
現在,您套用修補程式,執行一些測試,並提交變更。如果修補程式是多部分的系列,則您應該將每個部分作為此分支的個別提交套用。
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
當您對此變更的狀態感到滿意時,您可以將其合併到「測試」分支中,準備公開
$ git switch test && git merge speed-up-spinlocks
您在這裡不太可能遇到任何衝突……但如果您在此步驟上花費了一段時間,並且也從上游提取了新版本,則可能會遇到衝突。
一段時間後,當經過足夠的時間並完成測試後,您可以將相同的分支拉到 release
樹中,準備向上游發送。這就是您看到將每個修補程式(或修補程式系列)保留在自己的分支中的價值所在。這表示修補程式可以按任何順序移入 release
樹中。
$ git switch release && git merge speed-up-spinlocks
過一段時間後,您將擁有多個分支,而且儘管您為每個分支選擇了合適的名稱,您可能會忘記它們的用途或它們的狀態。要取得特定分支中變更的提醒,請使用
$ git log linux..branchname | git shortlog
要查看它是否已合併到測試或發佈分支中,請使用
$ git log test..branchname
或
$ git log release..branchname
(如果此分支尚未合併,您將會看到一些記錄項目。如果已合併,則不會有輸出。)
一旦一個補丁完成整個循環(從測試到發佈,然後被 Linus 拉取,最終回到你本地的 origin/master
分支),這個變更的分支就不再需要了。當以下指令的輸出為空時,你就可以檢測到這一點:
$ git log origin..branchname
此時,該分支就可以刪除了。
$ git branch -d branchname
有些變更非常微小,不需要建立獨立的分支,然後合併到測試和發佈分支。對於這些變更,直接應用到 release
分支,然後將其合併到 test
分支即可。
將你的工作推送到 mytree
後,你可以使用 git-request-pull[1] 來準備一個「請拉取」的請求訊息,發送給 Linus。
$ git push mytree $ git request-pull origin mytree release
以下是一些可以進一步簡化這些步驟的腳本。
==== update script ==== # Update a branch in my Git tree. If the branch to be updated # is origin, then pull from kernel.org. Otherwise merge # origin/master branch into test|release branch case "$1" in test|release) git checkout $1 && git pull . origin ;; origin) before=$(git rev-parse refs/remotes/origin/master) git fetch origin after=$(git rev-parse refs/remotes/origin/master) if [ $before != $after ] then git log $before..$after | git shortlog fi ;; *) echo "usage: $0 origin|test|release" 1>&2 exit 1 ;; esac
==== merge script ==== # Merge a branch into either the test or release branch pname=$0 usage() { echo "usage: $pname branch test|release" 1>&2 exit 1 } git show-ref -q --verify -- refs/heads/"$1" || { echo "Can't see branch <$1>" 1>&2 usage } case "$2" in test|release) if [ $(git log $2..$1 | wc -c) -eq 0 ] then echo $1 already merged into $2 1>&2 exit 1 fi git checkout $2 && git pull . $1 ;; *) usage ;; esac
==== status script ==== # report on status of my ia64 Git tree gb=$(tput setab 2) rb=$(tput setab 1) restore=$(tput setab 9) if [ `git rev-list test..release | wc -c` -gt 0 ] then echo $rb Warning: commits in release that are not in test $restore git log test..release fi for branch in `git show-ref --heads | sed 's|^.*/||'` do if [ $branch = test -o $branch = release ] then continue fi echo -n $gb ======= $branch ====== $restore " " status= for ref in test release origin/master do if [ `git rev-list $ref..$branch | wc -c` -gt 0 ] then status=$status${ref:0:1} fi done case $status in trl) echo $rb Need to pull into test $restore ;; rl) echo "In test" ;; l) echo "Waiting for linus" ;; "") echo $rb All done $restore ;; *) echo $rb "<$status>" $restore ;; esac git log origin/master..$branch | git shortlog done
重寫歷史紀錄和維護補丁系列
通常,提交只會被添加到專案中,永遠不會被移除或取代。Git 在設計時就假設了這一點,違反這個假設將會導致 Git 的合併機制(例如)做出錯誤的事情。
但是,在某些情況下,違反這個假設可能是有用的。
建立完美的補丁系列
假設你是大型專案的貢獻者,你想要添加一個複雜的功能,並且以一種讓其他開發人員容易閱讀你的變更、驗證它們是否正確,並理解你進行每個變更的原因的方式,將其呈現給他們。
如果你將所有變更呈現為單一補丁(或提交),他們可能會覺得一次要消化太多資訊。
如果你將包含錯誤、修正和死胡同的完整工作歷史呈現給他們,他們可能會感到不知所措。
因此,理想情況通常是產生一系列補丁,使其滿足以下條件:
-
每個補丁都可以按順序應用。
-
每個補丁都包含一個單一的邏輯變更,以及解釋該變更的訊息。
-
沒有任何補丁會引入回歸:在應用系列中的任何初始部分之後,產生的專案仍然可以編譯和工作,並且沒有之前沒有的錯誤。
-
完整的系列會產生與你自己的(可能混亂得多的!)開發過程相同的最終結果。
我們將介紹一些可以幫助你完成此操作的工具,解釋如何使用它們,然後解釋一些由於你正在重寫歷史紀錄而可能出現的問題。
使用 git rebase 來保持補丁系列的最新狀態
假設你在遠端追蹤分支 origin
上建立一個分支 mywork
,並在其之上建立一些提交。
$ git switch -c mywork origin $ vi file.txt $ git commit $ vi otherfile.txt $ git commit ...
你沒有將任何合併合併到 mywork 中,所以它只是 origin
之上的一個簡單線性補丁序列。
o--o--O <-- origin \ a--b--c <-- mywork
上游專案中已經完成了一些更有趣的工作,並且 origin
已經前進了。
o--o--O--o--o--o <-- origin \ a--b--c <-- mywork
此時,你可以使用 pull
將你的變更合併回來;結果會建立一個新的合併提交,如下所示:
o--o--O--o--o--o <-- origin \ \ a--b--c--m <-- mywork
但是,如果你希望將 mywork 中的歷史紀錄保持為一個簡單的提交系列,而沒有任何合併,你也可以選擇使用 git-rebase[1]。
$ git switch mywork $ git rebase origin
這會將你的每個提交從 mywork 中移除,暫時將它們儲存為補丁(在名為 .git/rebase-apply
的目錄中),更新 mywork 以指向最新版本的 origin,然後將每個儲存的補丁應用到新的 mywork。結果會像這樣:
o--o--O--o--o--o <-- origin \ a'--b'--c' <-- mywork
在此過程中,它可能會發現衝突。在這種情況下,它會停止並允許你修正衝突;修正衝突後,使用 git add
來更新索引,然後,不要執行 git commit
,只需執行:
$ git rebase --continue
Git 就會繼續套用其餘的補丁。
你可以在任何時候使用 --abort
選項中止此過程,並將 mywork 回復到開始 rebase 之前的狀態。
$ git rebase --abort
如果你需要重新排序或編輯分支中的多個提交,使用 git rebase -i
可能會更容易,這可以讓你重新排序和壓縮提交,以及在 rebase 期間將它們標記為個別編輯。有關詳細資訊,請參閱 使用互動式 rebase,有關替代方案,請參閱 重新排序或從補丁系列中選擇。
重寫單一提交
我們在 通過重寫歷史紀錄來修正錯誤 中看到,你可以使用以下指令來取代最近的提交:
$ git commit --amend
這將用包含你的變更的新提交來取代舊提交,讓你首先有機會編輯舊的提交訊息。這對於修正上次提交中的錯字,或者調整錯誤暫存的提交的補丁內容非常有用。
如果你需要修改歷史紀錄中更深層的提交,你可以使用 互動式 rebase 的 edit
指令。
重新排序或從補丁系列中選擇
有時候你想要編輯歷史紀錄中更深層的提交。一種方法是使用 git format-patch
來建立一系列補丁,然後將狀態重置為應用補丁之前的狀態。
$ git format-patch origin $ git reset --hard origin
然後根據需要修改、重新排序或刪除補丁,再使用 git-am[1] 重新套用它們。
$ git am *.patch
使用互動式 rebase
你也可以使用互動式 rebase 來編輯補丁系列。這與 使用 format-patch
重新排序補丁系列 相同,所以請使用你最喜歡的介面。
將你目前的 HEAD rebase 到你想要保留原樣的最後一個提交。例如,如果你想要重新排序最近的 5 個提交,請使用:
$ git rebase -i HEAD~5
這將開啟你的編輯器,其中列出了執行 rebase 的步驟。
pick deadbee The oneline of this commit pick fa1afe1 The oneline of the next commit ... # Rebase c0ffeee..deadbee onto c0ffeee # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # # These lines can be re-ordered; they are executed from top to bottom. # # If you remove a line here THAT COMMIT WILL BE LOST. # # However, if you remove everything, the rebase will be aborted. # # Note that empty commits are commented out
如註解中所述,你可以通過編輯列表來重新排序提交、將它們壓縮在一起、編輯提交訊息等。一旦你感到滿意,請儲存列表並關閉編輯器,rebase 就會開始。
rebase 會在 pick
被取代為 edit
時,或者當列表中的步驟無法機械地解決衝突並需要你的協助時停止。當你完成編輯和/或解決衝突時,你可以使用 git rebase --continue
繼續。如果你認為事情變得太複雜,你始終可以使用 git rebase --abort
來取消。即使在 rebase 完成後,你仍然可以使用 reflog 來恢復原始分支。
有關程序和額外提示的更詳細討論,請參閱 git-rebase[1] 的「互動模式」部分。
重寫歷史紀錄的問題
重寫分支歷史紀錄的主要問題與合併有關。假設有人提取你的分支並將其合併到他們的分支中,結果如下所示:
o--o--O--o--o--o <-- origin \ \ t--t--t--m <-- their branch:
然後假設你修改了最後三個提交:
o--o--o <-- new head of origin / o--o--O--o--o--o <-- old head of origin
如果我們在一個儲存庫中一起檢查所有這些歷史紀錄,它會看起來像這樣:
o--o--o <-- new head of origin / o--o--O--o--o--o <-- old head of origin \ \ t--t--t--m <-- their branch:
Git 無法知道新的 head 是舊 head 的更新版本;它會將這種情況視為與兩位開發人員平行地在舊 head 和新 head 上獨立完成工作的情況完全相同。此時,如果有人嘗試將新 head 合併到他們的分支中,Git 將會嘗試合併兩個(舊的和新的)開發線,而不是嘗試將舊的替換為新的。結果可能會出乎意料。
你仍然可以選擇發布其歷史紀錄被重寫的分支,其他人可以提取這些分支來檢查或測試它們,這可能很有用,但他們不應嘗試將這些分支拉取到自己的工作中。
為了實現支援正確合併的真正分散式開發,已發布的分支永遠不應被重寫。
為什麼二分合併提交可能比二分線性歷史紀錄更難
git-bisect[1] 命令可以正確處理包含合併提交的歷史紀錄。但是,當它找到的提交是合併提交時,使用者可能需要比平常更努力地找出為什麼該提交引入了問題。
想像一下這個歷史紀錄:
---Z---o---X---...---o---A---C---D \ / o---o---Y---...---o---B
假設在上層開發線上,存在於 Z 的某個函式的含義在提交 X 時發生了變更。從 Z 導致 A 的提交會變更函式的實作和所有存在於 Z 的呼叫位置,以及它們新增的新呼叫位置,使其保持一致。A 沒有錯誤。
假設同時,在下層開發線上,有人在提交 Y 時為該函式新增了一個新的呼叫位置。從 Z 導致 B 的提交都假設該函式的舊語義,且呼叫者和被呼叫者彼此一致。B 也沒有錯誤。
假設這兩條開發線在 C 處乾淨地合併,因此不需要解決衝突。
然而,C 的程式碼已損壞,因為下層開發線上新增的呼叫者尚未轉換為上層開發線上引入的新語義。因此,如果你只知道 D 是錯誤的,Z 是好的,而且 git-bisect[1] 將 C 識別為罪魁禍首,你將如何找出問題是由於語義的變更所造成的?
當 git bisect
的結果是非合併提交時,你通常應該能夠僅通過檢查該提交來發現問題。開發人員可以通過將他們的變更分解為小的、自我包含的提交來使這變得容易。然而,在上述情況下這沒有幫助,因為問題並非從檢查任何單一提交就顯而易見;相反,需要對開發進行整體檢視。更糟糕的是,有問題函式的語義變更可能只是上層開發線上變更的一小部分。
另一方面,如果你沒有在 C 處合併,而是將 Z 到 B 之間的歷史紀錄 rebase 到 A 之上,你將會得到這個線性歷史紀錄:
---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*
在 Z 和 D* 之間進行二分搜尋會命中單一的罪魁禍首提交 Y*,並且理解為什麼 Y* 已損壞可能會更容易。
部分由於這個原因,許多經驗豐富的 Git 使用者,即使在一個以合併為主的專案上工作,也會通過在發布之前針對最新的上游版本進行 rebase 來保持歷史紀錄的線性。
進階分支管理
提取個別分支
除了使用 git-remote[1],您也可以選擇一次僅更新一個分支,並將其以任意名稱儲存在本地。
$ git fetch origin todo:my-todo-work
第一個參數 origin
,只是告訴 Git 從您最初複製的儲存庫中提取。第二個參數告訴 Git 從遠端儲存庫提取名為 todo
的分支,並將其以 refs/heads/my-todo-work
的名稱儲存在本地。
您也可以從其他儲存庫提取分支;因此,
$ git fetch git://example.com/proj.git master:example-master
將會建立一個名為 example-master
的新分支,並將給定 URL 儲存庫中名為 master
的分支儲存在其中。如果您已經有一個名為 example-master 的分支,它將嘗試快轉到 example.com 的 master 分支所指定的提交。更詳細地說
git fetch 和快轉
在先前的範例中,當更新現有分支時,git fetch
會檢查遠端分支上的最新提交是否為您的分支副本上的最新提交的後代,然後才將您的分支副本更新為指向新的提交。Git 將此過程稱為快轉。
快轉看起來像這樣
o--o--o--o <-- old head of the branch \ o--o--o <-- new head of the branch
在某些情況下,新的 head 實際上可能不是舊 head 的後代。例如,開發人員可能意識到犯了一個嚴重的錯誤並決定回溯,導致如下情況
o--o--o--o--a--b <-- old head of the branch \ o--o--o <-- new head of the branch
在這種情況下,git fetch
將會失敗,並列印出警告。
在這種情況下,您仍然可以強制 Git 更新到新的 head,如下一節所述。但是,請注意在上述情況下,這可能意味著遺失標記為 a
和 b
的提交,除非您已經建立了自己的參考指向它們。
強制 git fetch 執行非快轉更新
如果 git fetch 因為分支的新 head 不是舊 head 的後代而失敗,您可以使用以下命令強制更新:
$ git fetch git://example.com/proj.git +master:refs/remotes/example/master
請注意新增的 +
符號。或者,您可以使用 -f
旗標強制更新所有提取的分支,如下所示
$ git fetch -f origin
請注意,舊版本的 example/master 所指向的提交可能會遺失,如我們在前一節中所見。
設定遠端追蹤分支
我們在上面看到 origin
只是指您最初複製的儲存庫的捷徑。此資訊儲存在 Git 設定變數中,您可以使用 git-config[1] 來檢視它們
$ git config -l core.repositoryformatversion=0 core.filemode=true core.logallrefupdates=true remote.origin.url=git://git.kernel.org/pub/scm/git/git.git remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master
如果有其他您也經常使用的儲存庫,您可以建立類似的設定選項以節省輸入;例如,
$ git remote add example git://example.com/proj.git
將以下內容新增至 .git/config
[remote "example"] url = git://example.com/proj.git fetch = +refs/heads/*:refs/remotes/example/*
另請注意,上述設定可以透過直接編輯檔案 .git/config
而不是使用 git-remote[1] 來執行。
設定遠端之後,以下三個命令將執行相同的操作
$ git fetch git://example.com/proj.git +refs/heads/*:refs/remotes/example/* $ git fetch example +refs/heads/*:refs/remotes/example/* $ git fetch example
請參閱 git-config[1] 以取得上述設定選項的更多詳細資訊,並參閱 git-fetch[1] 以取得 refspec 語法的更多詳細資訊。
Git 概念
Git 建立在少量簡單但強大的概念之上。雖然有可能在不了解它們的情況下完成工作,但如果您了解它們,您會發現 Git 更直觀。
物件資料庫
我們已經在了解歷史:提交中看到,所有提交都儲存在一個 40 位數的「物件名稱」下。實際上,表示專案歷史所需的所有資訊都儲存在具有此類名稱的物件中。在每種情況下,名稱都是透過取得物件內容的 SHA-1 雜湊值計算出來的。SHA-1 雜湊是一種加密雜湊函數。對我們來說,這意味著不可能找到兩個具有相同名稱的不同物件。這有許多優點;其中
-
Git 可以快速判斷兩個物件是否相同,只需比較名稱即可。
-
由於物件名稱在每個儲存庫中都以相同的方式計算,因此儲存在兩個儲存庫中的相同內容始終會以相同的名稱儲存。
-
當 Git 讀取物件時,它可以透過檢查物件的名稱是否仍然是其內容的 SHA-1 雜湊值來偵測錯誤。
(請參閱物件儲存格式以取得物件格式和 SHA-1 計算的詳細資訊。)
有四種不同的物件類型:「blob」、「tree」、「commit」和「tag」。
-
「blob」物件用於儲存檔案資料。
-
「tree」物件將一個或多個「blob」物件綁定到一個目錄結構中。此外,tree 物件可以參考其他 tree 物件,從而建立目錄階層。
-
「commit」物件將此類目錄階層綁定在一起,形成一個有向無環圖修訂版本—每個 commit 都包含一個 tree 的物件名稱,該 tree 指定 commit 時的目錄階層。此外,commit 參考描述我們如何到達該目錄階層的歷史記錄的「父」commit 物件。
-
「tag」物件以符號方式識別並可用於簽署其他物件。它包含另一個物件的物件名稱和類型、符號名稱(當然!)以及可選的簽名。
物件類型的更多詳細資訊
Commit 物件
「commit」物件將 tree 的實體狀態與我們如何到達那裡以及為什麼的描述連結起來。使用 --pretty=raw
選項來git-show[1] 或 git-log[1] 來檢查您最喜歡的 commit
$ git show -s --pretty=raw 2be7fcb476 commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4 tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf parent 257a84d9d02e90447b149af58b271c19405edb6a author Dave Watson <dwatson@mimvista.com> 1187576872 -0400 committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700 Fix misspelling of 'suppress' in docs Signed-off-by: Junio C Hamano <gitster@pobox.com>
如您所見,commit 由以下內容定義
-
tree:tree 物件的 SHA-1 名稱(如下定義),表示特定時間點的目錄內容。
-
父系:一些 commit 的 SHA-1 名稱,這些 commit 表示專案歷史中緊接著的前一個步驟。上面的範例有一個父系;合併提交可能有不止一個。沒有父系的 commit 稱為「根」commit,表示專案的初始修訂版本。每個專案必須至少有一個根。專案也可以有多個根,儘管這並不常見(或不一定是個好主意)。
-
作者:負責此變更的人員姓名,以及其日期。
-
提交者:實際建立 commit 的人員姓名,以及完成的日期。這可能與作者不同,例如,如果作者是撰寫修補程式並透過電子郵件發送給使用它來建立 commit 的人員。
-
描述此 commit 的註解。
請注意,commit 本身不包含任何有關實際變更的資訊;所有變更都是透過比較此 commit 所參考的 tree 的內容與其父系相關聯的 tree 來計算的。特別是,Git 不會嘗試明確記錄檔案重新命名,儘管它可以識別在變更路徑下存在相同檔案資料的情況,這表示重新命名。(例如,請參閱 git-diff[1] 的 -M
選項)。
commit 通常由 git-commit[1] 建立,該命令會建立一個 commit,其父系通常是目前的 HEAD,並且其 tree 取自目前儲存在索引中的內容。
Tree 物件
功能廣泛的 git-show[1] 命令也可用於檢查 tree 物件,但 git-ls-tree[1] 將提供您更多詳細資訊
$ git ls-tree fb3a8bdd0ce 100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c .gitignore 100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d .mailmap 100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3 COPYING 040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745 Documentation 100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200 GIT-VERSION-GEN 100644 blob 289b046a443c0647624607d471289b2c7dcd470b INSTALL 100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1 Makefile 100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52 README ...
如您所見,tree 物件包含一個項目清單,每個項目都有一個模式、物件類型、SHA-1 名稱和名稱,並按名稱排序。它表示單一目錄 tree 的內容。
物件類型可以是 blob,表示檔案的內容,也可以是另一個 tree,表示子目錄的內容。由於 tree 和 blob(如所有其他物件)都以其內容的 SHA-1 雜湊值命名,因此只有當兩個 tree 的內容(包括遞迴地,所有子目錄的內容)相同時,它們才具有相同的 SHA-1 名稱。這允許 Git 快速判斷兩個相關 tree 物件之間的差異,因為它可以忽略任何具有相同物件名稱的項目。
(注意:在存在子模組的情況下,tree 也可能有 commit 作為項目。有關說明,請參閱子模組。)
請注意,檔案都具有模式 644 或 755:Git 實際上只會注意可執行位元。
Blob 物件
您可以使用 git-show[1] 來檢查 blob 的內容;例如,以上述 tree 中 COPYING
的項目中的 blob 為例
$ git show 6ff87c4664 Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. ...
「blob」物件只是一個二進位資料 blob。它不參考任何其他內容或具有任何類型的屬性。
由於 blob 完全由其資料定義,因此如果目錄 tree 中的兩個檔案(或儲存庫的多個不同版本中)具有相同的內容,它們將共用相同的 blob 物件。物件完全獨立於其在目錄 tree 中的位置,並且重新命名檔案不會變更該檔案相關聯的物件。
請注意,任何 tree 或 blob 物件都可以使用 git-show[1] 和 <revision>:<path> 語法來檢查。這有時對於瀏覽目前未簽出的 tree 的內容非常有用。
信任
如果您從一個來源接收 blob 的 SHA-1 名稱,而從另一個(可能不受信任的)來源接收其內容,只要 SHA-1 名稱一致,您仍然可以信任這些內容是正確的。這是因為 SHA-1 的設計使其無法找到產生相同雜湊值的不同內容。
同樣地,您只需要信任頂層 tree 物件的 SHA-1 名稱,就可以信任它所參考的整個目錄的內容,如果您從受信任的來源接收 commit 的 SHA-1 名稱,那麼您可以輕鬆驗證透過該 commit 的父系可到達的所有 commit 的整個歷史記錄,以及這些 commit 所參考的 tree 的所有內容。
因此,為了在系統中引入一些真正的信任,您唯一需要做的就是以數位方式簽署一則特殊註解,其中包含頂層 commit 的名稱。您的數位簽名會向其他人表明您信任該 commit,並且 commit 歷史記錄的不可變性會告訴其他人他們可以信任整個歷史記錄。
換句話說,您只需傳送一封電子郵件,告訴人們頂層 commit 的名稱 (SHA-1 雜湊值),並使用 GPG/PGP 之類的東西以數位方式簽署該電子郵件,就可以輕鬆驗證整個封存檔。
為了協助完成此操作,Git 還提供了 tag 物件…
Tag 物件
tag 物件包含一個物件、物件類型、tag 名稱、建立 tag 的人員(「標籤者」)姓名以及訊息,訊息可能包含簽名,如使用 git-cat-file[1] 所見
$ git cat-file tag v1.5.0 object 437b1b20df4b356c9342dac8d38849f24ef44f27 type commit tag v1.5.0 tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000 GIT 1.5.0 -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux) iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui nLE/L9aUXdWeTFPron96DLA= =2E+0 -----END PGP SIGNATURE-----
請參閱 git-tag[1] 命令以了解如何建立和驗證 tag 物件。(請注意,git-tag[1] 也可用於建立「輕量標籤」,這些標籤根本不是 tag 物件,而只是名稱以 refs/tags/
開頭的簡單參考)。
Git 如何有效率地儲存物件:pack 檔案
新建立的物件最初會被建立在以物件的 SHA-1 雜湊值命名的檔案中 (儲存在 .git/objects
)。
不幸的是,當專案有很多物件時,這個系統會變得效率低落。在舊專案上試試看:
$ git count-objects 6930 objects, 47620 kilobytes
第一個數字是保留在個別檔案中的物件數量。第二個是這些「鬆散」物件佔用的空間量。
您可以將這些鬆散物件移動到「pack 檔案」中,藉此節省空間並加快 Git 的速度。pack 檔案會以有效率的壓縮格式儲存一組物件;pack 檔案格式的詳細資訊可以在 gitformat-pack[5] 中找到。
若要將鬆散物件放入 pack 中,只需執行 git repack
$ git repack Counting objects: 6020, done. Delta compression using up to 4 threads. Compressing objects: 100% (6020/6020), done. Writing objects: 100% (6020/6020), done. Total 6020 (delta 4070), reused 0 (delta 0)
這會在 .git/objects/pack/ 中建立一個單一的「pack 檔案」,其中包含所有目前未打包的物件。然後您可以執行
$ git prune
以移除任何現在包含在 pack 中的「鬆散」物件。這也會移除任何未被參考的物件(例如,當您使用 git reset
來移除 commit 時可能會建立)。您可以透過查看 .git/objects
目錄或執行以下命令來驗證鬆散物件是否已消失:
$ git count-objects 0 objects, 0 kilobytes
雖然物件檔案已消失,但任何參照這些物件的命令都會像以前一樣正常運作。
git-gc[1] 命令會為您執行打包、修剪等操作,因此通常是您唯一需要的高階命令。
懸空的物件
git-fsck[1] 命令有時會抱怨有懸空的物件。它們並不是問題。
懸空物件最常見的原因是您已對分支進行 rebase,或者您從對分支進行 rebase 的其他人那裡 pull。請參閱重寫歷史並維護補丁系列。在這種情況下,原始分支的舊 head 仍然存在,以及它指向的所有內容也都存在。分支指標本身只是不存在,因為您已將其替換為另一個指標。
還有其他情況也會導致懸空物件。例如,可能會出現「懸空的 blob」,因為您執行了檔案的 git add
,但是,在您實際 commit 並使其成為更大整體的一部分之前,您變更了該檔案中的其他內容並 commit 了**更新**的內容。您最初新增的舊狀態最終沒有被任何 commit 或 tree 指向,因此現在它是一個懸空的 blob 物件。
類似地,當執行 "ort" 合併策略時,發現存在交叉合併並且因此有多個合併基準(這是相當不尋常的,但確實會發生),它會產生一個臨時的中間 tree(或者如果有多個交叉合併和兩個以上的合併基準,可能會產生更多)作為臨時的內部合併基準,同樣地,這些是真實的物件,但最終結果不會指向它們,因此它們最終會在您的儲存庫中「懸空」。
一般來說,懸空的物件沒有什麼好擔心的。它們甚至可能非常有用:如果您搞砸了某些事情,懸空的物件可能是您恢復舊 tree 的方式(例如,您執行了 rebase,然後意識到您真的不想執行 rebase — 您可以查看您有哪些懸空的物件,並決定將您的 head reset 為一些舊的懸空狀態)。
對於 commit,您可以使用
$ gitk <dangling-commit-sha-goes-here> --not --all
這會要求從給定的 commit 可到達的所有歷史記錄,但不包括任何分支、標籤或其他參考。如果您確定它是一些您想要的東西,您可以隨時為它建立新的參考,例如:
$ git branch recovered-branch <dangling-commit-sha-goes-here>
對於 blob 和 tree,您無法執行相同的操作,但您仍然可以檢查它們。您可以執行
$ git show <dangling-blob/tree-sha-goes-here>
來顯示 blob 的內容(或者,對於 tree,基本上是該目錄的 ls
),這可能會讓您了解是什麼操作導致了該懸空物件。
通常,懸空的 blob 和 tree 並不是很有趣。它們幾乎總是以下兩種情況的結果:要不是一個半完成的合併基準(如果您曾經有過需要手動修復的衝突合併,blob 通常甚至會有合併中的衝突標記),要不就是因為您使用 ^C 或類似的東西中斷了 git fetch
,導致一些新的物件留在物件資料庫中,但只是懸空且無用。
無論如何,一旦您確定您對任何懸空狀態都不感興趣,您就可以修剪所有無法到達的物件
$ git prune
它們就會消失。(您應該只在靜止的儲存庫上執行 git prune
— 它有點像執行檔案系統 fsck 恢復:您不希望在檔案系統已掛載時執行此操作。git prune
的設計不會在同時存取儲存庫的情況下造成任何損害,但您可能會收到令人困惑或可怕的訊息。)
從儲存庫損壞中復原
依設計,Git 會謹慎處理信任它的資料。但是,即使 Git 本身沒有錯誤,硬體或作業系統錯誤仍然可能會損壞資料。
解決這些問題的第一道防線是備份。您可以使用 clone 或僅使用 cp、tar 或任何其他備份機制來備份 Git 目錄。
作為最後的手段,您可以搜尋損壞的物件並嘗試手動替換它們。在嘗試執行此操作之前,請先備份您的儲存庫,以防您在此過程中損壞更多內容。
我們假設問題是單一遺失或損壞的 blob,這有時是一個可以解決的問題。(復原遺失的 tree,特別是 commit 會**困難得多**)。
在開始之前,請使用 git-fsck[1] 驗證是否存在損壞,並找出損壞的位置。這可能很耗時。
假設輸出如下所示
$ git fsck --full --no-dangling broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 to blob 4b9458b3786228369c63936db65827de3cc06200 missing blob 4b9458b3786228369c63936db65827de3cc06200
現在您知道 blob 4b9458b3 遺失,並且 tree 2d9263c6 指向它。如果您可以找到遺失 blob 物件的單一副本,可能在其他儲存庫中,您可以將其移至 .git/objects/4b/9458b3...
並完成。假設您不能。您仍然可以使用 git-ls-tree[1] 檢查指向它的 tree,這可能會輸出類似如下的內容:
$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING ... 100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile ...
因此,現在您知道遺失的 blob 是名為 myfile
的檔案的資料。而且您很可能也可以識別目錄 — 假設它在 somedirectory
中。如果您幸運的話,遺失的副本可能與您在工作目錄中 somedirectory/myfile
檢出的副本相同;您可以使用 git-hash-object[1] 來測試是否正確:
$ git hash-object -w somedirectory/myfile
這將會建立並儲存一個具有 somedirectory/myfile 內容的 blob 物件,並輸出該物件的 SHA-1。如果您非常幸運,它可能是 4b9458b3786228369c63936db65827de3cc06200,在這種情況下,您猜對了,損壞已修復!
否則,您需要更多資訊。您如何判斷遺失了哪個版本的檔案?
最簡單的方法是使用
$ git log --raw --all --full-history -- somedirectory/myfile
因為您要求原始輸出,所以您現在會得到類似如下的內容:
commit abc Author: Date: ... :100644 100644 4b9458b newsha M somedirectory/myfile commit xyz Author: Date: ... :100644 100644 oldsha 4b9458b M somedirectory/myfile
這告訴您,緊接在後面的檔案版本是「newsha」,而緊接在前面的版本是「oldsha」。您也知道從 oldsha 變更為 4b9458b 以及從 4b9458b 變更為 newsha 的 commit 訊息。
如果您 commit 的變更夠小,您現在可能會很好地重建中間狀態 4b9458b 的內容。
如果您可以執行此操作,您現在可以使用以下命令重新建立遺失的物件:
$ git hash-object -w <recreated-file>
您的儲存庫又恢復正常了!
(順帶一提,您可以忽略 fsck
,並從執行以下操作開始:
$ git log --raw --all
然後在整個過程中搜尋遺失物件 (4b9458b) 的 sha。這取決於您 — Git **確實**有很多資訊,它只是缺少一個特定的 blob 版本。
索引
索引是一個二進位檔案(通常保存在 .git/index
中),其中包含一個已排序的路徑名稱清單,每個路徑名稱都具有權限和 blob 物件的 SHA-1;git-ls-files[1] 可以向您顯示索引的內容:
$ git ls-files --stage 100644 63c918c667fa005ff12ad89437f2fdc80926e21c 0 .gitignore 100644 5529b198e8d14decbe4ad99db3f7fb632de0439d 0 .mailmap 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 0 COPYING 100644 a37b2152bd26be2c2289e1f57a292534a51a93c7 0 Documentation/.gitignore 100644 fbefe9a45b00a54b58d94d06eca48b03d40a50e0 0 Documentation/Makefile ... 100644 2511aef8d89ab52be5ec6a5e46236b4b6bcd07ea 0 xdiff/xtypes.h 100644 2ade97b2574a9f77e7ae4002a4e07a6a38e46d07 0 xdiff/xutils.c 100644 d5de8292e05e7c36c4b68857c1cf9855e3d2f70a 0 xdiff/xutils.h
請注意,在較舊的文件中,您可能會看到索引稱為「目前目錄快取」或僅稱為「快取」。它有三個重要的屬性:
-
索引包含產生單一(唯一確定的)tree 物件所需的所有資訊。
例如,執行 git-commit[1] 會從索引產生此 tree 物件,將其儲存在物件資料庫中,並將其用作與新 commit 相關聯的 tree 物件。
-
索引可讓 tree 物件 (它定義的) 與工作樹之間進行快速比較。
它透過為每個條目儲存一些額外的資料(例如上次修改時間)來執行此操作。此資料不會顯示在上方,也不會儲存在建立的 tree 物件中,但是它可以快速判斷工作目錄中哪些檔案與索引中儲存的內容不同,從而避免 Git 必須從這些檔案中讀取所有資料來尋找變更。
-
它可以有效地表示不同 tree 物件之間的合併衝突資訊,允許每個路徑名稱都與足夠的有關相關 tree 的資訊相關聯,以便您可以在它們之間建立三向合併。
我們在合併期間取得衝突解決的協助中看到,在合併期間,索引可以儲存單一檔案的多個版本(稱為「階段」)。上面 git-ls-files[1] 輸出的第三欄是階段編號,並且對於具有合併衝突的檔案,其值將不是 0。
因此,索引是一種臨時的預備區域,其中填滿了您正在處理的 tree。
如果您完全清除索引,一般而言,只要您有它描述的 tree 的名稱,您就不會遺失任何資訊。
子模組
大型專案通常由較小、獨立的模組組成。例如,嵌入式 Linux 發行版的原始碼樹會包含發行版中的每個軟體及其一些本機修改;電影播放器可能需要針對特定的已知可運作的解壓縮程式庫進行建置;幾個獨立的程式可能都會共用相同的建置指令碼。
對於集中式版本控制系統,這通常是透過在單一儲存庫中包含每個模組來完成的。開發人員可以檢出所有模組,或者僅檢出他們需要使用的模組。他們甚至可以在單一 commit 中修改多個模組的檔案,同時移動東西或更新 API 和翻譯。
Git 不允許部分檢出,因此在 Git 中重複此方法會迫使開發人員保留他們不感興趣接觸的模組的本機副本。在巨大的檢出中進行 commit 會比您預期的慢,因為 Git 必須掃描每個目錄以尋找變更。如果模組有很多本機歷史記錄,複製將會花費很長時間。
從好的方面來說,分散式版本控制系統可以更好地與外部來源整合。在集中式模型中,會從外部專案的版本控制匯出單一任意快照,然後將其匯入到供應商分支上的本機版本控制中。所有歷史記錄都會被隱藏。使用分散式版本控制,您可以複製整個外部歷史記錄,並更容易地追蹤開發和重新合併本機變更。
Git 的子模組支援允許一個儲存庫將外部專案的檢出(checkout)包含為一個子目錄。子模組會維護自己的身分;子模組的支援僅儲存子模組儲存庫的位置和提交 ID,因此其他克隆包含專案(「父專案」)的開發人員可以輕鬆地在相同的修訂版本克隆所有子模組。父專案的部分檢出是可行的:你可以告訴 Git 不要克隆、克隆部分或克隆所有子模組。
git-submodule[1]
命令自 Git 1.5.3 版本起可用。使用 Git 1.5.2 的使用者可以在儲存庫中查找子模組提交,並手動檢出它們;更早的版本則完全無法識別子模組。
為了了解子模組支援是如何運作的,請建立四個範例儲存庫,這些儲存庫稍後可以用作子模組。
$ mkdir ~/git $ cd ~/git $ for i in a b c d do mkdir $i cd $i git init echo "module $i" > $i.txt git add $i.txt git commit -m "Initial commit, submodule $i" cd .. done
現在建立父專案並加入所有子模組。
$ mkdir super $ cd super $ git init $ for i in a b c d do git submodule add ~/git/$i $i done
注意
|
如果你打算發布你的父專案,請不要在此處使用本機 URL! |
查看 git submodule
建立的檔案
$ ls -a . .. .git .gitmodules a b c d
git submodule add <repo> <path>
命令會執行幾個動作:
-
它會將子模組從
<repo>
克隆到目前目錄下的指定<path>
,並且預設會檢出 master 分支。 -
它會將子模組的克隆路徑新增至
gitmodules[5]
檔案,並將此檔案新增至索引,準備進行提交。 -
它會將子模組的目前提交 ID 新增至索引,準備進行提交。
提交父專案
$ git commit -m "Add submodules a, b, c and d."
現在克隆父專案
$ cd .. $ git clone super cloned $ cd cloned
子模組目錄在那裡,但它們是空的。
$ ls -a a . .. $ git submodule status -d266b9873ad50488163457f025db7cdd9683d88b a -e81d457da15309b4fef4249aba9b50187999670d b -c1536a972b9affea0f16e0680ba87332dc059146 c -d96249ff5d57de5de093e6baff9e0aafa5276a74 d
注意
|
上面顯示的提交物件名稱對於你來說可能會有所不同,但它們應與你的儲存庫的 HEAD 提交物件名稱相符。你可以執行 git ls-remote ../a 來檢查。 |
下載子模組的過程分為兩個步驟。首先執行 git submodule init
,將子模組儲存庫 URL 新增至 .git/config
。
$ git submodule init
現在使用 git submodule update
來克隆儲存庫並檢出父專案中指定的提交。
$ git submodule update $ cd a $ ls -a . .. .git a.txt
git submodule update
和 git submodule add
之間的一個主要區別在於,git submodule update
會檢出特定的提交,而不是分支的頂端。這就像檢出標籤一樣:頭部會分離,所以你不是在分支上工作。
$ git branch * (detached from d266b98) master
如果你想在子模組內進行更改,且你的頭部已分離,那麼你應該建立或檢出一個分支,進行更改,在子模組內發布更改,然後更新父專案以參考新的提交。
$ git switch master
或
$ git switch -c fix-up
然後
$ echo "adding a line again" >> a.txt $ git commit -a -m "Updated the submodule from within the superproject." $ git push $ cd .. $ git diff diff --git a/a b/a index d266b98..261dfac 160000 --- a/a +++ b/a @@ -1 +1 @@ -Subproject commit d266b9873ad50488163457f025db7cdd9683d88b +Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24 $ git add a $ git commit -m "Updated submodule a." $ git push
如果你也想更新子模組,則在 git pull
之後必須執行 git submodule update
。
子模組的陷阱
永遠要在發布父專案中參考的更改之前,發布子模組的更改。如果你忘記發布子模組的更改,其他人將無法克隆儲存庫。
$ cd ~/git/super/a $ echo i added another line to this file >> a.txt $ git commit -a -m "doing it wrong this time" $ cd .. $ git add a $ git commit -m "Updated submodule a again." $ git push $ cd ~/git/cloned $ git pull $ git submodule update error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git. Did you forget to 'git add'? Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
在較舊的 Git 版本中,很容易忘記在子模組中提交新的或已修改的檔案,這會悄悄地導致與未推送子模組更改類似的問題。從 Git 1.7.0 開始,父專案中的 git status
和 git diff
都會在子模組包含新的或已修改的檔案時將其顯示為已修改,以防止意外提交此類狀態。git diff
在產生修補程式輸出或與 --submodule
選項一起使用時,也會在工作樹端加上 -dirty
。
$ git diff diff --git a/sub b/sub --- a/sub +++ b/sub @@ -1 +1 @@ -Subproject commit 3f356705649b5d566d97ff843cf193359229a453 +Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty $ git diff --submodule Submodule sub 3f35670..3f35670-dirty:
你也不應該在子模組中回溯分支,使其超出任何父專案中曾經記錄的提交。
如果你在子模組內進行了變更並提交,但沒有先檢出分支,則執行 git submodule update
是不安全的。它們會被無聲地覆蓋。
$ cat a.txt module a $ echo line added from private2 >> a.txt $ git commit -a -m "line added inside private2" $ cd .. $ git submodule update Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b' $ cd a $ cat a.txt module a
注意
|
這些變更仍然在子模組的 reflog 中可見。 |
如果你的子模組工作樹中有未提交的變更,git submodule update
將不會覆蓋它們。相反地,你會收到關於無法從髒分支切換的正常警告。
底層 Git 操作
許多高階命令最初是使用較小的底層 Git 命令核心實作為 shell 指令碼。當使用 Git 進行不尋常的操作時,或者僅僅是為了了解其內部運作方式,這些命令仍然很有用。
物件存取和操作
git-cat-file[1]
命令可以顯示任何物件的內容,儘管高階的 git-show[1]
通常更有用。
git-commit-tree[1]
命令允許使用任意父節點和樹狀結構建構提交。
可以使用 git-write-tree[1]
建立樹狀結構,並且可以使用 git-ls-tree[1]
存取其資料。可以使用 git-diff-tree[1]
比較兩個樹狀結構。
使用 git-mktag[1]
建立標籤,並且可以使用 git-verify-tag[1]
驗證簽章,儘管通常使用 git-tag[1]
進行這兩項操作更簡單。
工作流程
諸如 git-commit[1]
和 git-restore[1]
之類的高階操作,透過在工作樹、索引和物件資料庫之間移動資料來運作。Git 提供底層操作,可以單獨執行這些步驟中的每一個步驟。
一般來說,所有 Git 操作都作用於索引檔案。某些操作**純粹**作用於索引檔案(顯示索引的目前狀態),但大多數操作會在索引檔案與資料庫或工作目錄之間移動資料。因此,有四個主要組合:
工作目錄 → 索引
git-update-index[1]
命令會使用工作目錄中的資訊更新索引。你通常只需指定要更新的檔案名稱,即可更新索引資訊,如下所示:
$ git update-index filename
但是,為了避免檔案名稱使用通配符等常見錯誤,該命令通常不會新增全新的項目或移除舊項目,也就是說,它通常只會更新現有的快取項目。
為了告訴 Git 你確實知道某些檔案不再存在,或者應該新增新檔案,你應該分別使用 --remove
和 --add
旗標。
注意!--remove
旗標**不**表示後續的檔案名稱一定會被移除:如果這些檔案仍然存在於你的目錄結構中,則索引將會以它們的新狀態更新,而不會被移除。--remove
的唯一含義是,update-index 會認為已移除的檔案是有效的,並且如果該檔案確實不再存在,它將會相應地更新索引。
作為一種特殊情況,你也可以執行 git update-index --refresh
,這會更新每個索引的「stat」資訊,使其與目前的 stat 資訊相符。它**不**會更新物件狀態本身,並且只會更新用於快速測試物件是否仍然與其舊的支援儲存物件相符的欄位。
先前介紹的 git-add[1]
只是 git-update-index[1]
的包裝器。
索引 → 物件資料庫
你可以使用程式將目前的索引檔案寫入「樹狀」物件:
$ git write-tree
這個程式沒有任何選項 — 它只會將目前的索引寫入描述該狀態的樹狀物件集中,並且會傳回產生的頂層樹狀結構的名稱。你可以使用該樹狀結構隨時透過反方向重新產生索引:
物件資料庫 → 索引
你從物件資料庫讀取「樹狀」檔案,並使用它來填入(並覆寫 — 如果你的索引包含你可能想要稍後還原的任何未儲存狀態,請不要執行此操作!)目前的索引。正常操作只是:
$ git read-tree <SHA-1 of tree>
你的索引檔案現在將與你先前儲存的樹狀結構等效。但是,這只是你的*索引*檔案:你的工作目錄內容尚未被修改。
索引 → 工作目錄
你可以透過「檢出」檔案從索引更新你的工作目錄。這不是非常常見的操作,因為通常你會保持檔案更新,並且你不會寫入你的工作目錄,而是告訴索引檔案關於工作目錄中的變更(即 git update-index
)。
但是,如果你決定跳到新版本,或檢出其他人的版本,或只是還原先前的樹狀結構,你會使用 read-tree 填入你的索引檔案,然後你需要使用以下命令檢出結果:
$ git checkout-index filename
或者,如果你想檢出所有索引,請使用 -a
。
注意!git checkout-index
通常會拒絕覆寫舊檔案,因此如果你的舊樹狀結構版本已經檢出,你將需要使用 -f
旗標(在 -a
旗標或檔案名稱*之前*)來*強制*檢出。
最後,還有一些並非純粹從一種表示形式移動到另一種表示形式的雜項:
將所有內容結合在一起
要提交你使用 git write-tree
建立的樹狀結構,你將建立一個「提交」物件,該物件會參考該樹狀結構及其背後的歷史記錄 — 最值得注意的是,歷史記錄中在此之前的「父」提交。
通常,「提交」有一個父節點:在進行特定變更之前,樹狀結構的先前狀態。但是,有時它可能會有兩個或多個父節點提交,在這種情況下,我們稱之為「合併」,因為此類提交會將其他提交所代表的兩個或多個先前狀態結合在一起(「合併」)。
換句話說,「樹狀結構」代表工作目錄的特定目錄狀態,而「提交」則代表該狀態的時間點,並解釋了我們如何到達那裡。
你可以透過提供提交時描述狀態的樹狀結構,以及父節點的清單來建立提交物件:
$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]
然後在 stdin 上提供提交的原因(透過管道或檔案重新導向,或只是在 tty 上輸入)。
git commit-tree
將會傳回代表該提交的物件名稱,你應該將其儲存以供日後使用。通常,你會提交新的 HEAD
狀態,並且雖然 Git 不在意你將該狀態的筆記儲存在哪裡,但實際上我們傾向於將結果寫入 .git/HEAD
所指向的檔案,以便我們始終可以看到最後提交的狀態是什麼。
這是一張說明各個部分如何組合在一起的圖片:
commit-tree commit obj +----+ | | | | V V +-----------+ | Object DB | | Backing | | Store | +-----------+ ^ write-tree | | tree obj | | | | read-tree | | tree obj V +-----------+ | Index | | "cache" | +-----------+ update-index ^ blob obj | | | | checkout-index -u | | checkout-index stat | | blob obj V +-----------+ | Working | | Directory | +-----------+
檢查資料
你可以使用各種輔助工具檢查物件資料庫和索引中表示的資料。對於每個物件,你可以使用 git-cat-file[1]
來檢查有關物件的詳細資訊:
$ git cat-file -t <objectname>
顯示物件的類型,並且一旦你有了類型(通常在你找到物件的位置是隱含的),你就可以使用:
$ git cat-file blob|tree|commit|tag <objectname>
來顯示其內容。注意!樹狀結構具有二進位內容,因此有一個特殊的輔助工具用於顯示該內容,稱為 git ls-tree
,它會將二進位內容轉換為更容易讀取的形式。
查看「提交」物件特別有啟發性,因為它們往往很小且相當不言自明。特別是,如果你遵循將頂層提交名稱放在 .git/HEAD
中的慣例,則可以執行:
$ git cat-file commit HEAD
來查看頂層提交是什麼。
合併多個樹狀結構
Git 可以協助你執行三向合併,進而可以透過重複多次合併程序來用於多向合併。通常情況是,你只執行一次三向合併(調和兩個歷史記錄),然後提交結果,但是如果你願意,也可以一次合併多個分支。
要執行三向合併,你首先從要合併的兩個提交開始,找到它們最接近的共同父節點(第三個提交),然後比較這三個提交對應的樹狀結構。
為了獲得合併的「基礎」,請查閱兩個提交的共同父節點:
$ git merge-base <commit1> <commit2>
這會印出他們兩個都基於的提交名稱。您現在應該查找這些提交的樹狀物件,這可以透過以下方式輕鬆完成:
$ git cat-file commit <commitname> | head -1
因為樹狀物件資訊總是提交物件中的第一行。
一旦您知道要合併的三個樹狀結構(一個「原始」樹狀結構,又稱共同樹狀結構,以及兩個「結果」樹狀結構,又稱您要合併的分支),您就可以將「合併」讀取到索引中。如果必須捨棄您舊的索引內容,這會發出抱怨,因此您應該確保已提交這些內容 - 事實上,您通常總是針對您最後一次提交進行合併(因此這應該與您目前索引中的內容匹配)。
要進行合併,請執行:
$ git read-tree -m -u <origtree> <yourtree> <targettree>
這會直接在索引檔案中為您執行所有簡單的合併操作,您只需使用 git write-tree
寫出結果即可。
合併多個樹狀結構,續
遺憾的是,許多合併並非簡單的。如果有新增、移動或移除的檔案,或者如果兩個分支都修改了同一個檔案,您將會留下一個包含「合併條目」的索引樹。這樣的一個索引樹 *不能* 寫出到樹狀物件,您必須使用其他工具解決任何這類合併衝突,才能寫出結果。
您可以使用 git ls-files --unmerged
命令檢查此類索引狀態。一個範例:
$ git read-tree -m $orig HEAD $target $ git ls-files --unmerged 100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello.c 100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello.c 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello.c
git ls-files --unmerged
輸出的每一行都以 blob 模式位元、blob SHA-1、*階段編號* 和檔名開始。*階段編號* 是 Git 表示它來自哪個樹狀結構的方式:階段 1 對應於 $orig
樹狀結構,階段 2 對應於 HEAD
樹狀結構,階段 3 對應於 $target
樹狀結構。
稍早我們提到簡單的合併在 git read-tree -m
內部完成。例如,如果檔案從 $orig
到 HEAD
或 $target
沒有變更,或者如果檔案從 $orig
到 HEAD
和 $orig
到 $target
的變更方式相同,很明顯,最終結果是 HEAD
中的內容。上述範例顯示檔案 hello.c
從 $orig
到 HEAD
和 $orig
到 $target
的變更方式不同。您可以使用您最愛的 3 向合併程式(例如 diff3
、merge
或 Git 自己的 merge-file)對這三個階段的 blob 物件執行,例如:
$ git cat-file blob 263414f >hello.c~1 $ git cat-file blob 06fa6a2 >hello.c~2 $ git cat-file blob cc44c73 >hello.c~3 $ git merge-file hello.c~2 hello.c~1 hello.c~3
這會在 hello.c~2
檔案中留下合併結果,如果存在衝突,則會留下衝突標記。在驗證合併結果有意義之後,您可以透過以下方式告訴 Git 這個檔案的最終合併結果:
$ mv -f hello.c~2 hello.c $ git update-index hello.c
當路徑處於「未合併」狀態時,對該路徑執行 git update-index
會告訴 Git 將該路徑標示為已解決。
以上是最低層級的 Git 合併描述,以協助您瞭解在底層概念上發生了什麼。實際上,沒有人,甚至 Git 本身也不會為此執行三次 git cat-file
。有一個 git merge-index
程式會將階段提取到臨時檔案中,並對其呼叫「合併」指令碼:
$ git merge-index git-merge-one-file hello.c
這就是較高層級的 git merge -s resolve
的實作方式。
破解 Git
本章涵蓋 Git 實作的內部細節,可能只有 Git 開發人員需要了解。
物件儲存格式
所有物件都有靜態決定的「類型」,用於識別物件的格式(即物件的使用方式,以及如何參照其他物件)。目前有四種不同的物件類型:「blob」、「tree」、「commit」和「tag」。
無論物件類型為何,所有物件都具有以下特性:它們都使用 zlib 進行壓縮,並且具有標頭,標頭不僅指定了它們的類型,還提供了有關物件中資料大小的資訊。值得注意的是,用於命名物件的 SHA-1 雜湊是原始資料加上這個標頭的雜湊,因此 sha1sum
*檔案* 與 *檔案* 的物件名稱不符(最早版本的 Git 的雜湊方式略有不同,但結論仍然相同)。
以下是一個簡短的範例,示範如何手動產生這些雜湊:
假設一個小文字檔,其中包含一些簡單的內容:
$ echo "Hello world" >hello.txt
我們現在可以手動產生 Git 將用於此檔案的雜湊:
-
我們想要雜湊的物件類型為「blob」,大小為 12 個位元組。
-
將物件標頭附加到檔案內容,並將其饋送到
sha1sum
:
$ { printf "blob 12\0"; cat hello.txt; } | sha1sum 802992c4220de19a90767f3000a79a31b98d0df7 -
可以使用 git hash-object
驗證此手動建構的雜湊,這當然會隱藏標頭的添加:
$ git hash-object hello.txt 802992c4220de19a90767f3000a79a31b98d0df7
因此,可以始終獨立於物件的內容或類型來測試物件的一般一致性:可以透過驗證 (a) 它們的雜湊是否與檔案的內容匹配,以及 (b) 物件是否成功解壓縮為形成 <ascii-type-without-space> + <space> + <ascii-decimal-size> + <byte\0> + <binary-object-data>
序列的位元組串流,來驗證所有物件。
結構化物件可以進一步驗證其結構和與其他物件的連線。這通常使用 git fsck
程式完成,該程式會產生所有物件的完整相依性圖表,並驗證其內部一致性(除了透過雜湊驗證其表面一致性)。
Git 原始碼的鳥瞰圖
對於新的開發人員來說,在 Git 的原始碼中找到方向並不總是容易的。本節提供一些指導,說明從哪裡開始。
一個好的起點是初始提交的內容,使用:
$ git switch --detach e83c5163
初始修訂版為 Git 今天擁有的幾乎所有內容奠定了基礎(即使在某些地方細節可能有所不同),但它足夠小,可以在一次閱讀中閱讀。
請注意,自該修訂版以來,術語已發生變化。例如,該修訂版中的 README 使用「變更集」一詞來描述我們現在稱為的 commit。
此外,我們不再將其稱為「快取」,而是稱為「索引」;但是,該檔案仍然稱為 read-cache.h
。
如果您掌握了初始提交中的想法,您應該查看較新版本並瀏覽 read-cache-ll.h
、object.h
和 commit.h
。
在早期,Git(遵循 UNIX 的傳統)是一堆非常簡單的程式,您可以在指令碼中使用它們,將一個程式的輸出傳送到另一個程式。這對於初始開發來說效果很好,因為它更容易測試新事物。但是,最近這些部分中的許多部分已成為內建程式,並且一些核心已「libified」,即為了效能、可攜性原因和避免程式碼重複而放入 libgit.a 中。
到目前為止,您已經知道索引是什麼(並在 read-cache-ll.h
中找到對應的資料結構),並且只有幾種類型的物件(blob、樹狀結構、提交和標籤)從 struct object
繼承其通用結構,這是它們的第一個成員(因此,您可以將 (struct object *)commit
轉換為與 &commit->object
達到 *相同* 的效果,即取得物件名稱和旗標)。
現在是休息一下,讓這些資訊沉澱的好時機。
下一步:熟悉物件命名。閱讀 命名提交。有很多種方式可以命名物件(而不僅僅是修訂版!)。所有這些都在 sha1_name.c
中處理。快速查看函式 get_sha1()
。許多特殊處理都是由 get_sha1_basic()
或類似的函式完成的。
這只是為了讓您進入 Git 最 libified 部分的狀態:修訂版步行者。
基本上,git log
的初始版本是一個 shell 指令碼:
$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \ LESS=-S ${PAGER:-less}
這是什麼意思?
git rev-list
是修訂版步行者的原始版本,它 *總是* 將修訂版的清單列印到 stdout。它仍然可以正常運作,而且必須正常運作,因為大多數新的 Git 命令都以使用 git rev-list
的指令碼開始。
git rev-parse
現在不再那麼重要了;它僅用於篩選出與指令碼呼叫的不同管道命令相關的選項。
git rev-list
所做的大部分工作都包含在 revision.c
和 revision.h
中。它將選項包裝在名為 rev_info
的結構中,該結構控制如何以及步行哪些修訂版等等。
git rev-parse
的原始工作現在由函式 setup_revisions()
執行,該函式會剖析修訂版和修訂版步行者的通用命令列選項。此資訊儲存在 rev_info
結構中以供稍後使用。您可以在呼叫 setup_revisions()
之後執行您自己的命令列選項剖析。之後,您必須呼叫 prepare_revision_walk()
進行初始化,然後可以使用函式 get_revision()
一個接一個地取得提交。
如果您對修訂版步行流程的更多詳細資訊感興趣,只需查看 cmd_log()
的第一個實作即可;呼叫 git show v1.3.0~155^2~4
並向下捲動到該函式(請注意,您不再需要直接呼叫 setup_pager()
)。
現在,git log
是一個內建程式,這表示它 *包含* 在命令 git
中。內建程式的原始碼端是:
-
一個名為
cmd_<bla>
的函式,通常在builtin/<bla.c>
中定義(請注意,較舊版本的 Git 過去將其放在builtin-<bla>.c
中),並在builtin.h
中宣告。 -
git.c
中commands[]
陣列中的一個項目,以及 -
Makefile
中BUILTIN_OBJECTS
中的一個項目。
有時,多個內建程式包含在一個原始碼檔案中。例如,cmd_whatchanged()
和 cmd_log()
都位於 builtin/log.c
中,因為它們共用許多程式碼。在這種情況下,名稱與它們所在的 .c
檔案不相同的命令必須列在 Makefile
的 BUILT_INS
中。
git log
在 C 語言中看起來比在原始指令碼中更複雜,但這允許更大的彈性和效能。
在這裡,再次暫停一下是一個好時機。
第三課是:研究程式碼。真的,這是了解 Git 組織架構的最佳方式(在您了解基本概念之後)。
所以,想想你感興趣的事情,例如,「我如何只透過知道物件名稱來存取 blob?」 第一步是找到一個可以做到這一點的 Git 命令。 在這個例子中,是 git show
或 git cat-file
。
為了清楚起見,我們使用 git cat-file
,因為它
-
是底層命令,而且
-
早在最初的 commit 中就存在(它實際上只經過了大約 20 次修訂,最初是
cat-file.c
,在成為內建命令時被重新命名為builtin/cat-file.c
,然後又看到了不到 10 個版本)。
所以,看看 builtin/cat-file.c
,搜尋 cmd_cat_file()
,看看它做了什麼。
git_config(git_default_config); if (argc != 3) usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>"); if (get_sha1(argv[2], sha1)) die("Not a valid object name %s", argv[2]);
讓我們跳過顯而易見的細節;這裡唯一真正有趣的部分是對 get_sha1()
的呼叫。它試圖將 argv[2]
解釋為物件名稱,如果它指的是目前儲存庫中存在的物件,它會將結果的 SHA-1 寫入變數 sha1
。
這裡有兩件事很有趣
-
get_sha1()
在成功時返回 0。這可能會讓一些新的 Git 黑客感到驚訝,但在 UNIX 中有一個悠久的傳統,在發生不同錯誤時返回不同的負數——成功時返回 0。 -
在
get_sha1()
的函式簽名中的變數sha1
是unsigned char *
,但實際上預期是指向unsigned char[20]
的指標。這個變數將包含給定 commit 的 160 位元 SHA-1。請注意,每當 SHA-1 作為unsigned char *
傳遞時,它都是二進制表示,而不是以十六進制字元表示的 ASCII 表示,後者會作為char *
傳遞。
您會在程式碼中看到這兩件事。
現在,進入重點
case 0: buf = read_object_with_reference(sha1, argv[1], &size, NULL);
這是你如何讀取 blob 的方式(實際上,不僅僅是 blob,而是任何類型的物件)。要了解函式 read_object_with_reference()
實際是如何運作的,請找到它的原始碼(在 Git 儲存庫中類似 git grep read_object_with | grep ":[a-z]"
的東西),並閱讀原始碼。
要找出如何使用結果,只需在 cmd_cat_file()
中繼續閱讀即可
write_or_die(1, buf, size);
有時候,你不知道在哪裡尋找某個功能。在許多這種情況下,搜尋 git log
的輸出,然後 git show
對應的 commit 會很有幫助。
範例:如果你知道 git bundle
有一些測試案例,但不記得它在哪裡(是的,你可以 git grep bundle t/
,但那並沒有說明重點!)
$ git log --no-merges t/
在分頁器 (less
) 中,只需搜尋 "bundle",然後往回幾行,就會看到它在 commit 18449ab0 中。現在只需複製這個物件名稱,並將它貼到命令列中
$ git show 18449ab0
瞧!
另一個範例:找出為了讓某些腳本成為內建命令需要做什麼
$ git log --no-merges --diff-filter=A builtin/*.c
你看,Git 實際上是找出 Git 原始碼的最佳工具!
Git 詞彙表
Git 解釋
- 替代物件資料庫
- 裸儲存庫
-
裸儲存庫通常是一個帶有
.git
後綴的適當命名 目錄,該目錄沒有本地簽出的任何受版本控制的檔案副本。也就是說,通常會存在於隱藏的.git
子目錄中的所有 Git 管理和控制檔案直接存在於repository.git
目錄中,並且沒有其他檔案存在和簽出。通常,公共儲存庫的發佈者會提供裸儲存庫。 - blob 物件
-
未鍵入的 物件,例如檔案的內容。
- 分支
-
「分支」是一條開發線。分支上最近的 commit 被稱為該分支的頂端。分支的頂端由分支 head 引用,當在分支上進行額外的開發時,它會向前移動。單個 Git 儲存庫 可以追蹤任意數量的分支,但您的 工作樹 僅與其中一個分支(「目前」或「已簽出」分支)相關聯,並且 HEAD 指向該分支。
- 快取
-
已過時:請參考 索引。
- 鏈
- 變更集
-
BitKeeper/cvsps 用語中的「commit」。由於 Git 不儲存變更,而是儲存狀態,因此使用「變更集」一詞來描述 Git 實際上沒有意義。
- 簽出
-
使用來自 物件資料庫 的 樹狀物件 或 blob 更新全部或部分 工作樹 的動作,並在整個工作樹已指向新的 分支 時更新 索引 和 HEAD。
- 挑選 (cherry-picking)
-
在 SCM 行話中,「挑選」是指從一系列變更(通常是 commit)中選擇一個變更子集,並將它們記錄為不同程式碼庫之上的一系列新變更。在 Git 中,這是透過 "git cherry-pick" 命令來執行,以提取現有 commit 引入的變更,並根據目前 分支 的頂端將其記錄為新的 commit。
- 乾淨
- commit
-
作為名詞:Git 歷史中的單個點;專案的整個歷史被表示為一組相互關聯的 commit。「commit」一詞通常被 Git 用在其他版本控制系統使用「修訂版本」或「版本」等詞的地方。也用作 commit 物件 的簡稱。
- commit 圖概念、表示和用法
-
由物件資料庫中 commit 所形成的 DAG 結構的同義詞,這些 commit 由分支頂端 引用,使用它們的連結 commit 鏈。此結構是明確的 commit 圖。該圖可以用其他方式表示,例如 「commit-graph」檔案。
- commit-graph 檔案
-
「commit-graph」檔案(通常使用連字號)是 commit 圖 的補充表示形式,可加速 commit 圖的遍歷。「commit-graph」檔案儲存在 .git/objects/info 目錄或替代物件資料庫的 info 目錄中。
- commit 物件
- 類似 commit (commit-ish)(也稱為 committish)
-
一個 commit 物件 或可以遞迴 取消引用 到 commit 物件的 物件。以下都是類似 commit:commit 物件、指向 commit 物件的 標籤物件、指向指向 commit 物件的標籤物件的標籤物件,等等。
- 核心 Git
-
Git 的基本資料結構和工具。僅公開有限的原始碼管理工具。
- DAG
-
有向無環圖。commit 物件 形成有向無環圖,因為它們有父項(有向),而且 commit 物件圖是無環的(沒有以相同的 物件 開始和結束的 鏈)。
- 懸掛物件
- 取消引用
-
引用 符號引用:存取符號引用指向的 引用 的動作。遞迴取消引用涉及對結果引用重複上述過程,直到找到非符號引用為止。
引用 標籤物件:存取標籤指向的 物件 的動作。透過對結果物件重複操作來遞迴取消引用標籤,直到結果具有指定的 物件類型(如果適用)或任何非「標籤」物件類型。在標籤的上下文中,「遞迴取消引用」的同義詞是「剝離」。
引用 commit 物件:存取 commit 的樹狀物件的動作。Commit 無法遞迴取消引用。
除非另有指定,否則在 Git 命令或協定的上下文中使用的「取消引用」是隱含地遞迴的。
- 分離的 HEAD
-
通常,HEAD 儲存 分支 的名稱,並且對 HEAD 表示的歷史記錄進行操作的命令會對導致 HEAD 指向的分支頂端的歷史記錄進行操作。但是,Git 也允許您 簽出 一個任意的 commit,該 commit 不一定是任何特定分支的頂端。處於這種狀態的 HEAD 稱為「分離」。
請注意,當 HEAD 處於分離狀態時,對目前分支歷史記錄進行操作的指令(例如,使用
git commit
在其之上建立新的歷史記錄)仍然有效。它們會更新 HEAD 以指向更新後歷史記錄的頂端,而不會影響任何分支。而更新或查詢關於目前分支資訊的指令(例如,git branch --set-upstream-to
設定目前分支與哪個遠端追蹤分支整合)則顯然無法運作,因為在這種狀態下沒有(真實的)目前分支可供查詢。 - 目錄
-
您用 "ls" 看到的列表 :-)
- 髒的
- 邪惡合併
- 快轉
-
快轉是一種特殊的合併類型,當您有一個修訂版本,並且「合併」另一個分支的變更時,而這些變更剛好是您擁有的版本的後代。在這種情況下,您不會建立新的合併提交,而只是更新您的分支以指向與您要合併的分支相同的修訂版本。這種情況經常發生在遠端儲存庫的遠端追蹤分支上。
- 提取
-
提取分支是指從遠端儲存庫取得分支的頭部參考,以找出本機物件資料庫中遺失的物件,並同時取得它們。另請參閱 git-fetch[1]。
- 檔案系統
-
Linus Torvalds 最初將 Git 設計為使用者空間的檔案系統,即用於保存檔案和目錄的基礎結構。這確保了 Git 的效率和速度。
- Git 歸檔
-
(對於 arch 使用者)儲存庫的同義詞。
- gitfile
-
位於工作樹根目錄的純文字檔案
.git
,指向真實儲存庫所在的目錄。如要正確使用,請參閱 git-worktree[1] 或 git-submodule[1]。有關語法,請參閱 gitrepository-layout[5]。 - 嫁接
-
嫁接允許透過記錄提交的虛假祖先資訊,將兩個原本不同的開發路線連接在一起。這樣您就可以讓 Git 假裝提交擁有的父節點與建立提交時記錄的不同。透過
.git/info/grafts
檔案進行設定。請注意,嫁接機制已過時,可能會導致在儲存庫之間傳輸物件時出現問題;請參閱 git-replace[1],以取得更彈性且穩健的系統來執行相同的操作。
- 雜湊
-
在 Git 的上下文中,是物件名稱的同義詞。
- 頭部
-
指向分支頂端提交的具名參考。頭部儲存在
$GIT_DIR/refs/heads/
目錄中的檔案中,但使用壓縮參考時除外。(請參閱 git-pack-refs[1]。) - HEAD
-
目前的分支。更詳細地說:您的工作樹通常是從 HEAD 引用的樹狀結構狀態衍生而來。HEAD 是您儲存庫中其中一個頭部的參考,除非使用分離 HEAD,在這種情況下,它會直接參考任意的提交。
- 頭部參考
-
是頭部的同義詞。
- 鉤子
-
在數個 Git 指令的正常執行過程中,會呼叫可選指令碼,讓開發人員可以新增功能或檢查。通常,鉤子允許對指令進行預先驗證,並可能中止指令,並允許在操作完成後發出後通知。鉤子指令碼位於
$GIT_DIR/hooks/
目錄中,只需從檔名中移除.sample
字尾即可啟用。在舊版的 Git 中,您必須將它們設為可執行。 - 索引
-
具有狀態資訊的檔案集合,其內容以物件形式儲存。索引是工作樹的已儲存版本。事實上,它也可以包含工作樹的第二個甚至第三個版本,這些版本會在合併時使用。
- 索引項目
-
儲存在索引中,關於特定檔案的資訊。如果合併已啟動但尚未完成(即,如果索引包含該檔案的多個版本),則索引項目可能處於未合併狀態。
- master
-
預設的開發分支。每當您建立 Git 儲存庫時,都會建立一個名為「master」的分支,並成為活動分支。在大多數情況下,它包含本機開發,儘管這純粹是慣例,並非必要。
- 合併
-
作為動詞:將另一個分支(可能來自外部儲存庫)的內容帶入目前分支。如果合併的分支來自不同的儲存庫,則會先提取遠端分支,然後將結果合併到目前分支。這種提取和合併操作的組合稱為拉取。合併是由自動流程執行,該流程會識別分支發散後所做的變更,然後將所有這些變更一起套用。如果變更發生衝突,可能需要人工介入才能完成合併。
- 物件
-
Git 中的儲存單元。它會由其內容的 SHA-1 唯一識別。因此,無法變更物件。
- 物件資料庫
- 物件識別碼 (oid)
-
是物件名稱的同義詞。
- 物件名稱
- 物件類型
- 章魚
-
合併兩個以上的分支。
- 孤立
-
進入尚未存在的分支(即未誕生分支)的動作。在這種操作之後,第一個建立的提交會成為沒有父節點的提交,並開始新的歷史記錄。
- origin
-
預設的上游儲存庫。大多數專案至少有一個追蹤的上游專案。依預設,origin 用於該目的。新的上游更新將會提取到名為 origin/upstream-branch-name 的遠端追蹤分支中,您可以使用
git branch -r
來檢視。 - 覆蓋
-
只更新檔案並將檔案新增至工作目錄,但不刪除它們,類似於 cp -R 如何更新目標目錄中的內容。這是從索引或tree-ish 中取出檔案時,取出的預設模式。相反地,非覆蓋模式也會刪除來源中不存在的追蹤檔案,類似於 rsync --delete。
- 封裝
-
將一組物件壓縮成一個檔案(以節省空間或有效傳輸它們)。
- 封裝索引
-
位於封裝中之物件的識別碼及其他資訊清單,以協助有效存取封裝的內容。
- 路徑規格
-
用於限制 Git 指令中路徑的模式。
路徑規格用於 "git ls-files"、"git ls-tree"、"git add"、"git grep"、"git diff"、"git checkout" 和許多其他指令的命令列中,以將操作範圍限制為樹狀結構或工作樹的某個子集。有關路徑是相對於目前目錄還是最上層目錄,請參閱每個指令的文件。路徑規格語法如下
-
任何路徑都與其自身匹配
-
路徑規格直到最後一個斜線都代表目錄前綴。該路徑規格的範圍僅限於該子樹。
-
路徑規格的其餘部分是路徑名稱其餘部分的模式。使用 fnmatch(3) 將相對於目錄前綴的路徑與該模式進行比對;特別是,* 和 ? 可以 匹配目錄分隔符號。
例如,Documentation/*.jpg 將會比對 Documentation 子樹中的所有 .jpg 檔案,包括 Documentation/chapter_1/figure_1.jpg。
以冒號
:
開頭的路徑規格 (pathspec) 具有特殊含義。在簡短形式中,開頭的冒號:
後面會跟著零個或多個「魔法簽名」字母(可選擇以另一個冒號:
終止),而其餘部分則是與路徑比對的模式。「魔法簽名」由 ASCII 符號組成,這些符號既不是字母數字、glob、正規表示式特殊字元,也不是冒號。如果模式以不屬於「魔法簽名」符號集且不是冒號的字元開頭,則終止「魔法簽名」的可選冒號可以省略。在長形式中,開頭的冒號
:
後面會跟著一個開括號(
、一個以逗號分隔的零個或多個「魔法字」列表,以及一個閉括號)
,而其餘部分則是與路徑比對的模式。僅包含冒號的路徑規格表示「沒有路徑規格」。這種形式不應與其他路徑規格組合使用。
- 頂端
-
魔法字
top
(魔法簽名:/
)會使模式從工作樹的根目錄開始比對,即使您從子目錄內部執行命令也是如此。 - 字面
-
模式中的萬用字元(例如
*
或?
)會被視為字面字元。 - icase
-
不區分大小寫的比對。
- glob
-
Git 將模式視為 shell glob,適用於搭配 FNM_PATHNAME 標誌使用 fnmatch(3):模式中的萬用字元不會比對路徑名稱中的 /。例如,「Documentation/*.html」會比對「Documentation/git.html」,但不會比對「Documentation/ppc/ppc.html」或「tools/perf/Documentation/perf.html」。
在針對完整路徑名稱比對的模式中,連續兩個星號(「
**
」)可能具有特殊含義-
開頭的「
**
」後接斜線表示在所有目錄中比對。例如,「**/foo
」會比對任何位置的檔案或目錄「foo
」,與模式「foo
」相同。「**/foo/bar
」會比對任何位置,直接位於目錄「foo
」下的檔案或目錄「bar
」。 -
結尾的「
/**
」會比對內部的一切。例如,「abc/**
」會比對相對於.gitignore
檔案位置,目錄「abc」內的所有檔案,且深度無限。 -
斜線後接連續兩個星號,再接斜線,表示比對零個或多個目錄。例如,「
a/**/b
」會比對「a/b
」、「a/x/b
」、「a/x/y/b
」等等。 -
其他連續的星號則被視為無效。
Glob 魔法與字面魔法不相容。
-
- attr
-
在
attr:
後面會接一個以空格分隔的「屬性需求」列表,所有需求都必須符合,路徑才被視為比對成功;這是除了通常的非魔法路徑規格模式比對之外的要求。請參閱 gitattributes[5]。路徑的每個屬性需求會採用以下形式之一
-
「
ATTR
」要求必須設定屬性ATTR
。 -
「
-ATTR
」要求不得設定屬性ATTR
。 -
「
ATTR=VALUE
」要求必須將屬性ATTR
設定為字串VALUE
。 -
「
!ATTR
」要求不得指定屬性ATTR
。請注意,當與樹狀物件比對時,屬性仍然是從工作樹取得,而不是從給定的樹狀物件取得。
-
- exclude
-
在路徑比對任何非排除路徑規格之後,它會執行所有排除路徑規格(魔法簽名:
!
或其同義詞^
)。如果比對成功,則會忽略該路徑。如果沒有非排除路徑規格,則會將排除應用於結果集,就像在沒有任何路徑規格的情況下調用一樣。
-
- 父
-
提交物件包含開發脈絡中(可能為空的)邏輯前置物件清單,也就是它的父物件。
- 剝離
- 鎬
-
術語鎬是指 diffcore 常式的一個選項,可協助選擇新增或刪除給定文字字串的變更。使用
--pickaxe-all
選項,它可以用於檢視引入或移除(例如)特定文字行的完整變更集。請參閱 git-diff[1]。 - 管線
-
對核心 Git 的可愛稱呼。
- 瓷器
- 每個工作樹參照
-
每個工作樹而非全域的參照。目前只有 HEAD 和任何以
refs/bisect/
開頭的參照,但之後可能會包含其他不常見的參照。 - 偽參照
-
語義與一般參照不同的參照。這些參照可以透過一般的 Git 命令讀取,但無法透過 git-update-ref[1] 等命令寫入。
Git 已知下列偽參照
-
FETCH_HEAD
由 git-fetch[1] 或 git-pull[1] 寫入。它可能參考多個物件 ID。每個物件 ID 都會以元資料進行註解,指出從何處擷取以及擷取狀態。 -
MERGE_HEAD
在解決合併衝突時由 git-merge[1] 寫入。它包含所有正在合併的提交 ID。
-
- 拉
-
拉取分支表示擷取它並合併它。另請參閱 git-pull[1]。
- 推
-
推送分支表示從遠端儲存庫取得分支的head 參照、找出它是否為分支的本機 head 參照的祖先,若是,則將所有從本機 head 參照可達,且遠端儲存庫中遺失的物件放入遠端物件資料庫,並更新遠端 head 參照。如果遠端head不是本機 head 的祖先,則推送會失敗。
- 可達
-
據說從給定提交的所有祖先都是從該提交「可達」的。更廣泛地說,如果我們可以藉由追蹤鏈結,將標籤追蹤到其標記的任何內容、提交追蹤到其父物件或樹狀結構,以及將樹狀結構追蹤到其包含的樹狀結構或二元大型物件,從一個物件到達另一個物件,則另一個物件是可達的。
- 可達性點陣圖
-
可達性點陣圖會儲存有關 packfile 或多重 pack 索引 (MIDX) 中選定的一組提交的可達性資訊,以加速物件搜尋。點陣圖儲存在「.bitmap」檔案中。儲存庫最多可以使用一個點陣圖檔案。點陣圖檔案可能屬於一個 pack,或儲存庫的多重 pack 索引(如果存在)。
- 變基
- 參照
-
指向物件名稱或另一個參照的名稱(後者稱為符號參照)。為方便起見,當參照作為 Git 命令的引數使用時,有時可以縮寫;請參閱 gitrevisions[7] 以取得詳細資訊。參照儲存在儲存庫中。
參照命名空間是階層式的。參照名稱必須以
refs/
開頭,或位於階層的根目錄中。對於後者,其名稱必須遵循以下規則-
名稱僅包含大寫字元或底線。
-
名稱以「
_HEAD
」結尾,或等於「HEAD
」。階層根目錄中有一些不符合這些規則的不規則參照。以下清單是詳盡的,未來不應擴充
-
AUTO_MERGE
-
BISECT_EXPECTED_REV
-
NOTES_MERGE_PARTIAL
-
NOTES_MERGE_REF
-
MERGE_AUTOSTASH
不同的子階層用於不同的目的。例如,
refs/heads/
階層用於表示本機分支,而refs/tags/
階層用於表示本機標籤。
-
- 參照記錄
-
參照記錄顯示參照的本機「歷程記錄」。換句話說,它可以告訴您此儲存庫中倒數第三個修訂版是什麼,以及昨天晚上 9:14 此儲存庫中的目前狀態是什麼。請參閱 git-reflog[1] 以取得詳細資訊。
- 參照規格
-
「參照規格」由擷取和推送使用,以描述遠端參照與本機參照之間的對應。請參閱 git-fetch[1] 或 git-push[1] 以取得詳細資訊。
- 遠端儲存庫
- 遠端追蹤分支
-
用於追蹤來自另一個儲存庫之變更的參照。它通常看起來像 refs/remotes/foo/bar(表示它追蹤名為 foo 的遠端中名為 bar 的分支),並比對已設定擷取參照規格的右側。遠端追蹤分支不應包含直接修改,也不應對其進行本機提交。
- 儲存庫
-
一個包含refs的集合,以及一個物件資料庫,其中包含從 refs 可存取的所有物件,可能還伴隨來自一個或多個瓷器的元數據。儲存庫可以透過替代機制與其他儲存庫共用物件資料庫。
- 解決(resolve)
-
手動修復失敗的自動合併所留下的殘局的動作。
- 版本(revision)
-
提交(commit,名詞)的同義詞。
- 倒帶(rewind)
- SCM
-
原始碼管理(工具)。
- SHA-1
-
「安全雜湊演算法 1」;一種加密雜湊函數。在 Git 的上下文中,作為物件名稱的同義詞。
- 淺層複製(shallow clone)
-
基本上是淺層儲存庫的同義詞,但這個詞組更明確地表示它是透過執行
git clone --depth=...
命令建立的。 - 淺層儲存庫(shallow repository)
-
淺層儲存庫具有不完整的歷史記錄,其中某些提交的父提交被截斷(換句話說,Git 被告知要假裝這些提交沒有父提交,即使它們被記錄在提交物件中)。當您只對專案的近期歷史感興趣時,即使上游記錄的真實歷史要大得多,這有時也很有用。淺層儲存庫是透過給 git-clone[1] 提供
--depth
選項來建立的,其歷史記錄稍後可以使用 git-fetch[1] 加深。 - 儲藏條目(stash entry)
- 子模組(submodule)
- 超專案(superproject)
- 符號引用(symref)
-
符號引用:它不是包含SHA-1 ID 本身,而是採用 ref: refs/some/thing 的格式,當被引用時,它會遞迴取值到這個引用。HEAD 就是符號引用的一個主要範例。符號引用是使用 git-symbolic-ref[1] 命令來操作的。
- 標籤(tag)
-
refs/tags/
命名空間下的引用,指向任意類型的物件(通常標籤指向標籤或提交物件)。與HEAD相反,標籤不會被commit
命令更新。Git 標籤與 Lisp 標籤無關(在 Git 的上下文中稱為物件類型)。標籤最常用於標記提交祖先鏈中的特定點。 - 標籤物件(tag object)
-
一個物件,包含一個指向另一個物件的引用,其中可以包含類似於提交物件的消息。它還可以包含(PGP)簽名,在這種情況下,它被稱為「已簽名的標籤物件」。
- 主題分支(topic branch)
-
開發人員用來識別概念開發路線的常規 Git 分支。由於分支非常容易且成本低廉,因此通常希望有多個小型分支,每個分支都包含定義非常明確的概念或小型漸進式但相關的變更。
- 樹狀結構(tree)
- 樹狀結構物件(tree object)
- 類樹狀結構(tree-ish,也稱為 treeish)
-
一個樹狀結構物件或一個可以遞迴取值為樹狀結構物件的物件。對提交物件取值會產生與版本的頂級目錄對應的樹狀結構物件。以下都是類樹狀結構:類提交、樹狀結構物件、指向樹狀結構物件的標籤物件、指向指向樹狀結構物件的標籤物件的標籤物件,等等。
- 未誕生(unborn)
-
HEAD 可以指向一個尚不存在且尚未有任何提交的分支,這種分支稱為未誕生分支。使用者遇到未誕生分支最典型的方式是建立一個新的儲存庫,而不是從其他地方複製。HEAD 會指向尚未誕生的 main (或 master,取決於您的配置)分支。此外,某些操作可以使用它們的孤立選項讓您進入未誕生分支。
- 未合併索引(unmerged index)
- 無法存取的物件(unreachable object)
- 上游分支(upstream branch)
-
被合併到有問題分支(或有問題分支重新基於其上)的預設分支。它透過 branch.<name>.remote 和 branch.<name>.merge 設定。如果 A 的上游分支是 origin/B,有時我們會說「A 正在追蹤 origin/B」。
- 工作樹(working tree)
-
實際檢出的檔案樹狀結構。工作樹通常包含HEAD 提交的樹狀結構的內容,以及您已進行但尚未提交的任何本機變更。
- 工作區(worktree)
-
一個儲存庫可以附加零個(即裸儲存庫)或一個或多個工作區。一個「工作區」由一個「工作樹」和儲存庫元數據組成,其中大部分在單個儲存庫的其他工作區之間共用,有些則是每個工作區單獨維護的(例如,索引、HEAD 和類似 MERGE_HEAD 的偽引用、每個工作區的引用和每個工作區的設定檔)。
附錄 A:Git 快速參考
這是主要命令的快速摘要;前面的章節更詳細地說明了它們的工作方式。
建立新的儲存庫
從 tarball
$ tar xzf project.tar.gz $ cd project $ git init Initialized empty Git repository in .git/ $ git add . $ git commit
從遠端儲存庫
$ git clone git://example.com/pub/project.git $ cd project
管理分支
$ git branch # list all local branches in this repo $ git switch test # switch working directory to branch "test" $ git branch new # create branch "new" starting at current HEAD $ git branch -d new # delete branch "new"
不要將新分支基於目前的 HEAD(預設),請使用
$ git branch new test # branch named "test" $ git branch new v2.6.15 # tag named v2.6.15 $ git branch new HEAD^ # commit before the most recent $ git branch new HEAD^^ # commit before that $ git branch new test~10 # ten commits before tip of branch "test"
同時建立並切換到新分支
$ git switch -c new v2.6.15
更新並檢查從您複製的儲存庫中的分支
$ git fetch # update $ git branch -r # list origin/master origin/next ... $ git switch -c masterwork origin/master
從不同的儲存庫提取分支,並在您的儲存庫中給它一個新名稱
$ git fetch git://example.com/project.git theirbranch:mybranch $ git fetch git://example.com/project.git v2.6.15:mybranch
保留您經常使用的儲存庫列表
$ git remote add example git://example.com/project.git $ git remote # list remote repositories example origin $ git remote show example # get details * remote example URL: git://example.com/project.git Tracked remote branches master next ... $ git fetch example # update branches from example $ git branch -r # list all remote branches
探索歷史記錄
$ gitk # visualize and browse history $ git log # list all commits $ git log src/ # ...modifying src/ $ git log v2.6.15..v2.6.16 # ...in v2.6.16, not in v2.6.15 $ git log master..test # ...in branch test, not in branch master $ git log test..master # ...in branch master, but not in test $ git log test...master # ...in one branch, not in both $ git log -S'foo()' # ...where difference contain "foo()" $ git log --since="2 weeks ago" $ git log -p # show patches as well $ git show # most recent commit $ git diff v2.6.15..v2.6.16 # diff between two tagged versions $ git diff v2.6.15..HEAD # diff with current head $ git grep "foo()" # search working directory for "foo()" $ git grep v2.6.15 "foo()" # search old tree for "foo()" $ git show v2.6.15:a.txt # look at old version of a.txt
搜尋回歸
$ git bisect start $ git bisect bad # current version is bad $ git bisect good v2.6.13-rc2 # last known good revision Bisecting: 675 revisions left to test after this # test here, then: $ git bisect good # if this revision is good, or $ git bisect bad # if this revision is bad. # repeat until done.
進行變更
確保 Git 知道要歸咎於誰
$ cat >>~/.gitconfig <<\EOF [user] name = Your Name Comes Here email = you@yourdomain.example.com EOF
選取要包含在下一次提交中的檔案內容,然後進行提交
$ git add a.txt # updated file $ git add b.txt # new file $ git rm c.txt # old file $ git commit
或者,一步準備並建立提交
$ git commit d.txt # use latest content only of d.txt $ git commit -a # use latest content of all tracked files
合併
$ git merge test # merge branch "test" into the current branch $ git pull git://example.com/project.git master # fetch and merge in remote branch $ git pull . test # equivalent to git merge test
分享您的變更
匯入或匯出修補程式
$ git format-patch origin..HEAD # format a patch for each commit # in HEAD but not in origin $ git am mbox # import patches from the mailbox "mbox"
提取不同 Git 儲存庫中的分支,然後合併到目前分支
$ git pull git://example.com/project.git theirbranch
在合併到目前分支之前,將提取的分支儲存到本機分支
$ git pull git://example.com/project.git theirbranch:mybranch
在本機分支上建立提交之後,使用您的提交更新遠端分支
$ git push ssh://example.com/project.git mybranch:theirbranch
當遠端和本機分支都命名為「test」時
$ git push ssh://example.com/project.git test
常用遠端儲存庫的快捷方式版本
$ git remote add example ssh://example.com/project.git $ git push example test
附錄 B:本手冊的注意事項和待辦事項清單
待辦事項清單
這是一個正在進行中的工作。
基本要求
-
對於具有 UNIX 命令列基本知識,但沒有任何 Git 專業知識的智慧人士來說,它必須從頭到尾依序閱讀。如有必要,任何其他先決條件都應在出現時具體提及。
-
在可行的情况下,章节标题应清楚地描述它们所解释的任务,并使用不超过必要知识的语言:例如,使用「將修補程式匯入專案」而不是「
git am
指令」。
思考如何建立清晰的章節相依性圖表,讓人們能夠在不必閱讀中間所有內容的情況下,就能找到重要的主題。
掃描 Documentation/
目錄,找出遺漏的其他內容;特別是
-
操作指南 (howto's)
-
一些
technical/
中的內容? -
鉤子 (hooks)
-
git[1] 中的指令列表
掃描電子郵件存檔,找出遺漏的其他內容
掃描 man page,看看是否有任何頁面假設讀者具備比本手冊提供的更多背景知識。
添加更多好的範例。或許可以考慮加入全是範例的章節,或是把「進階範例」章節設為標準的章末環節?
適當時加入詞彙表的交叉參考。
新增一個章節,說明如何使用其他版本控制系統,包括 CVS、Subversion,以及直接匯入發布 tarball 系列。
撰寫一個關於使用底層指令和編寫腳本的章節。
替代方案,clone -reference 等。