# 2.3? 查看提交歷史
在提交了若干更新之后,又或者克隆了某個項目,想回顧下提交歷史,可以使用?`git log`?命令查看。
接下來的例子會用我專門用于演示的 simplegit 項目,運行下面的命令獲取該項目源代碼:
~~~
git clone git://github.com/schacon/simplegit-progit.git
~~~
然后在此項目中運行?`git log`,應該會看到下面的輸出:
~~~
$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon
Date: Mon Mar 17 21:52:11 2008 -0700
changed the version number
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon
Date: Sat Mar 15 10:31:28 2008 -0700
first commit
~~~
默認不用任何參數的話,`git log`?會按提交時間列出所有的更新,最近的更新排在最上面。看到了嗎,每次更新都有一個 SHA-1 校驗和、作者的名字和電子郵件地址、提交時間,最后縮進一個段落顯示提交說明。
`git log`?有許多選項可以幫助你搜尋感興趣的提交,接下來我們介紹些最常用的。我們常用?`-p`?選項展開顯示每次提交的內容差異,用?`-2`?則僅顯示最近的兩次更新:
~~~
$ git log -p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon
Date: Mon Mar 17 21:52:11 2008 -0700
changed the version number
diff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'
spec = Gem::Specification.new do |s|
- s.version = "0.1.0"
+ s.version = "0.1.1"
s.author = "Scott Chacon"
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGit
end
end
-
-if $0 == __FILE__
- git = SimpleGit.new
- puts git.show
-end
\ No newline at end of file
~~~
在做代碼審查,或者要快速瀏覽其他協作者提交的更新都作了哪些改動時,就可以用這個選項。此外,還有許多摘要選項可以用,比如?`--stat`,僅顯示簡要的增改行數統計:
~~~
$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon
Date: Mon Mar 17 21:52:11 2008 -0700
changed the version number
Rakefile | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
lib/simplegit.rb | 5 -----
1 files changed, 0 insertions(+), 5 deletions(-)
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon
Date: Sat Mar 15 10:31:28 2008 -0700
first commit
README | 6 ++++++
Rakefile | 23 +++++++++++++++++++++++
lib/simplegit.rb | 25 +++++++++++++++++++++++++
3 files changed, 54 insertions(+), 0 deletions(-)
~~~
每個提交都列出了修改過的文件,以及其中添加和移除的行數,并在最后列出所有增減行數小計。還有個常用的?`--pretty`?選項,可以指定使用完全不同于默認格式的方式展示提交歷史。比如用`oneline`?將每個提交放在一行顯示,這在提交數很大時非常有用。另外還有?`short`,`full`?和`fuller`?可以用,展示的信息或多或少有些不同,請自己動手實踐一下看看效果如何。
~~~
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary testcode
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit?
~~~
但最有意思的是?`format`,可以定制要顯示的記錄格式,這樣的輸出便于后期編程提取分析,像這樣:
~~~
$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 11 months ago : changed the version number
085bb3b - Scott Chacon, 11 months ago : removed unnecessary testcode
a11bef0 - Scott Chacon, 11 months ago : first commit?
~~~
下表 ?列出了常用的格式占位符寫法及其代表的意義。
`選項?? 說明`
~~~
%H 提交對象(commit)的完整哈希字串
%h 提交對象的簡短哈希字串
%T 樹對象(tree)的完整哈希字串
%t 樹對象的簡短哈希字串
%P 父對象(parent)的完整哈希字串
%p 父對象的簡短哈希字串
%an 作者(author)的名字
%ae 作者的電子郵件地址
%ad 作者修訂日期(可以用 -date= 選項定制格式)
%ar 作者修訂日期,按多久以前的方式顯示
%cn 提交者(committer)的名字
%ce 提交者的電子郵件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式顯示
%s 提交說明
~~~
你一定奇怪_作者(author)_和_提交者(committer)_之間究竟有何差別,其實作者指的是實際作出修改的人,提交者指的是最后將此 工作成果提交到倉庫的人。所以,當你為某個項目發布補丁,然后某個核心成員將你的補丁并入項目時,你就是作者,而那個核心成員就是提交者。我們會在第五章 再詳細介紹兩者之間的細微差別。
用 oneline 或 format 時結合?`--graph`?選項,可以看到開頭多出一些 ASCII 字符串表示的簡單圖形,形象地展示了每個提交所在的分支及其分化衍合情況。在我們之前提到的 Grit 項目倉庫中可以看到:
~~~
$ git log --pretty=format:"%h %s"--graph
* 2d3acf9 ignore errors from SIGCHLD on trap
* 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit
|\
| * 420eac9 Added a method for getting the current branch.
* | 30e367c timeout code and tests
* | 5a09431 add timeout protection to grit
* | e1193f8 support for heads with slashes in them
|/
* d6016bc require time for xmlschema
* 11d191e Merge branch 'defunkt' into local
~~~
以上只是簡單介紹了一些?`git log`?命令支持的選項。下表還列出了一些其他常用的選項及其釋義。
~~~
選項 說明
-p 按補丁格式顯示每個更新之間的差異。
--stat 顯示每次更新的文件修改統計信息。
--shortstat 只顯示 --stat 中最后的行數修改添加移除統計。
--name-only 僅在提交信息后顯示已修改的文件清單。
--name-status 顯示新增、修改、刪除的文件清單。
--abbrev-commit 僅顯示 SHA-1 的前幾個字符,而非所有的 40 個字符。
--relative-date 使用較短的相對時間顯示(比如,“2 weeks ago”)。
--graph 顯示 ASCII 圖形表示的分支合并歷史。
--pretty 使用其他格式顯示歷史提交信息。可用的選項包括 oneline,short,full,fuller 和 format(后跟指定格式)。?
~~~
### 限制輸出長度
除了定制輸出格式的選項之外,`git log`?還有許多非常實用的限制輸出長度的選項,也就是只輸出部分提交信息。之前我們已經看到過?`-2`?了,它只顯示最近的兩條提交,實際上,這是?`-?`選項的寫法,其中的?`n`?可以是任何自然數,表示僅顯示最近的若干條提交。不過實踐中我們是不太用這個選項的,Git 在輸出所有提交時會自動調用分頁程序(less),要看更早的更新只需翻到下頁即可。
另外還有按照時間作限制的選項,比如?`--since`?和?`--until`。下面的命令列出所有最近兩周內的提交:
~~~
$ git log --since=2.weeks
~~~
你可以給出各種時間格式,比如說具體的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。
還可以給出若干搜索條件,列出符合的提交。用?`--author`?選項顯示指定作者的提交,用?`--grep`?選項搜索提交說明中的關鍵字。(請注意,如果要得到同時滿足這兩個選項搜索條件的提交,就必須用`--all-match`?選項。)
如果只關心某些文件或者目錄的歷史提交,可以在?`git log`?選項的最后指定它們的路徑。因為是放在最后位置上的選項,所以用兩個短劃線(`--`)隔開之前的選項和后面限定的路徑名。
下表 還列出了其他常用的類似選項。
~~~
選項 說明
-(n) 僅顯示最近的 n 條提交
--since, --after 僅顯示指定時間之后的提交。
--until, --before 僅顯示指定時間之前的提交。
--author 僅顯示指定作者相關的提交。
--committer 僅顯示指定提交者相關的提交。
~~~
來看一個實際的例子,如果要查看 Git 倉庫中,2008 年 10 月期間,Junio Hamano 提交的但未合并的測試腳本(位于項目的 t/ 目錄下的文件),可以用下面的查詢命令:
~~~
$ git log --pretty="%h - %s" --author=gitster --since="2008-10-01"\
--before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attribute
acd3b9e - Enhance hold_lock_file_for_{update,append}()
f563754 - demonstrate breakage of detached checkout wi
d1a43f2 - reset --hard/read-tree --reset -u: remove un
51a94af - Fix "checkout --track -b newbranch" on detac
b0ad11e - pull: allow "git pull origin $something:$cur
~~~
Git 項目有 20,000 多條提交,但我們給出搜索選項后,僅列出了其中滿足條件的 6 條。
### 使用圖形化工具查閱提交歷史
有時候圖形化工具更容易展示歷史提交的變化,隨 Git 一同發布的 gitk 就是這樣一種工具。它是用 Tcl/Tk 寫成的,基本上相當于?`git log`?命令的可視化版本,凡是`git log`?可以用的選項也都能用在 gitk 上。在項目工作目錄中輸入 gitk 命令后,就會啟動圖 2-2 所示的界面。

