Git 允許你通過幾種方法來指明特定的或者一定范圍內(nèi)的提交。了解它們并不是必需的,但是了解一下總沒壞處。
顯然你可以使用給出的 SHA-1 值來指明一次提交,不過也有更加人性化的方法來做同樣的事。本節(jié)概述了指明單個(gè)提交的諸多方法。
Git 很聰明,它能夠通過你提供的前幾個(gè)字符來識(shí)別你想要的那次提交,只要你提供的那部分 SHA-1 不短于四個(gè)字符,并且沒有歧義——也就是說,當(dāng)前倉庫中只有一個(gè)對(duì)象以這段 SHA-1 開頭。
例如,想要查看一次指定的提交,假設(shè)你運(yùn)行 git log
命令并找到你增加了功能的那次提交:
$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
fixed refs handling, added gc auto, updated 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
added some blame and merge stuff
假設(shè)是 1c002dd....
。如果你想 git show
這次提交,下面的命令是等價(jià)的(假設(shè)簡短的版本沒有歧義):
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d
Git 可以為你的 SHA-1 值生成出簡短且唯一的縮寫。如果你傳遞 --abbrev-commit
給 git log
命令,輸出結(jié)果里就會(huì)使用簡短且唯一的值;它默認(rèn)使用七個(gè)字符來表示,不過必要時(shí)為了避免 SHA-1 的歧義,會(huì)增加字符數(shù):
$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit
通常在一個(gè)項(xiàng)目中,使用八到十個(gè)字符來避免 SHA-1 歧義已經(jīng)足夠了。最大的 Git 項(xiàng)目之一,Linux 內(nèi)核,目前也只需要最長 40 個(gè)字符中的 12 個(gè)字符來保持唯一性。
許多人可能會(huì)擔(dān)心一個(gè)問題:在隨機(jī)的偶然情況下,在他們的倉庫里會(huì)出現(xiàn)兩個(gè)具有相同 SHA-1 值的對(duì)象。那會(huì)怎么樣呢?
如果你真的向倉庫里提交了一個(gè)跟之前的某個(gè)對(duì)象具有相同 SHA-1 值的對(duì)象,Git 將會(huì)發(fā)現(xiàn)之前的那個(gè)對(duì)象已經(jīng)存在在 Git 數(shù)據(jù)庫中,并認(rèn)為它已經(jīng)被寫入了。如果什么時(shí)候你想再次檢出那個(gè)對(duì)象時(shí),你會(huì)總是得到先前的那個(gè)對(duì)象的數(shù)據(jù)。
不過,你應(yīng)該了解到,這種情況發(fā)生的概率是多么微小。SHA-1 摘要長度是 20 字節(jié),也就是 160 位。為了保證有 50% 的概率出現(xiàn)一次沖突,需要 2^80 個(gè)隨機(jī)哈希的對(duì)象(計(jì)算沖突機(jī)率的公式是 p = (n(n-1)/2) * (1/2^160))
。2^80 是 1.2 x 10^24,也就是一億億億,那是地球上沙??倲?shù)的 1200 倍。
現(xiàn)在舉例說一下怎樣才能產(chǎn)生一次 SHA-1 沖突。如果地球上 65 億的人類都在編程,每人每秒都在產(chǎn)生等價(jià)于整個(gè) Linux 內(nèi)核歷史(一百萬個(gè) Git 對(duì)象)的代碼,并將之提交到一個(gè)巨大的 Git 倉庫里面,那將花費(fèi) 5 年的時(shí)間才會(huì)產(chǎn)生足夠的對(duì)象,使其擁有 50% 的概率產(chǎn)生一次 SHA-1 對(duì)象沖突。這要比你編程團(tuán)隊(duì)的成員同一個(gè)晚上在互不相干的意外中被狼襲擊并殺死的機(jī)率還要小。
指明一次提交的最直接的方法要求有一個(gè)指向它的分支引用。這樣,你就可以在任何需要一個(gè)提交對(duì)象或者 SHA-1 值的 Git 命令中使用該分支名稱了。如果你想要顯示一個(gè)分支的最后一次提交的對(duì)象,例如假設(shè) topic1
分支指向 ca82a6d
,那么下面的命令是等價(jià)的:
$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1
如果你想知道某個(gè)分支指向哪個(gè)特定的 SHA,或者想看任何一個(gè)例子中被簡寫的 SHA-1,你可以使用一個(gè)叫做 rev-parse
的 Git 探測(cè)工具。在第 9 章你可以看到關(guān)于探測(cè)工具的更多信息;簡單來說,rev-parse
是為了底層操作而不是日常操作設(shè)計(jì)的。不過,有時(shí)你想看 Git 現(xiàn)在到底處于什么狀態(tài)時(shí),它可能會(huì)很有用。這里你可以對(duì)你的分支運(yùn)執(zhí)行 rev-parse
。
$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949
在你工作的同時(shí),Git 在后臺(tái)的工作之一就是保存一份引用日志——一份記錄最近幾個(gè)月你的 HEAD 和分支引用的日志。
你可以使用 git reflog
來查看引用日志:
$ git reflog
734713b... HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970... HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd... HEAD@{2}: commit: added 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
每次你的分支頂端因?yàn)槟承┰虮恍薷臅r(shí),Git 就會(huì)為你將信息保存在這個(gè)臨時(shí)歷史記錄里面。你也可以使用這份數(shù)據(jù)來指明更早的分支。如果你想查看倉庫中 HEAD 在五次前的值,你可以使用引用日志的輸出中的 @{n}
引用:
$ git show HEAD@{5}
你也可以使用這個(gè)語法來查看某個(gè)分支在一定時(shí)間前的位置。例如,想看你的 master
分支昨天在哪,你可以輸入
$ git show master@{yesterday}
它就會(huì)顯示昨天分支的頂端在哪。這項(xiàng)技術(shù)只對(duì)還在你引用日志里的數(shù)據(jù)有用,所以不能用來查看比幾個(gè)月前還早的提交。
想要看類似于 git log
輸出格式的引用日志信息,你可以運(yùn)行 git log -g
:
$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
fixed refs handling, added gc auto, updated 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'
需要注意的是,引用日志信息只存在于本地——這是一個(gè)記錄你在你自己的倉庫里做過什么的日志。其他人拷貝的倉庫里的引用日志不會(huì)和你的相同;而你新克隆一個(gè)倉庫的時(shí)候,引用日志是空的,因?yàn)槟阍趥}庫里還沒有操作。git show HEAD@{2.months.ago}
這條命令只有在你克隆了一個(gè)項(xiàng)目至少兩個(gè)月時(shí)才會(huì)有用——如果你是五分鐘前克隆的倉庫,那么它將不會(huì)有結(jié)果返回。
另一種指明某次提交的常用方法是通過它的祖先。如果你在引用最后加上一個(gè) ^
,Git 將其理解為此次提交的父提交。 假設(shè)你的工程歷史是這樣的:
$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
* d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added 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'
你也可以在 ^
后添加一個(gè)數(shù)字——例如,d921970^2
意思是“d921970 的第二父提交”。這種語法只在合并提交時(shí)有用,因?yàn)楹喜⑻峤豢赡苡卸鄠€(gè)父提交。第一父提交是你合并時(shí)所在分支,而第二父提交是你所合并的分支:
$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
added 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
另外一個(gè)指明祖先提交的方法是 ~
。這也是指向第一父提交,所以 HEAD~
和 HEAD^
是等價(jià)的。當(dāng)你指定數(shù)字的時(shí)候就明顯不一樣了。HEAD~2
是指“第一父提交的第一父提交”,也就是“祖父提交”——它會(huì)根據(jù)你指定的次數(shù)檢索第一父提交。例如,在上面列出的歷史記錄里面,HEAD~3
會(huì)是
$ 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
指明先前引用的第二父提交(假設(shè)它是一個(gè)合并提交)。
現(xiàn)在你已經(jīng)可以指明單次的提交,讓我們來看看怎樣指明一定范圍的提交。這在你管理分支的時(shí)候尤顯重要——如果你有很多分支,你可以指明范圍來圈定一些問題的答案,比如:“這個(gè)分支上我有哪些工作還沒合并到主分支的?”
最常用的指明范圍的方法是雙點(diǎn)的語法。這種語法主要是讓 Git 區(qū)分出可從一個(gè)分支中獲得而不能從另一個(gè)分支中獲得的提交。例如,假設(shè)你有類似于圖 6-1 的提交歷史。
圖 6-1. 范圍選擇的提交歷史實(shí)例
你想要查看你的試驗(yàn)分支上哪些沒有被提交到主分支,那么你就可以使用 master..experiment
來讓 Git 顯示這些提交的日志——這句話的意思是“所有可從experiment分支中獲得而不能從master分支中獲得的提交”。為了使例子簡單明了,我使用了圖標(biāo)中提交對(duì)象的字母來代替真實(shí)日志的輸出,所以會(huì)顯示:
$ git log master..experiment
D
C
另一方面,如果你想看相反的——所有在 master
而不在 experiment
中的分支——你可以交換分支的名字。experiment..master
顯示所有可在 master
獲得而在 experiment
中不能的提交:
$ git log experiment..master
F
E
這在你想保持 experiment
分支最新和預(yù)覽你將合并的提交的時(shí)候特別有用。這個(gè)語法的另一種常見用途是查看你將把什么推送到遠(yuǎn)程:
$ git log origin/master..HEAD
這條命令顯示任何在你當(dāng)前分支上而不在遠(yuǎn)程origin
上的提交。如果你運(yùn)行 git push
并且的你的當(dāng)前分支正在跟蹤 origin/master
,被git log origin/master..HEAD
列出的提交就是將被傳輸?shù)椒?wù)器上的提交。 你也可以留空語法中的一邊來讓 Git 來假定它是 HEAD。例如,輸入 git log origin/master..
將得到和上面的例子一樣的結(jié)果—— Git 使用 HEAD 來代替不存在的一邊。
雙點(diǎn)語法就像速記一樣有用;但是你也許會(huì)想針對(duì)兩個(gè)以上的分支來指明修訂版本,比如查看哪些提交被包含在某些分支中的一個(gè),但是不在你當(dāng)前的分支上。Git允許你在引用前使用^
字符或者--not
指明你不希望提交被包含其中的分支。因此下面三個(gè)命令是等同的:
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA
這樣很好,因?yàn)樗试S你在查詢中指定多于兩個(gè)的引用,而這是雙點(diǎn)語法所做不到的。例如,如果你想查找所有從refA
或refB
包含的但是不被refC
包含的提交,你可以輸入下面中的一個(gè)
$ git log refA refB ^refC
$ git log refA refB --not refC
這建立了一個(gè)非常強(qiáng)大的修訂版本查詢系統(tǒng),應(yīng)該可以幫助你解決分支里包含了什么這個(gè)問題。
最后一種主要的范圍選擇語法是三點(diǎn)語法,這個(gè)可以指定被兩個(gè)引用中的一個(gè)包含但又不被兩者同時(shí)包含的分支。回過頭來看一下圖6-1里所列的提交歷史的例子。 如果你想查看master
或者experiment
中包含的但不是兩者共有的引用,你可以運(yùn)行
$ git log master...experiment
F
E
D
C
這個(gè)再次給出你普通的log
輸出但是只顯示那四次提交的信息,按照傳統(tǒng)的提交日期排列。
這種情形下,log
命令的一個(gè)常用參數(shù)是--left-right
,它會(huì)顯示每個(gè)提交到底處于哪一側(cè)的分支。這使得數(shù)據(jù)更加有用。
$ git log --left-right master...experiment
< F
< E
> D
> C
有了以上工具,讓Git知道你要察看哪些提交就容易得多了。
更多建議: