-
A1. 附錄 A: Git 在其他環境
- A1.1 圖形介面
- A1.2 Visual Studio 中的 Git
- A1.3 Visual Studio Code 中的 Git
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git
- A1.5 Sublime Text 中的 Git
- A1.6 Bash 中的 Git
- A1.7 Zsh 中的 Git
- A1.8 PowerShell 中的 Git
- A1.9 小結
-
A2. 附錄 B: 在應用程式中嵌入 Git
-
A3. 附錄 C: Git 命令
7.1 Git 工具 - 修訂版本選擇
到目前為止,你已經掌握了日常管理或維護 Git 倉庫以進行原始碼控制所需的大多數命令和工作流。你已經完成了檔案跟蹤和提交的基本任務,並掌握了暫存區和輕量級主題分支與合併的強大功能。
現在你將探索 Git 能夠做的一些非常強大的事情,這些事情可能不是你日常需要使用的,但你可能在某些時候需要它們。
修訂版本選擇
Git 允許你透過多種方式引用單個提交、一組提交或一系列提交。這些方式不一定顯而易見,但瞭解它們會很有幫助。
單個修訂版本
你顯然可以透過完整的 40 個字元的 SHA-1 雜湊值來引用任何單個提交,但也有更人性化的方式來引用提交。本節概述了可以引用任何提交的各種方式。
短 SHA-1
如果你提供 SHA-1 雜湊值的前幾個字元,只要該部分雜湊值至少有四個字元長並且是明確的(即物件資料庫中沒有其他物件的雜湊值以相同的字首開頭),Git 就足夠智慧,可以找出你所引用的提交。
例如,要檢查某個你確定添加了特定功能的提交,你可以首先執行 git log
命令來找到該提交
$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
Fix refs handling, add gc auto, update tests
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
Add some blame and merge stuff
在這種情況下,假設你對雜湊值以 1c002dd…
開頭的提交感興趣。你可以使用以下任何 git show
變體來檢查該提交(假設較短的版本是明確的)
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d
Git 可以為你的 SHA-1 值找出唯一的短縮寫。如果你將 --abbrev-commit
傳遞給 git log
命令,輸出將使用較短的值,但保持它們的唯一性;它預設使用七個字元,但如有必要會加長,以保持 SHA-1 的明確性
$ git log --abbrev-commit --pretty=oneline
ca82a6d Change the version number
085bb3b Remove unnecessary test code
a11bef0 Initial commit
通常,八到十個字元足以在一個專案中保持唯一性。例如,截至 2019 年 2 月,Linux 核心(一個相當大的專案)在其物件資料庫中擁有超過 875,000 個提交和近七百萬個物件,沒有兩個物件的 SHA-1 值在前 12 個字元上是相同的。
注意
|
關於 SHA-1 的簡短說明
許多人會在某個時候擔心,他們可能會偶然地在倉庫中出現兩個不同的物件,它們的 SHA-1 雜湊值相同。那會怎樣? 如果你確實提交了一個與倉庫中之前的不同物件具有相同 SHA-1 雜湊值的物件,Git 會在你的 Git 資料庫中發現已有的舊物件,假定它已經被寫入並直接重用它。如果你在某個時候嘗試再次檢出該物件,你將始終獲得第一個物件的資料。 然而,你應該清楚這種情景發生的可能性是多麼微乎其微。SHA-1 摘要是 20 位元組或 160 位。要確保單個碰撞發生機率達到 50% 所需的隨機雜湊物件數量大約是 280 (確定碰撞機率的公式是 這裡有一個例子可以讓你瞭解發生 SHA-1 碰撞需要什麼條件。如果地球上所有 65 億人都在程式設計,並且每秒鐘每個人都產生相當於整個 Linux 核心歷史(650 萬個 Git 物件)的程式碼,並將其推送到一個巨大的 Git 倉庫中,那麼大約需要 2 年時間,該倉庫才會包含足夠的 Git 物件,從而有 50% 的機率發生一次 SHA-1 物件碰撞。因此,自然發生的 SHA-1 碰撞比你的程式設計團隊的每個成員在同一晚因不相關的事件被狼攻擊致死的可能性還要小。 如果你投入價值數千美元的計算能力,則有可能合成兩個具有相同雜湊值的檔案,這已於 2017 年 2 月在 https://shattered.io/ 上得到證實。Git 正在朝著使用 SHA256 作為預設雜湊演算法的方向發展,這種演算法對碰撞攻擊更具彈性,並且已經有了幫助緩解這種攻擊的程式碼(儘管不能完全消除)。 |
分支引用
引用特定提交的一種直接方法是,如果它是分支的最新提交;在這種情況下,你可以在任何需要提交引用的 Git 命令中直接使用分支名稱。例如,如果你想檢查分支上的最新提交物件,以下命令是等效的,假設 topic1
分支指向提交 ca82a6d…
$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1
如果你想檢視一個分支指向哪個具體的 SHA-1,或者你想了解這些示例在 SHA-1 方面最終歸結於什麼,你可以使用一個名為 rev-parse
的 Git 底層工具。有關底層工具的更多資訊,請參閱 Git 內部原理;rev-parse
主要用於低階操作,不適合日常使用。然而,有時當你需要了解真實情況時,它會很有幫助。在這裡,你可以在你的分支上執行 rev-parse
。
$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949
Reflog 簡寫
你在工作時,Git 在後臺做的一件事是維護一個“reflog”(引用日誌)——一個記錄你的 HEAD 和分支引用在過去幾個月中所處位置的日誌。
你可以使用 git reflog
來檢視你的 reflog
$ git reflog
734713b HEAD@{0}: commit: Fix refs handling, add gc auto, update tests
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: Add some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD
每次你的分支提示因任何原因更新時,Git 都會將該資訊儲存在這個臨時歷史記錄中。你也可以使用你的 reflog 資料來引用舊的提交。例如,如果你想檢視倉庫 HEAD 的第五個先前值,你可以使用在 reflog 輸出中看到的 @{5}
引用
$ git show HEAD@{5}
你也可以使用這種語法來檢視分支在特定時間點之前的位置。例如,要檢視你的 master
分支昨天在哪裡,你可以輸入
$ git show master@{yesterday}
這將顯示你的 master
分支昨天所指向的位置。這種技術只適用於仍存在於你的 reflog 中的資料,因此你不能用它來查詢幾個月之前的提交。
要檢視格式與 git log
輸出類似的 reflog 資訊,你可以執行 git log -g
$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: Fix refs handling, add gc auto, update tests
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
Fix refs handling, add gc auto, update tests
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
需要注意的是,reflog 資訊是嚴格本地的——它只記錄了你在你的倉庫中做了什麼。在別人的倉庫副本上,這些引用將不同;此外,在你初次克隆倉庫後,你的 reflog 將是空的,因為你的倉庫中尚未發生任何活動。執行 git show HEAD@{2.months.ago}
將僅在你至少在兩個月前克隆了專案時顯示匹配的提交——如果你在此之後克隆,你將只看到你的第一個本地提交。
提示
|
可以將 reflog 視為 Git 版本的 shell 歷史記錄
如果你有 UNIX 或 Linux 背景,你可以將 reflog 視為 Git 版本的 shell 歷史記錄,它強調其中所記錄的內容顯然只與你和你的“會話”相關,與可能在同一臺機器上工作的其他人無關。 |
注意
|
在 PowerShell 中轉義大括號
在使用 PowerShell 時,大括號如
|
祖先引用
指定提交的另一種主要方式是透過其祖先。如果你在引用末尾放置一個 ^
(脫字號),Git 會將其解析為該提交的父級。假設你檢視你的專案歷史記錄
$ git log --pretty=format:'%h %s' --graph
* 734713b Fix refs handling, add gc auto, update tests
* d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd Add some blame and merge stuff
|/
* 1c36188 Ignore *.gem
* 9b29157 Add open3_detach to gemspec file list
然後,你可以透過指定 HEAD^
來檢視上一個提交,這意味著“HEAD 的父級”
$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
注意
|
在 Windows 上轉義脫字號
在 Windows 的
|
你也可以在 ^
後面指定一個數字,以標識你想要的哪個父級;例如,d921970^2
意味著“d921970 的第二個父級”。這種語法僅對合並提交有用,因為合併提交有多個父級——合併提交的第一個父級來自你合併時所在的分支(通常是 master
),而合併提交的第二個父級來自被合併的分支(例如 topic
)。
$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
Add some blame and merge stuff
$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date: Wed Dec 10 22:22:03 2008 +0000
Some rdoc changes
另一種主要的祖先指定方式是 ~
(波浪號)。這也指向第一個父級,因此 HEAD~
和 HEAD^
是等效的。當你指定一個數字時,區別就變得明顯了。HEAD~2
意味著“第一個父級的第一個父級”,或者說“祖父級”——它按照你指定的次數遍歷第一個父級。例如,在前面列出的歷史記錄中,HEAD~3
將是
$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date: Fri Nov 7 13:47:59 2008 -0500
Ignore *.gem
這也可以寫作 HEAD~~~
,同樣是第一個父級的第一個父級的第一個父級
$ git show HEAD~~~
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date: Fri Nov 7 13:47:59 2008 -0500
Ignore *.gem
你也可以組合這些語法——例如,透過使用 HEAD~3^2
等方式,你可以獲得先前引用的第二個父級(假設它是一個合併提交)。
提交範圍
既然你已經可以指定單個提交,那麼讓我們看看如何指定提交範圍。這對於管理你的分支特別有用——如果你有許多分支,你可以使用範圍指定來回答諸如“這個分支上有哪些工作尚未合併到我的主分支?”這樣的問題。
雙點
最常見的範圍指定是雙點語法。這基本上是要求 Git 解析一個提交範圍,這些提交可從一個提交到達,但不能從另一個提交到達。例如,假設你的提交歷史記錄看起來像 範圍選擇示例歷史記錄。

假設你想檢視 experiment
分支中尚未合併到 master
分支中的內容。你可以要求 Git 使用 master..experiment
顯示這些提交的日誌——這意味著“所有可從 experiment
到達但不可從 master
到達的提交”。為了這些示例的簡潔和清晰,圖中提交物件的字母用於代替實際日誌輸出,並按它們將顯示的順序排列
$ git log master..experiment
D
C
另一方面,如果你想看到相反的情況——所有在 master
中但不在 experiment
中的提交——你可以反轉分支名稱。experiment..master
會顯示 master
中所有不可從 experiment
到達的內容
$ git log experiment..master
F
E
如果你想保持 experiment
分支最新並預覽你即將合併的內容,這會很有用。這種語法的另一個常見用途是檢視你即將推送到遠端倉庫的內容
$ git log origin/master..HEAD
此命令顯示你當前分支中不在 origin
遠端倉庫的 master
分支中的任何提交。如果你執行 git push
並且你的當前分支正在跟蹤 origin/master
,那麼 git log origin/master..HEAD
列出的提交就是將要傳輸到伺服器的提交。你也可以省略語法的一側,讓 Git 假定為 HEAD
。例如,透過輸入 git log origin/master..
,你可以獲得與上一個示例相同的結果——如果缺少一側,Git 會自動替換為 HEAD
。
多點
雙點語法作為一個簡寫很有用,但也許你想指定兩個以上的分支來表示你的修訂版本,例如檢視多個分支中哪些提交不在你當前所在的分支中。Git 允許你透過在任何你不想看到可達提交的引用前使用 ^
字元或 --not
來實現此目的。因此,以下三個命令是等效的
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA
這很不錯,因為使用這種語法你可以在查詢中指定兩個以上的引用,而雙點語法無法做到這一點。例如,如果你想檢視所有可從 refA
或 refB
到達但不可從 refC
到達的提交,你可以使用以下任一方式
$ git log refA refB ^refC
$ git log refA refB --not refC
這構成了一個非常強大的修訂版本查詢系統,應該能幫助你弄清楚分支中的內容。
三點
最後一種主要的範圍選擇語法是三點語法,它指定了可由兩個引用中任一到達但不能由兩者同時到達的所有提交。回顧 範圍選擇示例歷史記錄 中的提交歷史示例。如果你想檢視 master
或 experiment
中有但沒有任何公共引用的內容,你可以執行
$ git log master...experiment
F
E
D
C
同樣,這會給你正常的 log
輸出,但只顯示這四個提交的提交資訊,並按照傳統的提交日期順序顯示。
在這種情況下,與 log
命令一起使用的常見開關是 --left-right
,它顯示每個提交屬於範圍的哪一側。這有助於使輸出更有用
$ git log --left-right master...experiment
< F
< E
> D
> C
有了這些工具,你可以更輕鬆地讓 Git 知道你想檢查哪個或哪些提交。