6.1 Git 工具 - 修訂版本(Revision)選擇

修訂版本(Revision)選擇

Git 允許你通過幾種方法來指明特定的或者一定範圍內的提交。瞭解它們並不是必需的,但是瞭解一下總沒壞處。

單個修訂版本

顯然你可以使用給出的 SHA-1 值來指明一次提交,不過也有更加人性化的方法來做同樣的事。本節概述了指明單個提交的諸多方法。

簡短的SHA

Git 很聰明,它能夠通過你提供的前幾個字符來識別你想要的那次提交,只要你提供的那部分 SHA-1 不短於四個字符,並且沒有歧義——也就是說,當前倉庫中只有一個對象以這段 SHA-1 開頭。

例如,想要查看一次指定的提交,假設你運行 git log 命令並找到你增加了功能的那次提交:

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <[email protected]>
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 <[email protected]>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <[email protected]>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added 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 changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

通常在一個項目中,使用八到十個字符來避免 SHA-1 歧義已經足夠了。最大的 Git 項目之一,Linux 內核,目前也只需要最長 40 個字符中的 12 個字符來保持唯一性。

關於 SHA-1 的簡短說明

許多人可能會擔心一個問題:在隨機的偶然情況下,在他們的倉庫裏會出現兩個具有相同 SHA-1 值的對象。那會怎麼樣呢?

如果你真的向倉庫裏提交了一個跟之前的某個對象具有相同 SHA-1 值的對象,Git 將會發現之前的那個對象已經存在在 Git 數據庫中,並認爲它已經被寫入了。如果什麼時候你想再次檢出那個對象時,你會總是得到先前的那個對象的數據。

不過,你應該瞭解到,這種情況發生的概率是多麼微小。SHA-1 摘要長度是 20 字節,也就是 160 位。爲了保證有 50% 的概率出現一次衝突,需要 2^80 個隨機哈希的對象(計算衝突機率的公式是 p = (n(n-1)/2) * (1/2^160))。2^80 是 1.2 x 10^24,也就是一億億億,那是地球上沙粒總數的 1200 倍。

現在舉例說一下怎樣才能產生一次 SHA-1 衝突。如果地球上 65 億的人類都在編程,每人每秒都在產生等價於整個 Linux 內核歷史(一百萬個 Git 對象)的代碼,並將之提交到一個巨大的 Git 倉庫裏面,那將花費 5 年的時間纔會產生足夠的對象,使其擁有 50% 的概率產生一次 SHA-1 對象衝突。這要比你編程團隊的成員同一個晚上在互不相干的意外中被狼襲擊並殺死的機率還要小。

分支引用

指明一次提交的最直接的方法要求有一個指向它的分支引用。這樣,你就可以在任何需要一個提交對象或者 SHA-1 值的 Git 命令中使用該分支名稱了。如果你想要顯示一個分支的最後一次提交的對象,例如假設topic1 分支指向 ca82a6d,那麼下面的命令是等價的:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

如果你想知道某個分支指向哪個特定的 SHA,或者想看任何一個例子中被簡寫的 SHA-1,你可以使用一個叫做 rev-parse 的 Git 探測工具。在第 9 章你可以看到關於探測工具的更多信息;簡單來說,rev-parse 是爲了底層操作而不是日常操作設計的。不過,有時你想看 Git 現在到底處於什麼狀態時,它可能會很有用。這裏你可以對你的分支運執行 rev-parse

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

引用日誌裏的簡稱

在你工作的同時,Git 在後臺的工作之一就是保存一份引用日誌——一份記錄最近幾個月你的 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

每次你的分支頂端因爲某些原因被修改時,Git 就會爲你將信息保存在這個臨時歷史記錄裏面。你也可以使用這份數據來指明更早的分支。如果你想查看倉庫中 HEAD 在五次前的值,你可以使用引用日誌的輸出中的 @{n} 引用:

$ git show HEAD@{5}

你也可以使用這個語法來查看某個分支在一定時間前的位置。例如,想看你的 master 分支昨天在哪,你可以輸入

$ git show master@{yesterday}

它就會顯示昨天分支的頂端在哪。這項技術只對還在你引用日誌裏的數據有用,所以不能用來查看比幾個月前還早的提交。

想要看類似於 git log 輸出格式的引用日誌信息,你可以運行 git log -g

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <[email protected]>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <[email protected]>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <[email protected]>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <[email protected]>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

需要注意的是,引用日誌信息只存在於本地——這是一個記錄你在你自己的倉庫裏做過什麼的日誌。其他人拷貝的倉庫裏的引用日誌不會和你的相同;而你新克隆一個倉庫的時候,引用日誌是空的,因爲你在倉庫裏還沒有操作。git show HEAD@{2.months.ago} 這條命令只有在你克隆了一個項目至少兩個月時纔會有用——如果你是五分鐘前克隆的倉庫,那麼它將不會有結果返回。

祖先引用

另一種指明某次提交的常用方法是通過它的祖先。如果你在引用最後加上一個 ^,Git 將其理解爲此次提交的父提交。 假設你的工程歷史是這樣的:

$ 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 <[email protected]>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

你也可以在 ^ 後添加一個數字——例如,d921970^2 意思是“d921970 的第二父提交”。這種語法只在合併提交時有用,因爲合併提交可能有多個父提交。第一父提交是你合併時所在分支,而第二父提交是你所合併的分支:

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <[email protected]>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <[email protected]>
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 <[email protected]>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

也可以寫成 HEAD^^^,同樣是第一父提交的第一父提交的第一父提交:

$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <[email protected]>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

你也可以混合使用這些語法——你可以通過 HEAD~3^2 指明先前引用的第二父提交(假設它是一個合併提交)。

提交範圍

現在你已經可以指明單次的提交,讓我們來看看怎樣指明一定範圍的提交。這在你管理分支的時候尤顯重要——如果你有很多分支,你可以指明範圍來圈定一些問題的答案,比如:“這個分支上我有哪些工作還沒合併到主分支的?”

雙點

最常用的指明範圍的方法是雙點的語法。這種語法主要是讓 Git 區分出可從一個分支中獲得而不能從另一個分支中獲得的提交。例如,假設你有類似於圖 6-1 的提交歷史。


圖 6-1. 範圍選擇的提交歷史實例

你想要查看你的試驗分支上哪些沒有被提交到主分支,那麼你就可以使用 master..experiment 來讓 Git 顯示這些提交的日誌——這句話的意思是“所有可從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 上的提交。如果你運行 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

這樣很好,因爲它允許你在查詢中指定多於兩個的引用,而這是雙點語法所做不到的。例如,如果你想查找所有從refArefB包含的但是不被refC包含的提交,你可以輸入下面中的一個

$ git log refA refB ^refC
$ git log refA refB --not refC

這建立了一個非常強大的修訂版本查詢系統,應該可以幫助你解決分支裏包含了什麼這個問題。

三點

最後一種主要的範圍選擇語法是三點語法,這個可以指定被兩個引用中的一個包含但又不被兩者同時包含的分支。回過頭來看一下圖6-1裏所列的提交歷史的例子。 如果你想查看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知道你要察看哪些提交就容易得多了。


本文來自 http://git-scm.com/ 保存下來,方便自己查閱。


發佈了3 篇原創文章 · 獲贊 2 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章