設定和配置
獲取和建立專案
基本快照
分支與合併
共享和更新專案
檢查和比較
打補丁
除錯
電子郵件
外部系統
伺服器管理
指南
管理
底層命令
- 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 進行開發 和 與他人共享開發。
後續章節涵蓋更專業的主題。
完整的參考文件可透過手冊頁或 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
對於大型專案,初始克隆可能很耗時,但您只需克隆一次。
克隆命令會建立一個以專案命名的目錄(在上面的示例中為 git
或 linux
)。cd 進入此目錄後,您會發現它包含專案檔案的副本,稱為工作樹,以及一個名為 .git
的特殊頂級目錄,其中包含專案的所有歷史資訊。
如何檢出專案的不同版本
Git 最適合被視為儲存檔案集合歷史記錄的工具。它將歷史記錄儲存為專案內容的相互關聯的壓縮快照集合。在 Git 中,每個這樣的版本都稱為提交。
這些快照不一定都從舊到新排列成一行;相反,工作可能會同時沿著平行的開發線進行,這些開發線稱為分支,它們可以合併和分化。
一個 Git 倉庫可以跟蹤多個分支上的開發。它透過維護一個頭列表來實現這一點,這些頭引用了每個分支上的最新提交;git-branch[1] 命令顯示分支頭的列表
$ git branch * master
新克隆的倉庫預設包含一個名為“master”的單個分支頭,工作目錄初始化為該分支頭所引用的專案狀態。
大多數專案也使用標籤。標籤和頭一樣,是對專案歷史的引用,可以使用 git-tag[1] 命令列出
$ git tag -l v2.6.11 v2.6.11-tree v2.6.12 v2.6.12-rc2 v2.6.12-rc3 v2.6.12-rc4 v2.6.12-rc5 v2.6.12-rc6 v2.6.13 ...
標籤預期始終指向專案的相同版本,而頭則預期隨著開發進展而前進。
建立一個指向其中一個版本的新分支頭,並使用 git-switch[1] 檢出它
$ git switch -c new v2.6.13
然後工作目錄會反映專案在被標記為 v2.6.13 時的內容,並且 git-branch[1] 顯示兩個分支,星號標記當前已檢出的分支
$ git branch master * new
如果您決定想檢視版本 2.6.17,您可以修改當前分支以指向 v2.6.17,使用
$ git reset --hard v2.6.17
請注意,如果當前分支頭是您對歷史上特定點的唯一引用,那麼重置該分支可能會導致您無法找到它曾經指向的歷史記錄;因此請謹慎使用此命令。
理解歷史:提交
專案歷史中的每個更改都由一個提交表示。git-show[1] 命令顯示當前分支上的最新提交
$ git show commit 17cf781661e6d38f737f15f53ab552f1e95960d7 Author: Linus Torvalds <torvalds@ppc970.osdl.org.(none)> Date: Tue Apr 19 14:11:06 2005 -0700 Remove duplicate getenv(DB_ENVIRONMENT) call Noted by Tony Luck. diff --git a/init-db.c b/init-db.c index 65898fa..b002dc6 100644 --- a/init-db.c +++ b/init-db.c @@ -7,7 +7,7 @@ int main(int argc, char **argv) { - char *sha1_dir = getenv(DB_ENVIRONMENT), *path; + char *sha1_dir, *path; int len, i; if (mkdir(".git", 0755) < 0) {
如您所見,一次提交顯示了誰進行了最新更改、他們做了什麼以及原因。
每個提交都有一個 40 位的十六進位制 ID,有時稱為“物件名稱”或“SHA-1 ID”,顯示在 git
show
輸出的第一行。您通常可以使用更短的名稱(例如標籤或分支名稱)來引用提交,但這個更長的名稱也很有用。最重要的是,它是此提交的全域性唯一名稱:因此如果您告訴其他人物件名稱(例如在電子郵件中),則可以保證該名稱將引用其倉庫中與您倉庫中相同的提交(假設他們的倉庫根本有該提交)。由於物件名稱是根據提交內容計算的雜湊值,因此可以保證提交永遠不會在名稱不變的情況下發生更改。
事實上,在 Git 概念 中我們將看到,Git 歷史中儲存的所有內容,包括檔案資料和目錄內容,都儲存在一個物件中,該物件的名稱是其內容的雜湊值。
理解歷史:提交、父提交和可達性
每個提交(專案中的第一個提交除外)也都有一個父提交,它顯示此提交之前發生的情況。沿著父提交鏈最終會將您帶回專案的起點。
然而,提交併不形成一個簡單的列表;Git 允許開發線分歧然後重新聚合,兩條開發線重新聚合的點稱為“合併”。因此,表示合併的提交可以有多個父提交,每個父提交代表導致該點的其中一條開發線上最新的提交。
瞭解其工作原理的最佳方式是使用 gitk[1] 命令;現在在 Git 倉庫上執行 gitk 並查詢合併提交將有助於理解 Git 如何組織歷史。
在下文中,如果提交 X 是提交 Y 的祖先,我們說提交 X 可以從提交 Y “可達”。同樣地,您可以說 Y 是 X 的後代,或者存在一條從提交 Y 到提交 X 的父提交鏈。
操作分支
建立、刪除和修改分支既快速又簡單;以下是命令摘要
git
branch
-
列出所有分支。
git
branch
<branch>-
建立一個名為 <branch> 的新分支,引用與當前分支相同的歷史點。
git
branch
<branch> <start-point>-
建立一個名為 <branch> 的新分支,引用 <start-point>,該起點可以以您喜歡的任何方式指定,包括使用分支名稱或標籤名稱。
git
branch
-d
<branch>-
刪除分支 <branch>;如果該分支未完全合併到其上游分支中或未包含在當前分支中,此命令將發出警告並失敗。
git
branch
-D
<branch>-
無論其合併狀態如何,都刪除分支 <branch>。
git
switch
<branch>-
將當前分支設為 <branch>,更新工作目錄以反映 <branch> 所引用的版本。
git
switch
-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
以檢視或編寫一次性補丁。請參閱 分離頭。
請注意,“origin”名稱只是 Git 預設用來指代您克隆來源倉庫的名稱。
命名分支、標籤及其他引用
分支、遠端跟蹤分支和標籤都是對提交的引用。所有引用都以 refs
開頭的斜槓分隔路徑名命名;我們目前使用的名稱實際上是簡寫
-
分支
test
是refs/heads/test
的簡寫。 -
標籤
v2.6.18
是refs/tags/v2.6.18
的簡寫。 -
origin/master
是refs/remotes/origin/master
的簡寫。
如果例如存在同名的標籤和分支,則完整名稱偶爾會很有用。
(新建立的引用實際上儲存在 .git/refs
目錄下,位於其名稱給定的路徑下。然而,出於效率原因,它們也可以打包在一個檔案中;請參閱 git-pack-refs[1])。
另一個有用的快捷方式是,倉庫的“HEAD”可以直接使用該倉庫的名稱來引用。因此,例如,“origin”通常是倉庫“origin”中 HEAD 分支的快捷方式。
有關 Git 檢查引用的完整路徑列表,以及當存在多個具有相同簡寫名稱的引用時它用於決定選擇哪個的順序,請參閱 gitrevisions[7] 手冊頁的“指定修訂”部分。
使用 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] 的“配置檔案”部分。)
探索 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 暫時將您移動到了“(無分支)”狀態。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] 手冊頁的“指定修訂”部分。一些示例
$ git show fb47ddb2 # the first few characters of the object name # are usually enough to specify it uniquely $ git show HEAD^ # the parent of the HEAD commit $ git show HEAD^^ # the grandparent $ git show HEAD~4 # the great-great-grandparent
回想一下,合併提交可能有一個以上的父提交;預設情況下,^
和 ~
跟隨提交中列出的第一個父提交,但您也可以選擇
$ git show HEAD^1 # show the first parent of HEAD $ git show HEAD^2 # show the second parent of HEAD
除了 HEAD 之外,還有其他幾個特殊的提交名稱
合併(稍後討論)以及諸如 git
reset
之類的操作(它們會更改當前檢出的提交),通常會將 ORIG_HEAD 設定為當前操作之前 HEAD 的值。
git
fetch
操作始終將最後獲取的分支的頭儲存在 FETCH_HEAD 中。例如,如果您執行 git
fetch
而不指定本地分支作為操作目標
$ git fetch git://example.com/proj.git theirbranch
獲取的提交仍將可透過 FETCH_HEAD 獲取。
當我們討論合併時,我們還會看到特殊名稱 MERGE_HEAD,它指的是我們要合併到當前分支中的另一個分支。
git-rev-parse[1] 命令是一個低階命令,偶爾可用於將提交的某個名稱轉換為該提交的物件名稱
$ git rev-parse origin e05db0fd4f31dde7005f075a84f96b360d05984b
建立標籤
我們還可以建立一個標籤來引用某個特定提交;執行以下命令後
$ git tag stable-1 1b2e1d63ff
您可以使用 stable-1
來引用提交 1b2e1d63ff。
這會建立一個“輕量級”標籤。如果您還想在標籤中包含評論,並可能對其進行加密簽名,那麼您應該改為建立一個標籤物件;有關詳細資訊,請參閱 git-tag[1] 手冊頁。
瀏覽修訂版本
git-log[1] 命令可以顯示提交列表。它本身顯示所有可從父提交到達的提交;但您也可以提出更具體的請求
$ git log v2.5.. # commits since (not reachable from) v2.5 $ git log test..master # commits reachable from master but not test $ git log master..test # ...reachable from test but not master $ git log master...test # ...reachable from either test or master, # but not both $ git log --since="2 weeks ago" # commits from the last 2 weeks $ git log Makefile # commits which modify Makefile $ git log fs/ # ... which modify any file under fs/ $ git log -S'foo()' # commits which add or remove any file data # matching the string 'foo()'
當然,您可以將所有這些結合起來;以下命令查詢自 v2.5 以來修改過 Makefile
或 fs
下任何檔案的提交
$ git log v2.5.. Makefile fs/
您還可以要求 git log 顯示補丁
$ git log -p
有關更多顯示選項,請參閱 git-log[1] 手冊頁中的 --pretty
選項。
請注意,git log 從最新提交開始,然後向後遍歷其父提交;然而,由於 Git 歷史可以包含多個獨立的開發線,因此提交的特定列出順序可能有些隨意。
生成差異
您可以使用 git-diff[1] 在任意兩個版本之間生成差異
$ git diff master..test
這會生成兩個分支尖端之間的差異。如果您希望找到從它們的共同祖先到測試的差異,您可以使用三個點而不是兩個點
$ git diff master...test
有時您需要的是一組補丁;為此您可以使用 git-format-patch[1]
$ git format-patch master..test
將生成一個檔案,其中包含可從 test 訪問但不能從 master 訪問的每個提交的補丁。
檢視舊檔案版本
您總是可以透過先檢出正確的修訂版本來檢視檔案的舊版本。但有時更方便的是無需檢出任何內容即可檢視單個檔案的舊版本;此命令可實現這一點
$ git show v2.5:fs/locks.c
冒號前可以是任何命名提交的內容,冒號後可以是 Git 跟蹤的檔案的任何路徑。
示例
計算分支上的提交數量
假設您想知道 mybranch
自與 origin
分歧以來您已提交了多少次
$ 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 )
(有關諸如 --not
等提交選擇語法的解釋,請參閱 gitrevisions[7]。)
為軟體釋出建立變更日誌和 tarball
git-archive[1] 命令可以從專案的任何版本建立 tar 或 zip 歸檔檔案;例如
$ git archive -o latest.tar.gz --prefix=project/ HEAD
將使用 HEAD 生成一個 gzip 壓縮的 tar 歸檔檔案,其中每個檔名都以 project/
為字首。如果可能,輸出檔案格式將從輸出副檔名推斷,請參閱 git-archive[1] 獲取詳細資訊。
版本早於 1.7.7 的 Git 不知道 tar.gz
格式,您需要明確使用 gzip
$ git archive --format=tar --prefix=project/ HEAD | gzip >latest.tar.gz
如果您正在釋出新版本的軟體專案,您可能希望同時製作一個變更日誌以包含在釋出公告中。
例如,Linus Torvalds 透過給新核心版本打標籤,然後執行
$ release-script 2.6.12 2.6.13-rc6 2.6.13-rc7
其中 release-script 是一個看起來像這樣的 shell 指令碼
#!/bin/sh stable="$1" last="$2" new="$3" echo "# git tag v$new" echo "git archive --prefix=linux-$new/ v$new | gzip -9 > ../linux-$new.tar.gz" echo "git diff v$stable v$new | gzip -9 > ../patch-$new.gz" echo "git log --no-merges v$new ^v$last > ../ChangeLog-$new" echo "git shortlog --no-merges v$new ^v$last > ../ShortLog" echo "git diff --stat --summary -M v$last v$new > ../diffstat-$new"
然後他只需在驗證輸出命令看起來正常後剪下並貼上它們。
查詢引用了給定檔案內容的提交
有人給了你一個檔案副本,並詢問哪些提交修改了該檔案,使得該檔案在提交之前或之後包含給定的內容。你可以透過以下方式找到答案
$ git log --raw --abbrev=40 --pretty=oneline | grep -B 1 `git hash-object filename`
弄清楚其工作原理留給(高階)學生作為練習。git-log[1]、git-diff-tree[1] 和 git-hash-object[1] 手冊頁可能會有所幫助。
使用 Git 進行開發
告訴 Git 你的名字
在建立任何提交之前,你應該向 Git 介紹你自己。最簡單的方法是使用 git-config[1]
$ git config --global user.name 'Your Name Comes Here' $ git config --global user.email 'you@yourdomain.example.com'
這將在你的主目錄中名為 .gitconfig
的檔案中新增以下內容
[user] name = Your Name Comes Here email = you@yourdomain.example.com
有關配置檔案的詳細資訊,請參閱 git-config[1] 的“配置檔案”部分。該檔案是純文字檔案,因此你也可以使用你喜歡的編輯器對其進行編輯。
建立新倉庫
從頭開始建立一個新倉庫非常簡單
$ mkdir project $ cd project $ git init
如果你有一些初始內容(例如,一個 tarball)
$ tar xzvf project.tar.gz $ cd project $ git init $ git add . # include everything below ./ in the first commit: $ git commit
如何進行提交
建立一個新提交需要三個步驟
-
使用你喜歡的編輯器對工作目錄進行一些更改。
-
告知 Git 你的更改。
-
使用你在第 2 步中告知 Git 的內容建立提交。
在實踐中,你可以根據需要多次交替和重複步驟 1 和 2:為了跟蹤你在步驟 3 中想要提交的內容,Git 在一個名為“索引”(index)的特殊暫存區中維護了樹內容的快照。
最初,索引的內容將與 HEAD 的內容相同。因此,顯示 HEAD 和索引之間差異的命令 git
diff
--cached
在那時應該不會產生任何輸出。
修改索引很簡單
要使用新檔案或修改後的檔案的內容更新索引,請使用
$ git add path/to/file
要從索引和工作樹中刪除檔案,請使用
$ git rm path/to/file
每一步之後,你都可以驗證
$ git diff --cached
始終顯示 HEAD 和索引檔案之間的差異——這是如果你現在建立提交將提交的內容——並且
$ git diff
顯示工作樹和索引檔案之間的差異。
請注意,git
add
總是隻將檔案的當前內容新增到索引中;對同一檔案的進一步更改將被忽略,除非你再次對該檔案執行 git
add
。
當你準備好時,只需執行
$ git commit
Git 將提示你輸入提交資訊,然後建立新提交。使用以下命令檢查以確保它符合你的預期
$ git show
作為一項特殊快捷方式,
$ git commit -a
將更新索引中所有已修改或已刪除的檔案,並一步完成提交。
有許多命令對於跟蹤你即將提交的內容很有用
$ git diff --cached # difference between HEAD and the index; what # would be committed if you ran "commit" now. $ git diff # difference between the index file and your # working directory; changes that would not # be included if you ran "commit" now. $ git diff HEAD # difference between HEAD and working tree; what # would be committed if you ran "commit -a" now. $ git status # a brief per-file summary of the above.
你還可以使用 git-gui[1] 來建立提交、檢視索引和工作樹檔案中的更改,並單獨選擇要包含在索引中的差異塊(透過右鍵單擊差異塊並選擇“Stage Hunk For Commit”)。
建立好的提交資訊
雖然不是必需的,但最好以一行簡短(不超過 50 個字元)的更改摘要作為提交資訊的開頭,後面跟一個空行,然後是更詳細的描述。提交資訊中直到第一個空行之前的文字被視為提交標題,該標題在整個 Git 中都會使用。例如,git-format-patch[1] 將提交轉換為電子郵件,它將標題用作主題行,其餘提交內容用作正文。
忽略檔案
專案通常會生成你不想用 Git 跟蹤的檔案。這通常包括構建過程生成的檔案或編輯器建立的臨時備份檔案。當然,不用 Git 跟蹤檔案只是不對其呼叫 git
add
的問題。但這些未跟蹤的檔案散落在各處很快就會變得令人煩惱;例如,它們使得 git
add
.
幾乎毫無用處,並且它們會不斷出現在 git
status
的輸出中。
你可以透過在工作目錄的頂層建立一個名為 .gitignore
的檔案來告訴 Git 忽略某些檔案,其內容如下
# Lines starting with '#' are considered comments. # Ignore any file named foo.txt. foo.txt # Ignore (generated) html files, *.html # except foo.html which is maintained by hand. !foo.html # Ignore objects and archives. *.[oa]
有關語法的詳細說明,請參閱 gitignore[5]。你還可以將 .gitignore 檔案放置在工作樹中的其他目錄中,它們將應用於這些目錄及其子目錄。.gitignore
檔案可以像其他任何檔案一樣新增到你的倉庫中(只需像往常一樣執行 git
add
.gitignore
和 git
commit
),當排除模式(例如匹配構建輸出檔案的模式)對克隆你倉庫的其他使用者也有意義時,這會很方便。
如果你希望排除模式僅影響某些倉庫(而不是給定專案的每個倉庫),你可以將它們放入你的倉庫中名為 .git/info/exclude
的檔案,或由 core.excludesFile
配置變數指定的任何檔案中。某些 Git 命令也可以直接在命令列上接受排除模式。有關詳細資訊,請參閱 gitignore[5]。
如何合併
你可以使用 git-merge[1] 將兩個分叉的開發分支重新合併
$ git merge branchname
將分支 branchname
中的開發內容合併到當前分支中。
合併是透過合併自其歷史分叉以來 branchname
中所做的更改以及當前分支中直到最新提交所做的更改來完成的。當合並乾淨完成時,工作樹會被合併結果覆蓋;當合並導致衝突時,則會被部分合並的結果覆蓋。因此,如果你有未提交的更改涉及到與合併受影響的檔案相同的檔案,Git 將拒絕繼續。大多數情況下,你會希望在合併之前提交你的更改,如果你沒有這樣做,那麼 git-stash[1] 可以在你進行合併時將這些更改暫存起來,並在之後重新應用它們。
如果更改足夠獨立,Git 將自動完成合並並提交結果(或者在快進的情況下重用現有提交,見下文)。另一方面,如果存在衝突——例如,如果同一個檔案在遠端分支和本地分支中以兩種不同的方式被修改——那麼你將收到警告;輸出可能看起來像這樣
$ git merge next 100% (4/4) done Auto-merged file.txt CONFLICT (content): Merge conflict in file.txt Automatic merge failed; fix conflicts and then commit the result.
衝突標記會留在有問題的檔案中,手動解決衝突後,你可以用新內容更新索引並執行 Git commit,就像你通常建立新檔案時那樣。
如果你使用 gitk 檢查結果提交,你將看到它有兩個父提交,一個指向當前分支的頂部,另一個指向另一個分支的頂部。
解決合併
當合並未能自動解決時,Git 會將索引和工作樹置於一種特殊狀態,為你提供解決合併所需的所有資訊。
有衝突的檔案在索引中會被特殊標記,因此在你解決問題並更新索引之前,git-commit[1] 將會失敗
$ git commit file.txt: needs merge
此外,git-status[1] 會將這些檔案列為“未合併”,並且有衝突的檔案會新增衝突標記,如下所示
<<<<<<< HEAD:file.txt Hello world ======= Goodbye >>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
你只需編輯檔案來解決衝突,然後
$ git add file.txt $ git commit
請注意,提交資訊將已為你填寫了一些關於合併的資訊。通常你可以直接使用此預設資訊而無需更改,但如果需要,你可以新增自己的額外註釋。
以上就是解決簡單合併所需瞭解的全部內容。但 Git 還提供了更多資訊來幫助解決衝突
在合併期間獲取衝突解決幫助
所有 Git 能夠自動合併的更改都已新增到索引檔案中,因此 git-diff[1] 只顯示衝突。它使用一種不尋常的語法
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,5 @@@ ++<<<<<<< HEAD:file.txt +Hello world ++======= + Goodbye ++>>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt
請記住,我們解決此衝突後將提交的提交將有兩個父提交,而不是通常的一個:一個父提交將是 HEAD,即當前分支的尖端;另一個將是另一個分支的尖端,它暫時儲存在 MERGE_HEAD 中。
在合併期間,索引會保留每個檔案的三個版本。這三個“檔案階段”中的每一個都代表檔案的不同版本
$ git show :1:file.txt # the file in a common ancestor of both branches $ git show :2:file.txt # the version from HEAD. $ git show :3:file.txt # the version from MERGE_HEAD.
當你要求 git-diff[1] 顯示衝突時,它會在工作樹中對沖突的合併結果與階段 2 和 3 進行三方比較,以僅顯示內容來自雙方(混合)的差異塊(換句話說,當一個差異塊的合併結果僅來自階段 2 時,該部分不衝突且不顯示。階段 3 亦然)。
上面的差異顯示了 file.txt 的工作樹版本與階段 2 和階段 3 版本之間的差異。因此,它現在不使用單個 +
或 -
字首每行,而是使用兩列:第一列用於顯示第一個父提交與工作目錄副本之間的差異,第二列用於顯示第二個父提交與工作目錄副本之間的差異。(有關格式的詳細資訊,請參閱 git-diff-files[1] 的“組合差異格式”部分。)
以明顯的方式解決衝突後(但在更新索引之前),差異將如下所示
$ git diff diff --cc file.txt index 802992c,2b60207..0000000 --- a/file.txt +++ b/file.txt @@@ -1,1 -1,1 +1,1 @@@ - Hello world -Goodbye ++Goodbye world
這表明我們解決後的版本從第一個父提交中刪除了“Hello world”,從第二個父提交中刪除了“Goodbye”,並添加了“Goodbye world”,而這在之前兩者中都不存在。
一些特殊的差異選項允許將工作目錄與這些階段中的任何一個進行比較
$ git diff -1 file.txt # diff against stage 1 $ git diff --base file.txt # same as the above $ git diff -2 file.txt # diff against stage 2 $ git diff --ours file.txt # same as the above $ git diff -3 file.txt # diff against stage 3 $ git diff --theirs file.txt # same as the above.
當使用 ort 合併策略(預設)時,在用合併結果更新工作樹之前,Git 會寫入一個名為 AUTO_MERGE 的引用,反映它即將寫入的樹的狀態。具有文字衝突且無法自動合併的衝突路徑將像在工作樹中一樣,帶有衝突標記寫入到此樹中。因此,AUTO_MERGE 可以與 git-diff[1] 一起使用,以顯示你到目前為止為解決衝突所做的更改。以上述相同示例,解決衝突後我們得到
$ git diff AUTO_MERGE diff --git a/file.txt b/file.txt index cd10406..8bf5ae7 100644 --- a/file.txt +++ b/file.txt @@ -1,5 +1 @@ -<<<<<<< HEAD:file.txt -Hello world -======= -Goodbye ->>>>>>> 77976da35a11db4580b80ae27e8d65caf5208086:file.txt +Goodbye world
請注意,差異顯示我們刪除了衝突標記和內容行的兩個版本,並改寫為“Goodbye world”。
git-log[1] 和 gitk[1] 命令也為合併提供了特殊幫助
$ git log --merge $ gitk --merge
這些將顯示所有僅存在於 HEAD 或 MERGE_HEAD 上,並涉及到未合併檔案的提交。
你還可以使用 git-mergetool[1],它允許你使用 Emacs 或 kdiff3 等外部工具合併未合併的檔案。
每次你解決檔案中的衝突並更新索引時
$ git add file.txt
該檔案的不同階段將被“摺疊”,之後 git
diff
將(預設情況下)不再顯示該檔案的差異。
撤銷合併
如果你陷入困境並決定放棄一切,你總是可以透過以下方式返回到合併前的狀態
$ git merge --abort
或者,如果你已經提交了你想要放棄的合併,
$ git reset --hard ORIG_HEAD
然而,在某些情況下,最後一個命令可能很危險——如果某個你已經提交的提交本身可能已經合併到另一個分支中,則切勿丟棄它,因為這樣做可能會使後續合併混淆。
快進合併
上面沒有提到一個特殊情況,它被區別對待。通常,合併會產生一個合併提交,它有兩個父提交,分別指向兩條被合併的開發線。
然而,如果當前分支是另一個分支的祖先——即當前分支中存在的每個提交都已包含在另一個分支中——那麼 Git 只會執行“快進”;當前分支的 HEAD 會向前移動,指向被合併分支的 HEAD,而不會建立任何新提交。
修復錯誤
如果你搞砸了工作樹,但尚未提交你的錯誤,你可以使用以下命令將整個工作樹恢復到上次提交的狀態
$ git restore --staged --worktree :/
如果你做了一個後來希望沒有做過的提交,有兩種根本不同的方法來解決問題
-
你可以建立一個新提交來撤銷舊提交所做的任何更改。如果你的錯誤已經公開,這是正確的做法。
-
你可以回溯並修改舊提交。如果歷史已經公開,你絕不應該這樣做;Git 通常不期望專案的“歷史”發生變化,並且無法正確執行從歷史已更改的分支進行重複合併。
用新提交修復錯誤
建立一個新提交來恢復之前的更改非常容易;只需將 git-revert[1] 命令傳遞給錯誤的提交引用即可;例如,要恢復最近的提交
$ git revert HEAD
這將建立一個新提交,它會撤銷 HEAD 中的更改。你將有機會編輯新提交的提交資訊。
你也可以恢復更早的更改,例如倒數第二個
$ git revert HEAD^
在這種情況下,Git 會嘗試撤銷舊更改,同時保留自那時以來的任何更改。如果最近的更改與要恢復的更改重疊,那麼你將被要求手動修復衝突,就像解決合併的情況一樣。
透過重寫歷史來修復錯誤
如果問題提交是最新提交,並且你尚未公開該提交,那麼你可以直接使用 git
reset
銷燬它。
或者,你可以編輯工作目錄並更新索引來修復你的錯誤,就像你要建立一個新提交一樣,然後執行
$ git commit --amend
這將用一個包含你更改的新提交替換舊提交,同時給你一個機會先編輯舊提交資訊。
再次強調,你絕不應該對可能已經合併到另一個分支的提交進行此操作;在這種情況下,請改用 git-revert[1]。
也可以替換歷史中更早的提交,但這是一個高階主題,將留待另一章討論。
檢出檔案的舊版本
在撤銷之前不良更改的過程中,你可能會發現使用 git-restore[1] 檢出特定檔案的舊版本很有用。該命令
$ git restore --source=HEAD^ path/to/file
將 path/to/file 替換為其在提交 HEAD^ 中的內容,並更新索引以匹配。它不改變分支。
如果你只是想檢視檔案的舊版本,而不修改工作目錄,你可以使用 git-show[1] 來實現
$ git show HEAD^:path/to/file
這將顯示檔案的給定版本。
暫時擱置進行中的工作
當你正在進行復雜工作時,你發現了一個不相關但顯而易見的微不足道的 bug。你希望在繼續之前修復它。你可以使用 git-stash[1] 來儲存你當前的工作狀態,並在修復 bug 後(或者,可選地在不同的分支上完成修復後再回來),將進行中的更改從暫存區取出。
$ 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
分支 HEAD 的先前版本可達的提交。這種語法可以與任何接受提交的 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 會保留一個單獨的引用日誌,所以
$ git show HEAD@{"1 week ago"}
將顯示 HEAD 一週前指向的內容,而不是當前分支一週前指向的內容。這使你可以檢視你已檢出內容的修改歷史。
引用日誌預設保留 30 天,之後可能會被修剪。有關如何控制此修剪的詳細資訊,請參閱 git-reflog[1] 和 git-gc[1],並參閱 gitrevisions[7] 的“指定修訂”部分。
請注意,引用日誌歷史與普通 Git 歷史非常不同。普通曆史由處理同一專案的每個倉庫共享,而引用日誌歷史不共享:它只告訴你本地倉庫中的分支如何隨時間變化。
檢查懸空物件
在某些情況下,引用日誌可能無法幫助你。例如,假設你刪除了一個分支,然後意識到你需要它包含的歷史記錄。引用日誌也會被刪除;但是,如果你尚未修剪倉庫,那麼你可能仍然可以在 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
配置為從 origin 倉庫的 HEAD 分支獲取更改。因此,通常你只需簡單地執行以下操作即可完成上述任務
$ git pull
此命令將從遠端分支獲取更改到你的遠端跟蹤分支 origin/*
,並將預設分支合併到當前分支。
更一般地說,從遠端跟蹤分支建立的分支將預設從該分支拉取。有關如何控制這些預設值的詳細資訊,請參閱 branch.
<name>.remote
和 branch.
<name>.merge
選項在 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[1];它將在埠 9418 上監聽。預設情況下,它將允許訪問任何看起來像 Git 目錄幷包含魔術檔案 git-daemon-export-ok 的目錄。將一些目錄路徑作為 git
daemon
引數傳遞將進一步限制匯出到這些路徑。
你也可以將 git
daemon
作為 inetd 服務執行;有關詳細資訊,請參閱 git-daemon[1] 手冊頁。(特別參閱示例部分。)
透過 HTTP 匯出 Git 倉庫
Git 協議提供了更好的效能和可靠性,但在設定了 Web 伺服器的主機上,HTTP 匯出可能更容易設定。
你所需要做的就是將新建立的裸 Git 倉庫放置在由 Web 伺服器匯出的目錄中,並進行一些調整,以向 Web 客戶端提供他們所需的一些額外資訊
$ mv proj.git /home/you/public_html/proj.git $ cd proj.git $ git --bare update-server-info $ mv hooks/post-update.sample hooks/post-update
(有關最後兩行的解釋,請參閱 git-update-server-info[1] 和 githooks[5]。)
宣傳 proj.git
的 URL。然後其他人應該能夠從該 URL 克隆或拉取,例如使用如下命令列
$ git clone http://yourserver.com/~you/proj.git
(另請參閱 setup-git-server-over-http,瞭解使用 WebDAV 的更復雜設定,該設定也允許透過 HTTP 推送。)
推送更改到公共倉庫
最簡單的方法是使用 git-push[1] 和 ssh;要用你的 master
分支的最新狀態更新名為 master
的遠端分支,執行
$ git push ssh://yourserver.com/~you/proj.git master:master
或者直接
$ git push ssh://yourserver.com/~you/proj.git master
與 git
fetch
類似,如果這沒有導致 快進(fast-forward),git
push
將會報錯;有關處理此情況的詳細資訊,請參閱以下部分。
請注意,push
的目標通常是一個 裸倉庫(bare repository)。你也可以推送到一個包含已檢出工作樹的倉庫,但預設情況下,推送到更新當前已檢出分支的操作是被拒絕的,以防止混淆。有關詳細資訊,請參閱 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.
<name>.url
、branch.
<name>.remote
和 remote.
<name>.push
選項的解釋,請參閱 git-config[1]。
當 push 失敗時該怎麼辦
如果 push 不會導致遠端分支的 快進,那麼它將以類似以下內容的錯誤失敗
! [rejected] master -> master (non-fast-forward) error: failed to push some refs to '...' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
例如,如果你出現以下情況,可能會發生這種情況
-
使用
git
reset
--hard
移除已釋出的提交,或者 -
使用
git
commit
--amend
替換已釋出的提交(如 透過重寫歷史來修復錯誤 中所述),或者 -
使用
git
rebase
對任何已釋出的提交進行變基(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
提供了一種簡單的方法,讓維護者可以將這項工作委託給其他維護者,同時仍然允許對傳入更改進行可選審查。 -
由於每個開發者的倉庫都擁有專案歷史的完整副本,因此沒有哪個倉庫是特殊的,其他開發者接管專案維護工作非常簡單,無論是透過雙方協議,還是因為維護者變得不響應或難以合作。
-
缺乏一箇中心的“提交者”群體意味著對誰“加入”和誰“退出”的正式決策需求減少。
允許透過網頁瀏覽倉庫
gitweb cgi 指令碼為使用者提供了一種無需安裝 Git 即可輕鬆瀏覽專案修訂、檔案內容和日誌的方式。RSS/Atom feeds 和 blame/annotation 詳細資訊等功能可以有選擇地啟用。
git-instaweb[1] 命令提供了一種使用 gitweb 啟動瀏覽倉庫的簡單方法。使用 instaweb 時的預設伺服器是 lighttpd。
有關使用支援 CGI 或 Perl 的伺服器進行永久安裝的詳細說明,請參閱 Git 原始碼樹中的 gitweb/INSTALL 檔案和 gitweb[1]。
如何獲取僅包含最少歷史的 Git 倉庫
一個具有截斷歷史的 淺克隆(shallow clone),在只對專案近期歷史感興趣且從上游獲取完整歷史成本很高時非常有用。
透過指定 git-clone[1] 的 --depth
開關來建立 淺克隆。深度稍後可以使用 git-fetch[1] 的 --depth
開關進行更改,或者使用 --unshallow
恢復完整歷史。
在 淺克隆 中進行合併是可行的,只要合併基礎(merge base)在近期歷史中。否則,這將類似於合併不相關的歷史,並可能導致巨大的衝突。這個限制可能會使這種倉庫不適合用於基於合併的工作流。
示例
為 Linux 子系統維護者維護主題分支
這描述了 Tony Luck 如何以 Linux 核心 IA64 架構維護者的身份使用 Git。
他使用兩個公共分支
-
一個“測試”樹,補丁最初放置於此,以便在與其他正在進行的開發整合時獲得一些曝光。只要 Andrew 願意,他就可以將此樹拉入 -mm。
-
一個“釋出”樹,經過測試的補丁會移至此,進行最終的健全性檢查,並作為向上遊 Linus 傳送(透過給他傳送“請拉取”請求)的載體。
他還使用了一組臨時分支(“主題分支”),每個分支都包含一組邏輯分組的補丁。
要進行此設定,首先透過克隆 Linus 的公共樹來建立你的工作樹
$ git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git work $ cd work
Linus 的樹將儲存在名為 origin/master 的遠端跟蹤分支中,可以使用 git-fetch[1] 進行更新;你可以使用 git-remote[1] 設定一個“遠端”並使用 git-fetch[1] 保持其最新,來跟蹤其他公共樹;請參閱 倉庫和分支。
現在建立你將要工作的分支;這些分支從 origin/master 分支的當前尖端開始,並且應該被設定(使用 git-branch[1] 的 --track
選項)以便預設從 Linus 合併更改。
$ git branch --track test origin/master $ git branch --track release origin/master
這些可以使用 git-pull[1] 輕鬆保持最新。
$ git switch test && git pull $ git switch release && git pull
重要提示!如果這些分支中有任何本地更改,則此合併將在歷史中建立一個提交物件(如果沒有本地更改,Git 將簡單地執行“快進”合併)。許多人不喜歡這在 Linux 歷史中產生的“噪音”,因此你應該避免在 release
分支中隨意執行此操作,因為當你要求 Linus 從 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] 推送測試和釋出樹
$ git push mytree
或者只推送測試和釋出分支中的一個
$ git push mytree test
或
$ git push mytree release
現在應用社群的一些補丁。為儲存此補丁(或相關補丁組)的分支想一個簡潔有力的名稱,並從 Linus 分支的一個最近的穩定標籤建立一個新分支。為你的分支選擇一個穩定的基礎將:1) 幫助你:透過避免包含不相關或可能未經充分測試的更改 2) 幫助未來使用 git
bisect
查詢問題的錯誤獵手
$ git switch -c speed-up-spinlocks v2.6.35
現在你應用補丁,執行一些測試,並提交更改。如果補丁是多部分的系列,那麼你應該將每個部分作為單獨的提交應用到這個分支。
$ ... patch ... test ... commit [ ... patch ... test ... commit ]*
當你對這項更改的狀態滿意時,你可以將其合併到“測試”分支中,以便使其公開
$ git switch test && git merge speed-up-spinlocks
你不太可能在這裡遇到任何衝突……但如果你在這個步驟上花了一些時間並且也從上游拉取了新版本,那麼你可能會遇到。
過了一段時間,當足夠的時間過去並且測試完成時,你可以將相同的分支拉入 release
樹,準備向上遊提交。這就是將每個補丁(或補丁系列)保留在自己的分支中的價值所在。這意味著補丁可以按任何順序移動到 release
樹中。
$ git switch release && git merge speed-up-spinlocks
一段時間後,你會有許多分支,儘管你為它們選擇了好聽的名字,你可能會忘記它們是做什麼的,或者它們處於什麼狀態。要獲取特定分支中包含哪些更改的提醒,請使用
$ git log linux..branchname | git shortlog
要檢視它是否已合併到測試或釋出分支中,請使用
$ git log test..branchname
或
$ git log release..branchname
(如果此分支尚未合併,你將看到一些日誌條目。如果它已合併,則不會有任何輸出。)
一旦補丁完成大迴圈(從測試移至釋出,然後被 Linus 拉取,最後回到你的本地 origin/master
分支),則不再需要此更改的分支。當以下命令的輸出為空時,你將檢測到此情況
$ git log origin..branchname
為空。此時可以刪除分支
$ git branch -d branchname
有些更改非常微不足道,以至於沒有必要建立一個單獨的分支,然後合併到測試和釋出分支中。對於這些更改,只需直接應用到 release
分支,然後將其合併到 test
分支。
將你的工作推送到 mytree
後,你可以使用 git-request-pull[1] 準備一個“請拉取”請求訊息傳送給 Linus
$ git push mytree $ git request-pull origin mytree release
以下是一些可以進一步簡化所有這些操作的指令碼。
==== update script ==== # Update a branch in my Git tree. If the branch to be updated # is origin, then pull from kernel.org. Otherwise merge # origin/master branch into test|release branch case "$1" in test|release) git checkout $1 && git pull . origin ;; origin) before=$(git rev-parse refs/remotes/origin/master) git fetch origin after=$(git rev-parse refs/remotes/origin/master) if [ $before != $after ] then git log $before..$after | git shortlog fi ;; *) echo "usage: $0 origin|test|release" 1>&2 exit 1 ;; esac
==== merge script ==== # Merge a branch into either the test or release branch pname=$0 usage() { echo "usage: $pname branch test|release" 1>&2 exit 1 } git show-ref -q --verify -- refs/heads/"$1" || { echo "Can't see branch <$1>" 1>&2 usage } case "$2" in test|release) if [ $(git log $2..$1 | wc -c) -eq 0 ] then echo $1 already merged into $2 1>&2 exit 1 fi git checkout $2 && git pull . $1 ;; *) usage ;; esac
==== status script ==== # report on status of my ia64 Git tree gb=$(tput setab 2) rb=$(tput setab 1) restore=$(tput setab 9) if [ `git rev-list test..release | wc -c` -gt 0 ] then echo $rb Warning: commits in release that are not in test $restore git log test..release fi for branch in `git show-ref --heads | sed 's|^.*/||'` do if [ $branch = test -o $branch = release ] then continue fi echo -n $gb ======= $branch ====== $restore " " status= for ref in test release origin/master do if [ `git rev-list $ref..$branch | wc -c` -gt 0 ] then status=$status${ref:0:1} fi done case $status in trl) echo $rb Need to pull into test $restore ;; rl) echo "In test" ;; l) echo "Waiting for linus" ;; "") echo $rb All done $restore ;; *) echo $rb "<$status>" $restore ;; esac git log origin/master..$branch | git shortlog done
重寫歷史和維護補丁系列
通常,提交只新增到專案中,從不刪除或替換。Git 的設計基於此假設,違反此假設將導致 Git 的合併機制(例如)出錯。
然而,在某些情況下,違反這個假設可能很有用。
建立完美的補丁系列
假設你是一個大型專案的貢獻者,並且你想新增一個複雜的功能,並以一種讓其他開發者容易閱讀你的更改、驗證其正確性並理解你為什麼進行每項更改的方式來呈現它。
如果你將所有更改作為單個補丁(或提交)呈現,他們可能會發現一次消化太多。
如果你向他們展示你工作的完整歷史,包括錯誤、更正和死衚衕,他們可能會不知所措。
因此,理想情況通常是生成一系列補丁,使得
-
每個補丁都可以按順序應用。
-
每個補丁都包含一個邏輯更改,以及解釋該更改的訊息。
-
沒有補丁引入迴歸:在應用系列中的任何初始部分後,生成專案仍然可以編譯和工作,並且沒有以前沒有的錯誤。
-
完整的系列產生的結果與你自己的(可能更混亂!)開發過程相同。
我們將介紹一些可以幫助你完成此操作的工具,解釋如何使用它們,然後解釋由於你正在重寫歷史可能出現的一些問題。
使用 git rebase 保持補丁系列最新
假設你在遠端跟蹤分支 origin
上建立了一個分支 mywork
,並在其之上建立了一些提交
$ git switch -c mywork origin $ vi file.txt $ git commit $ vi otherfile.txt $ git commit ...
你尚未將任何內容合併到 mywork 中,因此它只是在 origin
之上的一系列簡單線性補丁
o--o--O <-- origin \ a--b--c <-- mywork
上游專案已經完成了一些更有趣的工作,並且 origin
已經向前推進
o--o--O--o--o--o <-- origin \ a--b--c <-- mywork
此時,你可以使用 pull
將你的更改合併回來;結果將建立一個新的合併提交,像這樣
o--o--O--o--o--o <-- origin \ \ a--b--c--m <-- mywork
然而,如果你更喜歡將 mywork 中的歷史保持為簡單的提交系列而不包含任何合併,你可以選擇使用 git-rebase[1]
$ git switch mywork $ git rebase origin
這將從 mywork 中移除你的每個提交,暫時將它們儲存為補丁(在一個名為 .git/rebase-apply
的目錄中),更新 mywork 以指向 origin 的最新版本,然後將每個已儲存的補丁應用到新的 mywork。結果將如下所示
o--o--O--o--o--o <-- origin \ a'--b'--c' <-- mywork
在此過程中,它可能會發現衝突。在這種情況下,它會停止並允許你解決衝突;解決衝突後,使用 git
add
更新索引內容,然後,不要執行 git
commit
,只需執行
$ git rebase --continue
Git 將繼續應用其餘的補丁。
在任何時候,你都可以使用 --abort
選項來中止此過程並將 mywork 恢復到你開始變基之前的狀態
$ git rebase --abort
如果你需要在分支中重新排序或編輯多個提交,使用 git
rebase
-i
可能會更容易,它允許你在變基期間重新排序和壓縮提交,以及標記它們進行單獨編輯。有關詳細資訊,請參閱 使用互動式變基,有關替代方案,請參閱 重新排序或從補丁系列中選擇。
重寫單個提交
我們在 透過重寫歷史來修復錯誤 中看到,你可以使用以下命令替換最近的提交
$ git commit --amend
這會將舊提交替換為包含你的更改的新提交,讓你有機會首先編輯舊的提交訊息。這對於修復你上次提交中的拼寫錯誤,或調整暫存不佳的提交的補丁內容非常有用。
如果你需要修改歷史中更深層的提交,可以使用 互動式變基的 edit
指令。
重新排序或從補丁系列中選擇
有時你想編輯歷史中更深層的提交。一種方法是使用 git
format-patch
建立一系列補丁,然後將狀態重置到補丁之前
$ git format-patch origin $ git reset --hard origin
然後根據需要修改、重新排序或刪除補丁,然後再使用 git-am[1] 再次應用它們
$ git am *.patch
使用互動式變基
你也可以使用互動式變基來編輯補丁系列。這與 使用 format-patch
重新排序補丁系列 相同,因此請使用你最喜歡的介面。
將你當前的 HEAD 變基到你想要原樣保留的最後一個提交上。例如,如果你想重新排序最後 5 個提交,請使用
$ git rebase -i HEAD~5
這將在你的編輯器中開啟一個列表,其中包含執行變基所需的步驟。
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
如註釋中所述,你可以透過編輯列表來重新排序提交、將它們壓縮在一起、編輯提交訊息等。一旦你滿意,儲存列表並關閉編輯器,變基將開始。
當 pick
被替換為 edit
,或者列表中的某個步驟無法自動解決衝突並需要你幫助時,變基將停止。當你完成編輯和/或解決衝突後,你可以使用 git
rebase
--continue
繼續。如果你覺得情況變得過於複雜,你始終可以使用 git
rebase
--abort
退出。即使變基完成後,你仍然可以透過使用 reflog 恢復原始分支。
有關此過程和更多技巧的詳細討論,請參閱 git-rebase[1] 的“互動模式”部分。
重寫歷史的問題
重寫分支歷史的主要問題與合併有關。假設有人拉取了你的分支並將其合併到他們的分支中,結果大致如下
o--o--O--o--o--o <-- origin \ \ t--t--t--m <-- their branch:
然後假設你修改了最後三個提交
o--o--o <-- new head of origin / o--o--O--o--o--o <-- old head of origin
如果我們在一個倉庫中一起檢查所有這些歷史,它將看起來像
o--o--o <-- new head of origin / o--o--O--o--o--o <-- old head of origin \ \ t--t--t--m <-- their branch:
Git 無法知道新頭是舊頭的更新版本;它對待這種情況的方式與兩個開發者並行獨立地對舊頭和新頭進行工作時完全相同。此時,如果有人試圖將新頭合併到他們的分支中,Git 將嘗試將兩條(舊的和新的)開發線合併在一起,而不是嘗試用新的替換舊的。結果很可能出乎意料。
你仍然可以選擇釋出其歷史被重寫的分支,並且其他人能夠拉取這些分支以檢查或測試它們可能很有用,但他們不應嘗試將此類分支拉入他們自己的工作中。
對於支援正確合併的真正分散式開發,已釋出的分支絕不應該被重寫。
為什麼二分查詢合併提交比二分查詢線性歷史更難
git-bisect[1] 命令可以正確處理包含合併提交的歷史。然而,當它找到的提交是合併提交時,使用者可能需要比平時更努力地找出為什麼該提交引入了問題。
想象一下這段歷史
---Z---o---X---...---o---A---C---D \ / o---o---Y---...---o---B
假設在上方開發線中,存在於 Z 的某個函式的含義在提交 X 處發生了改變。從 Z 到 A 的提交改變了函式的實現和所有存在於 Z 的呼叫點,以及它們新增的新呼叫點,以保持一致。在 A 處沒有錯誤。
同時,假設在下方開發線中,某人在提交 Y 處為該函式添加了一個新的呼叫點。從 Z 到 B 的提交都假定該函式的舊語義,並且呼叫者和被呼叫者彼此一致。在 B 處也沒有錯誤。
進一步假設兩條開發線在 C 處乾淨地合併,因此不需要解決衝突。
然而,C 處的程式碼是損壞的,因為在下方開發線上新增的呼叫者尚未轉換為上方開發線上引入的新語義。因此,如果你只知道 D 是壞的,Z 是好的,並且 git-bisect[1] 將 C 標識為罪魁禍首,你將如何找出問題是由於語義的這種改變?
當 git
bisect
的結果是一個非合併提交時,你通常應該能夠透過只檢查該提交來發現問題。開發者可以透過將他們的更改分解為小的獨立提交來簡化此過程。然而,在上述情況下這無濟於事,因為問題從任何單個提交的檢查中都不明顯;相反,需要對開發有一個全域性的檢視。更糟糕的是,有問題的函式中的語義更改可能只是上方開發線中更改的一小部分。
另一方面,如果在 C 處不是合併,而是將 Z 到 B 之間的歷史變基到 A 的頂部,你將得到這個線性歷史
---Z---o---X--...---o---A---o---o---Y*--...---o---B*--D*
在 Z 和 D* 之間進行二分查詢將命中單個罪魁禍首提交 Y*,並且理解 Y* 為什麼損壞可能會更容易。
部分出於這個原因,許多經驗豐富的 Git 使用者,即使在進行一個大量使用合併的專案時,也會在釋出前透過對最新上游版本進行變基來保持歷史的線性。
高階分支管理
抓取單個分支
除了使用 git-remote[1],你還可以選擇一次只更新一個分支,並將其儲存在本地的任意名稱下
$ git fetch origin todo:my-todo-work
第一個引數 origin
只是告訴 Git 從你最初克隆的倉庫中抓取。第二個引數告訴 Git 從遠端倉庫抓取名為 todo
的分支,並將其儲存在本地名為 refs/heads/my-todo-work
的位置。
你也可以從其他倉庫抓取分支;因此
$ git fetch git://example.com/proj.git master:example-master
將建立一個名為 example-master
的新分支,並在其中儲存來自給定 URL 倉庫中名為 master
的分支。如果你已經有一個名為 example-master 的分支,它將嘗試 快進 到 example.com 的 master 分支所指定的提交。更詳細地說
git fetch 和快進
在前面的示例中,當更新現有分支時,git
fetch
會檢查以確保遠端分支上的最新提交是你本地分支副本上最新提交的後代,然後才將你的本地分支副本更新為指向新提交。Git 將此過程稱為 快進。
快進看起來像這樣
o--o--o--o <-- old head of the branch \ o--o--o <-- new head of the branch
在某些情況下,新頭可能實際上不是舊頭的後代。例如,開發者可能意識到犯了一個嚴重錯誤並決定回溯,導致出現以下情況
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
標誌來強制更新所有抓取的分支,如下所示
$ 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 更直觀。
我們從最重要的 物件資料庫(object database) 和 索引(index) 開始。
物件資料庫
我們已經在 理解歷史:提交 中看到,所有提交都儲存在一個 40 位的“物件名稱”下。實際上,表示專案歷史所需的所有資訊都儲存在具有此類名稱的物件中。在每種情況下,名稱都是透過對物件內容進行 SHA-1 雜湊計算得出的。SHA-1 雜湊是一種加密雜湊函式。這對我們來說意味著不可能找到兩個具有相同名稱的不同物件。這有許多優點,其中包括
-
Git 可以透過比較名稱來快速確定兩個物件是否相同。
-
由於物件名稱在每個倉庫中都以相同的方式計算,因此儲存在兩個倉庫中的相同內容將始終以相同的名稱儲存。
-
Git 在讀取物件時可以檢測到錯誤,透過檢查物件的名稱是否仍然是其內容的 SHA-1 雜湊。
(有關物件格式和 SHA-1 計算的詳細資訊,請參閱 物件儲存格式。)
有四種不同型別的物件:“blob”、“tree”、“commit”和“tag”。
-
“blob”物件 用於儲存檔案資料。
-
“tree”物件 將一個或多個“blob”物件繫結到目錄結構中。此外,tree 物件可以引用其他 tree 物件,從而建立目錄層次結構。
-
“commit”物件 將此類目錄層次結構繫結到修訂的 有向無環圖(directed acyclic graph) 中——每個提交都包含一個 tree 物件的名稱,該 tree 物件指定了提交時的目錄層次結構。此外,一個提交引用“父”提交物件,這些物件描述了我們如何到達該目錄層次結構的歷史。
-
“tag”物件 象徵性地標識並可用於簽署其他物件。它包含另一個物件的物件名稱和型別、一個符號名稱(當然!)以及可選的簽名。
物件型別的一些更詳細資訊
提交物件
“commit”物件將 tree 的物理狀態與我們如何到達那裡以及為什麼到達那裡的描述聯絡起來。使用 git-show[1] 或 git-log[1] 的 --pretty=raw
選項來檢查你最喜歡的提交
$ 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 名稱(如下所述),表示某個時間點目錄的內容。
-
父提交:代表專案歷史中緊前步驟的一個或多個提交的 SHA-1 名稱。上面的示例有一個父提交;合併提交可能有一個以上。沒有父提交的提交稱為“根”提交,代表專案的初始修訂。每個專案必須至少有一個根。一個專案也可以有多個根,儘管這不常見(或不一定是好主意)。
-
作者:負責此更改的人的姓名,以及日期。
-
提交者:實際建立提交的人的姓名,以及完成日期。這可能與作者不同,例如,如果作者是撰寫補丁並將其透過電子郵件傳送給使用它建立提交的人。
-
描述此提交的註釋。
請注意,提交本身不包含任何有關實際更改的資訊;所有更改都是透過比較此提交引用的 tree 內容與其父提交關聯的 tree 來計算的。特別是,Git 不會嘗試顯式記錄檔案重新命名,儘管它可以識別在更改路徑上存在相同檔案資料時暗示重新命名的情況。(例如,請參閱 git-diff[1] 的 -M
選項)。
一個提交通常由 git-commit[1] 建立,該命令建立一個提交,其父級通常是當前的 HEAD,其樹則取自當前儲存在索引中的內容。
樹物件(Tree Object)
多功能的 git-show[1] 命令也可用於檢查樹物件,但 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 ...
如你所見,一個樹物件包含一個條目列表,每個條目都有模式、物件型別、SHA-1 名稱和名稱,並按名稱排序。它代表了單個目錄樹的內容。
物件型別可以是 blob,代表檔案的內容,或者另一個樹,代表子目錄的內容。由於樹和 blob,像所有其他物件一樣,都透過其內容的 SHA-1 雜湊值命名,因此兩個樹的 SHA-1 名稱相同,當且僅當它們的內容(包括遞迴地,所有子目錄的內容)完全相同。這使得 Git 可以快速確定兩個相關樹物件之間的差異,因為它會忽略任何具有相同物件名稱的條目。
(注意:在存在子模組的情況下,樹也可以將提交作為條目。有關文件,請參見 子模組。)
請注意,所有檔案的模式都是 644 或 755:Git 實際上只關注可執行位。
Blob 物件
你可以使用 git-show[1] 來檢查 blob 的內容;例如,從上面的樹中,COPYING
條目中的 blob:
$ git show 6ff87c4664 Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. ...
“blob”物件不過是資料的二進位制大塊。它不引用任何其他內容,也沒有任何屬性。
由於 blob 完全由其資料定義,如果目錄樹中(或儲存庫的多個不同版本中)的兩個檔案具有相同的內容,它們將共享同一個 blob 物件。該物件完全獨立於其在目錄樹中的位置,重新命名檔案不會改變該檔案所關聯的物件。
請注意,任何樹或 blob 物件都可以使用 git-show[1] 配合 <revision>:<path> 語法進行檢查。這有時對於瀏覽當前未檢出的樹內容很有用。
信任
如果你從一個來源收到一個 blob 的 SHA-1 名稱,並從另一個(可能不受信任的)來源收到其內容,只要 SHA-1 名稱一致,你仍然可以相信這些內容是正確的。這是因為 SHA-1 的設計使得找到產生相同雜湊值的不同內容是不可行的。
類似地,你只需要信任一個頂層樹物件的 SHA-1 名稱,就可以信任它所指向的整個目錄的內容;如果你從一個受信任的來源收到一個提交的 SHA-1 名稱,那麼你可以輕鬆驗證透過該提交的父級可達的整個提交歷史,以及這些提交所引用的所有樹的內容。
因此,為了在系統中引入一些真正的信任,你唯一需要做的就是數字簽名一份特殊的筆記,其中包含一個頂層提交的名稱。你的數字簽名向他人表明你信任該提交,而提交歷史的不可變性則告訴他人他們可以信任整個歷史。
換句話說,你只需傳送一封電子郵件,告訴人們頂層提交的名稱(SHA-1 雜湊),然後使用 GPG/PGP 等工具對該電子郵件進行數字簽名,就可以輕鬆驗證整個存檔。
為了協助這一點,Git 還提供了標籤物件……
標籤物件(Tag Object)
一個標籤物件包含一個物件、物件型別、標籤名稱、建立標籤的人(“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-----
請參閱 git-tag[1] 命令,瞭解如何建立和驗證標籤物件。(請注意,git-tag[1] 也可以用於建立“輕量級標籤”,它們根本不是標籤物件,而只是名稱以 refs/tags/
開頭的簡單引用)。
Git 如何高效儲存物件:pack 檔案
新建立的物件最初以物件 SHA-1 雜湊命名(儲存在 .git/objects
中)的檔案形式建立。
不幸的是,一旦專案擁有大量物件,這個系統就會變得效率低下。在一箇舊專案上試試這個:
$ git count-objects 6930 objects, 47620 kilobytes
第一個數字是儲存在單個檔案中的物件數量。第二個是這些“鬆散”物件佔用的空間大小。
你可以透過將這些鬆散物件移動到“pack 檔案”中來節省空間並加快 Git 速度,pack 檔案以高效壓縮格式儲存一組物件;pack 檔案的格式細節可以在 gitformat-pack[5] 中找到。
要將鬆散物件放入 pack 檔案,只需執行 git repack:
$ git repack Counting objects: 6020, done. Delta compression using up to 4 threads. Compressing objects: 100% (6020/6020), done. Writing objects: 100% (6020/6020), done. Total 6020 (delta 4070), reused 0 (delta 0)
這會在 .git/objects/pack/ 中建立一個單獨的“pack 檔案”,其中包含所有當前未打包的物件。然後你可以執行:
$ git prune
來移除所有現在包含在 pack 檔案中的“鬆散”物件。這還會移除任何未引用的物件(例如,當你使用 git
reset
移除提交時可能建立的物件)。你可以透過檢視 .git/objects
目錄或執行以下命令來驗證鬆散物件是否已消失:
$ git count-objects 0 objects, 0 kilobytes
儘管物件檔案已經消失,但引用這些物件的任何命令都將像以前一樣正常工作。
git-gc[1] 命令為你執行打包、修剪等操作,因此通常是你唯一需要的高階命令。
懸空物件
git-fsck[1] 命令有時會抱怨懸空物件。它們不是問題。
懸空物件最常見的原因是你重新基於了一個分支,或者你從其他重新基於分支的人那裡拉取了程式碼——請參閱 重寫歷史和維護補丁系列。在這種情況下,原始分支的舊 HEAD 仍然存在,它所指向的一切也仍然存在。只是分支指標本身不再存在了,因為你用另一個替換了它。
還有其他情況會導致懸空物件。例如,“懸空 blob”可能是因為你 git
add
了一個檔案,但在你實際提交併使其成為大局的一部分之前,你更改了該檔案中的其他內容並提交了那個**更新的**內容——你最初新增的舊狀態最終沒有被任何提交或樹指向,所以它現在是一個懸空 blob 物件。
類似地,當“ort”合併策略執行時,如果發現存在交叉合併,從而有多個合併基礎(這相當不尋常,但確實會發生),它將生成一個臨時的中間樹(如果有很多交叉合併並且超過兩個合併基礎,甚至可能更多)作為臨時的內部合併基礎,同樣,這些都是真實的物件,但最終結果不會指向它們,因此它們最終會“懸空”在你的倉庫中。
通常,懸空物件沒什麼好擔心的。它們甚至可能非常有用:如果你搞砸了什麼,懸空物件可以是你恢復舊樹的方式(比如說,你執行了 rebase,然後意識到你真的不想那樣做——你可以檢視你有哪些懸空物件,並決定將你的 HEAD 重置到某個舊的懸空狀態)。
對於提交,你可以直接使用:
$ gitk <dangling-commit-sha-goes-here> --not --all
這會要求顯示所有可從給定提交到達,但不可從任何分支、標籤或其他引用到達的歷史記錄。如果你認為它是你想要的東西,你總是可以為它建立一個新的引用,例如:
$ git branch recovered-branch <dangling-commit-sha-goes-here>
對於 blob 和樹,你不能做同樣的事情,但你仍然可以檢查它們。你可以直接執行:
$ git show <dangling-blob/tree-sha-goes-here>
來顯示 blob 的內容(或者,對於樹,基本上是該目錄的 ls
的內容),這可能會給你一些關於留下該懸空物件的操作的線索。
通常,懸空 blob 和樹並不是很有趣。它們幾乎總是因為是半途而廢的合併基礎(如果你的合併衝突是手動解決的,blob 甚至常常包含合併標記),或者僅僅是因為你用 Ctrl+C 之類的操作中斷了 git
fetch
,導致一些新物件留在物件資料庫中,但只是懸空且無用。
總之,一旦你確定你對任何懸空狀態不感興趣,你就可以修剪所有不可達的物件:
$ git prune
它們就會消失。(你只應在靜止的倉庫上執行 git
prune
——這有點像執行檔案系統 fsck 恢復:你不想在檔案系統掛載時這樣做。git
prune
的設計是為了在同時訪問倉庫的情況下不造成任何傷害,但你可能會收到令人困惑或可怕的訊息。)
從倉庫損壞中恢復
根據設計,Git 謹慎對待被信任的資料。然而,即使 Git 本身沒有 bug,硬體或作業系統錯誤仍然可能導致資料損壞。
解決此類問題的首要防禦是備份。你可以使用克隆(clone),或者僅僅使用 cp、tar 或任何其他備份機制來備份 Git 目錄。
作為最後手段,你可以搜尋損壞的物件並嘗試手動替換它們。在嘗試此操作之前,請備份你的倉庫,以防在此過程中造成更大的損壞。
我們將假設問題是單個缺失或損壞的 blob,這有時是一個可解決的問題。(恢復缺失的樹,尤其是提交,要**困難得多**)。
開始之前,請驗證是否存在損壞,並使用 git-fsck[1] 找出損壞的位置;這可能很耗時。
假設輸出如下所示:
$ git fsck --full --no-dangling broken link from tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 to blob 4b9458b3786228369c63936db65827de3cc06200 missing blob 4b9458b3786228369c63936db65827de3cc06200
現在你知道 blob 4b9458b3 缺失,並且樹 2d9263c6 指向它。如果你能找到一個該缺失 blob 物件的副本,可能在其他倉庫中,你可以將其移動到 .git/objects/4b/9458b3...
中就完成了。假設你找不到。你仍然可以使用 git-ls-tree[1] 檢查指向它的樹,它可能會輸出類似以下內容:
$ git ls-tree 2d9263c6d23595e7cb2a21e5ebbb53655278dff8 100644 blob 8d14531846b95bfa3564b58ccfb7913a034323b8 .gitignore 100644 blob ebf9bf84da0aab5ed944264a5db2a65fe3a3e883 .mailmap 100644 blob ca442d313d86dc67e0a2e5d584b465bd382cbf5c COPYING ... 100644 blob 4b9458b3786228369c63936db65827de3cc06200 myfile ...
所以現在你知道缺失的 blob 是名為 myfile
的檔案的資料。而且你很有可能也能識別出目錄——假設它在 somedirectory
中。如果你幸運的話,缺失的副本可能與你在工作樹中檢出的 somedirectory/myfile
處的副本相同;你可以使用 git-hash-object[1] 測試是否正確:
$ git hash-object -w somedirectory/myfile
它將建立並存儲一個包含 somedirectory/myfile 內容的 blob 物件,並輸出該物件的 SHA-1 值。如果你非常幸運,它可能是 4b9458b3786228369c63936db65827de3cc06200,在這種情況下,你猜對了,損壞已修復!
否則,你需要更多資訊。你如何判斷哪個版本的檔案丟失了?
最簡單的方法是使用:
$ git log --raw --all --full-history -- somedirectory/myfile
因為你要求原始輸出,所以你現在會得到類似以下內容:
commit abc Author: Date: ... :100644 100644 4b9458b newsha M somedirectory/myfile commit xyz Author: Date: ... :100644 100644 oldsha 4b9458b M somedirectory/myfile
這告訴你檔案的緊隨其後的版本是“newsha”,而緊隨其前的版本是“oldsha”。你還知道從 oldsha 變為 4b9458b 以及從 4b9458b 變為 newsha 的變更所附帶的提交訊息。
如果你一直提交足夠小的更改,你現在很可能能夠重建中間狀態 4b9458b 的內容。
如果你能做到這一點,你現在可以建立缺失的物件:
$ git hash-object -w <recreated-file>
你的倉庫就又恢復正常了!
(順便說一句,你可以忽略 fsck
,直接開始做:
$ git log --raw --all
然後只需在該整個內容中查詢缺失物件的 sha (4b9458b)。這取決於你——Git 確實**擁有**大量資訊,只是缺少一個特定的 blob 版本。
索引(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
請注意,在舊的文件中,你可能會看到索引被稱為“當前目錄快取”或簡稱“快取”。它有三個重要的特性:
-
索引包含生成單個(唯一確定的)樹物件所需的所有資訊。
例如,執行 git-commit[1] 會從索引生成這個樹物件,將其儲存在物件資料庫中,並將其用作與新提交關聯的樹物件。
-
索引能夠快速比較它定義的樹物件和工作樹。
它透過為每個條目儲存一些額外資料(例如最後修改時間)來實現這一點。這些資料在上面沒有顯示,也沒有儲存在建立的樹物件中,但它可以用來快速確定工作目錄中的哪些檔案與索引中儲存的內容不同,從而避免 Git 必須讀取這些檔案的所有資料來查詢更改。
-
它能夠有效地表示不同樹物件之間的合併衝突資訊,允許每個路徑名與關於所涉樹的足夠資訊關聯,以便你可以在它們之間建立三向合併。
我們在 合併期間獲取衝突解決幫助 中看到,在合併期間,索引可以儲存單個檔案的多個版本(稱為“階段”)。上面 git-ls-files[1] 輸出中的第三列是階段號,對於有合併衝突的檔案,其值將非 0。
因此,索引是一種臨時暫存區,其中填充了你正在處理的樹。
如果你完全清除索引,只要你擁有它所描述的樹的名稱,你通常就不會丟失任何資訊。
子模組
大型專案通常由更小、獨立的模組組成。例如,一個嵌入式 Linux 發行版原始碼樹會包含發行版中的每個軟體元件及其一些本地修改;一個電影播放器可能需要針對特定、已知可用的解壓縮庫版本進行構建;幾個獨立的程式可能共享相同的構建指令碼。
使用集中式版本控制系統,這通常透過將每個模組包含在一個單一倉庫中來實現。開發人員可以檢出所有模組或只檢出他們需要處理的模組。他們甚至可以在一個提交中修改多個模組的檔案,同時移動事物或更新 API 和翻譯。
Git 不允許部分檢出,因此在 Git 中複製這種方法將迫使開發人員保留他們不感興趣觸及的模組的本地副本。在巨大的檢出中進行提交會比你預期的慢,因為 Git 必須掃描每個目錄以查詢更改。如果模組有大量本地歷史記錄,克隆將耗費很長時間。
從好的方面看,分散式版本控制系統可以更好地與外部源整合。在集中式模型中,外部專案的單一任意快照從其自己的版本控制中匯出,然後匯入到本地版本控制的供應商分支中。所有歷史記錄都被隱藏了。透過分散式版本控制,你可以克隆整個外部歷史記錄,並更容易地跟蹤開發和重新合併本地更改。
Git 的子模組支援允許倉庫包含作為子目錄的外部專案的檢出。子模組保持其自身的身份;子模組支援僅儲存子模組倉庫位置和提交 ID,因此克隆包含專案(“superproject”)的其他開發人員可以輕鬆地克隆所有子模組的相同版本。可以對超級專案進行部分檢出:你可以告訴 Git 不克隆、克隆部分或克隆所有子模組。
從 Git 1.5.3 開始,git-submodule[1] 命令可用。Git 1.5.2 的使用者可以在倉庫中查詢子模組提交併手動檢出;更早的版本根本無法識別子模組。
要了解子模組支援如何工作,請建立四個示例倉庫,它們稍後可以用作子模組:
$ mkdir ~/git $ cd ~/git $ for i in a b c d do mkdir $i cd $i git init echo "module $i" > $i.txt git add $i.txt git commit -m "Initial commit, submodule $i" cd .. done
現在建立超級專案並新增所有子模組:
$ mkdir super $ cd super $ git init $ for i in a b c d do git submodule add ~/git/$i $i done
注意
|
如果你打算釋出你的超級專案,請不要在此處使用本地 URL! |
檢視 git
submodule
建立了哪些檔案:
$ ls -a . .. .git .gitmodules a b c d
命令 git
submodule
add
<repo> <path> 執行以下幾項操作:
-
它將子模組從 <repo> 克隆到當前目錄下的指定 <path>,並預設檢出 master 分支。
-
它將子模組的克隆路徑新增到 gitmodules[5] 檔案中,並將此檔案新增到索引中,準備提交。
-
它將子模組的當前提交 ID 新增到索引中,準備提交。
提交超級專案:
$ git commit -m "Add submodules a, b, c and d."
現在克隆超級專案:
$ cd .. $ git clone super cloned $ cd cloned
子模組目錄在那裡,但它們是空的:
$ ls -a a . .. $ git submodule status -d266b9873ad50488163457f025db7cdd9683d88b a -e81d457da15309b4fef4249aba9b50187999670d b -c1536a972b9affea0f16e0680ba87332dc059146 c -d96249ff5d57de5de093e6baff9e0aafa5276a74 d
注意
|
上面顯示的提交物件名稱對你來說會有所不同,但它們應該與你的倉庫的 HEAD 提交物件名稱匹配。你可以透過執行 git ls-remote ../a 來檢查。 |
拉取子模組是一個兩步過程。首先執行 git
submodule
init
將子模組倉庫 URL 新增到 .git/config
:
$ git submodule init
現在使用 git
submodule
update
克隆倉庫並檢出超級專案中指定的提交:
$ git submodule update $ cd a $ ls -a . .. .git a.txt
git
submodule
update
和 git
submodule
add
之間的一個主要區別是,git
submodule
update
檢出的是一個特定的提交,而不是分支的尖端。它就像檢出一個標籤:HEAD 是分離的,所以你不在分支上工作。
$ git branch * (detached from d266b98) master
如果你想在子模組中進行更改,並且你的 HEAD 是分離的,那麼你應該建立或檢出一個分支,進行更改,在子模組內釋出更改,然後更新超級專案以引用新的提交:
$ git switch master
或
$ git switch -c fix-up
然後:
$ echo "adding a line again" >> a.txt $ git commit -a -m "Updated the submodule from within the superproject." $ git push $ cd .. $ git diff diff --git a/a b/a index d266b98..261dfac 160000 --- a/a +++ b/a @@ -1 +1 @@ -Subproject commit d266b9873ad50488163457f025db7cdd9683d88b +Subproject commit 261dfac35cb99d380eb966e102c1197139f7fa24 $ git add a $ git commit -m "Updated submodule a." $ git push
如果你也想更新子模組,那麼在 git
pull
之後你必須執行 git
submodule
update
。
子模組的陷阱
在釋出引用子模組的超級專案的更改之前,請務必先發布子模組的更改。如果你忘記釋出子模組的更改,其他人將無法克隆倉庫:
$ cd ~/git/super/a $ echo i added another line to this file >> a.txt $ git commit -a -m "doing it wrong this time" $ cd .. $ git add a $ git commit -m "Updated submodule a again." $ git push $ cd ~/git/cloned $ git pull $ git submodule update error: pathspec '261dfac35cb99d380eb966e102c1197139f7fa24' did not match any file(s) known to git. Did you forget to 'git add'? Unable to checkout '261dfac35cb99d380eb966e102c1197139f7fa24' in submodule path 'a'
在較舊的 Git 版本中,很容易忘記在子模組中提交新增或修改的檔案,這會悄無聲息地導致與不推送子模組更改類似的問題。從 Git 1.7.0 開始,超級專案中的 git
status
和 git
diff
都會在子模組包含新增或修改檔案時將其顯示為已修改,以防止意外提交這種狀態。git
diff
在生成補丁輸出或與 --submodule
選項一起使用時,也會在工作樹一側新增 -dirty
:
$ git diff diff --git a/sub b/sub --- a/sub +++ b/sub @@ -1 +1 @@ -Subproject commit 3f356705649b5d566d97ff843cf193359229a453 +Subproject commit 3f356705649b5d566d97ff843cf193359229a453-dirty $ git diff --submodule Submodule sub 3f35670..3f35670-dirty:
你也不應該將子模組中的分支回溯到超出任何超級專案曾經記錄過的提交之外。
如果在未檢出分支的情況下在子模組中進行並提交了更改,那麼執行 git
submodule
update
是不安全的。它們將被靜默覆蓋:
$ cat a.txt module a $ echo line added from private2 >> a.txt $ git commit -a -m "line added inside private2" $ cd .. $ git submodule update Submodule path 'a': checked out 'd266b9873ad50488163457f025db7cdd9683d88b' $ cd a $ cat a.txt module a
注意
|
這些更改在子模組的 reflog 中仍然可見。 |
如果你的子模組工作樹中有未提交的更改,git
submodule
update
將不會覆蓋它們。相反,你會收到通常的關於無法從髒分支切換的警告。
低層 Git 操作
許多高階命令最初都是作為 shell 指令碼實現的,它們使用少量核心的低層 Git 命令。這些在執行不尋常的 Git 操作時仍然很有用,或者只是作為理解其內部工作原理的一種方式。
物件訪問和操作
git-cat-file[1] 命令可以顯示任何物件的內容,儘管高階的 git-show[1] 通常更有用。
git-commit-tree[1] 命令允許構建具有任意父級和樹的提交。
可以使用 git-write-tree[1] 建立樹,並可以透過 git-ls-tree[1] 訪問其資料。兩個樹可以使用 git-diff-tree[1] 進行比較。
標籤使用 git-mktag[1] 建立,簽名可以使用 git-verify-tag[1] 驗證,儘管通常使用 git-tag[1] 更簡單。
工作流程
高階操作,例如 git-commit[1] 和 git-restore[1],透過在工作樹、索引和物件資料庫之間移動資料來工作。Git 提供了單獨執行這些步驟的低層操作。
通常,所有 Git 操作都在索引檔案上進行。有些操作**純粹**在索引檔案上工作(顯示索引的當前狀態),但大多數操作在索引檔案與資料庫或工作目錄之間移動資料。因此,有四種主要組合:
工作目錄 → 索引
git-update-index[1] 命令用工作目錄中的資訊更新索引。你通常透過指定要更新的檔名來更新索引資訊,如下所示:
$ git update-index filename
但是為了避免檔名萬用字元等常見錯誤,該命令通常不會新增全新的條目或刪除舊的條目,即它通常只會更新現有的快取條目。
要告訴 Git,你確實意識到某些檔案不再存在,或者應該新增新檔案,你應該分別使用 --remove
和 --add
標誌。
注意!--remove
標誌**不**意味著後續檔名必然會被刪除:如果檔案仍然存在於你的目錄結構中,索引將根據它們的新狀態進行更新,而不是刪除。唯一 --remove
的意思是 update-index 將把已刪除的檔案視為一個有效的東西,如果檔案確實不再存在,它將相應地更新索引。
作為一個特例,你還可以執行 git
update-index
--refresh
,它將重新整理每個索引的“stat”資訊以匹配當前的 stat 資訊。它**不會**更新物件狀態本身,它只會更新用於快速測試物件是否仍與其舊的後端儲存物件匹配的欄位。
前面介紹的 git-add[1] 只是 git-update-index[1] 的一個封裝。
索引 → 物件資料庫
你使用程式將當前的索引檔案寫入一個“樹”物件:
$ git write-tree
它沒有任何選項——它只會將當前索引寫入描述該狀態的樹物件集合中,並返回結果頂層樹的名稱。你可以隨時使用該樹透過反向操作重新生成索引:
物件資料庫 → 索引
你從物件資料庫中讀取一個“樹”檔案,並用它來填充(並覆蓋——如果你的索引包含任何你可能想以後恢復的未儲存狀態,請不要這樣做!)你當前的索引。正常操作就是:
$ git read-tree <SHA-1 of tree>
你的索引檔案現在將與你之前儲存的樹等效。然而,那只是你的*索引*檔案:你的工作目錄內容尚未被修改。
索引 → 工作目錄
你透過“檢出”檔案來從索引更新你的工作目錄。這不是一個非常常見的操作,因為通常你會保持檔案更新,而不是寫入你的工作目錄,你會告訴索引檔案關於你的工作目錄中的更改(即 git
update-index
)。
然而,如果你決定跳轉到一個新版本,或者檢出別人的版本,或者只是恢復一箇舊的樹,你會用 read-tree 填充你的索引檔案,然後你需要用以下命令檢出結果:
$ git checkout-index filename
或者,如果你想檢出所有索引,使用 -a
。
注意!git
checkout-index
通常拒絕覆蓋舊檔案,所以如果你已經檢出了一箇舊版本的樹,你需要使用 -f
標誌(在 -a
標誌或檔名**之前**)來**強制**檢出。
最後,還有一些零散的東西不純粹是表示形式之間的轉換:
將所有連線起來
要提交一個你用 git
write-tree
例項化的樹,你需要建立一個引用該樹和其背後歷史的“提交”物件——最值得注意的是歷史中在其之前的“父”提交。
通常,“提交”只有一個父級:特定更改發生之前樹的先前狀態。然而,有時它可以有兩個或更多父提交,在這種情況下我們稱之為“合併”,因為這樣的提交將兩個或更多由其他提交代表的先前狀態聚合(“合併”)在一起。
換句話說,雖然“樹”代表工作目錄的特定目錄狀態,“提交”則代表該狀態在時間上的存在,並解釋了我們是如何達到那裡的。
你透過給出描述提交時狀態的樹和父級列表來建立提交物件:
$ git commit-tree <tree> -p <parent> [(-p <parent2>)...]
然後透過 stdin(透過管道或檔案重定向,或者直接在 tty 中輸入)給出提交的原因。
git
commit-tree
將返回代表該提交的物件的名稱,你應該將其儲存以備後用。通常,你會提交一個新的 HEAD
狀態,雖然 Git 不關心你將該狀態的記錄儲存在何處,但在實踐中,我們傾向於直接將結果寫入 .git/HEAD
指向的檔案中,這樣我們就可以隨時檢視上次提交的狀態是什麼。
這是一張圖片,它說明了各個部分是如何協同工作的:
commit-tree commit obj +----+ | | | | V V +-----------+ | Object DB | | Backing | | Store | +-----------+ ^ write-tree | | tree obj | | | | read-tree | | tree obj V +-----------+ | Index | | "cache" | +-----------+ update-index ^ blob obj | | | | checkout-index -u | | checkout-index stat | | blob obj V +-----------+ | Working | | Directory | +-----------+
檢查資料
你可以使用各種輔助工具檢查物件資料庫和索引中表示的資料。對於每個物件,你可以使用 git-cat-file[1] 來檢查物件的詳細資訊:
$ git cat-file -t <objectname>
顯示物件的型別,一旦你知道型別(通常在你找到物件的位置隱含),你可以使用:
$ git cat-file blob|tree|commit|tag <objectname>
來顯示其內容。注意!樹具有二進位制內容,因此有一個特殊的輔助工具用於顯示該內容,稱為 git
ls-tree
,它將二進位制內容轉換為更易於閱讀的形式。
檢視“提交”物件特別有啟發性,因為它們往往很小並且相當自解釋。特別是,如果你遵循在 .git/HEAD
中放置頂層提交名稱的約定,你可以這樣做:
$ git cat-file commit HEAD
來檢視頂層提交是什麼。
合併多棵樹
Git 可以幫助你執行三向合併,透過多次重複合併過程,這又可以用於多向合併。常見的情況是你只進行一次三向合併(協調兩條歷史線)並提交結果,但如果你願意,你可以一次性合併多個分支。
要執行三向合併,你需要從你想要合併的兩個提交開始,找到它們最近的共同父級(第三個提交),然後比較與這三個提交對應的樹。
要獲取合併的“基礎”,請查詢兩個提交的共同父級:
$ git merge-base <commit1> <commit2>
這會打印出它們都基於的提交名稱。你現在應該查詢這些提交的樹物件,這可以透過以下方式輕鬆完成:
$ git cat-file commit <commitname> | head -1
因為樹物件資訊總是提交物件中的第一行。
一旦你知道要合併的三棵樹(一棵“原始”樹,也就是共同樹,以及兩棵“結果”樹,也就是你想合併的分支),你就可以將“合併”讀取到索引中。如果它必須丟棄你舊的索引內容,它會發出警告,所以你應該確保你已經提交了這些內容——事實上,你通常總是會根據你的最後一次提交進行合併(無論如何,這應該與你當前索引中的內容匹配)。
要執行合併,請執行:
$ git read-tree -m -u <origtree> <yourtree> <targettree>
這將在索引檔案中直接為你執行所有瑣碎的合併操作,你只需使用 git
write-tree
寫入結果。
合併多棵樹,續
遺憾的是,許多合併並非微不足道。如果有檔案被新增、移動或刪除,或者如果兩個分支都修改了同一個檔案,你將得到一個包含“合併條目”的索引樹。這樣的索引樹無法寫入樹物件,你必須在使用其他工具解決任何此類合併衝突後,才能寫入結果。
你可以使用 git
ls-files
--unmerged
命令檢查此類索引狀態。示例:
$ git read-tree -m $orig HEAD $target $ git ls-files --unmerged 100644 263414f423d0e4d70dae8fe53fa34614ff3e2860 1 hello.c 100644 06fa6a24256dc7e560efa5687fa84b51f0263c3a 2 hello.c 100644 cc44c73eb783565da5831b4d820c962954019b69 3 hello.c
git
ls-files
--unmerged
命令的每行輸出都以 blob 模式位、blob SHA-1、階段號和檔名開頭。階段號是 Git 表示其來源樹的方式:階段 1 對應於 $orig
樹,階段 2 對應於 HEAD
樹,階段 3 對應於 $target
樹。
前面我們提到,簡單的合併是在 git
read-tree
-m
內部完成的。例如,如果檔案從 $orig
到 HEAD
或 $target
沒有變化,或者如果檔案從 $orig
到 HEAD
以及從 $orig
到 $target
的變化方式相同,那麼最終結果顯然是 HEAD
中的內容。上述示例表明,檔案 hello.c
從 $orig
到 HEAD
以及從 $orig
到 $target
的變化方式不同。你可以透過在你喜歡的 3-way 合併程式(例如 diff3
、merge
或 Git 自帶的 merge-file)上,自行處理來自這三個階段的 blob 物件來解決此問題,如下所示:
$ git cat-file blob 263414f >hello.c~1 $ git cat-file blob 06fa6a2 >hello.c~2 $ git cat-file blob cc44c73 >hello.c~3 $ git merge-file hello.c~2 hello.c~1 hello.c~3
這會將合併結果留在 hello.c~2
檔案中,如果存在衝突,還會帶有衝突標記。在驗證合併結果合理後,你可以透過以下方式告訴 Git 此檔案的最終合併結果:
$ mv -f hello.c~2 hello.c $ git update-index hello.c
當一個路徑處於“未合併”狀態時,對該路徑執行 git
update-index
會告訴 Git 將該路徑標記為已解決。
以上是 Git 最底層合併的描述,旨在幫助你理解其內部概念上發生了什麼。實際上,沒有人,甚至 Git 本身,會為此執行三次 git
cat-file
。有一個 git
merge-index
程式,它將階段提取到臨時檔案,並在此檔案上呼叫一個“合併”指令碼:
$ git merge-index git-merge-one-file hello.c
更高級別的 git
merge
-s
resolve
就是用這種方式實現的。
Git 內部探秘
本章涵蓋了 Git 實現的內部細節,可能只有 Git 開發者才需要了解。
物件儲存格式
所有物件都具有靜態確定的“型別”,用於標識物件的格式(即如何使用它,以及它如何引用其他物件)。目前有四種不同的物件型別:“blob”、“tree”、“commit”和“tag”。
無論物件型別如何,所有物件都共享以下特性:它們都使用 zlib 進行壓縮,並帶有一個頭部,該頭部不僅指定了它們的型別,還提供了物件中資料的大小資訊。值得注意的是,用於命名物件的 SHA-1 雜湊是原始資料加上此頭部的雜湊,因此 sha1sum
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 傳統)是一組極其簡單的程式,你在指令碼中使用它們,將一個程式的輸出透過管道傳遞給另一個。這對初始開發很有利,因為它更容易測試新事物。然而,最近這些部分中的許多已經變成了內建命令,並且一些核心部分已經“庫化”,即為了效能、可移植性以及避免程式碼重複而被放入 libgit.a。
到目前為止,你已經知道索引是什麼(並且在 read-cache-ll.h
中找到相應的資料結構),並且知道只有幾種物件型別(blobs、trees、commits 和 tags),它們都從 struct
object
繼承其通用結構,後者是它們的第一個成員(因此,你可以例如將 (struct
object
*
)commit
強制轉換為與 &commit->object 相同的效果,即獲取物件名稱和標誌)。
現在是休息一下,讓這些資訊消化吸收的好時機。
下一步:熟悉物件命名。閱讀命名提交。有很多方式來命名一個物件(不僅僅是修訂版!)。所有這些都在 sha1_name.c
中處理。快速檢視函式 get_sha1
()。很多特殊處理都是由諸如 get_sha1_basic
() 之類的函式完成的。
這只是為了讓你進入 Git 最庫化的部分:修訂遍歷器(revision walker)。
基本上,git
log
的初始版本是一個 shell 指令碼:
$ git-rev-list --pretty $(git-rev-parse --default HEAD "$@") | \ LESS=-S ${PAGER:-less}
這意味著什麼?
git
rev-list
是修訂遍歷器的原始版本,它總是將修訂列表列印到標準輸出。它仍然可用,並且需要如此,因為大多數新的 Git 命令都是從使用 git
rev-list
的指令碼開始的。
git
rev-parse
現在不再那麼重要了;它僅用於過濾出與指令碼呼叫的不同底層命令相關的選項。
git
rev-list
所做的主要工作包含在 revision.c
和 revision.h
中。它將選項封裝在一個名為 rev_info
的結構體中,該結構體控制如何以及遍歷哪些修訂版等。
git
rev-parse
的原始工作現在由函式 setup_revisions
() 完成,它解析修訂版和修訂遍歷器的常用命令列選項。這些資訊儲存在結構體 rev_info
中,供以後使用。呼叫 setup_revisions
() 後,你可以進行自己的命令列選項解析。之後,你必須呼叫 prepare_revision_walk
() 進行初始化,然後可以使用函式 get_revision
() 逐個獲取提交。
如果你對修訂遍歷過程的更多細節感興趣,只需檢視 cmd_log
() 的首次實現;呼叫 git
show
v1.3.0~155^2~4
並向下滾動到該函式(請注意,你不再需要直接呼叫 setup_pager
())。
如今,git
log
是一個內建命令,這意味著它包含在命令 git
中。一個內建命令的原始碼方面是:
-
一個名為
cmd_
<bla> 的函式,通常在 builtin/<bla.c> 中定義(請注意,舊版 Git 曾將其放在builtin-
<bla>.c
中),並在builtin.h
中宣告。 -
在
git.c
中的commands
[] 陣列中有一個條目,以及 -
在
Makefile
中的BUILTIN_OBJECTS
中有一個條目。
有時,一個原始檔中包含多個內建命令。例如,cmd_whatchanged
() 和 cmd_log
() 都位於 builtin/log.c
中,因為它們共享相當多的程式碼。在這種情況下,那些不以其所在 .c
檔名命名的命令必須在 Makefile
的 BUILT_INS
中列出。
git
log
在 C 語言中看起來比原始指令碼複雜,但這帶來了更大的靈活性和效能。
在這裡再次暫停一下是個不錯的時機。
第三課是:研究程式碼。真的,這是瞭解 Git 組織結構的最好方法(在你瞭解基本概念之後)。
那麼,思考一下你感興趣的事情,比如:“如何僅透過 blob 的物件名稱來訪問它?”第一步是找到一個可以實現此功能的 Git 命令。在本例中,它要麼是 git
show
,要麼是 git
cat-file
。
為了清晰起見,我們繼續使用 git
cat-file
,因為它:
-
是底層命令(plumbing),並且
-
甚至在初始提交中就已存在(它字面上只作為
cat-file.c
經歷了大約 20 次修訂,在成為內建命令時被重新命名為builtin/cat-file.c
,然後只經歷了不到 10 個版本)。
因此,檢視 builtin/cat-file.c
,搜尋 cmd_cat_file
() 並檢視它的作用。
git_config(git_default_config); if (argc != 3) usage("git cat-file [-t|-s|-e|-p|<type>] <sha1>"); if (get_sha1(argv[2], sha1)) die("Not a valid object name %s", argv[2]);
我們跳過顯而易見的細節;這裡唯一真正有趣的部分是對 get_sha1
() 的呼叫。它嘗試將 argv
[2
] 解釋為物件名稱,如果它引用了當前倉庫中存在的物件,它會將生成的 SHA-1 寫入變數 sha1
。
這裡有兩點值得注意:
-
get_sha1
() 在成功時返回 0。這可能會讓一些新的 Git 開發者感到驚訝,但 UNIX 中有一個悠久的傳統:在不同錯誤情況下返回不同的負數,而在成功時返回 0。 -
get_sha1
() 函式簽名中的變數sha1
是unsigned
char
*
型別,但實際上期望它是一個指向unsigned
char
[20
] 的指標。該變數將包含給定提交的 160 位 SHA-1 值。請注意,當 SHA-1 作為unsigned
char
*
傳遞時,它是二進位制表示,而十六進位制字元的 ASCII 表示則作為char
*
傳遞。
你會在整個程式碼中看到這兩種情況。
現在,重點來了:
case 0: buf = read_object_with_reference(sha1, argv[1], &size, NULL);
這就是你讀取 blob(實際上,不僅是 blob,而是任何型別的物件)的方式。要了解函式 read_object_with_reference
() 實際如何工作,請查詢其原始碼(例如在 Git 倉庫中執行 git
grep
read_object_with
| grep
":
[a-z
]"
),然後閱讀原始碼。
要了解如何使用結果,只需繼續閱讀 cmd_cat_file
():
write_or_die(1, buf, size);
有時,你不知道在哪裡查詢某個特性。在許多此類情況下,透過搜尋 git
log
的輸出,然後 git
show
相應的提交,會有所幫助。
示例:如果你知道 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 物件
-
無型別物件,例如檔案內容。
- 分支
-
“分支”是一條開發線。分支上最新的提交被稱為該分支的“尖端”(tip)。分支的尖端由一個分支頭引用,隨著分支上進行更多開發,該頭會向前移動。單個 Git 倉庫可以跟蹤任意數量的分支,但你的工作樹僅與其中一個(“當前”或“已檢出”分支)關聯,並且 HEAD 指向該分支。
- 快取
-
已棄用,指:索引。
- 鏈
- 變更集
-
BitKeeper/cvsps 中的“提交”的說法。由於 Git 不儲存變更,而是儲存狀態,因此在 Git 中使用“變更集”一詞確實沒有意義。
- 檢出
- 揀選(cherry-picking)
-
在SCM術語中,“揀選(cherry pick)”意味著從一系列變更(通常是提交)中選擇一個子集,並將其作為一組新的變更記錄在不同的程式碼庫之上。在 Git 中,這透過“git cherry-pick”命令執行,用於提取現有提交引入的更改,並將其基於當前分支的尖端記錄為一個新的提交。
- 清潔的
- 提交
-
作為名詞:Git 歷史中的一個單獨點;一個專案的整個歷史表示為一組相互關聯的提交。Git 經常在其他版本控制系統使用“修訂(revision)”或“版本(version)”的地方使用“提交(commit)”一詞。也用作提交物件的簡稱。
- 提交圖概念、表示和用法
-
物件資料庫中由提交形成的DAG結構的同義詞,由分支尖端引用,使用其連結提交的鏈。這種結構是權威的提交圖。該圖可以透過其他方式表示,例如“commit-graph”檔案。
- commit-graph 檔案
-
“commit-graph”(通常帶連字元)檔案是提交圖的補充表示,它加速了提交圖的遍歷。“commit-graph”檔案儲存在 .git/objects/info 目錄或備用物件資料庫的 info 目錄中。
- 提交物件
- commit-ish(或稱 committish)
-
一個提交物件或可以遞迴解引用為提交物件的物件。以下都是 commit-ish:一個提交物件,一個指向提交物件的標籤物件,一個指向標籤物件再指向提交物件的標籤物件,等等。
- Git 核心
-
Git 的基本資料結構和工具。僅暴露有限的原始碼管理工具。
- DAG
-
有向無環圖。提交物件形成一個有向無環圖,因為它們有父提交(有向),並且提交物件的圖是無環的(沒有以相同物件開始和結束的鏈)。
- 懸空物件
- 解引用
-
指的是符號引用:訪問符號引用所指向的引用的行為。遞迴解引用涉及對結果引用重複上述過程,直到找到一個非符號引用。
指的是標籤物件:訪問標籤所指向的物件的行為。標籤透過對結果物件重複操作進行遞迴解引用,直到結果具有指定的物件型別(如果適用)或任何非“tag”物件型別。在標籤上下文中,“遞迴解引用”的同義詞是“剝離(peel)”。
指的是提交物件:訪問提交的樹物件的行為。提交不能被遞迴解引用。
除非另有說明,否則在 Git 命令或協議上下文中使用的“解引用”是隱式遞迴的。
- 分離 HEAD
-
通常,HEAD 儲存一個分支的名稱,對 HEAD 所代表的歷史進行操作的命令,都是對 HEAD 指向的分支尖端的歷史進行操作。然而,Git 也允許你檢出任意一個不一定是任何特定分支尖端的提交。處於這種狀態的 HEAD 被稱為“分離的”(detached)。
請注意,當 HEAD 分離時,對當前分支歷史進行操作的命令(例如使用
git
commit
在其之上構建新歷史)仍然有效。它們會更新 HEAD 以指向更新後歷史的尖端,而不會影響任何分支。而更新或查詢當前分支資訊的命令(例如設定當前分支與哪個遠端跟蹤分支整合的git
branch
--set-upstream-to
)顯然不起作用,因為在此狀態下沒有(真正的)當前分支可供查詢。 - 目錄
-
用“ls”命令獲得的列表 :-)
- 髒的
- 邪惡合併
- 快進
-
快進是一種特殊型別的合併,當你有一個修訂版本,並且你正在“合併”另一個分支的更改,而這些更改恰好是你所擁有版本的後代時,就會發生快進。在這種情況下,你不會建立一個新的合併提交,而是直接更新你的分支以指向與你正在合併的分支相同的修訂版本。這在遠端倉庫的遠端跟蹤分支上會經常發生。
- 獲取
-
獲取一個分支意味著從遠端倉庫中獲取該分支的頭引用,找出本地物件資料庫中缺少哪些物件,並獲取它們。另請參見git-fetch[1]。
- 檔案系統
-
Linus Torvalds 最初將 Git 設計為使用者空間檔案系統,即用於儲存檔案和目錄的基礎設施。這確保了 Git 的效率和速度。
- Git 歸檔
-
倉庫的同義詞(對於 arch 人員)。
- gitfile
-
工作樹根目錄下的一個普通檔案
.git
,它指向實際倉庫所在的目錄。有關正確使用方法,請參閱git-worktree[1]或git-submodule[1]。有關語法,請參閱gitrepository-layout[5]。 - 嫁接
-
嫁接(Grafts)透過記錄提交的虛假祖先資訊,使得兩條原本不同的開發線能夠連線在一起。這樣,你可以讓 Git 假裝一個提交的父提交集合與建立該提交時記錄的不同。透過
.git/info/grafts
檔案配置。請注意,嫁接機制已過時,可能導致倉庫間物件傳輸出現問題;請參閱git-replace[1]以獲取更靈活和健壯的實現相同功能的系統。
- 雜湊
-
在 Git 的語境中,是物件名稱的同義詞。
- 頭
-
指向分支尖端提交的命名引用。頭儲存在
$GIT_DIR/refs/heads/
目錄下的檔案中,打包引用(packed refs)除外。(參閱git-pack-refs[1]。) - HEAD
-
當前的分支。更詳細地說:你的工作樹通常派生自HEAD所指向的樹的狀態。HEAD是你倉庫中某個頭的引用,除非在使用分離頭指標,在這種情況下它直接引用一個任意的提交。
- 頭引用
-
頭的同義詞。
- 鉤子
-
在多個Git命令的正常執行期間,會呼叫可選指令碼,允許開發者新增功能或進行檢查。通常,鉤子允許命令在執行前進行預驗證並可能被中止,並在操作完成後進行事後通知。鉤子指令碼位於
$GIT_DIR/hooks/
目錄下,透過簡單地刪除檔名中的.sample
字尾即可啟用。在Git的早期版本中,你需要使它們可執行。 - 索引
-
一個包含檔案統計資訊的集合,其內容作為物件儲存。索引是你的工作樹的一個儲存版本。實際上,它還可以包含工作樹的第二個甚至第三個版本,這些版本在合併時使用。
- 索引項
- master
-
預設的開發分支。無論何時建立Git倉庫,都會建立一個名為"master"的分支,併成為活動分支。在大多數情況下,這包含本地開發,儘管這純粹是約定,並非強制要求。
- 合併
-
作為動詞:將另一個分支(可能來自外部倉庫)的內容帶入當前分支。如果被合併的分支來自不同的倉庫,則首先抓取遠端分支,然後將結果合併到當前分支。這種抓取和合並操作的組合稱為拉取。合併由一個自動過程執行,該過程識別自各分支分歧以來所做的更改,然後將所有這些更改一起應用。如果更改發生衝突,可能需要手動干預才能完成合並。
- 物件
-
Git中的儲存單元。它透過其內容的SHA-1唯一標識。因此,物件不能被更改。
- 物件資料庫
- 物件識別符號 (oid)
-
物件名稱的同義詞。
- 物件名稱
- 物件型別
- 章魚式合併
- 孤立分支
-
指進入一個尚不存在的分支(即未生分支)的行為。在此操作之後,首次建立的提交將成為一個沒有父提交的提交,從而開始一個新的歷史。
- origin
-
預設的上游倉庫。大多數專案至少有一個它們跟蹤的上游專案。預設情況下,origin 用於此目的。新的上游更新將被抓取到名為 origin/name-of-upstream-branch 的遠端跟蹤分支中,你可以使用
git
branch
-r
檢視。 - 覆蓋
-
僅更新和新增檔案到工作目錄,但不刪除它們,類似於 cp -R 更新目標目錄內容的方式。這是從索引或樹狀物件檢出檔案時檢出的預設模式。相比之下,no-overlay 模式還會刪除源中不存在的已跟蹤檔案,類似於 rsync --delete。
- 打包
-
一組已壓縮到一個檔案中的物件(為了節省空間或高效傳輸)。
- 包索引
-
包中物件的識別符號列表及其他資訊,用於協助高效訪問包的內容。
- 路徑規範
-
用於限制Git命令中路徑的模式。
路徑規範用於“git ls-files”、“git ls-tree”、“git add”、“git grep”、“git diff”、“git checkout”以及許多其他命令的命令列,以將操作範圍限制在樹或工作樹的某個子集。有關路徑是相對於當前目錄還是頂層目錄的,請參閱每個命令的文件。路徑規範語法如下:
-
任何路徑都匹配自身
-
路徑規範直到最後一個斜槓表示一個目錄字首。該路徑規範的範圍僅限於該子樹。
-
路徑規範的其餘部分是 pathname 剩餘部分的模式。相對於目錄字首的路徑將使用 fnmatch(3) 與該模式進行匹配;特別是,* 和 ? 可以匹配目錄分隔符。
例如,Documentation/*.jpg 將匹配 Documentation 子樹中的所有 .jpg 檔案,包括 Documentation/chapter_1/figure_1.jpg。
以冒號
:
開頭的路徑規範具有特殊含義。在短形式中,前導冒號:
後跟零個或多個“魔術簽名”字母(可選地由另一個冒號:
終止),其餘部分是要與路徑匹配的模式。“魔術簽名”由非字母數字、非萬用字元、非正則表示式特殊字元且非冒號的 ASCII 符號組成。如果模式以不屬於“魔術簽名”符號集且不是冒號的字元開頭,則可以省略終止“魔術簽名”的可選冒號。在長形式中,前導冒號
:
後跟一個左括號(、一個逗號分隔的零個或多個“魔術詞”列表,以及一個右括號),其餘部分是要與路徑匹配的模式。僅包含冒號的路徑規範表示“沒有路徑規範”。這種形式不應與其他路徑規範結合使用。
- top
-
魔術詞
top
(魔術簽名:/
)使模式從工作樹的根目錄開始匹配,即使你在子目錄中執行命令也是如此。 - 字面量
-
模式中的萬用字元,例如
*
或?,將被視為字面字元。 - icase
-
不區分大小寫的匹配。
- 萬用字元模式
-
Git 將模式視為適用於 fnmatch(3) 並帶有 FNM_PATHNAME 標誌的 shell 萬用字元模式:模式中的萬用字元不會匹配路徑名中的 /。例如,“Documentation/*.html”匹配“Documentation/git.html”,但不匹配“Documentation/ppc/ppc.html”或“tools/perf/Documentation/perf.html”。
與完整路徑名匹配的模式中,兩個連續的星號(
**
)可能具有特殊含義:-
前導的
**
後跟斜槓表示匹配所有目錄。例如,**/foo
匹配任意位置的檔案或目錄foo
,與模式foo
相同。**/foo/bar
匹配直接在目錄foo
下的任意位置的檔案或目錄bar
。 -
後置的
/**
匹配其內部的所有內容。例如,abc/**
匹配目錄“abc”內的所有檔案,相對於.gitignore
檔案的位置,深度無限。 -
斜槓後跟兩個連續的星號再跟一個斜槓,匹配零個或多個目錄。例如,
a/**/b
匹配a/b
、a/x/b
、a/x/y/b
等。 -
其他連續的星號被視為無效。
萬用字元魔術與字面量魔術不相容。
-
- attr
-
在
attr:
之後是空格分隔的“屬性要求”列表,所有這些要求都必須滿足才能將路徑視為匹配;這補充了通常的非魔術路徑規範模式匹配。參見gitattributes[5]。路徑的每個屬性要求採取以下形式之一:
-
"
ATTR
" 要求屬性ATTR
被設定。 -
"
-ATTR
" 要求屬性ATTR
未設定。 -
"
ATTR=VALUE
" 要求屬性ATTR
被設定為字串VALUE
。 -
"
!ATTR
" 要求屬性ATTR
未指定。請注意,在與樹物件匹配時,屬性仍然從工作樹獲取,而不是從給定的樹物件獲取。
-
- 排除
-
路徑匹配任何非排除路徑規範後,它將透過所有排除路徑規範(魔術簽名:
!
或其同義詞^
)。如果匹配,則該路徑將被忽略。如果沒有非排除路徑規範,則排除將應用於結果集,就像在不帶任何路徑規範的情況下呼叫一樣。
-
- 父提交
-
提交物件包含一個(可能為空的)開發線中邏輯前身(即其父提交)的列表。
- 剝離
- 鎬式選擇
-
術語鎬式選擇指的是 diffcore 例程的一個選項,它有助於選擇新增或刪除給定文字字串的更改。使用
--pickaxe-all
選項,它可以用於檢視引入或刪除(例如)特定文字行的完整變更集。參見git-diff[1]。 - 底層命令
-
Git核心的可愛名稱。
- 上層命令
- 每個工作樹引用
-
那些屬於每個工作樹而非全域性的引用。目前僅包括HEAD以及所有以
refs/bisect/
開頭的引用,但將來可能會包含其他不尋常的引用。 - 偽引用
-
一種與普通引用具有不同語義的引用。這些引用可以透過普通的Git命令讀取,但不能透過諸如git-update-ref[1]之類的命令寫入。
Git已知的偽引用如下:
-
FETCH_HEAD
由git-fetch[1]或git-pull[1]寫入。它可能引用多個物件ID。每個物件ID都附帶元資料,指示其從何處抓取以及抓取狀態。 -
MERGE_HEAD
在解決合併衝突時由git-merge[1]寫入。它包含所有正在合併的提交ID。
-
- 拉取
-
拉取一個分支意味著抓取它並合併它。另請參閱git-pull[1]。
- 推送
-
推送一個分支意味著從遠端倉庫獲取該分支的頭引用,查明它是否是該分支本地頭引用的祖先,如果是,則將所有從本地頭引用可達且遠端倉庫中缺失的物件放入遠端物件資料庫,並更新遠端頭引用。如果遠端頭不是本地頭的祖先,則推送失敗。
- 可達的
-
給定提交的所有祖先都被認為是可從該提交“可達的”。更一般地,一個物件如果透過一條鏈可以從另一個物件到達,則稱其可達:該鏈遵循標籤指向其標記的物件,提交指向其父提交或樹,以及樹指向其包含的樹或Blob。
- 可達性點陣圖
-
可達性點陣圖儲存打包檔案或多包索引(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]。
- 樹
- 樹物件
- 樹狀物件 (也作 treeish)
-
一個樹物件或一個可以遞迴解引用為樹物件的物件。解引用提交物件會得到對應修訂版本頂層目錄的樹物件。以下都是樹狀物件:一個提交狀物件、一個樹物件、一個指向樹物件的標籤物件、一個指向指向樹物件的標籤物件的標籤物件等。
- 未生
-
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特殊知識的聰明人來說,它必須能夠按順序從頭到尾閱讀。如有必要,任何其他先決條件應在出現時具體提及。
-
儘可能地,章節標題應以無需過多必要知識的語言,清楚地描述它們所解釋的任務:例如,“將補丁匯入專案”而不是“
git
am
命令”
考慮如何建立一個清晰的章節依賴圖,這將允許人們在不必閱讀所有中間內容的情況下,直接跳轉到重要主題。
掃描Documentation/
以查詢遺漏的其他內容;特別是:
-
操作指南
-
technical/
中的一些內容? -
鉤子
-
git[1]中的命令列表
掃描電子郵件存檔以查詢遺漏的其他內容
掃描手冊頁,檢視是否有任何內容假設了比本手冊提供的更多背景知識。
新增更多好的示例。完全由“食譜”式示例組成的章節可能是一個好主意;也許可以把“高階示例”部分作為章節的標準結尾部分?
在適當的地方包含到詞彙表的交叉引用。
新增一個關於與其他版本控制系統(包括CVS、Subversion以及單純的釋出tarball系列匯入)協作的章節。
編寫一個關於使用底層命令和編寫指令碼的章節。
替代方案、克隆 -reference 等。