# 選擇修訂版本
Git 允許你通過幾種方法來指明特定的或者一定范圍內的提交。 了解它們并不是必需的,但是了解一下總沒壞處。
## 單個修訂版本
你可以通過 Git 給出的 SHA-1 值來獲取一次提交,不過還有很多更人性化的方式來做同樣的事情。 本節將會介紹獲取單個提交的多種方法。
### 簡短的 SHA-1
Git 十分智能,你只需要提供 SHA-1 的前幾個字符就可以獲得對應的那次提交,當然你提供的 SHA-1 字符數量不得少于 4 個,并且沒有歧義——也就是說,當前倉庫中只有一個對象以這段 SHA-1 開頭。
例如查看一次指定的提交,假設你執行?`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
~~~
假設這個提交是?`1c002dd....`,如果你想?`git show`?這個提交,下面的命令是等價的(假設簡短的版本沒有歧義):
~~~
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d
~~~
Git 可以為 SHA-1 值生成出簡短且唯一的縮寫。 如果你在?`git log`?后加上?`--abbrev-commit`?參數,輸出結果里就會顯示簡短且唯一的值;默認使用七個字符,不過有時為了避免 SHA-1 的歧義,會增加字符數:
~~~
$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit
~~~
通常 8 到 10 個字符就已經足夠在一個項目中避免 SHA-1 的歧義。
比如 Linux 內核這個相當大的 Git 項目,目前有超過 45 萬個提交,包含 360 萬個對象,也只需要前 11 個字符就能保證唯一性。
> ###### NOTE
> ## 關于 SHA-1 的簡短說明
> 許多人覺得他們的倉庫里有可能出現兩個 SHA-1 值相同的對象。 然后呢?
> 如果你真的向倉庫里提交了一個跟之前的某個對象具有相同 SHA-1 值的對象,Git 發現倉庫里已經存在了擁有相同 HASH 值的對象,就會認為這個新的提交是已經被寫入倉庫的。 如果之后你想檢出那個對象時,你將得到先前那個對象的數據。
> 但是這種情況發生的概率十分渺小。 SHA-1 摘要長度是 20 字節,也就是 160 位。 280 個隨機哈希對象才有 50% 的概率出現一次沖突 (計算沖突機率的公式是?`p = (n(n-1)/2) * (1/2^160))`?)。280 是 1.2 x 10^24 也就是一億億億。 那是地球上沙粒總數的 1200 倍。
> 舉例說一下怎樣才能產生一次 SHA-1 沖突。 如果地球上 65 億個人類都在編程,每人每秒都在產生等價于整個 Linux 內核歷史(360 萬個 Git 對象)的代碼,并將之提交到一個巨大的 Git 倉庫里面,這樣持續兩年的時間才會產生足夠的對象,使其擁有 50% 的概率產生一次 SHA-1 對象沖突。 這要比你編程團隊的成員同一個晚上在互不相干的意外中被狼襲擊并殺死的機率還要小。
## 分支引用
指明一次提交最直接的方法是有一個指向它的分支引用。 這樣你就可以在任意一個 Git 命令中使用這個分支名來代替對應的提交對象或者 SHA-1 值。 例如,你想要查看一個分支的最后一次提交的對象,假設?`topic1`?分支指向?`ca82a6d`?,那么以下的命令是等價的:
~~~
$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1
~~~
如果你想知道某個分支指向哪個特定的 SHA-1,或者想看任何一個例子中被簡寫的 SHA-1 ,你可以使用一個叫做?`rev-parse`?的 Git 探測工具。 你可以在?[Git 內部原理](http://git-scm.com/book/zh/v2/1-git-internals/_git_internals)?中查看更多關于探測工具的信息。簡單來說,`rev-parse`?是為了底層操作而不是日常操作設計的。 不過,有時你想看 Git 現在到底處于什么狀態時,它可能會很有用。 你可以在你的分支上執行?`rev-parse`
~~~
$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949
~~~
### 引用日志
當你在工作時, Git 會在后臺保存一個引用日志(reflog),引用日志記錄了最近幾個月你的 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
~~~
每當你的 HEAD 所指向的位置發生了變化,Git 就會將這個信息存儲到引用日志這個歷史記錄里。 通過這些數據,你可以很方便地獲取之前的提交歷史。 如果你想查看倉庫中 HEAD 在五次前的所指向的提交,你可以使用?`@{n}`?來引用 reflog 中輸出的提交記錄。
~~~
$ git show HEAD@{5}
~~~
你同樣可以使用這個語法來查看某個分支在一定時間前的位置。 例如,查看你的?`master`?分支在昨天的時候指向了哪個提交,你可以輸入
~~~
$ git show master@{yesterday}
~~~
就會顯示昨天該分支的頂端指向了哪個提交。 這個方法只對還在你引用日志里的數據有用,所以不能用來查好幾個月之前的提交。
可以運行?`git log -g`?來查看類似于?`git log`?輸出格式的引用日志信息:
~~~
$ 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'
~~~
值得注意的是,引用日志只存在于本地倉庫,一個記錄你在你自己的倉庫里做過什么的日志。 其他人拷貝的倉庫里的引用日志不會和你的相同;而你新克隆一個倉庫的時候,引用日志是空的,因為你在倉庫里還沒有操作。?`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 <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
~~~
你也可以在?`^`?后面添加一個數字——例如?`d921970^2`?代表 “d921970 的第二父提交” 這個語法只適用于合并(merge)的提交,因為合并提交會有多個父提交。 第一父提交是你合并時所在分支,而第二父提交是你所合并的分支:
~~~
$ 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
~~~
另一種指明祖先提交的方法是?`~`。 同樣是指向第一父提交,因此?`HEAD~`?和?`HEAD^`?是等價的。 而區別在于你在后面加數字的時候。?`HEAD~2`?代表 “第一父提交的第一父提交”,也就是 “祖父提交” —— Git 會根據你指定的次數獲取對應的第一父提交。 例如,在之前的列出的提交歷史中,`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 選出在一個分支中而不在另一個分支中的提交。 例如,你有如下的提交歷史?[Figure?7-1](http://git-scm.com/book/zh/v2/ch00/double_dot)

Figure 7-1.?Example history for range selection.
你想要查看 experiment 分支中還有哪些提交尚未被合并入 master 分支。 你可以使用`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`?來指明你不希望提交被包含其中的分支。 因此下列3個命令是等價的:
~~~
$ 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`?按日期排序的輸出一樣,僅僅給出了4個提交的信息。
這種情形下,`log`?命令的一個常用參數是?`--left-right`,它會顯示每個提交到底處于哪一側的分支。 這會讓輸出數據更加清晰。
~~~
$ git log --left-right master...experiment
< F
< E
> D
> C
~~~
有了這些工具,你就可以十分方便地查看你 Git 倉庫中的提交。
- 前言
- Scott Chacon 序
- Ben Straub 序
- 獻辭
- 貢獻者
- 引言
- 1. 起步
- 1.1 關于版本控制
- 1.2 Git 簡史
- 1.3 Git 基礎
- 1.4 命令行
- 1.5 安裝 Git
- 1.6 初次運行 Git 前的配置
- 1.7 獲取幫助
- 1.8 總結
- 2. Git 基礎
- 2.1 獲取 Git 倉庫
- 2.2 記錄每次更新到倉庫
- 2.3 查看提交歷史
- 2.4 撤消操作
- 2.5 遠程倉庫的使用
- 2.6 打標簽
- 2.7 Git 別名
- 2.8 總結
- 3. Git 分支
- 3.1 分支簡介
- 3.2 分支的新建與合并
- 3.3 分支管理
- 3.4 分支開發工作流
- 3.5 遠程分支
- 3.6 變基
- 3.7 總結
- 4. 服務器上的 Git
- 4.1 協議
- 4.2 在服務器上搭建 Git
- 4.3 生成 SSH 公鑰
- 4.4 配置服務器
- 4.5 Git 守護進程
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 第三方托管的選擇
- 4.10 總結
- 5. 分布式 Git
- 5.1 分布式工作流程
- 5.2 向一個項目貢獻
- 5.3 維護項目
- 5.4 總結
- 6. GitHub
- 6.1 賬戶的創建和配置
- 6.2 對項目做出貢獻
- 6.3 維護項目
- 6.4 管理組織
- 6.5 腳本 GitHub
- 6.6 總結
- 7. Git 工具
- 7.1 選擇修訂版本
- 7.2 交互式暫存
- 7.3 儲藏與清理
- 7.4 簽署工作
- 7.5 搜索
- 7.6 重寫歷史
- 7.7 重置揭密
- 7.8 高級合并
- 7.9 Rerere
- 7.10 使用 Git 調試
- 7.11 子模塊
- 7.12 打包
- 7.13 替換
- 7.14 憑證存儲
- 7.15 總結
- 8. 自定義 Git
- 8.1 配置 Git
- 8.2 Git 屬性
- 8.3 Git 鉤子
- 8.4 使用強制策略的一個例子
- 8.5 總結
- 9. Git 與其他系統
- 9.1 作為客戶端的 Git
- 9.2 遷移到 Git
- 9.3 總結
- 10. Git 內部原理
- 10.1 底層命令和高層命令
- 10.2 Git 對象
- 10.3 Git 引用
- 10.4 包文件
- 10.5 引用規格
- 10.6 傳輸協議
- 10.7 維護與數據恢復
- 10.8 環境變量
- 10.9 總結
- A. 其它環境中的 Git
- A1.1 圖形界面
- A1.2 Visual Studio 中的 Git
- A1.3 Eclipse 中的 Git
- A1.4 Bash 中的 Git
- A1.5 Zsh 中的 Git
- A1.6 Powershell 中的 Git
- A1.7 總結
- B. 將 Git 嵌入你的應用
- A2.1 命令行 Git 方式
- A2.2 Libgit2
- A2.3 JGit
- C. Git 命令
- A3.1 設置與配置
- A3.2 獲取與創建項目
- A3.3 快照基礎
- A3.4 分支與合并
- A3.5 項目分享與更新
- A3.6 檢查與比較
- A3.7 調試
- A3.8 補丁
- A3.9 郵件
- A3.10 外部系統
- A3.11 管理
- A3.12 底層命令