圖 2-2. gitk 的圖形界面
上半個窗口顯示的是歷次提交的分支祖先圖譜,下半個窗口顯示當前點選的提交對應的具體差異。
# 2.4? 撤消操作
任何時候,你都有可能需要撤消剛才所做的某些操作。接下來,我們會介紹一些基本的撤消操作相關的命令。請注意,有些操作并不總是可以撤消的,所以請務必謹慎小心,一旦失誤,就有可能丟失部分工作成果。
### 修改最后一次提交
有時候我們提交完了才發現漏掉了幾個文件沒有加,或者提交信息寫錯了。想要撤消剛才的提交操作,可以使用?`--amend`選項重新提交:
~~~
$ git commit –amend
~~~
此命令將使用當前的暫存區域快照提交。如果剛才提交完沒有作任何改動,直接運行此命令的話,相當于有機會重新編輯提交說明,但將要提交的文件快照和之前的一樣。
啟動文本編輯器后,會看到上次提交時的說明,編輯它確認沒問題后保存退出,就會使用新的提交說明覆蓋剛才失誤的提交。
如果剛才提交時忘了暫存某些修改,可以先補上暫存操作,然后再運行?`--amend`?提交:
~~~
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
~~~
上面的三條命令最終只是產生一個提交,第二個提交命令修正了第一個的提交內容。
### 取消已經暫存的文件
接下來的兩個小節將演示如何取消暫存區域中的文件,以及如何取消工作目錄中已修改的文件。不用擔心,查看文件狀態的時候就提示了該如何撤消,所以不需要死記硬背。來看下面的例子,有兩個修改過的文件,我們想要分開提交,但不小心用`git add .`?全加到了暫存區域。該如何撤消暫存其中的一個文件呢?其實,`git status`?的命令輸出已經告訴了我們該怎么做:
~~~
$ git add .
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# modified: README.txt
# modified: benchmarks.rb
#
~~~
就在 “Changes to be committed” 下面,括號中有提示,可以使用?`git reset HEAD?...?`的方式取消暫存。好吧,我們來試試取消暫存 benchmarks.rb 文件:
~~~
$ git reset HEAD benchmarks.rb
benchmarks.rb: locally modified
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# modified: README.txt
#
# Changed but not updated:
# (use "git add
..." to update what will be committed)
# (use "git checkout --
..." to discard changes in working directory)
#
# modified: benchmarks.rb
#
~~~
這條命令看起來有些古怪,先別管,能用就行。現在 benchmarks.rb 文件又回到了之前已修改未暫存的狀態。
### 取消對文件的修改
如果覺得剛才對 benchmarks.rb 的修改完全沒有必要,該如何取消修改,回到之前的狀態(也就是修改之前的版本)呢?`git status`?同樣提示了具體的撤消方法,接著上面的例子,現在未暫存區域看起來像這樣:
~~~
# Changed but not updated:
# (use "git add
..." to update what will be committed)
# (use "git checkout --
..." to discard changes in working directory)
#
# modified: benchmarks.rb
#
~~~
在第二個括號中,我們看到了拋棄文件修改的命令(至少在 Git 1.6.1 以及更高版本中會這樣提示,如果你還在用老版本,我們強烈建議你升級,以獲取最佳的用戶體驗),讓我們試試看:
~~~
$ git checkout -- benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# modified: README.txt
#
~~~
可以看到,該文件已經恢復到修改前的版本。你可能已經意識到了,這條命令有些危險,所有對文件的修改都沒有了,因為我們剛剛把之前版本的文件復制過 來重寫了此文件。所以在用這條命令前,請務必確定真的不再需要保留剛才的修改。如果只是想回退版本,同時保留剛才的修改以便將來繼續工作,可以用下章介紹 的 stashing 和分支來處理,應該會更好些。
記住,任何已經提交到 Git 的都可以被恢復。即便在已經刪除的分支中的提交,或者用?`--amend`?重新改寫的提交,都可以被恢復(關于數據恢復的內容見第九章)。所以,你可能失去的數據,僅限于沒有提交過的,對 Git 來說它們就像從未存在過一樣。
# 2.5? 遠程倉庫的使用
要參與任何一個 Git 項目的協作,必須要了解該如何管理遠程倉庫。遠程倉庫是指托管在網絡上的項目倉庫,可能會有好多個,其中有些你只能讀,另外有些可以寫。同他人協作開發某 個項目時,需要管理這些遠程倉庫,以便推送或拉取數據,分享各自的工作進展。管理遠程倉庫的工作,包括添加遠程庫,移除廢棄的遠程庫,管理各式遠程庫分 支,定義是否跟蹤這些分支,等等。本節我們將詳細討論遠程庫的管理和使用。
### 查看當前的遠程庫
要查看當前配置有哪些遠程倉庫,可以用?`git remote`?命令,它會列出每個遠程庫的簡短名字。在克隆完某個項目后,至少可以看到一個名為 origin 的遠程庫,Git 默認使用這個名字來標識你所克隆的原始倉庫:
~~~
$ git clone git://github.com/schacon/ticgit.git
Initialized empty Git repository in /private/tmp/ticgit/.git/
remote: Counting objects: 595, done.
remote: Compressing objects: 100% (269/269), done.
remote: Total 595 (delta 255), reused 589 (delta 253)
Receiving objects: 100% (595/595), 73.31 KiB | 1 KiB/s,done.
Resolving deltas: 100% (255/255), done.
$ cd ticgit
$ git remote
origin
~~~
也可以加上?`-v`?選項(譯注:此為?`--verbose`?的簡寫,取首字母),顯示對應的克隆地址:
~~~
$ git remote -v
origin git://github.com/schacon/ticgit.git
~~~
如果有多個遠程倉庫,此命令將全部列出。比如在我的 Grit 項目中,可以看到:
~~~
$ cd grit
$ git remote -v
bakkdoor git://github.com/bakkdoor/grit.git
cho45 git://github.com/cho45/grit.git
defunkt git://github.com/defunkt/grit.git
koke git://github.com/koke/grit.git
origin git@ github.com:mojombo/grit.git
~~~
這樣一來,我就可以非常輕松地從這些用戶的倉庫中,拉取他們的提交到本地。請注意,上面列出的地址只有 origin 用的是 SSH URL 鏈接,所以也只有這個倉庫我能推送數據上去(我們會在第四章解釋原因)。
### 添加遠程倉庫
要添加一個新的遠程倉庫,可以指定一個簡單的名字,以便將來引用,運行?`git remote add [shortname] [url]`:
~~~
$ git remote
origin
$ git remote add pb git://github.com/paulboone/ticgit.git
$ git remote -v
origin git://github.com/schacon/ticgit.git
pb git://github.com/paulboone/ticgit.git
~~~
現在可以用字串 pb 指代對應的倉庫地址了。比如說,要抓取所有 Paul 有的,但本地倉庫沒有的信息,可以運行?`git fetch pb`:
~~~
$ git fetch pb
remote: Counting objects: 58, done.
remote: Compressing objects: 100% (41/41), done.
remote: Total 44 (delta 24), reused 1 (delta 0)
Unpacking objects: 100% (44/44), done.
From git://github.com/paulboone/ticgit
* [new branch] master -> pb/master
* [new branch] ticgit -> pb/ticgit
~~~
現在,Paul 的主干分支(master)已經完全可以在本地訪問了,對應的名字是?`pb/master`,你可以將它合并到自己的某個分支,或者切換到這個分支,看看有些什么有趣的更新。
### 從遠程倉庫抓取數據
正如之前所看到的,可以用下面的命令從遠程倉庫抓取數據到本地:
~~~
$ git fetch [remote-name]
~~~
此命令會到遠程倉庫中拉取所有你本地倉庫中還沒有的數據。運行完成后,你就可以在本地訪問該遠程倉庫中的所有分支,將其中某個分支合并到本地,或者只是取出某個分支,一探究竟。(我們會在第三章詳細討論關于分支的概念和操作。)
如果是克隆了一個倉庫,此命令會自動將遠程倉庫歸于 origin 名下。所以,`git fetch origin`?會抓取從你上次克隆以來別人上傳到此遠程倉庫中的所有更新(或是上次 fetch 以來別人提交的更新)。有一點很重要,需要記住,fetch 命令只是將遠端的數據拉到本地倉庫,并不自動合并到當前工作分支,只有當你確實準備好了,才能手工合并。
如果設置了某個分支用于跟蹤某個遠端倉庫的分支(參見下節及第三章的內容),可以使用?`git pull`?命令自動抓取數據下來,然后將遠端分支自動合并到本地倉庫中當前分支。在日常工作中我們經常這么用,既快且好。實際上,默認情況下`git clone`?命令本質上就是自動創建了本地的 master 分支用于跟蹤遠程倉庫中的 master 分支(假設遠程倉庫確實有 master 分支)。所以一般我們運行`git pull`,目的都是要從原始克隆的遠端倉庫中抓取數據后,合并到工作目錄中的當前分支。
### 推送數據到遠程倉庫
項目進行到一個階段,要同別人分享目前的成果,可以將本地倉庫中的數據推送到遠程倉庫。實現這個任務的命令很簡單:?`git push [remote-name] [branch-name]`。如果要把本地的 master 分支推送到`origin`?服務器上(再次說明下,克隆操作會自動使用默認的 master 和 origin 名字),可以運行下面的命令:
~~~
$ git push origin master
~~~
只有在所克隆的服務器上有寫權限,或者同一時刻沒有其他人在推數據,這條命令才會如期完成任務。如果在你推數據前,已經有其他人推送了若干更新,那 你的推送操作就會被駁回。你必須先把他們的更新抓取到本地,合并到自己的項目中,然后才可以再次推送。有關推送數據到遠程倉庫的詳細內容見第三章。
### 查看遠程倉庫信息
我們可以通過命令?`git remote show [remote-name]`?查看某個遠程倉庫的詳細信息,比如要看所克隆的?`origin`?倉庫,可以運行:
~~~
$ git remote show origin
* remote origin
URL: git://github.com/schacon/ticgit.git
Remote branch merged with 'git pull' whileon branch master
master
Tracked remote branches
master
ticgit
~~~
除了對應的克隆地址外,它還給出了許多額外的信息。它友善地告訴你如果是在 master 分支,就可以用?`git pull`?命令抓取數據合并到本地。另外還列出了所有處于跟蹤狀態中的遠端分支。
上面的例子非常簡單,而隨著使用 Git 的深入,`git remote show`?給出的信息可能會像這樣:
~~~
$ git remote show origin
* remote origin
URL: git@ github.com:defunkt/github.git
Remote branch merged with 'git pull' whileon branch issues
issues
Remote branch merged with 'git pull' whileon branch master
master
New remote branches (next fetch will store in remotes/origin)
caching
Stale tracking branches (use 'git remote prune')
libwalker
walker2
Tracked remote branches
acl
apiv2
dashboard2
issues
master
postgres
Local branch pushed with 'git push'
master:master
~~~
它告訴我們,運行?`git push`?時缺省推送的分支是什么(譯注:最后兩行)。它還顯示了有哪些遠端分支還沒有同步到本地(譯注:第六行的`caching`?分支),哪些已同步到本地的遠端分支在遠端服務器上已被刪除(譯注:`Stale tracking branches`?下面的兩個分支),以及運行`git pull`?時將自動合并哪些分支(譯注:前四行中列出的?`issues`?和?`master`?分支)。
### 遠程倉庫的刪除和重命名
在新版 Git 中可以用?`git remote rename`?命令修改某個遠程倉庫在本地的簡短名稱,比如想把?`pb`?改成`paul`,可以這么運行:
~~~
$ git remote rename pb paul
$ git remote
origin
paul
~~~
注意,對遠程倉庫的重命名,也會使對應的分支名稱發生變化,原來的?`pb/master`?分支現在成了?`paul/master`。
碰到遠端倉庫服務器遷移,或者原來的克隆鏡像不再使用,又或者某個參與者不再貢獻代碼,那么需要移除對應的遠端倉庫,可以運行?`git remote rm`?命令:
~~~
$ git remote rm paul
$ git remote
origin
~~~
# 2.6? 打標簽
同大多數 VCS 一樣,Git 也可以對某一時間點上的版本打上標簽。人們在發布某個軟件版本(比如 v1.0 等等)的時候,經常這么做。本節我們一起來學習如何列出所有可用的標簽,如何新建標簽,以及各種不同類型標簽之間的差別。
### 列顯已有的標簽
列出現有標簽的命令非常簡單,直接運行?`git tag`?即可:
~~~
$ git tag v0.1 v1.3
~~~
顯示的標簽按字母順序排列,所以標簽的先后并不表示重要程度的輕重。
我們可以用特定的搜索模式列出符合條件的標簽。在 Git 自身項目倉庫中,有著超過 240 個標簽,如果你只對 1.4.2 系列的版本感興趣,可以運行下面的命令:
~~~
$ git tag -l 'v1.4.2.*' v1.4.2.1 v1.4.2.2 v1.4.2.3 v1.4.2.4
~~~
### 新建標簽
Git 使用的標簽有兩種類型:輕量級的(lightweight)和含附注的(annotated)。輕量級標簽就像是個不會變化的分支,實際上它就是個指向特 定提交對象的引用。而含附注標簽,實際上是存儲在倉庫中的一個獨立對象,它有自身的校驗和信息,包含著標簽的名字,電子郵件地址和日期,以及標簽說明,標 簽本身也允許使用 GNU Privacy Guard (GPG) 來簽署或驗證。一般我們都建議使用含附注型的標簽,以便保留相關信息;當然,如果只是臨時性加注標簽,或者不需要旁注額外信息,用輕量級標簽也沒問題。
### 含附注的標簽
創建一個含附注類型的標簽非常簡單,用?`-a`?(譯注:取?`annotated`?的首字母)指定標簽名字即可:
~~~
$ git tag -a v1.4 -m 'my version 1.4'
$ git tag
v0.1
v1.3
v1.4
~~~
而?`-m`?選項則指定了對應的標簽說明,Git 會將此說明一同保存在標簽對象中。如果沒有給出該選項,Git 會啟動文本編輯軟件供你輸入標簽說明。
可以使用?`git show`?命令查看相應標簽的版本信息,并連同顯示打標簽時的提交對象。
~~~
$ git show v1.4
tag v1.4
Tagger: Scott Chacon
Date: Mon Feb 9 14:45:11 2009 -0800
my version 1.4
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
~~~
我們可以看到在提交對象信息上面,列出了此標簽的提交者和提交時間,以及相應的標簽說明。
### 簽署標簽
如果你有自己的私鑰,還可以用 GPG 來簽署標簽,只需要把之前的?`-a`?改為?`-s`?(譯注: 取?`signed`?的首字母)即可:
~~~
$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon
"
1024-bit DSA key, ID F721C45A, created 2009-02-09
~~~
現在再運行?`git show`?會看到對應的 GPG 簽名也附在其內:
~~~
$ git show v1.5
tag v1.5
Tagger: Scott Chacon
Date: Mon Feb 9 15:22:20 2009 -0800
my signed 1.5 tag
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Darwin)
iEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN
Ki0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/
=WryJ
-----END PGP SIGNATURE-----
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
~~~
稍后我們再學習如何驗證已經簽署的標簽。
### 輕量級標簽
輕量級標簽實際上就是一個保存著對應提交對象的校驗和信息的文件。要創建這樣的標簽,一個?`-a`,`-s`?或?`-m`?選項都不用,直接給出標簽名字即可:
~~~
$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5
~~~
現在運行?`git show`?查看此標簽信息,就只有相應的提交對象摘要:
~~~
$ git show v1.4-lw
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
~~~
### 驗證標簽
可以使用?`git tag -v [tag-name]`?(譯注:取?`verify`?的首字母)的方式驗證已經簽署的標簽。此命令會調用 GPG 來驗證簽名,所以你需要有簽署者的公鑰,存放在 keyring 中,才能驗證:
~~~
$ git tag -v v1.4.2.1
object 883653babd8ee7ea23e6a5c392bb739348b1eb61
type commit
tag v1.4.2.1
tagger Junio C Hamano
1158138501 -0700
GIT 1.4.2.1
Minor fixes since 1.4.2, including git-mv and git-http with alternates.
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Good signature from "Junio C Hamano
"
gpg: aka "[jpeg image of size 1513]"
Primary key fingerprint: 3565 2A26 2040 E066 C9A7 4A7D C0C6 D9A4 F311 9B9A
~~~
若是沒有簽署者的公鑰,會報告類似下面這樣的錯誤:
~~~
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Can't check signature: public key not found
error: could not verify the tag 'v1.4.2.1'
~~~
### 后期加注標簽
你甚至可以在后期對早先的某次提交加注標簽。比如在下面展示的提交歷史中:
~~~
$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
~~~
我們忘了在提交 “updated rakefile” 后為此項目打上版本號 v1.2,沒關系,現在也能做。只要在打標簽的時候跟上對應提交對象的校驗和(或前幾位字符)即可:
~~~
$ git tag -a v1.2 9fceb02
~~~
可以看到我們已經補上了標簽:
~~~
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon
Date: Sun Apr 27 20:43:35 2008 -0700
updated rakefile
...
~~~
### 分享標簽
默認情況下,`git push`?并不會把標簽傳送到遠端服務器上,只有通過顯式命令才能分享標簽到遠端倉庫。其命令格式如同推送分支,運行`git push origin [tagname]`?即可:
~~~
$ git push origin v1.5
Counting objects: 50, done.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (44/44), 4.56 KiB, done.
Total 44 (delta 18), reused 8 (delta 1)
To git@ github.com:schacon/simplegit.git
* [new tag] v1.5 -> v1.5
~~~
如果要一次推送所有本地新增的標簽上去,可以使用?`--tags`?選項:
~~~
$ git push origin --tags
Counting objects: 50, done.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (44/44), 4.56 KiB, done.
Total 44 (delta 18), reused 8 (delta 1)
To git@ github.com:schacon/simplegit.git
* [new tag] v0.1 -> v0.1
* [new tag] v1.2 -> v1.2
* [new tag] v1.4 -> v1.4
* [new tag] v1.4-lw -> v1.4-lw
* [new tag] v1.5 -> v1.5
~~~
現在,其他人克隆共享倉庫或拉取數據同步后,也會看到這些標簽。
# 2.7? 技巧和竅門
在結束本章之前,我還想和大家分享一些 Git 使用的技巧和竅門。很多使用 Git 的開發者可能根本就沒用過這些技巧,我們也不是說在讀過本書后非得用這些技巧不可,但至少應該有所了解吧。說實話,有了這些小竅門,我們的工作可以變得更簡單,更輕松,更高效。
### 自動完成
如果你用的是 Bash shell,可以試試看 Git 提供的自動完成腳本。下載 Git 的源代碼,進入?`contrib/completion`?目錄,會看到一個`git-completion.bash`?文件。將此文件復制到你自己的用戶主目錄中(譯注:按照下面的示例,還應改名加上點:`cp git-completion.bash ~/.git-completion.bash`),并把下面一行內容添加到你的`.bashrc`?文件中:
~~~
source ~/.git-completion.bash
~~~
也可以為系統上所有用戶都設置默認使用此腳本。Mac 上將此腳本復制到?`/opt/local/etc/bash_completion.d`?目錄中,Linux 上則復制到`/etc/bash_completion.d/`?目錄中。這兩處目錄中的腳本,都會在 Bash 啟動時自動加載。
如果在 Windows 上安裝了 msysGit,默認使用的 Git Bash 就已經配好了這個自動完成腳本,可以直接使用。
在輸入 Git 命令的時候可以敲兩次跳格鍵(Tab),就會看到列出所有匹配的可用命令建議:
~~~
$ git co commit config
~~~
此例中,鍵入 git co 然后連按兩次 Tab 鍵,會看到兩個相關的建議(命令) commit 和 config。繼而輸入?`m?`會自動完成`git commit`?命令的輸入。
命令的選項也可以用這種方式自動完成,其實這種情況更實用些。比如運行?`git log`?的時候忘了相關選項的名字,可以輸入開頭的幾個字母,然后敲 Tab 鍵看看有哪些匹配的:
~~~
$ git log --s
--shortstat --since= --src-prefix= --stat --summary
~~~
這個技巧不錯吧,可以節省很多輸入和查閱文檔的時間。
### Git 命令別名
Git 并不會推斷你輸入的幾個字符將會是哪條命令,不過如果想偷懶,少敲幾個命令的字符,可以用?`git config`?為命令設置別名。來看看下面的例子:
~~~
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
~~~
現在,如果要輸入?`git commit`?只需鍵入?`git ci`?即可。而隨著 Git 使用的深入,會有很多經常要用到的命令,遇到這種情況,不妨建個別名提高效率。
使用這種技術還可以創造出新的命令,比方說取消暫存文件時的輸入比較繁瑣,可以自己設置一下:
~~~
$ git config --global alias.unstage 'reset HEAD --'
~~~
這樣一來,下面的兩條命令完全等同:
~~~
$ git unstage fileA
$ git reset HEAD fileA
~~~
顯然,使用別名的方式看起來更清楚。另外,我們還經常設置?`last`?命令:
~~~
$ git config --global alias.last 'log -1 HEAD'
~~~
然后要看最后一次的提交信息,就變得簡單多了:
~~~
$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel
Date: Tue Aug 26 19:48:51 2008 +0800
test for current head
Signed-off-by: Scott Chacon
~~~
可以看出,實際上 Git 只是簡單地在命令中替換了你設置的別名。不過有時候我們希望運行某個外部命令,而非 Git 的附屬工具,這個好辦,只需要在命令前加上?`!`?就行。如果你自己寫了些處理 Git 倉庫信息的腳本的話,就可以用這種技術包裝起來。作為演示,我們可以設置用?`git visual`?啟動`gitk`:
~~~
$ git config --global alias.visual "!gitk"
~~~
# 2.8? 小結
到目前為止,你已經學會了最基本的 Git 操作:創建和克隆倉庫,做出更新,暫存并提交這些更新,以及查看所有歷史更新記錄。接下來,我們將學習 Git 的必殺技特性:分支模型。