設定和配置
獲取和建立專案
基本快照
分支與合併
共享和更新專案
檢查和比較
打補丁
除錯
電子郵件
外部系統
伺服器管理
指南
管理
底層命令
- 2.51.1 → 2.52.0 無更改
-
2.51.0
2025-08-18
- 2.48.1 → 2.50.1 無更改
-
2.48.0
2025-01-10
- 2.46.1 → 2.47.3 無更改
-
2.46.0
2024-07-29
- 2.45.1 → 2.45.4 無更改
-
2.45.0
2024-04-29
- 2.44.1 → 2.44.4 無更改
-
2.44.0
2024-02-23
- 2.43.2 → 2.43.7 無變更
-
2.43.1
2024-02-09
-
2.43.0
2023-11-20
- 2.42.1 → 2.42.4 無更改
-
2.42.0
2023-08-21
- 2.39.1 → 2.41.3 無變更
-
2.39.0
2022-12-12
- 2.38.1 → 2.38.5 無更改
-
2.38.0
2022-10-02
- 2.36.1 → 2.37.7 無更改
-
2.36.0
2022-04-18
- 2.34.1 → 2.35.8 無更改
-
2.34.0
2021-11-15
- 2.33.1 → 2.33.8 無更改
-
2.33.0
2021-08-16
- 2.32.1 → 2.32.7 無變更
-
2.32.0
2021-06-06
- 2.30.1 → 2.31.8 無更改
-
2.30.0
2020-12-27
- 2.28.1 → 2.29.3 無更改
-
2.28.0
2020-07-27
- 2.25.1 → 2.27.1 無變化
-
2.25.0
2020-01-13
- 2.24.1 → 2.24.4 無更改
-
2.24.0
2019-11-04
- 2.23.1 → 2.23.4 無更改
-
2.23.0
2019-08-16
- 2.22.1 → 2.22.5 無更改
-
2.22.0
2019-06-07
- 2.21.1 → 2.21.4 無更改
-
2.21.0
2019-02-24
- 2.19.1 → 2.20.5 無更改
-
2.19.0
2018-09-10
- 2.18.1 → 2.18.5 無更改
-
2.18.0
2018-06-21
- 2.17.0 → 2.17.6 無更改
-
2.16.6
2019-12-06
-
2.15.4
2019-12-06
-
2.14.6
2019-12-06
-
2.13.7
2018-05-22
-
2.12.5
2017-09-22
- 2.9.5 → 2.11.4 無更改
-
2.8.6
2017-07-30
-
2.7.6
2017-07-30
-
2.6.7
2017-05-05
- 2.5.6 無更改
-
2.4.12
2017-05-05
- 2.3.10 無更改
-
2.2.3
2015-09-04
-
2.1.4
2014-12-17
-
2.0.5
2014-12-17
簡介
Git 是一個快速的分散式版本控制系統。
本手冊旨在讓具備基本 UNIX 命令列技能但對 Git 沒有先驗知識的人能夠閱讀。
需要進行實際開發的人也希望閱讀 使用 Git 進行開發 和 與他人共享開發成果。
後續章節涵蓋了更專業的主題。
可以透過 man pages 或 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 的父提交鏈。
操作分支
建立、刪除和修改分支既快速又簡單;以下是命令的摘要。
gitbranch-
列出所有分支。
gitbranch<branch>-
建立一個名為 <branch> 的新分支,指向與當前分支相同的歷史點。
gitbranch<branch> <start-point>-
建立一個名為 <branch> 的新分支,指向 <start-point>,它可以以任何您喜歡的方式指定,包括使用分支名或標籤名。
gitbranch-d<branch>-
刪除分支 <branch>;如果該分支未完全合併到其上游分支或未包含在當前分支中,此命令將以警告失敗。
gitbranch-D<branch>-
刪除分支 <branch>,無論其合併狀態如何。
gitswitch<branch>-
將當前分支設定為 <branch>,並更新工作目錄以反映 <branch> 所引用的版本。
gitswitch-c<new> <start-point>-
建立一個名為 <new> 的新分支,指向 <start-point>,並檢出它。
特殊符號 "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 是“分離的”。
這是檢出特定版本而無需為其建立新分支名稱的簡便方法。如果您決定要,以後仍然可以為該版本建立新分支(或標籤)。
檢視遠端倉庫的分支
克隆時建立的 "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”稱為遠端倉庫,簡稱“remote”。從我們的角度來看,該倉庫的分支稱為“遠端分支”。上面的遠端跟蹤分支是在克隆時基於遠端分支建立的,並且會被 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])。
作為另一個有用的快捷方式,“origin”通常是“origin”倉庫中 HEAD 分支的快捷方式。
有關 Git 檢查引用的路徑的完整列表,以及當存在多個具有相同簡寫名稱的引用時,它決定選擇哪個的順序,請參閱 gitrevisions[7] 的“指定修訂”部分。
使用 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] 的“配置 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 現在已脫離任何分支,並直接指向一個提交(提交 ID 為 65934),該提交可從“master”到達但不能從 v2.6.18 到達。編譯並測試它,看看它是否崩潰。假設它確實崩潰了。然後。
$ 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 bisect visualize 然後 git reset --hard fb47ddb2db,您可能只想告訴 Git 您要跳過當前提交。
$ git bisect skip
然而,在這種情況下,Git 可能最終無法區分某些第一個跳過的提交和一個稍後的壞提交之間的第一個壞提交。
如果有一個測試指令碼可以區分好提交和壞提交,也有自動執行二分法的方法。有關此及其他 git bisect 功能的更多資訊,請參閱 git-bisect[1]。
命名提交
我們已經看到了幾種命名提交的方法。
-
40 位十六進位制物件名稱
-
分支名:引用給定分支頭指標的提交。
-
標籤名:引用由給定標籤指向的提交(我們已經看到分支和標籤是引用的特例)。
-
HEAD:引用當前分支的頭指標。
還有更多;請參閱 gitrevisions[7] man 頁面的“指定修訂”部分,以獲取命名修訂方法 的完整列表。一些示例:
$ 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] man 頁面。
瀏覽修訂
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] man 頁面的 --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 分叉以來,您在該分支上進行了多少次提交。
$ 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。)
建立軟體釋出版的變更日誌和 tar 包
git-archive 命令可以從專案的任何版本建立 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] 的 man 頁可能會有所幫助。
使用 Git 進行開發
告訴 Git 您的名字
在建立任何提交之前,您應該向 Git 介紹自己。最簡單的方法是使用 git-config
$ 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] 的“CONFIGURATION FILE”部分。該檔案是純文字,因此您也可以使用您喜歡的編輯器進行編輯。
建立新的倉庫
從頭開始建立一個新的倉庫非常簡單
$ mkdir project $ cd project $ git init
如果您有一些初始內容(例如,一個 tar 包)
$ 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 的內容相同。因此,此時命令 git diff --cached,它顯示 HEAD 和索引之間的差異,應該不產生任何輸出。
修改索引很簡單
要使用新檔案或修改檔案的內容更新索引,請使用
$ 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] 來建立提交,檢視索引和工作樹檔案中的更改,以及單獨選擇要包含在索引中的 diff 塊(透過右鍵單擊 diff 塊並選擇“Stage Hunk For Commit”)。
建立良好的提交訊息
雖然不是必需的,但最好在提交訊息的開頭使用一個簡短的(最多 50 個字元)行來總結更改,後面跟著一個空行,然後是一個更詳細的描述。提交訊息中第一個空行之前的文字被視為提交標題,並且該標題在 Git 中被廣泛使用。例如,git-format-patch[1] 將提交轉換為電子郵件,它在主題行中使用標題,並在正文中包含提交的其餘部分。
忽略檔案
專案通常會生成一些您不希望 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 分支的開發合併到當前分支。)
合併是透過合併 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 commit。
如果您使用 gitk 檢查結果提交,您會看到它有兩個父提交,一個指向當前分支的頂部,另一個指向另一個分支的頂部。
解決合併衝突
當合並沒有自動解決時,Git 會將索引和工作樹保留在一種特殊狀態,為您提供解決合併所需的所有資訊。
帶有衝突的檔案在索引中被特殊標記,因此在您解決問題並更新索引之前,git-commit 將會失敗
$ 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 之間進行三方 diff,以僅顯示來自雙方的內容塊,混合在一起(換句話說,當一個塊的合併結果僅來自階段 2 時,該部分不衝突而不顯示。同一情況適用於階段 3)。
上面的 diff 顯示了 file.txt 的工作樹版本與階段 2 和階段 3 版本之間的差異。因此,而不是在每行前面加上一個單一的 + 或 -,它現在使用兩列:第一列用於第一個父提交和工作目錄副本之間的差異,第二列用於第二個父提交和工作目錄副本之間的差異。(有關格式的詳細資訊,請參閱 git-diff-files[1] 的“COMBINED DIFF FORMAT”部分。)
在以明顯的方式解決衝突後(但在更新索引之前),diff 將如下所示
$ 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”,而後者以前在兩者中都不存在。
一些特殊的 diff 選項允許 diff 工作目錄與這些階段中的任何一個進行比較
$ 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 的引用,該引用反映了它將要寫入的樹的狀態。文字衝突無法自動合併的路徑會像在工作樹中一樣,以衝突標記的形式寫入此樹。因此,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
請注意,diff 顯示我們刪除了衝突標記和內容行的兩個版本,而是寫入了“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(預設情況下)將不再顯示該檔案的 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 --hard 來銷燬它。
或者,您可以像要 建立新提交 一樣,編輯工作目錄並更新索引來修復您的錯誤,然後執行
$ git commit --amend
這將用包含您更改的新提交替換舊提交,讓您有機會先編輯舊的提交訊息。
同樣,您永遠不應該對可能已經合併到另一個分支的提交執行此操作;在這種情況下,請使用 git-revert[1]。
也可以替換歷史中更遠的提交,但這屬於高階主題,應留待 另一章。
檢出檔案的舊版本
在撤銷先前錯誤的更改時,您可能會發現使用 git-restore[1] 檢出檔案的舊版本很有用。命令
$ git restore --source=HEAD^ path/to/file
將 path/to/file 替換為它在 HEAD^ 提交中的內容,並更新索引以匹配。它不會更改分支。
如果您只想檢視檔案的舊版本,而不想修改工作目錄,您可以使用 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 來抑制這些訊息,並仍然檢視真正的錯誤。
恢復丟失的更改
引用日誌
假設您使用 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 指向一星期前的位置,而不是當前分支一星期前指向的位置。這允許您檢視您簽出的歷史記錄。
reflogs 預設保留 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 和 tree)也是可能的,並且懸空物件可能在其他情況下出現。
與他人共享開發
使用 git pull 獲取更新
在克隆了倉庫並添加了自己的幾個提交之後,您可能希望檢查原始倉庫是否有更新並將其合併到您自己的工作中。
我們已經看到了 如何使遠端跟蹤分支保持最新 與 git-fetch[1],以及如何合併兩個分支。所以您可以透過以下方式合併來自原始倉庫的 master 分支的更改:
$ git fetch $ git merge origin/master
但是,git-pull[1] 命令提供了一種一步完成此操作的方法
$ git pull origin master
事實上,如果您簽出了 master,那麼 git clone 已配置此分支以從原始倉庫的 HEAD 分支獲取更改。所以通常您只需一個簡單的命令就可以完成上述操作:
$ git pull
此命令將從遠端分支獲取更改到您的遠端跟蹤分支 origin/*,並將預設分支合併到當前分支。
更一般地說,從遠端跟蹤分支建立的分支將預設從該分支拉取。請參閱 branch. 和 branch. 選項在 git-config[1] 中的描述,以及 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;它將監聽埠 9418。預設情況下,它允許訪問任何看起來像 Git 目錄且包含特殊檔案 git-daemon-export-ok 的目錄。將一些目錄路徑作為 git daemon 引數傳遞將進一步限制匯出的目錄到那些路徑。
您也可以將 git daemon 作為 inetd 服務執行;有關詳細資訊,請參閱 git-daemon[1] man 頁。(尤其請參閱示例部分。)
透過 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
有關詳細資訊,請參閱 remote.、branch. 和 remote. 選項在 git-config[1] 中的說明。
推送失敗時該怎麼辦
如果推送不會導致遠端分支 快進,那麼它將以類似以下的錯誤失敗
! [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
通常,當公共倉庫中的分支頭髮生修改時,它會修改為指向之前指向的提交的後繼者。透過強制推送在這種情況下,您打破了這一慣例。(參見 重寫歷史的問題。)
儘管如此,這對於那些需要一種簡單方式來發布正在進行中的補丁系列的人來說是一種常見的做法,只要您警告其他開發人員您打算如何管理該分支,它就是一種可接受的折衷方案。
當其他人有權推送到同一個倉庫時,推送也可能以這種方式失敗。在這種情況下,正確的解決方案是在先更新工作後重試推送:可以透過 pull,或透過 fetch 後跟 rebase 來完成;有關更多資訊,請參見 下一節 和 gitcvs-migration[7]。
設定共享倉庫
另一種協作方式是使用與 CVS 中常用的模型相似的模型,其中具有特殊許可權的幾位開發人員都推送到並從單個共享倉庫中拉取。有關如何設定此項的說明,請參見 gitcvs-migration[7]。
但是,雖然 Git 對共享倉庫的支援沒有任何問題,但這種操作模式通常不推薦,僅僅因為 Git 支援的協作模式——透過交換補丁和從公共倉庫拉取——與中央共享倉庫相比有許多優勢。
-
Git 快速匯入和合並補丁的能力允許單個維護者即使在非常高的速率下也能處理傳入的更改。當這變得太多時,
git pull為該維護者提供了一種簡單的方法,可以將此任務委派給其他維護者,同時仍然允許可選地審查傳入的更改。 -
由於每個開發者的倉庫都擁有專案的完整歷史記錄的相同副本,因此沒有特殊的倉庫,並且另一位開發人員可以輕鬆地接管專案的維護,無論是透過雙方同意,還是因為維護者變得不響應或難以合作。
-
缺乏一箇中央“提交者”小組意味著減少了對誰“在”和誰“不在”的正式決策的需求。
允許透過 Web 瀏覽倉庫
gitweb cgi 指令碼為使用者提供了一種簡單的方式來瀏覽專案的修訂、檔案內容和日誌,而無需安裝 Git。像 RSS/Atom feed 和 blame/annotation 詳細資訊這樣的功能可以被選擇性地啟用。
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。
他使用兩個公共分支
-
一個“test”樹,補丁最初放置在此處,以便它們在與其他正在進行的發展整合時獲得一些曝光。當 Andrew 想要將其拉入 -mm 時,該樹是可用的。
-
一個“release”樹,測試過的補丁被移入此處進行最終的健全性檢查,並作為傳送給 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 從 release 分支拉取時,這些帶有噪音的提交將成為永久歷史的一部分。
一些配置變數(參見 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] 推送 test 和 release 樹。
$ git push mytree
或者使用以下命令僅推送 test 和 release 分支中的一個。
$ git push mytree test
或
$ git push mytree release
現在應用來自社群的一些補丁。考慮一個簡短的、有吸引力的名稱來儲存此補丁(或一組相關補丁)的分支,並從 Linus 分支的一個最近的穩定標籤建立新分支。為您的分支選擇一個穩定的基礎將:1)幫助您:透過避免包含不相關且可能未經充分測試的更改;2)幫助未來的 bug 獵人使用 git bisect 來查詢問題。
$ git switch -c speed-up-spinlocks v2.6.35
現在應用補丁,執行一些測試,並提交更改。如果補丁是多部分系列,則應將其作為單獨的提交應用於此分支。
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
當您對更改的狀態滿意時,您可以將其合併到“test”分支中,以準備公開。
$ git switch test && git merge speed-up-spinlocks
這裡不太可能發生衝突……但如果您在此步驟上花費了大量時間並且還從上游拉取了新版本,則可能會發生衝突。
過一段時間後,當有足夠的時間並且測試完成時,您可以將同一分支拉到 release 樹中,準備好向上遊提交。這就是您看到將每個補丁(或補丁系列)保留在其自己的分支中的價值的地方。這意味著補丁可以按任何順序移動到 release 樹中。
$ git switch release && git merge speed-up-spinlocks
一段時間後,您將擁有許多分支,並且儘管您為每個分支選擇了精心設計的名稱,您可能會忘記它們的作用或它們的狀態。要獲得關於特定分支中包含哪些更改的提醒,請使用
$ git log linux..branchname | git shortlog
要檢視它是否已合併到 test 或 release 分支中,請使用
$ git log test..branchname
或
$ git log release..branchname
(如果此分支尚未合併,您將看到一些日誌條目。如果已合併,則不會有輸出。)
一旦補丁完成了整個週期(從 test 移動到 release,然後被 Linus 拉取,最後回到您的本地 origin/master 分支),此更改的分支就不再需要了。當以下命令的輸出為空時,您會檢測到這一點。
$ git log origin..branchname
此時可以刪除該分支。
$ git branch -d branchname
有些更改非常微小,以至於不需要建立單獨的分支然後合併到 test 和 release 分支中。對於這些更改,只需直接應用於 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 的合併機制(例如)做錯事。
然而,在某些情況下,違反此假設可能很有用。
建立完美的補丁系列
假設您是一個大型專案的貢獻者,並且您想新增一個複雜的功能,並以一種易於其他開發人員閱讀、驗證其正確性並理解您為何進行每項更改的方式呈現給他們。
如果您將所有更改都作為單個補丁(或提交)呈現,他們可能會發現一次消化太多。
如果您向他們展示您工作的全部歷史記錄,包括錯誤、更正和死衚衕,他們可能會不知所措。
因此,理想情況通常是生成一系列補丁,使得
-
每個補丁都可以按順序應用。
-
每個補丁包含一個邏輯更改,以及解釋該更改的訊息。
-
沒有補丁會引入迴歸:在應用了系列的任何初始部分之後,結果專案仍然可以編譯和執行,並且沒有以前不存在的 bug。
-
完整的系列產生了與您自己(可能更混亂!)的開發過程相同的最終結果。
我們將介紹一些可以幫助您做到這一點的工具,解釋如何使用它們,然後解釋一些可能因您重寫歷史而出現的問題。
使用 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 重新排序補丁系列相同,因此請選擇您喜歡的介面。
在您想保留為不變的最後一個提交上 rebase 您當前的 HEAD。例如,如果您想重新排序最後 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] 的“互動模式”部分。
重寫歷史的問題
重寫分支歷史的主要問題與合併有關。假設某人 fetch 了您的分支並將其合併到他們的分支中,結果如下:
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 無法知道新的頭是舊頭的更新版本;它將這種情況視為兩個開發人員獨立地並行完成了舊頭和新頭上的工作。此時,如果有人嘗試將新的頭合併到他們的分支中,Git 將嘗試合併兩個(舊的和新的)開發線,而不是嘗試用新的替換舊的。結果可能會出乎意料。
您仍然可以選擇釋出歷史被重寫的分支,並且其他能夠 fetch 這些分支以檢查或測試它們可能有用,但他們不應嘗試將這些分支 pull 到他們自己的工作中。
為了支援正確合併的真正分散式開發,釋出的分支永遠不應被重寫。
為什麼 bisect 合併提交可能比 bisect 線性歷史更難
git-bisect[1] 命令正確地處理包含合併提交的歷史記錄。但是,當找到的提交是合併提交時,使用者可能需要比平時付出更多的努力來弄清楚為什麼該提交會引入問題。
想象一下這個歷史。
---Z---o---X---...---o---A---C---D
\ /
o---o---Y---...---o---B
假設在上層開發線上,Z 處的一個函式含義在提交 X 處發生了變化。從 Z 到 A 的提交同時改變了函式的實現以及 Z 處存在的所有呼叫點,以及它們新增的新呼叫點,以保持一致。A 處沒有 bug。
假設在此期間,在下層開發線上,有人在提交 Y 處添加了該函式的新呼叫點。從 Z 到 B 的提交都假定該函式的老語義,呼叫者和被呼叫者之間是保持一致的。B 處也沒有 bug。
假設進一步,兩條開發線在 C 處乾淨地合併,因此不需要衝突解決。
儘管如此,C 處的程式碼是損壞的,因為下層開發線上新增的呼叫者尚未轉換為上層開發線上引入的新語義。所以,如果您所知道的只是 D 是壞的,Z 是好的,並且 git-bisect[1] 將 C 識別為罪魁禍首,您將如何弄清楚問題是由於這種語義的變化?
當 git bisect 的結果是非合併提交時,您通常應該能夠透過檢查該提交來發現問題。開發人員可以透過將他們的更改分解為小的、自包含的提交來使其更容易。然而,在上述情況下,這無濟於事,因為問題並非從任何單個提交的檢查中就能明顯看出;相反,需要對開發進行全域性檢視。更糟糕的是,有問題的函式中的語義變化可能只是上層開發線上更改的一小部分。
另一方面,如果不是在 C 處合併,而是將 Z 到 B 之間的歷史基於 A 進行 rebase,您將得到這個線性歷史。
---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*
在 Z 和 D* 之間進行 bisect 將會命中一個單獨的罪魁禍首提交 Y*,並且理解 Y* 為什麼是錯誤的可能更容易。
部分由於這個原因,許多有經驗的 Git 使用者,即使在處理一個本身就是 merge-heavy 的專案時,也會在釋出前透過基於最新的上游版本進行 rebase 來保持歷史線性。
高階分支管理
fetch 單個分支
除了使用 git-remote[1] 之外,您還可以選擇一次只更新一個分支,並將其本地儲存在任意名稱下。
$ git fetch origin todo:my-todo-work
第一個引數 origin 只是告訴 Git 從您最初克隆的倉庫中 fetch。第二個引數告訴 Git 從遠端倉庫中 fetch 名為 todo 的分支,並將其本地儲存在名為 refs/heads/my-todo-work 的名稱下。
您也可以從其他倉庫 fetch 分支;所以
$ 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
在某些情況下,新的頭**不**會是舊頭的後繼者。例如,開發人員可能意識到犯了一個嚴重的錯誤並決定回溯,導致瞭如下情況:
o--o--o--o--a--b <-- old head of the branch
\
o--o--o <-- new head of the branch
在這種情況下,git fetch 將會失敗,並列印警告。
在這種情況下,您仍然可以強制 Git 更新到新的頭,如下一節所述。但是,請注意,在上述情況下,這可能意味著丟失標記為 a 和 b 的提交,除非您自己已經建立了指向它們的引用。
強制 git fetch 進行非快進更新
如果 git fetch 因分支的新頭不是舊頭的後繼者而失敗,您可以使用以下命令強制更新:
$ git fetch git://example.com/proj.git +master:refs/remotes/example/master
注意加號 + 的新增。或者,您可以使用 -f 標誌來強制更新所有 fetched 的分支,如
$ 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];有關 refspec 語法的更多詳細資訊,請參見 git-fetch[1]。
Git 概念
Git 基於一些簡單而強大的想法構建。雖然在不理解它們的情況下也可以完成工作,但如果您理解了它們,Git 會更容易理解。
物件資料庫
我們在 理解歷史:提交 中已經看到,所有提交都儲存在一個 40 位“物件名稱”下。事實上,表示專案歷史所需的所有資訊都儲存在具有此類名稱的物件中。在每種情況下,名稱都是透過取物件的 SHA-1 hash 來計算的。SHA-1 hash 是一種加密雜湊函式。這對我們來說意味著不可能找到兩個具有相同名稱的不同的物件。這有許多優點;其中:
-
Git 可以透過比較名稱快速確定兩個物件是否相同。
-
由於物件名稱在每個倉庫中的計算方式相同,因此儲存在兩個倉庫中的相同內容將始終儲存在相同的名稱下。
-
Git 可以在讀取物件時檢測錯誤,透過檢查物件的名稱是否仍然是其內容的 SHA-1 hash。
(有關物件格式和 SHA-1 計算的詳細資訊,請參見 物件儲存格式。)
有四種不同型別的物件:“blob”、“tree”、“commit”和“tag”。
-
一個 “blob”物件 用於儲存檔案資料。
-
一個 “tree”物件 將一個或多個“blob”物件繫結到目錄結構中。此外,一個 tree 物件可以引用其他 tree 物件,從而建立目錄層次結構。
-
一個 “commit”物件 將這些目錄層次結構繫結到一個修訂版的 有向無環圖 中——每個提交包含一個 tree 的物件名稱,該 tree 指定了提交時的目錄層次結構。此外,提交引用“父”提交物件,這些物件描述了到達該目錄層次結構的歷史。
-
一個 “tag”物件 符號地標識其他物件,並可用於對其進行簽名。它包含另一個物件的物件名稱和型別、一個符號名稱(當然!)以及可選的簽名。
物件型別更詳細一些:
提交物件
“commit”物件將 tree 的物理狀態與如何到達那裡以及為什麼的描述聯絡起來。使用 --pretty=raw 選項來 git-show[1] 或 git-log[1] 來檢查您最喜歡的提交。
$ 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>
如您所見,提交由以下內容定義:
-
tree:一個 tree 物件(如下定義)的 SHA-1 名稱,代表某個時間點的目錄內容。
-
parent(s): 專案歷史中代表緊隨其後的一個或多個提交的 SHA-1 名稱。上面的示例有一個 parent;合併提交可能有一個以上。沒有 parent 的提交稱為“根”提交,代表專案的初始版本。每個專案至少需要有一個根。一個專案也可以有多個根,儘管這並不常見(或者不一定是好事)。
-
an author: 負責此更改的人的姓名,以及更改日期。
-
a committer: 實際建立此提交的人的姓名,以及完成日期。這可能與 author 不同,例如,如果 author 是編寫了一個補丁並將其傳送給使用該補丁建立提交的人。
-
a comment describing this commit. 描述此提交的註釋。
Note that a commit does not itself contain any information about what actually changed; all changes are calculated by comparing the contents of the tree referred to by this commit with the trees associated with its parents. In particular, Git does not attempt to record file renames explicitly, though it can identify cases where the existence of the same file data at changing paths suggests a rename. (See, for example, the -M option to git-diff[1]). 注意,提交本身不包含任何關於實際更改的資訊;所有更改都是透過比較此提交引用的樹的內容與其 parent 相關的樹的內容來計算的。特別是,Git 不會顯式記錄檔案重新命名,儘管它可以識別出在不同路徑下存在相同檔案資料的情況,這表明存在重新命名。(例如,請參閱 git-diff[1] 的 -M 選項)。
A commit is usually created by git-commit[1], which creates a commit whose parent is normally the current HEAD, and whose tree is taken from the content currently stored in the index. 提交通常由 git-commit[1] 建立,它建立一個 parent 通常是當前 HEAD 的提交,其 tree 取自當前儲存在索引中的內容。
Tree Object
The ever-versatile git-show[1] command can also be used to examine tree objects, but git-ls-tree[1] will give you more details. 萬能的 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 ...
As you can see, a tree object contains a list of entries, each with a mode, object type, SHA-1 name, and name, sorted by name. It represents the contents of a single directory tree. 從圖中可以看到,一個 tree 物件包含一個條目列表,每個條目都具有模式、物件型別、SHA-1 名稱和名稱,並按名稱排序。它代表單個目錄樹的內容。
The object type may be a blob, representing the contents of a file, or another tree, representing the contents of a subdirectory. Since trees and blobs, like all other objects, are named by the SHA-1 hash of their contents, two trees have the same SHA-1 name if and only if their contents (including, recursively, the contents of all subdirectories) are identical. This allows Git to quickly determine the differences between two related tree objects, since it can ignore any entries with identical object names. 物件型別可以是 blob,代表檔案的內容,也可以是另一個 tree,代表子目錄的內容。由於 tree 和 blob,與其他所有物件一樣,都以其內容的 SHA-1 雜湊命名,因此兩個 tree 擁有相同的 SHA-1 名稱當且僅當它們的內容(包括遞迴地,所有子目錄的內容)相同。這使得 Git 能夠快速確定兩個相關 tree 物件之間的差異,因為它會忽略任何具有相同物件名稱的條目。
(Note: in the presence of submodules, trees may also have commits as entries. See Submodules for documentation.) (注意:在存在子模組的情況下,tree 也可能包含 commit 作為條目。有關文件,請參閱 Submodules。)
Note that the files all have mode 644 or 755: Git actually only pays attention to the executable bit. 請注意,檔案都具有 644 或 755 的許可權:Git 實際上只關注可執行位。
Blob Object
You can use git-show[1] to examine the contents of a blob; take, for example, the blob in the entry for COPYING from the tree above. 您可以使用 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. ...
A "blob" object is nothing but a binary blob of data. It doesn’t refer to anything else or have attributes of any kind. 一個“blob”物件只不過是一塊二進位制資料。它不指向任何其他東西,也沒有任何型別的屬性。
Since the blob is entirely defined by its data, if two files in a directory tree (or in multiple different versions of the repository) have the same contents, they will share the same blob object. The object is totally independent of its location in the directory tree, and renaming a file does not change the object that file is associated with. 由於 blob 完全由其資料定義,因此如果目錄樹中的兩個檔案(或儲存庫的多個不同版本)具有相同的內容,它們將共享相同的 blob 物件。該物件完全獨立於其在目錄樹中的位置,重新命名檔案不會改變該檔案關聯的物件。
Note that any tree or blob object can be examined using git-show[1] with the <revision>:<path> syntax. This can sometimes be useful for browsing the contents of a tree that is not currently checked out. 請注意,任何 tree 或 blob 物件都可以使用 git-show[1] 和 <revision>:<path> 語法進行檢查。這有時對於瀏覽當前未簽出的 tree 的內容很有用。
Trust
If you receive the SHA-1 name of a blob from one source, and its contents from another (possibly untrusted) source, you can still trust that those contents are correct as long as the SHA-1 name agrees. This is because the SHA-1 is designed so that it is infeasible to find different contents that produce the same hash. 如果您從一個來源接收到 blob 的 SHA-1 名稱,並從另一個(可能不可信)來源接收到其內容,只要 SHA-1 名稱匹配,您仍然可以信任這些內容是正確的。這是因為 SHA-1 的設計使得找到生成相同雜湊的不同內容是不可能的。
Similarly, you need only trust the SHA-1 name of a top-level tree object to trust the contents of the entire directory that it refers to, and if you receive the SHA-1 name of a commit from a trusted source, then you can easily verify the entire history of commits reachable through parents of that commit, and all of those contents of the trees referred to by those commits. 同樣,您只需要信任頂級 tree 物件的檔名即可信任它引用的整個目錄的內容,如果您從受信任的來源接收到 commit 的 SHA-1 名稱,那麼您就可以輕鬆地驗證透過該 commit 的 parent 可達的所有 commit 歷史,以及那些 commit 引用的 tree 的所有內容。
So to introduce some real trust in the system, the only thing you need to do is to digitally sign just one special note, which includes the name of a top-level commit. Your digital signature shows others that you trust that commit, and the immutability of the history of commits tells others that they can trust the whole history. 因此,要在系統中引入真正的信任,您只需要做的是對一個特殊的說明進行數字簽名,該說明包含頂級 commit 的名稱。您的數字簽名向他人表明您信任該 commit,而 commit 歷史的不可變性則告訴他人他們可以信任整個歷史。
In other words, you can easily validate a whole archive by just sending out a single email that tells the people the name (SHA-1 hash) of the top commit, and digitally sign that email using something like GPG/PGP. 換句話說,您可以透過傳送一封電子郵件來輕鬆驗證整個存檔,該電子郵件告訴人們頂級 commit 的名稱(SHA-1 雜湊),並使用 GPG/PGP 等工具對該電子郵件進行數字簽名。
To assist in this, Git also provides the tag object… 為了協助此目的,Git 還提供了 tag 物件…
Tag Object
A tag object contains an object, object type, tag name, the name of the person ("tagger") who created the tag, and a message, which may contain a signature, as can be seen using git-cat-file[1]. 一個 tag 物件包含一個物件、物件型別、tag 名稱、建立 tag 的人(“tagger”)的姓名以及一條訊息,該訊息可能包含簽名,如使用 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-----
See the git-tag[1] command to learn how to create and verify tag objects. (Note that git-tag[1] can also be used to create "lightweight tags", which are not tag objects at all, but just simple references whose names begin with refs/tags/). 請參閱 git-tag[1] 命令瞭解如何建立和驗證 tag 物件。(注意:git-tag[1] 也可用於建立“輕量級 tag”,它們根本不是 tag 物件,而只是名稱以 refs/tags/ 開頭的簡單引用)。
How Git stores objects efficiently: pack files. Git 如何高效地儲存物件:pack 檔案
Newly created objects are initially created in a file named after the object’s SHA-1 hash (stored in .git/objects). 新建立的物件最初會以物件的 SHA-1 雜湊命名(儲存在 .git/objects 中)的檔案形式建立。
Unfortunately this system becomes inefficient once a project has a lot of objects. Try this on an old project. 不幸的是,一旦專案包含大量物件,這種系統就會變得效率低下。在舊專案上嘗試此操作。
$ git count-objects 6930 objects, 47620 kilobytes
The first number is the number of objects which are kept in individual files. The second is the amount of space taken up by those "loose" objects. 第一個數字是保留在單個檔案中的物件數量。第二個數字是這些“鬆散”物件佔用的空間量。
You can save space and make Git faster by moving these loose objects in to a "pack file", which stores a group of objects in an efficient compressed format; the details of how pack files are formatted can be found in gitformat-pack[5]. 您可以透過將這些鬆散物件移至“pack 檔案”來節省空間並提高 Git 的速度,pack 檔案以高效的壓縮格式儲存一組物件;pack 檔案的格式詳情可以在 gitformat-pack[5] 中找到。
To put the loose objects into a pack, just run git repack. 要將鬆散物件放入 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)
This creates a single "pack file" in .git/objects/pack/ containing all currently unpacked objects. You can then run. 這會在 .git/objects/pack/ 中建立一個名為“pack 檔案”的檔案,其中包含所有當前未打包的物件。然後您可以執行。
$ git prune
to remove any of the "loose" objects that are now contained in the pack. This will also remove any unreferenced objects (which may be created when, for example, you use git reset to remove a commit). You can verify that the loose objects are gone by looking at the .git/objects directory or by running. 以刪除現在包含在 pack 中的任何“鬆散”物件。這還將刪除任何未引用的物件(例如,當您使用 git reset 刪除提交時可能會建立這些物件)。您可以透過檢視 .git/objects 目錄或執行來驗證鬆散物件是否已消失。
$ git count-objects 0 objects, 0 kilobytes
Although the object files are gone, any commands that refer to those objects will work exactly as they did before. 儘管物件檔案已消失,但任何引用這些物件的命令都會像以前一樣工作。
Dangling objects. 懸空物件
The git-fsck[1] command will sometimes complain about dangling objects. They are not a problem. git-fsck[1] 命令有時會抱怨懸空物件。它們不是問題。
The most common cause of dangling objects is that you’ve rebased a branch, or you have pulled from somebody else who rebased a branch—see Rewriting history and maintaining patch series. In that case, the old head of the original branch still exists, as does everything it pointed to. The branch pointer itself just doesn’t, since you replaced it with another one. 懸空物件最常見的原因是您 rebase 了某個分支,或者您從 rebase 了某個分支的其他人那裡 pull 了——請參閱 Rewriting history and maintaining patch series。在這種情況下,原始分支的舊 head 仍然存在,它指向的所有內容也一樣。分支指標本身不存在了,因為您用另一個替換了它。
There are also other situations that cause dangling objects. For example, a "dangling blob" may arise because you did a git add of a file, but then, before you actually committed it and made it part of the bigger picture, you changed something else in that file and committed that updated thing—the old state that you added originally ends up not being pointed to by any commit or tree, so it’s now a dangling blob object. 還有其他導致懸空物件的情況。例如,一個“懸空 blob”可能由於您添加了一個檔案,然後在您實際提交它並將其納入整體之前,您更改了該檔案中的其他內容並提交了更新的內容——您最初新增的舊狀態最終沒有被任何提交或 tree 指向,因此現在它是一個懸空 blob 物件。
Similarly, when the "ort" merge strategy runs, and finds that there are criss-cross merges and thus more than one merge base (which is fairly unusual, but it does happen), it will generate one temporary midway tree (or possibly even more, if you had lots of criss-crossing merges and more than two merge bases) as a temporary internal merge base, and again, those are real objects, but the end result will not end up pointing to them, so they end up "dangling" in your repository. 同樣,當“ort”合併策略執行時,發現存在交叉合併,從而有多個合併基(這相當罕見,但確實會發生),它將生成一個臨時中間 tree(如果有很多交叉合併和兩個以上的合併基,甚至可能更多)作為臨時的內部合併基,同樣,這些都是真實的物件,但最終結果不會指向它們,因此它們最終會“懸空”在您的儲存庫中。
Generally, dangling objects aren’t anything to worry about. They can even be very useful: if you screw something up, the dangling objects can be how you recover your old tree (say, you did a rebase, and realized that you really didn’t want to—you can look at what dangling objects you have, and decide to reset your head to some old dangling state). 通常,懸空物件沒什麼可擔心的。它們甚至可能非常有用:如果您搞砸了什麼,懸空物件可以幫助您恢復舊的 tree(例如,您 rebase 了,並意識到您真的不想這樣做——您可以檢視您擁有的懸空物件,並決定將您的 HEAD 重置到某個舊的懸空狀態)。
For commits, you can just use. 對於 commit,您只需使用。
$ gitk <dangling-commit-sha-goes-here> --not --all
This asks for all the history reachable from the given commit but not from any branch, tag, or other reference. If you decide it’s something you want, you can always create a new reference to it, e.g. 這會查詢從給定 commit 可達但不可從任何分支、tag 或其他引用可達的所有歷史。如果您決定想要它,您可以隨時建立一個新的引用指向它,例如。
$ git branch recovered-branch <dangling-commit-sha-goes-here>
For blobs and trees, you can’t do the same, but you can still examine them. You can just do. 對於 blob 和 tree,您不能做同樣的事情,但仍然可以檢查它們。您可以直接執行。
$ git show <dangling-blob/tree-sha-goes-here>
to show what the contents of the blob were (or, for a tree, basically what the ls for that directory was), and that may give you some idea of what the operation was that left that dangling object. 以顯示 blob 的內容(或者,對於 tree,基本上是該目錄的 ls 輸出),這可能會讓您對導致該懸空物件的那個操作有所瞭解。
Usually, dangling blobs and trees aren’t very interesting. They’re almost always the result of either being a half-way mergebase (the blob will often even have the conflict markers from a merge in it, if you have had conflicting merges that you fixed up by hand), or simply because you interrupted a git fetch with ^C or something like that, leaving some of the new objects in the object database, but just dangling and useless. 通常,懸空 blob 和 tree 並不太有趣。它們幾乎總是以下情況的結果:要麼是半途的合併基(如果發生過沖突合併且您手動修復過,blob 甚至可能包含合併的衝突標記),要麼僅僅是因為您用 ^C 或類似的方式中斷了 git fetch,導致一些新物件留在物件資料庫中,但它們是懸空的且無用的。
Anyway, once you are sure that you’re not interested in any dangling state, you can just prune all unreachable objects. 無論如何,一旦您確定您對任何懸空狀態不感興趣,您就可以修剪所有不可達的物件。
$ git prune
and they’ll be gone. (You should only run git prune on a quiescent repository—it’s kind of like doing a filesystem fsck recovery: you don’t want to do that while the filesystem is mounted. git prune is designed not to cause any harm in such cases of concurrent accesses to a repository but you might receive confusing or scary messages.) 它們就會消失。(您應該只在安靜的儲存庫上執行 git prune ——這有點像進行檔案系統 fsck 恢復:您不想在檔案系統掛載時進行此操作。git prune 的設計目的是在這種併發訪問儲存庫的情況下不會造成任何損害,但您可能會收到令人困惑或可怕的訊息。)
Recovering from repository corruption. 從儲存庫損壞中恢復
By design, Git treats data trusted to it with caution. However, even in the absence of bugs in Git itself, it is still possible that hardware or operating system errors could corrupt data. Git 的設計宗旨是謹慎對待其信任的資料。然而,即使 Git 本身沒有 bug,硬體或作業系統錯誤仍然可能損壞資料。
The first defense against such problems is backups. You can back up a Git directory using clone, or just using cp, tar, or any other backup mechanism. 防範此類問題的第一道防線是備份。您可以使用 clone 來備份 Git 目錄,或者直接使用 cp、tar 或任何其他備份機制。
As a last resort, you can search for the corrupted objects and attempt to replace them by hand. Back up your repository before attempting this in case you corrupt things even more in the process. 作為最後的手段,您可以搜尋損壞的物件並嘗試手動替換它們。在嘗試此操作之前,請備份您的儲存庫,以防您在此過程中使情況更加惡化。
We’ll assume that the problem is a single missing or corrupted blob, which is sometimes a solvable problem. (Recovering missing trees and especially commits is much harder). 我們假設問題是單個丟失或損壞的 blob,有時這是一個可解決的問題。(恢復丟失的 tree,尤其是 commit,會困難得多)。
Before starting, verify that there is corruption, and figure out where it is with git-fsck[1]; this may be time-consuming. 開始之前,請驗證是否存在損壞,並使用 git-fsck[1] 找出其位置;這可能很耗時。
Assume the output looks like this. 假設輸出如下所示。
$ git fsck --full --no-dangling
broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8
to blob 4b9458b3786228369c63936db65827de3cc06200
missing blob 4b9458b3786228369c63936db65827de3cc06200
Now you know that blob 4b9458b is missing, and that the tree 2d9263c6 points to it. If you could find just one copy of that missing blob object, possibly in some other repository, you could move it into .git/objects/4b/9458b3... and be done. Suppose you can’t. You can still examine the tree that pointed to it with git-ls-tree[1], which might output something like. 現在您知道 blob 4b9458b 丟失了,並且 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 ...
So now you know that the missing blob was the data for a file named myfile. And chances are you can also identify the directory—let’s say it’s in somedirectory. If you’re lucky the missing copy might be the same as the copy you have checked out in your working tree at somedirectory/myfile; you can test whether that’s right with git-hash-object[1]. 因此,現在您知道丟失的 blob 是名為 myfile 的檔案的內容。並且很有可能您也可以識別出目錄——假設它在 somedirectory 中。如果您幸運的話,丟失的副本可能與您在 somedirectory/myfile 的工作目錄中籤出的副本相同;您可以使用 git-hash-object[1] 來測試是否正確。
$ git hash-object -w somedirectory/myfile
which will create and store a blob object with the contents of somedirectory/myfile, and output the SHA-1 of that object. if you’re extremely lucky it might be 4b9458b3786228369c63936db65827de3cc06200, in which case you’ve guessed right, and the corruption is fixed! 這將建立並存儲一個具有 somedirectory/myfile 內容的 blob 物件,並輸出該物件的 SHA-1。如果您非常幸運,它可能是 4b9458b3786228369c63936db65827de3cc06200,在這種情況下,您就猜對了,損壞就修復了!
Otherwise, you need more information. How do you tell which version of the file has been lost? 否則,您需要更多資訊。您如何確定丟失了哪個檔案版本?
The easiest way to do this is with. 最簡單的方法是使用。
$ git log --raw --all --full-history -- somedirectory/myfile
Because you’re asking for raw output, you’ll now get something like. 因為您要求原始輸出,您現在會得到類似以下的內容。
commit abc Author: Date: ... :100644 100644 4b9458b newsha M somedirectory/myfile commit xyz Author: Date: ... :100644 100644 oldsha 4b9458b M somedirectory/myfile
This tells you that the immediately following version of the file was "newsha", and that the immediately preceding version was "oldsha". You also know the commit messages that went with the change from oldsha to 4b9458b and with the change from 4b9458b to newsha. 這告訴您,緊隨其後的檔案版本是“newsha”,而緊隨其前的版本是“oldsha”。您還知道與從 oldsha 到 4b9458b 的更改以及從 4b9458b 到 newsha 的更改相關的 commit 訊息。
If you’ve been committing small enough changes, you may now have a good shot at reconstructing the contents of the in-between state 4b9458b. 如果您提交的更改足夠小,您現在可能很有希望重建 4b9458b 之間的狀態內容。
If you can do that, you can now recreate the missing object with. 如果您能做到這一點,您現在就可以用…建立丟失的物件。
$ git hash-object -w <recreated-file>
and your repository is good again! 您的儲存庫又好了!
(Btw, you could have ignored the fsck, and started with doing a. (順便說一句,您可以忽略 fsck,然後開始執行)。
$ git log --raw --all
and just looked for the sha of the missing object (4b9458b) in that whole thing. It’s up to you—Git does have a lot of information, it is just missing one particular blob version. 並在其中查詢丟失物件的 sha(4b9458b)。這取決於您——Git確實有大量資訊,只是缺少一個特定的 blob 版本。
The index. 索引
The index is a binary file (generally kept in .git/index) containing a sorted list of path names, each with permissions and the SHA-1 of a blob object; git-ls-files[1] can show you the contents of the index. 索引是一個二進位制檔案(通常儲存在 .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
Note that in older documentation you may see the index called the "current directory cache" or just the "cache". It has three important properties. 請注意,在舊文件中,您可能會看到索引被稱為“當前目錄快取”或僅稱為“快取”。它有三個重要屬性。
-
The index contains all the information necessary to generate a single (uniquely determined) tree object. 索引包含生成單個(唯一確定的)tree 物件所需的所有資訊。
For example, running git-commit[1] generates this tree object from the index, stores it in the object database, and uses it as the tree object associated with the new commit. 例如,執行 git-commit[1] 會從索引生成此 tree 物件,將其儲存在物件資料庫中,並將其用作與新提交關聯的 tree 物件。
-
The index enables fast comparisons between the tree object it defines and the working tree. 索引能夠快速比較它定義的 tree 物件和工作目錄。
It does this by storing some additional data for each entry (such as the last modified time). This data is not displayed above, and is not stored in the created tree object, but it can be used to determine quickly which files in the working directory differ from what was stored in the index, and thus save Git from having to read all of the data from such files to look for changes. 它透過為每個條目儲存一些額外資料(例如最後修改時間)來實現這一點。此資料未在上面顯示,也不會儲存在建立的 tree 物件中,但它可以用來快速確定工作目錄中的哪些檔案與索引中儲存的內容不同,從而避免 Git 讀取所有這些檔案的資料來查詢更改。
-
It can efficiently represent information about merge conflicts between different tree objects, allowing each pathname to be associated with sufficient information about the trees involved that you can create a three-way merge between them. 它可以有效地表示不同 tree 物件之間的合併衝突資訊,允許將每個路徑名與涉及的 tree 的足夠資訊相關聯,以便您可以建立它們之間的三方合併。
We saw in Getting conflict-resolution help during a merge that during a merge the index can store multiple versions of a single file (called "stages"). The third column in the git-ls-files[1] output above is the stage number, and will take on values other than 0 for files with merge conflicts. 在 Getting conflict-resolution help during a merge 中我們看到,在合併期間,索引可以儲存單個檔案的多個版本(稱為“階段”)。上面 git-ls-files[1] 輸出中的第三列是階段號,對於有合併衝突的檔案,它將取非 0 的值。
The index is thus a sort of temporary staging area, which is filled with a tree which you are in the process of working on. 因此,索引是一種臨時暫存區,其中填充了您正在處理的 tree。
If you blow the index away entirely, you generally haven’t lost any information as long as you have the name of the tree that it described. 如果您完全清除索引,通常不會丟失任何資訊,只要您擁有它所描述的 tree 的名稱。
Submodules. 子模組
Large projects are often composed of smaller, self-contained modules. For example, an embedded Linux distribution’s source tree would include every piece of software in the distribution with some local modifications; a movie player might need to build against a specific, known-working version of a decompression library; several independent programs might all share the same build scripts. 大型專案通常由更小的、獨立的模組組成。例如,嵌入式 Linux 發行版的原始碼樹會包含發行版中的所有軟體及其本地修改;電影播放器可能需要針對特定、已知工作的解壓縮庫版本進行構建;多個獨立程式可能共享相同的構建指令碼。
With centralized revision control systems this is often accomplished by including every module in one single repository. Developers can check out all modules or only the modules they need to work with. They can even modify files across several modules in a single commit while moving things around or updating APIs and translations. 在集中式版本控制系統中,這通常透過將所有模組包含在一個儲存庫中來實現。開發人員可以簽出所有模組或僅簽出他們需要處理的模組。他們甚至可以在移動內容或更新 API 和翻譯時,在單個提交中修改多個模組的檔案。
Git does not allow partial checkouts, so duplicating this approach in Git would force developers to keep a local copy of modules they are not interested in touching. Commits in an enormous checkout would be slower than you’d expect as Git would have to scan every directory for changes. If modules have a lot of local history, clones would take forever. Git 不允許部分簽出,因此在 Git 中複製這種方法將迫使開發人員保留他們不感興趣的模組的本地副本。在一個巨大的簽出中進行提交會比您預期的慢,因為 Git 需要掃描每個目錄以查詢更改。如果模組有大量的本地歷史記錄,克隆將花費永恆的時間。
On the plus side, distributed revision control systems can much better integrate with external sources. In a centralized model, a single arbitrary snapshot of the external project is exported from its own revision control and then imported into the local revision control on a vendor branch. All the history is hidden. With distributed revision control you can clone the entire external history and much more easily follow development and re-merge local changes. 優點是,分散式版本控制系統可以更好地與外部源整合。在集中式模型中,外部專案的單個任意快照會從其自己的版本控制中匯出,然後匯入到供應商分支上的本地版本控制中。所有歷史記錄都隱藏起來。使用分散式版本控制,您可以克隆完整的外部歷史記錄,並更輕鬆地跟蹤開發和重新合併本地更改。
Git’s submodule support allows a repository to contain, as a subdirectory, a checkout of an external project. Submodules maintain their own identity; the submodule support just stores the submodule repository location and commit ID, so other developers who clone the containing project ("superproject") can easily clone all the submodules at the same revision. Partial checkouts of the superproject are possible: you can tell Git to clone none, some or all of the submodules. Git 的子模組支援允許儲存庫作為子目錄包含外部專案的簽出。子模組保持自己的身份;子模組支援只儲存子模組儲存庫的位置和 commit ID,因此克隆包含專案(“superproject”)的其他開發人員可以輕鬆地在相同的版本上克隆所有子模組。superproject 的部分簽出是可能的:您可以告訴 Git 克隆零個、一些或所有子模組。
The git-submodule[1] command is available since Git 1.5.3. Users with Git 1.5.2 can look up the submodule commits in the repository and manually check them out; earlier versions won’t recognize the submodules at all. git-submodule[1] 命令自 Git 1.5.3 起可用。擁有 Git 1.5.2 的使用者可以在儲存庫中查詢子模組 commit 並手動簽出;早期版本根本不識別子模組。
To see how submodule support works, create four example repositories that can be used later as a submodule. 要檢視子模組支援如何工作,請建立四個稍後可用作子模組的示例儲存庫。
$ 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
Now create the superproject and add all the submodules. 現在建立 superproject 並新增所有子模組。
$ mkdir super $ cd super $ git init $ for i in a b c d do git submodule add ~/git/$i $i done
|
注意
|
Do not use local URLs here if you plan to publish your superproject! 如果您打算釋出您的 superproject,請不要在此處使用本地 URL! |
See what files git submodule created. 檢視 git submodule 建立了哪些檔案。
$ ls -a . .. .git .gitmodules a b c d
The git submodule add <repo> <path> command does a couple of things. git submodule add <repo> <path> 命令執行以下幾項操作。
-
It clones the submodule from <repo> to the given <path> under the current directory and by default checks out the master branch. 它將子模組從 <repo> 克隆到當前目錄下的指定 <path>,並預設簽出 master 分支。
-
It adds the submodule’s clone path to the gitmodules[5] file and adds this file to the index, ready to be committed. 它將子模組的克隆路徑新增到 gitmodules[5] 檔案,並將此檔案新增到索引,準備提交。
-
It adds the submodule’s current commit ID to the index, ready to be committed. 它將子模組的當前 commit ID 新增到索引,準備提交。
Commit the superproject. 提交 superproject。
$ git commit -m "Add submodules a, b, c and d."
Now clone the superproject. 現在克隆 superproject。
$ cd .. $ git clone super cloned $ cd cloned
The submodule directories are there, but they’re empty. 子模組目錄存在,但它們是空的。
$ ls -a a . .. $ git submodule status -d266b9873ad50488163457f025db7cdd9683d88b a -e81d457da15309b4fef4249aba9b50187999670d b -c1536a972b9affea0f16e0680ba87332dc059146 c -d96249ff5d57de5de093e6baff9e0aafa5276a74 d
|
注意
|
The commit object names shown above would be different for you, but they should match the HEAD commit object names of your repositories. You can check it by running git ls-remote ../a. 上面顯示的 commit 物件名稱對您來說會不同,但它們應該與您儲存庫的 HEAD commit 物件名稱匹配。您可以透過執行 git ls-remote ../a 來檢查。 |
Pulling down the submodules is a two-step process. First run git submodule init to add the submodule repository URLs to .git/config. 拉取子模組是一個兩步過程。首先執行 git submodule init 將子模組儲存庫 URL 新增到 .git/config。
$ git submodule init
Now use git submodule update to clone the repositories and check out the commits specified in the superproject. 現在使用 git submodule update 來克隆儲存庫並簽出 superproject 中指定的 commit。
$ git submodule update $ cd a $ ls -a . .. .git a.txt
One major difference between git submodule update and git submodule add is that git submodule update checks out a specific commit, rather than the tip of a branch. It’s like checking out a tag: the head is detached, so you’re not working on a branch. git submodule update 和 git submodule add 之間的一個主要區別是 git submodule update 簽出的是一個特定的 commit,而不是分支的最新提交。這就像簽出一個 tag:HEAD 是分離的,所以您不是在分支上工作。
$ git branch * (detached from d266b98) master
If you want to make a change within a submodule and you have a detached head, then you should create or checkout a branch, make your changes, publish the change within the submodule, and then update the superproject to reference the new commit. 如果您想在子模組中進行更改並且您有一個分離的 HEAD,那麼您應該建立一個或簽出一個分支,進行更改,在子模組中釋出更改,然後更新 superproject 以引用新的 commit。
$ git switch master
或
$ git switch -c fix-up
then. 然後。
$ 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
You have to run git submodule update after git pull if you want to update submodules, too. 如果您也想更新子模組,則必須在 git pull 之後執行 git submodule update。
Pitfalls with submodules. 子模組的陷阱
Always publish the submodule change before publishing the change to the superproject that references it. If you forget to publish the submodule change, others won’t be able to clone the repository. 始終在釋出引用它的 superproject 的更改之前釋出子模組的更改。如果您忘記釋出子模組的更改,其他人將無法克隆儲存庫。
$ 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'
In older Git versions it could be easily forgotten to commit new or modified files in a submodule, which silently leads to similar problems as not pushing the submodule changes. Starting with Git 1.7.0 both git status and git diff in the superproject show submodules as modified when they contain new or modified files to protect against accidentally committing such a state. git diff will also add a -dirty to the work tree side when generating patch output or used with the --submodule option. 在舊的 Git 版本中,很容易忘記提交子模組中的新檔案或修改過的檔案,這會靜默地導致與不推送子模組更改類似的問題。從 Git 1.7.0 開始,superproject 中的 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:
You also should not rewind branches in a submodule beyond commits that were ever recorded in any superproject. 您也不應該將子模組中的分支回溯到任何 superproject 中記錄過的 commit 之前。
It’s not safe to run git submodule update if you’ve made and committed changes within a submodule without checking out a branch first. They will be silently overwritten. 如果您在沒有先簽出分支的情況下在子模組中進行了更改並提交了,那麼執行 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
|
注意
|
The changes are still visible in the submodule’s reflog. 更改在子模組的 reflog 中仍然可見。 |
If you have uncommitted changes in your submodule working tree, git submodule update will not overwrite them. Instead, you get the usual warning about not being able switch from a dirty branch. 如果您的子模組工作目錄中有未提交的更改,git submodule update 不會覆蓋它們。相反,您會收到關於無法從髒分支切換的常規警告。
Low-level Git operations. 低階 Git 操作
Many of the higher-level commands were originally implemented as shell scripts using a smaller core of low-level Git commands. These can still be useful when doing unusual things with Git, or just as a way to understand its inner workings. 許多高階命令最初是作為 shell 指令碼實現的,使用了更小的低階 Git 命令核心。當使用 Git 做不尋常的事情時,或者僅僅是為了理解其內部工作原理,它們仍然有用。
Object access and manipulation. 物件訪問和操作
The git-cat-file[1] command can show the contents of any object, though the higher-level git-show[1] is usually more useful. git-cat-file[1] 命令可以顯示任何物件的內容,儘管更高階的 git-show[1] 通常更有用。
The git-commit-tree[1] command allows constructing commits with arbitrary parents and trees. git-commit-tree[1] 命令允許使用任意 parent 和 tree 來構建提交。
A tree can be created with git-write-tree[1] and its data can be accessed by git-ls-tree[1]. Two trees can be compared with git-diff-tree[1]. 可以使用 git-write-tree[1] 建立 tree,並可以透過 git-ls-tree[1] 訪問其資料。可以使用 git-diff-tree[1] 比較兩個 tree。
A tag is created with git-mktag[1], and the signature can be verified by git-verify-tag[1], though it is normally simpler to use git-tag[1] for both. tag 是使用 git-mktag[1] 建立的,並且可以使用 git-verify-tag[1] 驗證簽名,儘管通常使用 git-tag[1] 來同時完成這兩項操作更簡單。
The Workflow. 工作流程
High-level operations such as git-commit[1] and git-restore[1] work by moving data between the working tree, the index, and the object database. Git provides low-level operations which perform each of these steps individually. git-commit[1] 和 git-restore[1] 等高階操作透過在工作目錄、索引和物件資料庫之間移動資料來工作。Git 提供低階操作來單獨執行這些步驟中的每一步。
Generally, all Git operations work on the index file. Some operations work purely on the index file (showing the current state of the index), but most operations move data between the index file and either the database or the working directory. Thus there are four main combinations. 通常,所有 Git 操作都作用於索引檔案。有些操作僅作用於索引檔案(顯示索引的當前狀態),但大多數操作在索引檔案與資料庫或工作目錄之間移動資料。因此有四種主要組合。
working directory → index. 工作目錄 → 索引
The git-update-index[1] command updates the index with information from the working directory. You generally update the index information by just specifying the filename you want to update, like so. git-update-index[1] 命令使用工作目錄中的資訊更新索引。您通常只需指定要更新的檔名即可更新索引資訊,例如。
$ git update-index filename
but to avoid common mistakes with filename globbing etc., the command will not normally add totally new entries or remove old entries, i.e. it will normally just update existing cache entries. 但為了避免檔名通配等常見錯誤,該命令通常不會新增全新的條目或刪除舊的條目,也就是說,它通常只會更新現有的快取條目。
To tell Git that yes, you really do realize that certain files no longer exist, or that new files should be added, you should use the --remove and --add flags respectively. 要告訴 Git 是的,您確實意識到某些檔案已不存在,或者應該新增新檔案,您應該分別使用 --remove 和 --add 標誌。
NOTE! A --remove flag does not mean that subsequent filenames will necessarily be removed: if the files still exist in your directory structure, the index will be updated with their new status, not removed. The only thing --remove means is that update-index will be considering a removed file to be a valid thing, and if the file really does not exist any more, it will update the index accordingly. 注意!--remove 標誌不意味著後續檔名一定會被刪除:如果檔案仍然存在於您的目錄結構中,索引將更新為它們的新狀態,而不是被刪除。 --remove 僅表示 update-index 將考慮刪除的檔案是有效的內容,如果檔案確實不再存在,它將相應地更新索引。
As a special case, you can also do git update-index --refresh, which will refresh the "stat" information of each index to match the current stat information. It will not update the object status itself, and it will only update the fields that are used to quickly test whether an object still matches its old backing store object. 作為一種特殊情況,您還可以執行 git update-index --refresh,它將重新整理每個索引的“stat”資訊以匹配當前的 stat 資訊。它不會更新物件本身的狀態,並且它只會更新用於快速測試物件是否仍與其舊的後臺儲存物件匹配的欄位。
The previously introduced git-add[1] is just a wrapper for git-update-index[1]. 之前介紹的 git-add[1] 只是 git-update-index[1] 的一個包裝器。
index → object database. 索引 → 物件資料庫
You write your current index file to a "tree" object with the program. 您使用程式將當前的索引檔案寫入一個“tree”物件。
$ git write-tree
that doesn’t come with any options—it will just write out the current index into the set of tree objects that describe that state, and it will return the name of the resulting top-level tree. You can use that tree to re-generate the index at any time by going in the other direction. 它沒有任何選項——它只會將當前索引寫入描述該狀態的 tree 物件集合,並返回結果的頂級 tree 的名稱。您可以在任何時候透過反向操作來使用該 tree 來重新生成索引。
object database → index. 物件資料庫 → 索引
You read a "tree" file from the object database, and use that to populate (and overwrite—don’t do this if your index contains any unsaved state that you might want to restore later!) your current index. Normal operation is just. 您從物件資料庫讀取一個“tree”檔案,並使用它來填充(並覆蓋——如果您的索引包含任何您以後可能想恢復的未儲存狀態,請不要這樣做!)您的當前索引。正常操作只是。
$ git read-tree <SHA-1 of tree>
and your index file will now be equivalent to the tree that you saved earlier. However, that is only your index file: your working directory contents have not been modified. 並且您的索引檔案現在將等同於您之前儲存的 tree。但是,那只是您的索引檔案:您的工作目錄內容尚未被修改。
index → working directory. 索引 → 工作目錄
You update your working directory from the index by "checking out" files. This is not a very common operation, since normally you’d just keep your files updated, and rather than write to your working directory, you’d tell the index files about the changes in your working directory (i.e. git update-index). 您透過“簽出”檔案來從索引更新您的工作目錄。這不是一個非常常見的操作,因為通常您會保持檔案更新,而不是寫入您的工作目錄,您會告訴索引檔案您工作目錄中的更改(即 git update-index)。
However, if you decide to jump to a new version, or check out somebody else’s version, or just restore a previous tree, you’d populate your index file with read-tree, and then you need to check out the result with. 但是,如果您決定跳轉到新版本,或簽出別人的版本,或僅恢復以前的 tree,您將使用 read-tree 填充您的索引檔案,然後您需要使用…簽出結果。
$ git checkout-index filename
or, if you want to check out all of the index, use -a. 或者,如果您想簽出索引中的所有內容,請使用 -a。
NOTE! git checkout-index normally refuses to overwrite old files, so if you have an old version of the tree already checked out, you will need to use the -f flag (before the -a flag or the filename) to force the checkout. 注意!git checkout-index 通常拒絕覆蓋舊檔案,因此如果您已經簽出了舊版本的 tree,您將需要使用 -f 標誌(在 -a 標誌或檔名之前)來強制簽出。
Finally, there are a few odds and ends which are not purely moving from one representation to the other. 最後,還有一些零散的東西,它們不是純粹地從一種表示形式移動到另一種形式。
Tying it all together. 將它們聯絡起來
要提交您透過 git write-tree 例項化出來的樹,您需要建立一個“提交”物件,該物件引用該樹以及其背後的歷史記錄——最重要的是,它引用了在歷史記錄中排在其前面的“父”提交。
通常,“提交”只有一個父提交:在某個更改發生之前的樹的先前狀態。但是,有時它可能有兩個或更多父提交,在這種情況下,我們稱之為“合併”,因為這種提交會將由其他提交表示的兩個或多個先前狀態“合併”在一起。
換句話說,雖然“樹”表示工作目錄的特定目錄狀態,“提交”表示該狀態在時間軸上的位置,並解釋了我們是如何到達那裡的。
您透過提供描述提交時狀態的樹以及父提交列表來建立一個提交物件
$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]
然後透過標準輸入(透過管道重定向或檔案,或者直接在終端輸入)提供提交原因。
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,它將二進位制內容轉換為更易讀的形式。
檢視“提交”物件尤其有啟發性,因為它們通常很小且相當自explanatory。特別是,如果您遵循將頂級提交名稱放在 .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 程式,它將階段提取到臨時檔案中,並在此之上呼叫一個“merge”指令碼
$ git merge-index git-merge-one-file hello.c
這就是高級別的 git merge -s resolve 的實現方式。
深入 Git
本章涵蓋了 Git 實現的內部細節,可能只有 Git 開發者才需要理解。
物件儲存格式
所有物件都有一個靜態確定的“型別”,該型別標識物件的格式(即它的用途以及它如何引用其他物件)。目前有四種不同的物件型別:“blob”、“tree”、“commit”和“tag”。
無論物件型別如何,所有物件都具有以下共同特徵:它們都使用 zlib 進行 deflated,並且有一個頭部,該頭部不僅指定了它們的型別,還提供了有關物件中資料大小的資訊。值得注意的是,用於命名物件的 SHA-1 雜湊是原始資料加上此頭部的雜湊,因此 sha1sum file 與 file 的物件名稱不匹配(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 使用“changeset”(變更集)一詞來描述我們現在稱之為提交的內容。
此外,我們不再稱之為“cache”(快取),而是“index”(索引);但是,該檔案仍稱為 read-cache.h。
如果您理解了初始提交中的思想,您應該檢視一個更近期的版本,並瀏覽 read-cache-ll.h、object.h 和 commit.h。
在早期,Git(遵循 UNIX 的傳統)是一堆極其簡單的程式,您可以在指令碼中使用它們,將一個程式的輸出透過管道傳遞給另一個程式。這對於初始開發來說是很好的,因為它更容易測試新事物。然而,最近許多這些部分已成為內建命令,並且一些核心已被“lib化”,即為了效能、可移植性以及避免程式碼重複而被放入 libgit.a 中。
現在,您知道索引是什麼了(並在 read-cache-ll.h 中找到相應的資料結構),並且只有幾種物件型別(blob、tree、commit 和 tag),它們從 struct object 繼承了它們的通用結構,而 struct object 是它們的第一個成員(因此,您可以將例如 (struct object *)commit 強制轉換為 &commit->object,即訪問物件名稱和標誌)。
現在是時候休息一下,讓這些資訊沉澱下來了。
下一步:熟悉物件命名。閱讀 命名提交。命名物件(不僅是修訂!)有相當多的方法。所有這些都在 sha1_name.c 中處理。只需快速檢視 get_sha1() 函式。許多特殊處理是由 get_sha1_basic() 或類似的函式完成的。
這只是為了讓您熟悉 Git 中最“lib化”的部分:修訂遍歷器。
基本上,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_show() 和 cmd_log() 都位於 builtin/log.c 中,因為它們共享相當多的程式碼。在這種情況下,名稱與它們所在 .c 檔名稱不同的命令必須在 Makefile 的 BUILT_INS 中列出。
git log 在 C 語言中的實現比原始指令碼看起來更復雜,但這提供了更大的靈活性和效能。
在這裡,又是一個可以暫停一下的好時機。
第三課是:研究程式碼。真的,這是瞭解 Git 組織的最佳方式(在您瞭解基本概念之後)。
所以,想想您感興趣的事情,比如,“如何僅憑物件的名稱訪問一個 blob?”。第一步是找到一個 Git 命令,您可以用它來做到這一點。在此示例中,它是 git show 或 git cat-file。
為了清晰起見,我們仍然以 git cat-file 為例,因為它
-
是底層命令,並且
-
在初始提交時就已經存在了(它在
cat-file.c中經歷了大約 20 次修訂,在成為內建命令後被重新命名為builtin/cat-file.c,然後經歷了不到 10 個版本)。
所以,檢視 builtin/cat-file.c,搜尋 cmd_cat_file() 並檢視它的作用。
repo_config(the_repository, 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是unsignedchar*,但實際上期望它是一個指向unsignedchar[20] 的指標。該變數將包含給定提交的 160 位 SHA-1。請注意,每當 SHA-1 作為unsignedchar*傳遞時,它就是二進位制表示,而不是十六進位制字元的 ASCII 表示,後者作為char*傳遞。
您將在程式碼中看到這兩種情況。
現在,進入核心部分
case 0:
buf = odb_read_object_peeled(r->objects, sha1, argv[1], &size, NULL);
這就是讀取 blob(實際上,不只是 blob,而是任何型別的物件)的方法。要了解 odb_read_object_peeled() 函式實際上是如何工作的,請找到它的原始碼(在 Git 儲存庫中可能類似於 git grep read_object_with | grep ":[a-z]"),然後閱讀原始碼。
要了解結果如何使用,只需繼續閱讀 cmd_cat_file()
write_or_die(1, buf, size);
有時,您不知道要在哪裡查詢某個功能。在許多此類情況下,透過搜尋 git log 的輸出,然後 git show 相應的提交,會有所幫助。
示例:如果您知道有一個 git bundle 的測試用例,但不記得它在哪裡(是的,您可以 git grep bundle t/,但這並沒有說明重點!)
$ git log --no-merges t/
在分頁器(less)中,只需搜尋“bundle”,然後回溯幾行,您就會發現它在提交 18449ab0 中。現在只需複製此物件名稱,然後將其貼上到命令列
$ git show 18449ab0
搞定。
另一個示例:找出如何將某個指令碼變成內建命令
$ git log --no-merges --diff-filter=A builtin/*.c
您看,Git 實際上是瞭解 Git 自身原始碼的最佳工具!
Git 詞彙表
Git 解釋
- 備用物件資料庫
- 裸倉庫
-
裸倉庫通常是一個以適當命名的、帶有
.git字尾的目錄,但沒有本地簽出的任何受版本控制的檔案副本。也就是說,通常存在於隱藏的.git子目錄中的所有 Git 管理和控制檔案都直接存在於repository.git目錄中,並且不存在其他檔案和簽出的檔案。公共倉庫的釋出者通常會提供裸倉庫。 - blob 物件
-
未型別化的物件,例如檔案的內容。
- 分支
-
“分支”是一條開發線。分支上最新的提交被稱為該分支的尖端。分支的尖端由一個分支頭引用,當在該分支上進行額外開發時,它會向前移動。一個 Git 倉庫可以跟蹤任意數量的分支,但您的工作樹僅與其中一個(“當前”或“已簽出的”分支)相關聯,並且 HEAD 指向該分支。
- 快取
-
已廢棄,用於:索引。
- 鏈
- 變更集
-
BitKeeper/cvsps 中“提交”的說法。由於 Git 不儲存更改,而是儲存狀態,因此使用“變更集”一詞對 Git 來說確實沒有意義。
- 簽出
-
透過樹物件或blob更新工作樹的全部或部分的操作,並將索引和HEAD更新為指向新的分支(如果整個工作樹都指向了新分支)。
- 挑選
-
在SCM行話中,“cherry pick”(挑選)意味著從一系列更改(通常是提交)中選擇一個子集,並將其作為新一系列更改記錄在不同的程式碼庫之上。在 Git 中,這透過“git cherry-pick”命令執行,以提取現有提交引入的更改,並將其記錄在當前分支的尖端之上,作為新的提交。
- 乾淨
- 提交
-
名詞:Git 歷史中的一個點;專案的整個歷史表示為一組相互關聯的提交。Git 中“提交”一詞的使用場合與其它版本控制系統使用“修訂”或“版本”的場合相同。也用作提交物件的簡寫。
- 提交圖概念、表示和用法
-
物件資料庫中提交形成的DAG結構的同義詞,由分支尖端引用,使用它們鏈式連結的提交。該結構是最終的提交圖。該圖可以用其他方式表示,例如“提交圖”檔案。
- 提交圖檔案
-
“提交圖”(通常是帶連字元的)檔案是提交圖的補充表示,它加快了提交圖的遍歷。“提交圖”檔案儲存在 .git/objects/info 目錄或備用物件資料庫的 info 目錄中。
- 提交物件
- commit-ish(也稱為 committish)
-
一個提交物件或一個可以遞迴解引用到提交物件的物件。以下都是 commit-ishes:一個提交物件,一個指向提交物件的標籤物件,一個指向指向提交物件的標籤物件的標籤物件,等等。
- 核心 Git
-
Git 的基本資料結構和實用程式。僅公開有限的原始碼管理工具。
- DAG
-
有向無環圖。由於提交物件具有父提交(有向),並且提交物件圖是無環的(沒有以相同物件開始和結束的鏈),因此提交物件構成一個有向無環圖。
- 懸空物件
- 解引用
-
指向符號引用:訪問由符號引用指向的引用的操作。遞迴解引用涉及在結果引用上重複上述過程,直到找到非符號引用。
指向提交物件:訪問提交的樹物件的操作。提交不能遞迴解引用。
除非另有說明,“解引用”在 Git 命令或協議的上下文中是隱式遞迴的。
- 分離 HEAD
-
通常 HEAD 儲存一個分支的名稱,並且對 HEAD 代表的歷史進行操作的命令是對到達 HEAD 所指向的分支尖端處的歷史進行操作。但是,Git 也允許您簽出一個任意的提交,該提交不一定是任何特定分支的尖端。處於這種狀態的 HEAD 被稱為“分離”。
請注意,在 HEAD 分離狀態下,對當前分支歷史進行操作的命令(例如,
gitcommit以在其之上構建新歷史)仍然有效。它們會更新 HEAD 以指向更新後的歷史的尖端,而不會影響任何分支。顯然,更新或查詢關於當前分支資訊的命令(例如,設定當前分支整合的遠端跟蹤分支的gitbranch--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 的上下文中,是物件名稱的同義詞。
- 頭
-
對分支末端的提交的命名引用。主幹(Heads)儲存在
$GIT_DIR/refs/heads/目錄下的檔案中,除非使用打包引用(packed refs)。(參見 git-pack-refs[1]。) - HEAD
-
當前分支。更詳細地說:你的工作目錄通常派生自HEAD引用的樹的狀態。HEAD是對你倉庫中的一個主幹的引用,除非在使用分離HEAD時,此時它直接引用任意提交。
- 主幹引用
-
與主幹同義。
- 鉤子
-
在一些Git命令的正常執行過程中,會呼叫可選的指令碼,允許開發者新增功能或檢查。通常,鉤子允許命令在執行前進行驗證並可能中止,以及在操作完成後進行後通知。鉤子指令碼位於
$GIT_DIR/hooks/目錄中,只需從檔名中刪除.sample字尾即可啟用。在早期版本的Git中,你還需要使它們可執行。 - 索引
-
包含檔案狀態資訊,其內容作為物件儲存的集合。索引是你工作目錄的儲存版本。實際上,它還可以包含工作目錄的第二版甚至第三版,這些版本在合併時使用。
- 索引條目
-
儲存在索引中關於特定檔案的資訊。如果一個合併已開始但尚未完成(即索引包含該檔案的多個版本),則該索引條目可能是未合併的。
- 主分支
-
預設的開發分支。每當你建立一個Git倉庫時,都會建立一個名為“master”的分支,並將其設為活動分支。在大多數情況下,這包含本地開發,儘管這純粹是約定俗成,並非強制要求。
- 合併
-
作為動詞:將另一個分支的內容(可能來自外部倉庫)引入當前分支。如果合併的分支來自不同的倉庫,則首先透過抓取遠端分支,然後將結果合併到當前分支。這種抓取和合並操作的組合稱為拉取。合併透過一個自動過程執行,該過程識別自分支分叉以來所做的更改,然後將所有這些更改應用在一起。在發生衝突的情況下,可能需要手動干預才能完成合並。
- 物件
-
Git中的儲存單位。它透過其內容的SHA-1雜湊值進行唯一標識。因此,物件的內容不能被更改。
- 物件資料庫
- 物件識別符號(oid)
-
與物件名同義。
- 物件名
-
物件的唯一識別符號。物件名通常表示為一個40個字元的十六進位制字串。也通俗地稱為SHA-1。
- 物件型別
- 章魚合併
-
合併兩個以上的分支。
- 孤兒
-
切換到一個尚不存在(即,一個未出生的分支)的分支。執行此操作後,建立的第一個提交將成為一個沒有父提交的提交,從而開始一個新的歷史。
- 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。
以冒號
:開頭的路徑說明符具有特殊含義。在短形式中,前導冒號:後面跟著零個或多個“魔術簽名”字母(可以選擇後跟另一個冒號:),其餘部分是要與路徑匹配的模式。“魔術簽名”由非字母數字、萬用字元、正則表示式特殊字元或冒號的ASCII符號組成。可選的終止“魔術簽名”的冒號,如果模式以不屬於“魔術簽名”符號集且非冒號的字元開頭,則可以省略。在長形式中,前導冒號
:後面跟著一個開括號(,一個逗號分隔的零個或多個“魔術單詞”列表,以及一個閉括號),其餘部分是要與路徑匹配的模式。僅包含冒號的路徑說明符表示“沒有路徑說明符”。此形式不應與其他路徑說明符結合使用。
- top
-
魔術單詞
top(魔術簽名:/)使模式從工作目錄的根目錄開始匹配,即使您正在從子目錄中執行命令。 - literal
-
模式中的萬用字元,如
*或?,被視為字面字元。 - icase
-
不區分大小寫的匹配。
- glob
-
Git將模式視為shell glob,適用於fnmatch(3)並帶有FNM_PATHNAME標誌:模式中的萬用字元不會匹配路徑名中的/。例如,“Documentation/*.html”匹配“Documentation/git.html”,但不匹配“Documentation/ppc/ppc.html”或“tools/perf/Documentation/perf.html”。
與完整路徑名匹配的模式中,兩個連續的星號(
**)可能具有特殊含義:-
前導“
**”後跟斜槓表示在所有目錄中匹配。例如,“**/foo”匹配任何地方的“foo”檔案或目錄。“**/foo/bar”匹配任何地方直接在“foo”目錄下的“bar”檔案或目錄。 -
尾隨的“
/**”匹配內部的所有內容。例如,“abc/**”匹配目錄“abc”內的所有檔案,相對於.gitignore檔案所在的位置,深度無限。 -
斜槓後跟兩個連續的星號,然後是斜槓,匹配零個或多個目錄。例如,“
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
-
在路徑匹配任何非排除路徑說明符後,它將透過所有排除路徑說明符(魔術簽名:
!或其同義詞^)進行處理。如果匹配,則忽略該路徑。當沒有非排除路徑說明符時,排除將應用於結果集,就好像沒有指定任何路徑說明符一樣。
-
- 父提交
-
一個提交物件包含一個(可能為空的)開發線路上游(logical predecessor)列表,即其父提交。
- 剝離
- 鋤頭
-
術語鋤頭是指diffcore例程的一個選項,該選項有助於選擇新增或刪除給定文字字串的更改。使用
--pickaxe-all選項,可以用來檢視引入或刪除某特定文字行的完整更改集。參見git-diff[1]。 - 底層命令
-
表示核心Git的暱稱。
- 上層命令
-
表示依賴於核心Git的程式和程式套件的暱稱,它們提供了對核心Git的高階訪問。上層命令比底層命令更多地暴露了SCM的介面。
- 每個工作區引用
-
每個工作區的引用,而不是全域性的。目前只有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]。
- 推送
-
推送一個分支意味著從遠端倉庫獲取該分支的主幹引用,找出它是否是該分支本地主幹引用的祖先,如果是,則將所有可達於本地主幹引用但遠端倉庫缺失的物件放入遠端物件資料庫,並更新遠端主幹引用。如果遠端主幹不是本地主幹的祖先,則推送失敗。
- 可達
-
給定提交的所有祖先都被稱為從該提交“可達”。更廣泛地說,一個物件如果可以透過遵循標籤到其標記的物件,提交到其父提交或樹,以及樹到其包含的樹或檔案的鏈從另一個物件到達該物件,則稱其為可達。
- 可達性點陣圖
-
可達性點陣圖儲存有關打包檔案或多打包索引(MIDX)中選定提交集可達性的資訊,以加快物件搜尋速度。點陣圖儲存在“.bitmap”檔案中。一個倉庫最多隻能有一個位圖檔案在使用中。點陣圖檔案可以屬於單個打包檔案,或倉庫的多打包索引(如果存在)。
- 變基
- 引用
-
指向物件名或其他引用(後者稱為符號引用)的名稱。為了方便起見,在將引用用作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分支),並且匹配已配置抓取引用規範的右側。遠端跟蹤分支不應包含直接修改或在其上進行本地提交。
- 倉庫
-
一組引用以及包含所有可達於引用的物件的物件資料庫,可能還附帶一個或多個上層命令的元資料。倉庫可以透過備用機制與其他倉庫共享物件資料庫。
- 解決
-
手動修復失敗的自動合併所遺留問題的操作。
- 修訂
-
與提交(名詞)同義。
- 回滾
- SCM
-
原始碼管理(工具)。
- SHA-1
-
“安全雜湊演算法1”;一種加密雜湊函式。在Git的上下文中,用作物件名的同義詞。
- 淺層克隆
-
在大多數情況下,與淺層倉庫同義,但該短語更明確地表明它是透過執行
git clone --depth=...命令建立的。 - 淺層倉庫
-
一個淺層倉庫具有不完整的歷史,其中一些提交的父提交已被“燒燬”(換句話說,Git被告知假裝這些提交沒有父提交,即使它們已記錄在提交物件中)。當您只對專案的近期歷史感興趣,而上游記錄的實際歷史非常大時,這有時很有用。透過給git-clone[1]提供
--depth選項來建立淺層倉庫,並且其歷史可以透過git-fetch[1]稍後加深。 - 暫存條目
- 子模組
- 父專案
- 符號引用
-
符號引用:它不是包含SHA-1 ID本身,而是格式為ref: refs/some/thing,當被引用時,它會遞迴解引用到該引用。HEAD是符號引用的一個主要例子。符號引用透過git-symbolic-ref[1]命令進行操作。
- 標籤
-
refs/tags/名稱空間下的引用,指向任意型別的物件(通常標籤指向標籤或提交物件)。與主幹不同,標籤不會被commit命令更新。Git標籤與Lisp標籤(在Git的上下文中稱為物件型別)無關。標籤最常用於標記提交祖先鏈中的特定點。 - 標籤物件
-
包含指向另一個物件的引用的物件,它可以像提交物件一樣包含訊息。它還可以包含(PGP)簽名,在這種情況下,它被稱為“已簽名標籤物件”。
- 主題分支
-
一個常規的Git分支,由開發者用於標識一個概念性的開發線。由於分支非常容易和廉價,因此通常需要有幾個小分支,每個分支都包含非常明確的概念或小的增量但相關的更改。
- 尾部
-
鍵值元資料。尾部可選地出現在提交訊息的末尾。在其他社群中可能被稱為“頁尾”或“標籤”。參見git-interpret-trailers[1]。
- 樹
- 樹物件
- tree-ish (也可 treeish)
-
一個樹物件或一個可以遞迴解引用到樹物件的物件。解引用提交物件會得到與修訂的頂層目錄對應的樹物件。以下都屬於tree-ishes:一個commit-ish,一個樹物件,一個指向樹物件的標籤物件,一個指向指向樹物件的標籤物件的標籤物件,等等。
- 未出生
-
HEAD可以指向一個尚不存在且還沒有任何提交的分支,這樣的分支稱為未出生分支。使用者最常遇到未出生分支的情況是建立新倉庫而不是從別處克隆。HEAD將指向尚未誕生的主(main)(或master,取決於您的配置)分支。此外,一些操作可以透過其孤兒選項讓您進入未出生分支。 - 未合併索引
- 不可達物件
- 上游分支
-
要合併到當前分支(或當前分支被變基到其上)的預設分支。它透過branch.<name>.remote和branch.<name>.merge進行配置。如果A的上游分支是origin/B,有時我們說“A正在跟蹤origin/B”。
- 工作目錄
-
實際檢出檔案的樹。工作目錄通常包含HEAD提交樹的內容,加上您已做的但尚未提交的任何本地更改。
- 工作區
-
一個倉庫可以有零個(即裸倉庫)或一個或多個附加到它的工作區。一個“工作區”由一個“工作目錄”和倉庫元資料組成,其中大部分在單個倉庫的其他工作區之間共享,而有些則為每個工作區單獨維護(例如,索引、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 沒有特殊瞭解的聰明人按順序、從頭到尾地閱讀。如果需要,任何其他先決條件都應在出現時專門提及。
-
儘可能使章節標題清楚地描述它們解釋如何完成的任務,使用的語言不需要超出必要的知識:例如,“將補丁匯入專案”而不是“
gitam命令”
思考如何建立一個清晰的章節依賴圖,允許人們在不一定閱讀所有中間內容的情況下找到重要主題。
掃描 Documentation/ 目錄以查詢其他遺漏的內容;特別是
-
操作指南
-
technical/中的一些內容? -
鉤子
-
git[1] 中的命令列表
掃描郵件歸檔以查詢其他遺漏的內容
掃描 man pages 以檢視是否有任何內容假設的背景知識比本手冊提供的更多。
新增更多好的示例。僅包含食譜示例的整個部分可能是一個好主意;也許可以做一個標準的章節結尾部分“高階示例”?
適當時包含到詞彙表的交叉引用。
新增一節關於與其他版本控制系統(包括 CVS、Subversion)協同工作的內容,以及僅匯入一系列釋出 tarball 的內容。
編寫一章關於使用底層命令和編寫指令碼的內容。
備選方案、clone -reference 等。