Git 基礎(chǔ)
讀完本章你就能上手使用 Git 了。本章將介紹幾個(gè)最基本的,也是最常用的 Git 命令,以后絕大多數(shù)時(shí)間里用到的也就是這幾個(gè)命令。讀完本章,你就能初始化一個(gè)新的代碼倉(cāng)庫(kù),做一些適當(dāng)配置;開始或停止跟蹤某些文件;暫存或提交某些更 新。我們還會(huì)展示如何讓 Git 忽略某些文件,或是名稱符合特定模式的文件;如何既快且容易地撤消犯下的小錯(cuò)誤;如何瀏覽項(xiàng)目的更新歷史,查看某兩次更新之間的差異;以及如何從遠(yuǎn)程倉(cāng)庫(kù) 拉數(shù)據(jù)下來(lái)或者推數(shù)據(jù)上去。
2.1 取得項(xiàng)目的 Git 倉(cāng)庫(kù)
有兩種取得 Git 項(xiàng)目倉(cāng)庫(kù)的方法。第一種是在現(xiàn)存的目錄下,通過導(dǎo)入所有文件來(lái)創(chuàng)建新的 Git 倉(cāng)庫(kù)。第二種是從已有的 Git 倉(cāng)庫(kù)克隆出一個(gè)新的鏡像倉(cāng)庫(kù)來(lái)。
在工作目錄中初始化新倉(cāng)庫(kù)
要對(duì)現(xiàn)有的某個(gè)項(xiàng)目開始用 Git 管理,只需到此項(xiàng)目所在的目錄,執(zhí)行:
$ git init
初始化后,在當(dāng)前目錄下會(huì)出現(xiàn)一個(gè)名為 .git 的目錄,所有 Git 需要的數(shù)據(jù)和資源都存放在這個(gè)目錄中。不過目前,僅僅是按照既有的結(jié)構(gòu)框架初始化好了里邊所有的文件和目錄,但我們還沒有開始跟蹤管理項(xiàng)目中的任何一個(gè)文件。(在第九章我們會(huì)詳細(xì)說明剛才創(chuàng)建的.git 目錄中究竟有哪些文件,以及都起些什么作用。)
如果當(dāng)前目錄下有幾個(gè)文件想要納入版本控制,需要先用 git add 命令告訴 Git 開始對(duì)這些文件進(jìn)行跟蹤,然后提交:
$ git add *.c
$ git add README
$ git commit -m 'initial project version'
稍后我們?cè)僦鹨唤忉屆織l命令的意思。不過現(xiàn)在,你已經(jīng)得到了一個(gè)實(shí)際維護(hù)著若干文件的 Git 倉(cāng)庫(kù)。
從現(xiàn)有倉(cāng)庫(kù)克隆
如果想對(duì)某個(gè)開源項(xiàng)目出一份力,可以先把該項(xiàng)目的 Git 倉(cāng)庫(kù)復(fù)制一份出來(lái),這就需要用到 git clone 命令。如果你熟悉其他的 VCS 比如 Subversion,你可能已經(jīng)注意到這里使用的是 clone 而不是 checkout。這是個(gè)非常重要的差別,Git 收取的是項(xiàng)目歷史的所有數(shù)據(jù)(每一個(gè)文件的每一個(gè)版本),服務(wù)器上有的數(shù)據(jù)克隆之后本地也都有了。實(shí)際上,即便服務(wù)器的磁盤發(fā)生故障,用任何一個(gè)克隆出來(lái) 的客戶端都可以重建服務(wù)器上的倉(cāng)庫(kù),回到當(dāng)初克隆時(shí)的狀態(tài)(雖然可能會(huì)丟失某些服務(wù)器端的掛鉤設(shè)置,但所有版本的數(shù)據(jù)仍舊還在,有關(guān)細(xì)節(jié)請(qǐng)參考第四章)。
克隆倉(cāng)庫(kù)的命令格式為 git clone [url] 。比如,要克隆 Ruby 語(yǔ)言的 Git 代碼倉(cāng)庫(kù) Grit,可以用下面的命令:
$ git clone git://github.com/schacon/grit.git
這會(huì)在當(dāng)前目錄下創(chuàng)建一個(gè)名為“grit”的目錄,其中包含一個(gè) .git 的目錄,用于保存下載下來(lái)的所有版本記錄,然后從中取出最新版本的文件拷貝。如果進(jìn)入這個(gè)新建的grit 目錄,你會(huì)看到項(xiàng)目中的所有文件已經(jīng)在里邊了,準(zhǔn)備好后續(xù)的開發(fā)和使用。如果希望在克隆的時(shí)候,自己定義要新建的項(xiàng)目目錄名稱,可以在上面的命令末尾指定新的名字:
$ git clone git://github.com/schacon/grit.git mygrit
唯一的差別就是,現(xiàn)在新建的目錄成了 mygrit,其他的都和上邊的一樣。
Git 支持許多數(shù)據(jù)傳輸協(xié)議。之前的例子使用的是 git:// 協(xié)議,不過你也可以用 http(s):// 或者user@server:/path.git 表示的 SSH 傳輸協(xié)議。我們會(huì)在第四章詳細(xì)介紹所有這些協(xié)議在服務(wù)器端該如何配置使用,以及各種方式之間的利弊。
2.2 記錄每次更新到倉(cāng)庫(kù)
現(xiàn)在我們手上已經(jīng)有了一個(gè)真實(shí)項(xiàng)目的 Git 倉(cāng)庫(kù),并從這個(gè)倉(cāng)庫(kù)中取出了所有文件的工作拷貝。接下來(lái),對(duì)這些文件作些修改,在完成了一個(gè)階段的目標(biāo)之后,提交本次更新到倉(cāng)庫(kù)。
請(qǐng)記住,工作目錄下面的所有文件都不外乎這兩種狀態(tài):已跟蹤或未跟蹤。已跟蹤的文件是指本來(lái)就被納入版本控制管理的文件,在上次快照中有它們的記 錄,工作一段時(shí)間后,它們的狀態(tài)可能是未更新,已修改或者已放入暫存區(qū)。而所有其他文件都屬于未跟蹤文件。它們既沒有上次更新時(shí)的快照,也不在當(dāng)前的暫存 區(qū)域。初次克隆某個(gè)倉(cāng)庫(kù)時(shí),工作目錄中的所有文件都屬于已跟蹤文件,且狀態(tài)為未修改。
在編輯過某些文件之后,Git 將這些文件標(biāo)為已修改。我們逐步把這些修改過的文件放到暫存區(qū)域,直到最后一次性提交所有這些暫存起來(lái)的文件,如此重復(fù)。所以使用 Git 時(shí)的文件狀態(tài)變化周期如圖 2-1 所示。
圖 2-1. 文件的狀態(tài)變化周期
檢查當(dāng)前文件狀態(tài)
要確定哪些文件當(dāng)前處于什么狀態(tài),可以用 git status 命令。如果在克隆倉(cāng)庫(kù)之后立即執(zhí)行此命令,會(huì)看到類似這樣的輸出:
$ git status
# On branch master
nothing to commit (working directory clean)
這說明你現(xiàn)在的工作目錄相當(dāng)干凈。換句話說,當(dāng)前沒有任何跟蹤著的文件,也沒有任何文件在上次提交后更改過。此外,上面的信息還表明,當(dāng)前目錄下沒 有出現(xiàn)任何處于未跟蹤的新文件,否則 Git 會(huì)在這里列出來(lái)。最后,該命令還顯示了當(dāng)前所在的分支是 master,這是默認(rèn)的分支名稱,實(shí)際是可以修改的,現(xiàn)在先不用考慮。下一章我們就會(huì)詳細(xì)討論分支和引用。
現(xiàn)在讓我們用 vim 編輯一個(gè)新文件 README,保存退出后運(yùn)行 git status 會(huì)看到該文件出現(xiàn)在未跟蹤文件列表中:
$ vim README
$ git status
# On branch master
# Untracked files:
# (use "git add
..." to include in what will be committed)
#
# README
nothing added to commit but untracked files present (use "git add" to track)
就是在“Untracked files”這行下面。Git 不會(huì)自動(dòng)將之納入跟蹤范圍,除非你明明白白地告訴它“我需要跟蹤該文件”,因而不用擔(dān)心把臨時(shí)文件什么的也歸入版本管理。不過現(xiàn)在的例子中,我們確實(shí)想要跟蹤管理 README 這個(gè)文件。
跟蹤新文件
使用命令 git add 開始跟蹤一個(gè)新文件。所以,要跟蹤 README 文件,運(yùn)行:
$ git add README
此時(shí)再運(yùn)行 git status 命令,會(huì)看到 README 文件已被跟蹤,并處于暫存狀態(tài):
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
#
只要在 “Changes to be committed” 這行下面的,就說明是已暫存狀態(tài)。如果此時(shí)提交,那么該文件此時(shí)此刻的版本將被留存在歷史記錄中。你可能會(huì)想起之前我們使用git init 后就運(yùn)行了 git add 命令,開始跟蹤當(dāng)前目錄下的文件。在 git add 后面可以指明要跟蹤的文件或目錄路徑。如果是目錄的話,就說明要遞歸跟蹤該目錄下的所有文件。(譯注:其實(shí)git add 的潛臺(tái)詞就是把目標(biāo)文件快照放入暫存區(qū)域,也就是 add file into staged area,同時(shí)未曾跟蹤過的文件標(biāo)記為需要跟蹤。這樣就好理解后續(xù) add 操作的實(shí)際意義了。)
暫存已修改文件
現(xiàn)在我們修改下之前已跟蹤過的文件 benchmarks.rb ,然后再次運(yùn)行 status 命令,會(huì)看到這樣的狀態(tài)報(bào)告:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
#
# Changed but not updated:
# (use "git add
..." to update what will be committed)
#
# modified: benchmarks.rb
#
文件 benchmarks.rb 出現(xiàn)在 “Changed but not updated” 這行下面,說明已跟蹤文件的內(nèi)容發(fā)生了變化,但還沒有放到暫存區(qū)。要暫存這次更新,需要運(yùn)行git add 命令(這是個(gè)多功能命令,根據(jù)目標(biāo)文件的狀態(tài)不同,此命令的效果也不同:可以用它開始跟蹤新文件,或者把已跟蹤的文件放到暫存區(qū),還能用于合并時(shí)把有沖突的文件標(biāo)記為已解決狀態(tài)等)?,F(xiàn)在讓我們運(yùn)行git add 將 benchmarks.rb 放到暫存區(qū),然后再看看 git status 的輸出:
$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
現(xiàn)在兩個(gè)文件都已暫存,下次提交時(shí)就會(huì)一并記錄到倉(cāng)庫(kù)。假設(shè)此時(shí),你想要在 benchmarks.rb 里再加條注釋,重新編輯存盤后,準(zhǔn)備好提交。不過且慢,再運(yùn)行git status 看看:
$ vim benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
# Changed but not updated:
# (use "git add
..." to update what will be committed)
#
# modified: benchmarks.rb
#
怎么回事?benchmarks.rb 文件出現(xiàn)了兩次!一次算未暫存,一次算已暫存,這怎么可能呢?好吧,實(shí)際上 Git 只不過暫存了你運(yùn)行 git add 命令時(shí)的版本,如果現(xiàn)在提交,那么提交的是添加注釋前的版本,而非當(dāng)前工作目錄中的版本。所以,運(yùn)行了git add 之后又作了修訂的文件,需要重新運(yùn)行 git add 把最新版本重新暫存起來(lái):
$ git add benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
忽略某些文件
一般我們總會(huì)有些文件無(wú)需納入 Git 的管理,也不希望它們總出現(xiàn)在未跟蹤文件列表。通常都是些自動(dòng)生成的文件,比如日志文件,或者編譯過程中創(chuàng)建的臨時(shí)文件等。我們可以創(chuàng)建一個(gè)名為 .gitignore 的文件,列出要忽略的文件模式。來(lái)看一個(gè)實(shí)際的例子:
$ cat .gitignore
*.[oa]
*~
第一行告訴 Git 忽略所有以 .o 或 .a 結(jié)尾的文件。一般這類對(duì)象文件和存檔文件都是編譯過程中出現(xiàn)的,我們用不著跟蹤它們的版本。第二行告訴 Git 忽略所有以波浪符(~ )結(jié)尾的文件,許多文本編輯軟件(比如 Emacs)都用這樣的文件名保存副本。此外,你可能還需要忽略 log,tmp 或者 pid 目錄,以及自動(dòng)生成的文檔等等。要養(yǎng)成一開始就設(shè)置好 .gitignore 文件的習(xí)慣,以免將來(lái)誤提交這類無(wú)用的文件。
文件 .gitignore 的格式規(guī)范如下:
- 所有空行或者以注釋符號(hào) # 開頭的行都會(huì)被 Git 忽略。
- 可以使用標(biāo)準(zhǔn)的 glob 模式匹配。 * 匹配模式最后跟反斜杠(
/ )說明要忽略的是目錄。 * 要忽略指定模式以外的文件或目錄,可以在模式前加上驚嘆號(hào)(! )取反。
所謂的 glob 模式是指 shell 所使用的簡(jiǎn)化了的正則表達(dá)式。星號(hào)(* )匹配零個(gè)或多個(gè)任意字符;[abc] 匹配任何一個(gè)列在方括號(hào)中的字符(這個(gè)例子要么匹配一個(gè) a,要么匹配一個(gè) b,要么匹配一個(gè) c);問號(hào)(? )只匹配一個(gè)任意字符;如果在方括號(hào)中使用短劃線分隔兩個(gè)字符,表示所有在這兩個(gè)字符范圍內(nèi)的都可以匹配(比如[0-9] 表示匹配所有 0 到 9 的數(shù)字)。
我們?cè)倏匆粋€(gè) .gitignore 文件的例子:
# 此為注釋 – 將被 Git 忽略
*.a # 忽略所有 .a 結(jié)尾的文件
!lib.a # 但 lib.a 除外
/TODO # 僅僅忽略項(xiàng)目根目錄下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目錄下的所有文件
doc/*.txt # 會(huì)忽略 doc/notes.txt 但不包括 doc/server/arch.txt
查看已暫存和未暫存的更新
實(shí)際上 git status 的顯示比較簡(jiǎn)單,僅僅是列出了修改過的文件,如果要查看具體修改了什么地方,可以用 git diff 命令。稍后我們會(huì)詳細(xì)介紹git diff ,不過現(xiàn)在,它已經(jīng)能回答我們的兩個(gè)問題了:當(dāng)前做的哪些更新還沒有暫存?有哪些更新已經(jīng)暫存起來(lái)準(zhǔn)備好了下次提交? git diff 會(huì)使用文件補(bǔ)丁的格式顯示具體添加和刪除的行。
假如再次修改 README 文件后暫存,然后編輯 benchmarks.rb 文件后先別暫存,運(yùn)行 status 命令,會(huì)看到:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
#
# Changed but not updated:
# (use "git add
..." to update what will be committed)
#
# modified: benchmarks.rb
#
要查看尚未暫存的文件更新了哪些部分,不加參數(shù)直接輸入 git diff :
$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..da65585 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
@commit.parents[0].parents[0].parents[0]
end
+ run_code(x, 'commits 1') do
+ git.commits.size
+ end
+
run_code(x, 'commits 2') do
log = git.commits('master', 15)
log.size
此命令比較的是工作目錄中當(dāng)前文件和暫存區(qū)域快照之間的差異,也就是修改之后還沒有暫存起來(lái)的變化內(nèi)容。
若要看已經(jīng)暫存起來(lái)的文件和上次提交時(shí)的快照之間的差異,可以用 git diff --cached 命令。(Git 1.6.1 及更高版本還允許使用git diff --staged ,效果是相同的,但更好記些。)來(lái)看看實(shí)際的效果:
$ git diff --cached
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README2
@@ -0,0 +1,5 @@
+grit
+ by Tom Preston-Werner, Chris Wanstrath
+ http://github.com/mojombo/grit
+
+Grit is a Ruby library for extracting information from a Git repository
請(qǐng)注意,單單 git diff 不過是顯示還沒有暫存起來(lái)的改動(dòng),而不是這次工作和上次提交之間的差異。所以有時(shí)候你一下子暫存了所有更新過的文件后,運(yùn)行git diff 后卻什么也沒有,就是這個(gè)原因。
像之前說的,暫存 benchmarks.rb 后再編輯,運(yùn)行 git status 會(huì)看到暫存前后的兩個(gè)版本:
$ git add benchmarks.rb
$ echo '# test line' >> benchmarks.rb
$ git status
# On branch master
#
# Changes to be committed:
#
# modified: benchmarks.rb
#
# Changed but not updated:
#
# modified: benchmarks.rb
#
現(xiàn)在運(yùn)行 git diff 看暫存前后的變化:
$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index e445e28..86b2f7c 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -127,3 +127,4 @@ end
main()
##pp Grit::GitRuby.cache_client.stats
+# test line
然后用 git diff --cached 查看已經(jīng)暫存起來(lái)的變化:
$ git diff --cached
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..e445e28 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
@commit.parents[0].parents[0].parents[0]
end
+ run_code(x, 'commits 1') do
+ git.commits.size
+ end
+
run_code(x, 'commits 2') do
log = git.commits('master', 15)
log.size
提交更新
現(xiàn)在的暫存區(qū)域已經(jīng)準(zhǔn)備妥當(dāng)可以提交了。在此之前,請(qǐng)一定要確認(rèn)還有什么修改過的或新建的文件還沒有 git add 過,否則提交的時(shí)候不會(huì)記錄這些還沒暫存起來(lái)的變化。所以,每次準(zhǔn)備提交前,先用git status 看下,是不是都已暫存起來(lái)了,然后再運(yùn)行提交命令 git commit :
$ git commit
這種方式會(huì)啟動(dòng)文本編輯器以便輸入本次提交的說明。(默認(rèn)會(huì)啟用 shell 的環(huán)境變量 $EDITOR 所指定的軟件,一般都是 vim 或 emacs。當(dāng)然也可以按照第一章介紹的方式,使用git config --global core.editor 命令設(shè)定你喜歡的編輯軟件。)
編輯器會(huì)顯示類似下面的文本信息(本例選用 Vim 的屏顯方式展示):
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# new file: README
# modified: benchmarks.rb
~
~
~
".git/COMMIT_EDITMSG" 10L, 283C
可以看到,默認(rèn)的提交消息包含最后一次運(yùn)行 git status 的輸出,放在注釋行里,另外開頭還有一空行,供你輸入提交說明。你完全可以去掉這些注釋行,不過留著也沒關(guān)系,多少能幫你回想起這次更新的內(nèi)容有哪些。(如果覺得這還不夠,可以用-v 選項(xiàng)將修改差異的每一行都包含到注釋中來(lái)。)退出編輯器時(shí),Git 會(huì)丟掉注釋行,將說明內(nèi)容和本次更新提交到倉(cāng)庫(kù)。
另外也可以用 -m 參數(shù)后跟提交說明的方式,在一行命令中提交更新:
$ git commit -m "Story 182: Fix benchmarks for speed"
[master]: created 463dc4f: "Fix benchmarks for speed"
2 files changed, 3 insertions(+), 0 deletions(-)
create mode 100644 README
好,現(xiàn)在你已經(jīng)創(chuàng)建了第一個(gè)提交!可以看到,提交后它會(huì)告訴你,當(dāng)前是在哪個(gè)分支(master)提交的,本次提交的完整 SHA-1 校驗(yàn)和是什么(463dc4f ),以及在本次提交中,有多少文件修訂過,多少行添改和刪改過。
記住,提交時(shí)記錄的是放在暫存區(qū)域的快照,任何還未暫存的仍然保持已修改狀態(tài),可以在下次提交時(shí)納入版本管理。每一次運(yùn)行提交操作,都是對(duì)你項(xiàng)目作一次快照,以后可以回到這個(gè)狀態(tài),或者進(jìn)行比較。
跳過使用暫存區(qū)域
盡管使用暫存區(qū)域的方式可以精心準(zhǔn)備要提交的細(xì)節(jié),但有時(shí)候這么做略顯繁瑣。Git 提供了一個(gè)跳過使用暫存區(qū)域的方式,只要在提交的時(shí)候,給 git commit 加上-a 選項(xiàng),Git 就會(huì)自動(dòng)把所有已經(jīng)跟蹤過的文件暫存起來(lái)一并提交,從而跳過 git add 步驟:
$ git status
# On branch master
#
# Changed but not updated:
#
# modified: benchmarks.rb
#
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
1 files changed, 5 insertions(+), 0 deletions(-)
看到了嗎?提交之前不再需要 git add 文件 benchmarks.rb 了。
移除文件
要從 Git 中移除某個(gè)文件,就必須要從已跟蹤文件清單中移除(確切地說,是從暫存區(qū)域移除),然后提交??梢杂?git rm 命令完成此項(xiàng)工作,并連帶從工作目錄中刪除指定的文件,這樣以后就不會(huì)出現(xiàn)在未跟蹤文件清單中了。
如果只是簡(jiǎn)單地從工作目錄中手工刪除文件,運(yùn)行 git status 時(shí)就會(huì)在 “Changed but not updated” 部分(也就是_未暫存_清單)看到:
$ rm grit.gemspec
$ git status
# On branch master
#
# Changed but not updated:
# (use "git add/rm
..." to update what will be committed)
#
# deleted: grit.gemspec
#
然后再運(yùn)行 git rm 記錄此次移除文件的操作:
$ git rm grit.gemspec
rm 'grit.gemspec'
$ git status
# On branch master
#
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# deleted: grit.gemspec
#
最后提交的時(shí)候,該文件就不再納入版本管理了。如果刪除之前修改過并且已經(jīng)放到暫存區(qū)域的話,則必須要用強(qiáng)制刪除選項(xiàng) -f (譯注:即 force 的首字母),以防誤刪除文件后丟失修改的內(nèi)容。
另外一種情況是,我們想把文件從 Git 倉(cāng)庫(kù)中刪除(亦即從暫存區(qū)域移除),但仍然希望保留在當(dāng)前工作目錄中。換句話說,僅是從跟蹤清單中刪除。比如一些大型日志文件或者一堆.a 編譯文件,不小心納入倉(cāng)庫(kù)后,要移除跟蹤但不刪除文件,以便稍后在 .gitignore 文件中補(bǔ)上,用 --cached 選項(xiàng)即可:
$ git rm --cached readme.txt
后面可以列出文件或者目錄的名字,也可以使用 glob 模式。比方說:
$ git rm log/\*.log
注意到星號(hào) * 之前的反斜杠 \ ,因?yàn)?Git 有它自己的文件模式擴(kuò)展匹配方式,所以我們不用 shell 來(lái)幫忙展開(譯注:實(shí)際上不加反斜杠也可以運(yùn)行,只不過按照 shell 擴(kuò)展的話,僅僅刪除指定目錄下的文件而不會(huì)遞歸匹配。上面的例子本來(lái)就指定了目錄,所以效果等同,但下面的例子就會(huì)用遞歸方式匹配,所以必須加反斜 杠。)。此命令刪除所有log/ 目錄下擴(kuò)展名為 .log 的文件。類似的比如:
$ git rm \*~
會(huì)遞歸刪除當(dāng)前目錄及其子目錄中所有 ~ 結(jié)尾的文件。
移動(dòng)文件
不像其他的 VCS 系統(tǒng),Git 并不跟蹤文件移動(dòng)操作。如果在 Git 中重命名了某個(gè)文件,倉(cāng)庫(kù)中存儲(chǔ)的元數(shù)據(jù)并不會(huì)體現(xiàn)出這是一次改名操作。不過 Git 非常聰明,它會(huì)推斷出究竟發(fā)生了什么,至于具體是如何做到的,我們稍后再談。
既然如此,當(dāng)你看到 Git 的 mv 命令時(shí)一定會(huì)困惑不已。要在 Git 中對(duì)文件改名,可以這么做:
$ git mv file_from file_to
它會(huì)恰如預(yù)期般正常工作。實(shí)際上,即便此時(shí)查看狀態(tài)信息,也會(huì)明白無(wú)誤地看到關(guān)于重命名操作的說明:
$ git mv README.txt README
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# renamed: README.txt -> README
#
其實(shí),運(yùn)行 git mv 就相當(dāng)于運(yùn)行了下面三條命令:
$ mv README.txt README
$ git rm README.txt
$ git add README
如此分開操作,Git 也會(huì)意識(shí)到這是一次改名,所以不管何種方式都一樣。當(dāng)然,直接用 git mv 輕便得多,不過有時(shí)候用其他工具批處理改名的話,要記得在提交前刪除老的文件名,再添加新的文件名。
2.3 查看提交歷史
在提交了若干更新之后,又或者克隆了某個(gè)項(xiàng)目,想回顧下提交歷史,可以使用 git log 命令查看。
接下來(lái)的例子會(huì)用我專門用于演示的 simplegit 項(xiàng)目,運(yùn)行下面的命令獲取該項(xiàng)目源代碼:
git clone git://github.com/schacon/simplegit-progit.git
然后在此項(xiàng)目中運(yùn)行 git log ,應(yīng)該會(huì)看到下面的輸出:
$ 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
默認(rèn)不用任何參數(shù)的話,git log 會(huì)按提交時(shí)間列出所有的更新,最近的更新排在最上面??吹搅藛?,每次更新都有一個(gè) SHA-1 校驗(yàn)和、作者的名字和電子郵件地址、提交時(shí)間,最后縮進(jìn)一個(gè)段落顯示提交說明。
git log 有許多選項(xiàng)可以幫助你搜尋感興趣的提交,接下來(lái)我們介紹些最常用的。
我們常用 -p 選項(xiàng)展開顯示每次提交的內(nèi)容差異,用 -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
在做代碼審查,或者要快速瀏覽其他協(xié)作者提交的更新都作了哪些改動(dòng)時(shí),就可以用這個(gè)選項(xiàng)。此外,還有許多摘要選項(xiàng)可以用,比如 --stat ,僅顯示簡(jiǎn)要的增改行數(shù)統(tǒng)計(jì):
$ 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(-)
每個(gè)提交都列出了修改過的文件,以及其中添加和移除的行數(shù),并在最后列出所有增減行數(shù)小計(jì)。還有個(gè)常用的 --pretty 選項(xiàng),可以指定使用完全不同于默認(rèn)格式的方式展示提交歷史。比如用oneline 將每個(gè)提交放在一行顯示,這在提交數(shù)很大時(shí)非常有用。另外還有 short ,full 和fuller 可以用,展示的信息或多或少有些不同,請(qǐng)自己動(dòng)手實(shí)踐一下看看效果如何。
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test code
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 test code
a11bef0 - Scott Chacon, 11 months ago : first commit
表 2-1 列出了常用的格式占位符寫法及其代表的意義。
選項(xiàng) 說明
%H 提交對(duì)象(commit)的完整哈希字串
%h 提交對(duì)象的簡(jiǎn)短哈希字串
%T 樹對(duì)象(tree)的完整哈希字串
%t 樹對(duì)象的簡(jiǎn)短哈希字串
%P 父對(duì)象(parent)的完整哈希字串
%p 父對(duì)象的簡(jiǎn)短哈希字串
%an 作者(author)的名字
%ae 作者的電子郵件地址
%ad 作者修訂日期(可以用 -date= 選項(xiàng)定制格式)
%ar 作者修訂日期,按多久以前的方式顯示
%cn 提交者(committer)的名字
%ce 提交者的電子郵件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式顯示
%s 提交說明
你一定奇怪_作者(author)_和_提交者(committer)_之間究竟有何差別,其實(shí)作者指的是實(shí)際作出修改的人,提交者指的是最后將此 工作成果提交到倉(cāng)庫(kù)的人。所以,當(dāng)你為某個(gè)項(xiàng)目發(fā)布補(bǔ)丁,然后某個(gè)核心成員將你的補(bǔ)丁并入項(xiàng)目時(shí),你就是作者,而那個(gè)核心成員就是提交者。我們會(huì)在第五章 再詳細(xì)介紹兩者之間的細(xì)微差別。
用 oneline 或 format 時(shí)結(jié)合 --graph 選項(xiàng),可以看到開頭多出一些 ASCII 字符串表示的簡(jiǎn)單圖形,形象地展示了每個(gè)提交所在的分支及其分化衍合情況。在我們之前提到的 Grit 項(xiàng)目倉(cāng)庫(kù)中可以看到:
$ 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
以上只是簡(jiǎn)單介紹了一些 git log 命令支持的選項(xiàng)。表 2-2 還列出了一些其他常用的選項(xiàng)及其釋義。
選項(xiàng) 說明
-p 按補(bǔ)丁格式顯示每個(gè)更新之間的差異。
--stat 顯示每次更新的文件修改統(tǒng)計(jì)信息。
--shortstat 只顯示 --stat 中最后的行數(shù)修改添加移除統(tǒng)計(jì)。
--name-only 僅在提交信息后顯示已修改的文件清單。
--name-status 顯示新增、修改、刪除的文件清單。
--abbrev-commit 僅顯示 SHA-1 的前幾個(gè)字符,而非所有的 40 個(gè)字符。
--relative-date 使用較短的相對(duì)時(shí)間顯示(比如,“2 weeks ago”)。
--graph 顯示 ASCII 圖形表示的分支合并歷史。
--pretty 使用其他格式顯示歷史提交信息??捎玫倪x項(xiàng)包括 oneline,short,full,fuller 和 format(后跟指定格式)。
限制輸出長(zhǎng)度
除了定制輸出格式的選項(xiàng)之外,git log 還有許多非常實(shí)用的限制輸出長(zhǎng)度的選項(xiàng),也就是只輸出部分提交信息。之前我們已經(jīng)看到過 -2 了,它只顯示最近的兩條提交,實(shí)際上,這是 -
選項(xiàng)的寫法,其中的 n 可以是任何自然數(shù),表示僅顯示最近的若干條提交。不過實(shí)踐中我們是不太用這個(gè)選項(xiàng)的,Git 在輸出所有提交時(shí)會(huì)自動(dòng)調(diào)用分頁(yè)程序(less),要看更早的更新只需翻到下頁(yè)即可。
另外還有按照時(shí)間作限制的選項(xiàng),比如 --since 和 --until 。下面的命令列出所有最近兩周內(nèi)的提交:
$ git log --since=2.weeks
你可以給出各種時(shí)間格式,比如說具體的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。
還可以給出若干搜索條件,列出符合的提交。用 --author 選項(xiàng)顯示指定作者的提交,用 --grep 選項(xiàng)搜索提交說明中的關(guān)鍵字。(請(qǐng)注意,如果要得到同時(shí)滿足這兩個(gè)選項(xiàng)搜索條件的提交,就必須用--all-match 選項(xiàng)。)
如果只關(guān)心某些文件或者目錄的歷史提交,可以在 git log 選項(xiàng)的最后指定它們的路徑。因?yàn)槭欠旁谧詈笪恢蒙系倪x項(xiàng),所以用兩個(gè)短劃線(-- )隔開之前的選項(xiàng)和后面限定的路徑名。
表 2-3 還列出了其他常用的類似選項(xiàng)。
選項(xiàng) 說明
-(n) 僅顯示最近的 n 條提交
--since, --after 僅顯示指定時(shí)間之后的提交。
--until, --before 僅顯示指定時(shí)間之前的提交。
--author 僅顯示指定作者相關(guān)的提交。
--committer 僅顯示指定提交者相關(guān)的提交。
來(lái)看一個(gè)實(shí)際的例子,如果要查看 Git 倉(cāng)庫(kù)中,2008 年 10 月期間,Junio Hamano 提交的但未合并的測(cè)試腳本(位于項(xiàng)目的 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 項(xiàng)目有 20,000 多條提交,但我們給出搜索選項(xiàng)后,僅列出了其中滿足條件的 6 條。
使用圖形化工具查閱提交歷史
有時(shí)候圖形化工具更容易展示歷史提交的變化,隨 Git 一同發(fā)布的 gitk 就是這樣一種工具。它是用 Tcl/Tk 寫成的,基本上相當(dāng)于 git log 命令的可視化版本,凡是git log 可以用的選項(xiàng)也都能用在 gitk 上。在項(xiàng)目工作目錄中輸入 gitk 命令后,就會(huì)啟動(dòng)圖 2-2 所示的界面。
圖 2-2. gitk 的圖形界面
上半個(gè)窗口顯示的是歷次提交的分支祖先圖譜,下半個(gè)窗口顯示當(dāng)前點(diǎn)選的提交對(duì)應(yīng)的具體差異。
2.4 撤消操作
任何時(shí)候,你都有可能需要撤消剛才所做的某些操作。接下來(lái),我們會(huì)介紹一些基本的撤消操作相關(guān)的命令。請(qǐng)注意,有些操作并不總是可以撤消的,所以請(qǐng)務(wù)必謹(jǐn)慎小心,一旦失誤,就有可能丟失部分工作成果。
修改最后一次提交
有時(shí)候我們提交完了才發(fā)現(xiàn)漏掉了幾個(gè)文件沒有加,或者提交信息寫錯(cuò)了。想要撤消剛才的提交操作,可以使用 --amend 選項(xiàng)重新提交:
$ git commit --amend
此命令將使用當(dāng)前的暫存區(qū)域快照提交。如果剛才提交完沒有作任何改動(dòng),直接運(yùn)行此命令的話,相當(dāng)于有機(jī)會(huì)重新編輯提交說明,但將要提交的文件快照和之前的一樣。
啟動(dòng)文本編輯器后,會(huì)看到上次提交時(shí)的說明,編輯它確認(rèn)沒問題后保存退出,就會(huì)使用新的提交說明覆蓋剛才失誤的提交。
如果剛才提交時(shí)忘了暫存某些修改,可以先補(bǔ)上暫存操作,然后再運(yùn)行 --amend 提交:
$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend
上面的三條命令最終只是產(chǎn)生一個(gè)提交,第二個(gè)提交命令修正了第一個(gè)的提交內(nèi)容。
取消已經(jīng)暫存的文件
接下來(lái)的兩個(gè)小節(jié)將演示如何取消暫存區(qū)域中的文件,以及如何取消工作目錄中已修改的文件。不用擔(dān)心,查看文件狀態(tài)的時(shí)候就提示了該如何撤消,所以不需要死記硬背。來(lái)看下面的例子,有兩個(gè)修改過的文件,我們想要分開提交,但不小心用git add . 全加到了暫存區(qū)域。該如何撤消暫存其中的一個(gè)文件呢?其實(shí),git status 的命令輸出已經(jīng)告訴了我們?cè)撛趺醋觯?/p>
$ 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” 下面,括號(hào)中有提示,可以使用 git reset HEAD
...
的方式取消暫存。好吧,我們來(lái)試試取消暫存 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
#
這條命令看起來(lái)有些古怪,先別管,能用就行。現(xiàn)在 benchmarks.rb 文件又回到了之前已修改未暫存的狀態(tài)。
取消對(duì)文件的修改
如果覺得剛才對(duì) benchmarks.rb 的修改完全沒有必要,該如何取消修改,回到之前的狀態(tài)(也就是修改之前的版本)呢?git status 同樣提示了具體的撤消方法,接著上面的例子,現(xiàn)在未暫存區(qū)域看起來(lái)像這樣:
# 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
#
在第二個(gè)括號(hào)中,我們看到了拋棄文件修改的命令(至少在 Git 1.6.1 以及更高版本中會(huì)這樣提示,如果你還在用老版本,我們強(qiáng)烈建議你升級(jí),以獲取最佳的用戶體驗(yàn)),讓我們?cè)囋嚳矗?/p>
$ git checkout -- benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD
..." to unstage)
#
# modified: README.txt
#
可以看到,該文件已經(jīng)恢復(fù)到修改前的版本。你可能已經(jīng)意識(shí)到了,這條命令有些危險(xiǎn),所有對(duì)文件的修改都沒有了,因?yàn)槲覀儎倓偘阎鞍姹镜奈募?fù)制過 來(lái)重寫了此文件。所以在用這條命令前,請(qǐng)務(wù)必確定真的不再需要保留剛才的修改。如果只是想回退版本,同時(shí)保留剛才的修改以便將來(lái)繼續(xù)工作,可以用下章介紹 的 stashing 和分支來(lái)處理,應(yīng)該會(huì)更好些。
記住,任何已經(jīng)提交到 Git 的都可以被恢復(fù)。即便在已經(jīng)刪除的分支中的提交,或者用 --amend 重新改寫的提交,都可以被恢復(fù)(關(guān)于數(shù)據(jù)恢復(fù)的內(nèi)容見第九章)。所以,你可能失去的數(shù)據(jù),僅限于沒有提交過的,對(duì) Git 來(lái)說它們就像從未存在過一樣。
2.5 遠(yuǎn)程倉(cāng)庫(kù)的使用
要參與任何一個(gè) Git 項(xiàng)目的協(xié)作,必須要了解該如何管理遠(yuǎn)程倉(cāng)庫(kù)。遠(yuǎn)程倉(cāng)庫(kù)是指托管在網(wǎng)絡(luò)上的項(xiàng)目倉(cāng)庫(kù),可能會(huì)有好多個(gè),其中有些你只能讀,另外有些可以寫。同他人協(xié)作開發(fā)某 個(gè)項(xiàng)目時(shí),需要管理這些遠(yuǎn)程倉(cāng)庫(kù),以便推送或拉取數(shù)據(jù),分享各自的工作進(jìn)展。管理遠(yuǎn)程倉(cāng)庫(kù)的工作,包括添加遠(yuǎn)程庫(kù),移除廢棄的遠(yuǎn)程庫(kù),管理各式遠(yuǎn)程庫(kù)分 支,定義是否跟蹤這些分支,等等。本節(jié)我們將詳細(xì)討論遠(yuǎn)程庫(kù)的管理和使用。
查看當(dāng)前的遠(yuǎn)程庫(kù)
要查看當(dāng)前配置有哪些遠(yuǎn)程倉(cāng)庫(kù),可以用 git remote 命令,它會(huì)列出每個(gè)遠(yuǎn)程庫(kù)的簡(jiǎn)短名字。在克隆完某個(gè)項(xiàng)目后,至少可以看到一個(gè)名為 origin 的遠(yuǎn)程庫(kù),Git 默認(rèn)使用這個(gè)名字來(lái)標(biāo)識(shí)你所克隆的原始倉(cāng)庫(kù):
$ 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 選項(xiàng)(譯注:此為 --verbose 的簡(jiǎn)寫,取首字母),顯示對(duì)應(yīng)的克隆地址:
$ git remote -v
origin git://github.com/schacon/ticgit.git
如果有多個(gè)遠(yuǎn)程倉(cāng)庫(kù),此命令將全部列出。比如在我的 Grit 項(xiàng)目中,可以看到:
$ 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
這樣一來(lái),我就可以非常輕松地從這些用戶的倉(cāng)庫(kù)中,拉取他們的提交到本地。請(qǐng)注意,上面列出的地址只有 origin 用的是 SSH URL 鏈接,所以也只有這個(gè)倉(cāng)庫(kù)我能推送數(shù)據(jù)上去(我們會(huì)在第四章解釋原因)。
添加遠(yuǎn)程倉(cāng)庫(kù)
要添加一個(gè)新的遠(yuǎn)程倉(cāng)庫(kù),可以指定一個(gè)簡(jiǎn)單的名字,以便將來(lái)引用,運(yùn)行 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
現(xiàn)在可以用字串 pb 指代對(duì)應(yīng)的倉(cāng)庫(kù)地址了。比如說,要抓取所有 Paul 有的,但本地倉(cāng)庫(kù)沒有的信息,可以運(yùn)行 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
現(xiàn)在,Paul 的主干分支(master)已經(jīng)完全可以在本地訪問了,對(duì)應(yīng)的名字是 pb/master ,你可以將它合并到自己的某個(gè)分支,或者切換到這個(gè)分支,看看有些什么有趣的更新。
從遠(yuǎn)程倉(cāng)庫(kù)抓取數(shù)據(jù)
正如之前所看到的,可以用下面的命令從遠(yuǎn)程倉(cāng)庫(kù)抓取數(shù)據(jù)到本地:
$ git fetch [remote-name]
此命令會(huì)到遠(yuǎn)程倉(cāng)庫(kù)中拉取所有你本地倉(cāng)庫(kù)中還沒有的數(shù)據(jù)。運(yùn)行完成后,你就可以在本地訪問該遠(yuǎn)程倉(cāng)庫(kù)中的所有分支,將其中某個(gè)分支合并到本地,或者只是取出某個(gè)分支,一探究竟。(我們會(huì)在第三章詳細(xì)討論關(guān)于分支的概念和操作。)
如果是克隆了一個(gè)倉(cāng)庫(kù),此命令會(huì)自動(dòng)將遠(yuǎn)程倉(cāng)庫(kù)歸于 origin 名下。所以,git fetch origin 會(huì)抓取從你上次克隆以來(lái)別人上傳到此遠(yuǎn)程倉(cāng)庫(kù)中的所有更新(或是上次 fetch 以來(lái)別人提交的更新)。有一點(diǎn)很重要,需要記住,fetch 命令只是將遠(yuǎn)端的數(shù)據(jù)拉到本地倉(cāng)庫(kù),并不自動(dòng)合并到當(dāng)前工作分支,只有當(dāng)你確實(shí)準(zhǔn)備好了,才能手工合并。
如果設(shè)置了某個(gè)分支用于跟蹤某個(gè)遠(yuǎn)端倉(cāng)庫(kù)的分支(參見下節(jié)及第三章的內(nèi)容),可以使用 git pull 命令自動(dòng)抓取數(shù)據(jù)下來(lái),然后將遠(yuǎn)端分支自動(dòng)合并到本地倉(cāng)庫(kù)中當(dāng)前分支。在日常工作中我們經(jīng)常這么用,既快且好。實(shí)際上,默認(rèn)情況下git clone 命令本質(zhì)上就是自動(dòng)創(chuàng)建了本地的 master 分支用于跟蹤遠(yuǎn)程倉(cāng)庫(kù)中的 master 分支(假設(shè)遠(yuǎn)程倉(cāng)庫(kù)確實(shí)有 master 分支)。所以一般我們運(yùn)行git pull ,目的都是要從原始克隆的遠(yuǎn)端倉(cāng)庫(kù)中抓取數(shù)據(jù)后,合并到工作目錄中的當(dāng)前分支。
推送數(shù)據(jù)到遠(yuǎn)程倉(cāng)庫(kù)
項(xiàng)目進(jìn)行到一個(gè)階段,要同別人分享目前的成果,可以將本地倉(cāng)庫(kù)中的數(shù)據(jù)推送到遠(yuǎn)程倉(cāng)庫(kù)。實(shí)現(xiàn)這個(gè)任務(wù)的命令很簡(jiǎn)單: git push [remote-name] [branch-name] 。如果要把本地的 master 分支推送到origin 服務(wù)器上(再次說明下,克隆操作會(huì)自動(dòng)使用默認(rèn)的 master 和 origin 名字),可以運(yùn)行下面的命令:
$ git push origin master
只有在所克隆的服務(wù)器上有寫權(quán)限,或者同一時(shí)刻沒有其他人在推數(shù)據(jù),這條命令才會(huì)如期完成任務(wù)。如果在你推數(shù)據(jù)前,已經(jīng)有其他人推送了若干更新,那 你的推送操作就會(huì)被駁回。你必須先把他們的更新抓取到本地,合并到自己的項(xiàng)目中,然后才可以再次推送。有關(guān)推送數(shù)據(jù)到遠(yuǎn)程倉(cāng)庫(kù)的詳細(xì)內(nèi)容見第三章。
查看遠(yuǎn)程倉(cāng)庫(kù)信息
我們可以通過命令 git remote show [remote-name] 查看某個(gè)遠(yuǎn)程倉(cāng)庫(kù)的詳細(xì)信息,比如要看所克隆的 origin 倉(cāng)庫(kù),可以運(yùn)行:
$ git remote show origin
* remote origin
URL: git://github.com/schacon/ticgit.git
Remote branch merged with 'git pull' while on branch master
master
Tracked remote branches
master
ticgit
除了對(duì)應(yīng)的克隆地址外,它還給出了許多額外的信息。它友善地告訴你如果是在 master 分支,就可以用 git pull 命令抓取數(shù)據(jù)合并到本地。另外還列出了所有處于跟蹤狀態(tài)中的遠(yuǎn)端分支。
上面的例子非常簡(jiǎn)單,而隨著使用 Git 的深入,git remote show 給出的信息可能會(huì)像這樣:
$ git remote show origin
* remote origin
URL: git@github.com:defunkt/github.git
Remote branch merged with 'git pull' while on branch issues
issues
Remote branch merged with 'git pull' while on 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
它告訴我們,運(yùn)行 git push 時(shí)缺省推送的分支是什么(譯注:最后兩行)。它還顯示了有哪些遠(yuǎn)端分支還沒有同步到本地(譯注:第六行的caching 分支),哪些已同步到本地的遠(yuǎn)端分支在遠(yuǎn)端服務(wù)器上已被刪除(譯注:Stale tracking branches 下面的兩個(gè)分支),以及運(yùn)行git pull 時(shí)將自動(dòng)合并哪些分支(譯注:前四行中列出的 issues 和 master 分支)。
遠(yuǎn)程倉(cāng)庫(kù)的刪除和重命名
在新版 Git 中可以用 git remote rename 命令修改某個(gè)遠(yuǎn)程倉(cāng)庫(kù)在本地的簡(jiǎn)短名稱,比如想把 pb 改成paul ,可以這么運(yùn)行:
$ git remote rename pb paul
$ git remote
origin
paul
注意,對(duì)遠(yuǎn)程倉(cāng)庫(kù)的重命名,也會(huì)使對(duì)應(yīng)的分支名稱發(fā)生變化,原來(lái)的 pb/master 分支現(xiàn)在成了 paul/master 。
碰到遠(yuǎn)端倉(cāng)庫(kù)服務(wù)器遷移,或者原來(lái)的克隆鏡像不再使用,又或者某個(gè)參與者不再貢獻(xiàn)代碼,那么需要移除對(duì)應(yīng)的遠(yuǎn)端倉(cāng)庫(kù),可以運(yùn)行 git remote rm 命令:
$ git remote rm paul
$ git remote
origin
2.6 打標(biāo)簽
同大多數(shù) VCS 一樣,Git 也可以對(duì)某一時(shí)間點(diǎn)上的版本打上標(biāo)簽。人們?cè)诎l(fā)布某個(gè)軟件版本(比如 v1.0 等等)的時(shí)候,經(jīng)常這么做。本節(jié)我們一起來(lái)學(xué)習(xí)如何列出所有可用的標(biāo)簽,如何新建標(biāo)簽,以及各種不同類型標(biāo)簽之間的差別。
列顯已有的標(biāo)簽
列出現(xiàn)有標(biāo)簽的命令非常簡(jiǎn)單,直接運(yùn)行 git tag 即可:
$ git tag
v0.1
v1.3
顯示的標(biāo)簽按字母順序排列,所以標(biāo)簽的先后并不表示重要程度的輕重。
我們可以用特定的搜索模式列出符合條件的標(biāo)簽。在 Git 自身項(xiàng)目倉(cāng)庫(kù)中,有著超過 240 個(gè)標(biāo)簽,如果你只對(duì) 1.4.2 系列的版本感興趣,可以運(yùn)行下面的命令:
$ git tag -l 'v1.4.2.*'
v1.4.2.1
v1.4.2.2
v1.4.2.3
v1.4.2.4
新建標(biāo)簽
Git 使用的標(biāo)簽有兩種類型:輕量級(jí)的(lightweight)和含附注的(annotated)。輕量級(jí)標(biāo)簽就像是個(gè)不會(huì)變化的分支,實(shí)際上它就是個(gè)指向特 定提交對(duì)象的引用。而含附注標(biāo)簽,實(shí)際上是存儲(chǔ)在倉(cāng)庫(kù)中的一個(gè)獨(dú)立對(duì)象,它有自身的校驗(yàn)和信息,包含著標(biāo)簽的名字,電子郵件地址和日期,以及標(biāo)簽說明,標(biāo) 簽本身也允許使用 GNU Privacy Guard (GPG) 來(lái)簽署或驗(yàn)證。一般我們都建議使用含附注型的標(biāo)簽,以便保留相關(guān)信息;當(dāng)然,如果只是臨時(shí)性加注標(biāo)簽,或者不需要旁注額外信息,用輕量級(jí)標(biāo)簽也沒問題。
含附注的標(biāo)簽
創(chuàng)建一個(gè)含附注類型的標(biāo)簽非常簡(jiǎn)單,用 -a (譯注:取 annotated 的首字母)指定標(biāo)簽名字即可:
$ git tag -a v1.4 -m 'my version 1.4'
$ git tag
v0.1
v1.3
v1.4
而 -m 選項(xiàng)則指定了對(duì)應(yīng)的標(biāo)簽說明,Git 會(huì)將此說明一同保存在標(biāo)簽對(duì)象中。如果沒有給出該選項(xiàng),Git 會(huì)啟動(dòng)文本編輯軟件供你輸入標(biāo)簽說明。
可以使用 git show 命令查看相應(yīng)標(biāo)簽的版本信息,并連同顯示打標(biāo)簽時(shí)的提交對(duì)象。
$ 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'
我們可以看到在提交對(duì)象信息上面,列出了此標(biāo)簽的提交者和提交時(shí)間,以及相應(yīng)的標(biāo)簽說明。
簽署標(biāo)簽
如果你有自己的私鑰,還可以用 GPG 來(lái)簽署標(biāo)簽,只需要把之前的 -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
現(xiàn)在再運(yùn)行 git show 會(huì)看到對(duì)應(yīng)的 GPG 簽名也附在其內(nèi):
$ 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'
稍后我們?cè)賹W(xué)習(xí)如何驗(yàn)證已經(jīng)簽署的標(biāo)簽。
輕量級(jí)標(biāo)簽
輕量級(jí)標(biāo)簽實(shí)際上就是一個(gè)保存著對(duì)應(yīng)提交對(duì)象的校驗(yàn)和信息的文件。要?jiǎng)?chuàng)建這樣的標(biāo)簽,一個(gè) -a ,-s 或 -m 選項(xiàng)都不用,直接給出標(biāo)簽名字即可:
$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5
現(xiàn)在運(yùn)行 git show 查看此標(biāo)簽信息,就只有相應(yīng)的提交對(duì)象摘要:
$ 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'
驗(yàn)證標(biāo)簽
可以使用 git tag -v [tag-name] (譯注:取 verify 的首字母)的方式驗(yàn)證已經(jīng)簽署的標(biāo)簽。此命令會(huì)調(diào)用 GPG 來(lái)驗(yàn)證簽名,所以你需要有簽署者的公鑰,存放在 keyring 中,才能驗(yàn)證:
$ 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
若是沒有簽署者的公鑰,會(huì)報(bào)告類似下面這樣的錯(cuò)誤:
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'
后期加注標(biāo)簽
你甚至可以在后期對(duì)早先的某次提交加注標(biāo)簽。比如在下面展示的提交歷史中:
$ 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” 后為此項(xiàng)目打上版本號(hào) v1.2,沒關(guān)系,現(xiàn)在也能做。只要在打標(biāo)簽的時(shí)候跟上對(duì)應(yīng)提交對(duì)象的校驗(yàn)和(或前幾位字符)即可:
$ git tag -a v1.2 9fceb02
可以看到我們已經(jīng)補(bǔ)上了標(biāo)簽:
$ 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
...
分享標(biāo)簽
默認(rèn)情況下,git push 并不會(huì)把標(biāo)簽傳送到遠(yuǎn)端服務(wù)器上,只有通過顯式命令才能分享標(biāo)簽到遠(yuǎn)端倉(cāng)庫(kù)。其命令格式如同推送分支,運(yùn)行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
如果要一次推送所有本地新增的標(biāo)簽上去,可以使用 --tags 選項(xiàng):
$ 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
現(xiàn)在,其他人克隆共享倉(cāng)庫(kù)或拉取數(shù)據(jù)同步后,也會(huì)看到這些標(biāo)簽。
2.7 技巧和竅門
在結(jié)束本章之前,我還想和大家分享一些 Git 使用的技巧和竅門。很多使用 Git 的開發(fā)者可能根本就沒用過這些技巧,我們也不是說在讀過本書后非得用這些技巧不可,但至少應(yīng)該有所了解吧。說實(shí)話,有了這些小竅門,我們的工作可以變得更簡(jiǎn)單,更輕松,更高效。
自動(dòng)完成
如果你用的是 Bash shell,可以試試看 Git 提供的自動(dòng)完成腳本。下載 Git 的源代碼,進(jìn)入 contrib/completion 目錄,會(huì)看到一個(gè)git-completion.bash 文件。將此文件復(fù)制到你自己的用戶主目錄中(譯注:按照下面的示例,還應(yīng)改名加上點(diǎn):cp git-completion.bash ~/.git-completion.bash ),并把下面一行內(nèi)容添加到你的.bashrc 文件中:
source ~/.git-completion.bash
也可以為系統(tǒng)上所有用戶都設(shè)置默認(rèn)使用此腳本。Mac 上將此腳本復(fù)制到 /opt/local/etc/bash_completion.d 目錄中,Linux 上則復(fù)制到/etc/bash_completion.d/ 目錄中。這兩處目錄中的腳本,都會(huì)在 Bash 啟動(dòng)時(shí)自動(dòng)加載。
如果在 Windows 上安裝了 msysGit,默認(rèn)使用的 Git Bash 就已經(jīng)配好了這個(gè)自動(dòng)完成腳本,可以直接使用。
在輸入 Git 命令的時(shí)候可以敲兩次跳格鍵(Tab),就會(huì)看到列出所有匹配的可用命令建議:
$ git co
commit config
此例中,鍵入 git co 然后連按兩次 Tab 鍵,會(huì)看到兩個(gè)相關(guān)的建議(命令) commit 和 config。繼而輸入 m
會(huì)自動(dòng)完成git commit 命令的輸入。
命令的選項(xiàng)也可以用這種方式自動(dòng)完成,其實(shí)這種情況更實(shí)用些。比如運(yùn)行 git log 的時(shí)候忘了相關(guān)選項(xiàng)的名字,可以輸入開頭的幾個(gè)字母,然后敲 Tab 鍵看看有哪些匹配的:
$ git log --s
--shortstat --since= --src-prefix= --stat --summary
這個(gè)技巧不錯(cuò)吧,可以節(jié)省很多輸入和查閱文檔的時(shí)間。
Git 命令別名
Git 并不會(huì)推斷你輸入的幾個(gè)字符將會(huì)是哪條命令,不過如果想偷懶,少敲幾個(gè)命令的字符,可以用 git config 為命令設(shè)置別名。來(lái)看看下面的例子:
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
現(xiàn)在,如果要輸入 git commit 只需鍵入 git ci 即可。而隨著 Git 使用的深入,會(huì)有很多經(jīng)常要用到的命令,遇到這種情況,不妨建個(gè)別名提高效率。
使用這種技術(shù)還可以創(chuàng)造出新的命令,比方說取消暫存文件時(shí)的輸入比較繁瑣,可以自己設(shè)置一下:
$ git config --global alias.unstage 'reset HEAD --'
這樣一來(lái),下面的兩條命令完全等同:
$ git unstage fileA
$ git reset HEAD fileA
顯然,使用別名的方式看起來(lái)更清楚。另外,我們還經(jīng)常設(shè)置 last 命令:
$ git config --global alias.last 'log -1 HEAD'
然后要看最后一次的提交信息,就變得簡(jiǎn)單多了:
$ 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
可以看出,實(shí)際上 Git 只是簡(jiǎn)單地在命令中替換了你設(shè)置的別名。不過有時(shí)候我們希望運(yùn)行某個(gè)外部命令,而非 Git 的附屬工具,這個(gè)好辦,只需要在命令前加上 ! 就行。如果你自己寫了些處理 Git 倉(cāng)庫(kù)信息的腳本的話,就可以用這種技術(shù)包裝起來(lái)。作為演示,我們可以設(shè)置用 git visual 啟動(dòng)gitk :
$ git config --global alias.visual "!gitk"
2.8 小結(jié)
到目前為止,你已經(jīng)學(xué)會(huì)了最基本的 Git 操作:創(chuàng)建和克隆倉(cāng)庫(kù),做出更新,暫存并提交這些更新,以及查看所有歷史更新記錄。接下來(lái),我們將學(xué)習(xí) Git 的必殺技特性:分支模型。
|