git 學習筆記二-- 進階命令的使用

1. 忽略某些文件的管理

一般我們總會有些文件無需納入 Git 的管理,也不希望它們總出現在未跟蹤文件列表。通常都是些自動生成的文件,比如日誌文件,或者編譯過程中創建的臨時文件等。我們可以創建一個名爲 .gitignore 的文件,列出要忽略的文件模式。來看一個實際的例子:

$ cat .gitignore
*.[oa]
*~

第一行告訴 Git 忽略所有以 .o 或 .a 結尾的文件。一般這類對象文件和存檔文件都是編譯過程中出現的,我們用不着跟蹤它們的版本。第二行告訴 Git 忽略所有以波浪符(~)結尾的文件,許多文本編輯軟件(比如 Emacs)都用這樣的文件名保存副本。此外,你可能還需要忽略 log,tmp 或者 pid 目錄,以及自動生成的文檔等等。要養成一開始就設置好 .gitignore 文件的習慣,以免將來誤提交這類無用的文件。

文件 .gitignore 的格式規範如下:

  • 所有空行或者以註釋符號 # 開頭的行都會被 Git 忽略。
  • 可以使用標準的 glob 模式匹配。 * 匹配模式最後跟反斜槓(/)說明要忽略的是目錄。 * 要忽略指定模式以外的文件或目錄,可以在模式前加上驚歎號(!)取反。

所謂的 glob 模式是指 shell 所使用的簡化了的正則表達式。星號(*)匹配零個或多個任意字符;[abc] 匹配任何一個列在方括號中的字符(這個例子要麼匹配一個 a,要麼匹配一個 b,要麼匹配一個 c);問號(?)只匹配一個任意字符;如果在方括號中使用短劃線分隔兩個字符,表示所有在這兩個字符範圍內的都可以匹配(比如[0-9] 表示匹配所有 0 到 9 的數字)。

我們再看一個 .gitignore 文件的例子:

# 此爲註釋 – 將被 Git 忽略
*.a       # 忽略所有 .a 結尾的文件
!lib.a    # 但 lib.a 除外
/TODO     # 僅僅忽略項目根目錄下的 TODO 


2. 查看已暫存和未暫存的更新

實際上 git status 的顯示比較簡單,僅僅是列出了修改過的文件,如果要查看具體修改了什麼地方,可以用 git diff 命令。稍後我們會詳細介紹git diff,不過現在,它已經能回答我們的兩個問題了:當前做的哪些更新還沒有暫存?有哪些更新已經暫存起來準備好了下次提交?git diff 會使用文件補丁的格式顯示具體添加和刪除的行。

假如再次修改 README 文件後暫存,然後編輯 benchmarks.rb 文件後先別暫存,運行 status 命令,會看到:

$ 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 #  

要查看尚未暫存的文件更新了哪些部分,不加參數直接輸入 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

此命令比較的是工作目錄中當前文件和暫存區域快照之間的差異,也就是修改之後還沒有暫存起來的變化內容。

若要看已經暫存起來的文件和上次提交時的快照之間的差異,可以用 git diff --cached 命令。(Git 1.6.1 及更高版本還允許使用git diff --staged,效果是相同的,但更好記些。)來看看實際的效果:

$ 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

請注意,單單 git diff 不過是顯示還沒有暫存起來的改動,而不是這次工作和上次提交之間的差異。所以有時候你一下子暫存了所有更新過的文件後,運行git diff 後卻什麼也沒有,就是這個原因。

像之前說的,暫存 benchmarks.rb 後再編輯,運行 git status 會看到暫存前後的兩個版本:

$ 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
#

現在運行 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 查看已經暫存起來的變化:

$ git diff --cached
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..e445e28 100644
--- a/benchmarks

3.移除文件

要從 Git 中移除某個文件,就必須要從已跟蹤文件清單中移除(確切地說,是從暫存區域移除),然後提交。可以用 git rm 命令完成此項工作,並連帶從工作目錄中刪除指定的文件,這樣以後就不會出現在未跟蹤文件清單中了。

如果只是簡單地從工作目錄中手工刪除文件,運行 git status 時就會在 “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 
#

然後再運行 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 
#

最後提交的時候,該文件就不再納入版本管理了。如果刪除之前修改過並且已經放到暫存區域的話,則必須要用強制刪除選項 -f(譯註:即 force 的首字母),以防誤刪除文件後丟失修改的內容。

另外一種情況是,我們想把文件從 Git 倉庫中刪除(亦即從暫存區域移除),但仍然希望保留在當前工作目錄中。換句話說,僅是從跟蹤清單中刪除。比如一些大型日誌文件或者一堆.a 編譯文件,不小心納入倉庫後,要移除跟蹤但不刪除文件,以便稍後在.gitignore 文件中補上,用--cached 選項即可:

$ git rm --cached readme.txt

後面可以列出文件或者目錄的名字,也可以使用 glob 模式。比方說:

$ git rm log/\*.log

注意到星號 * 之前的反斜槓 \,因爲 Git 有它自己的文件模式擴展匹配方式,所以我們不用 shell 來幫忙展開(譯註:實際上不加反斜槓也可以運行,只不過按照 shell 擴展的話,僅僅刪除指定目錄下的文件而不會遞歸匹配。上面的例子本來就指定了目錄,所以效果等同,但下面的例子就會用遞歸方式匹配,所以必須加反斜 槓。)。此命令刪除所有log/ 目錄下擴展名爲.log 的文件。類似的比如:

$ git rm \*~

會遞歸刪除當前目錄及其子目錄中所有 ~ 結尾的文件。


4.移動文件

不像其他的 VCS 系統,Git 並不跟蹤文件移動操作。如果在 Git 中重命名了某個文件,倉庫中存儲的元數據並不會體現出這是一次改名操作。不過 Git 非常聰明,它會推斷出究竟發生了什麼,至於具體是如何做到的,我們稍後再談。

既然如此,當你看到 Git 的 mv 命令時一定會困惑不已。要在 Git 中對文件改名,可以這麼做:

$ git mv file_from file_to

它會恰如預期般正常工作。實際上,即便此時查看狀態信息,也會明白無誤地看到關於重命名操作的說明:

$ 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 #

其實,運行 git mv 就相當於運行了下面三條命令:

$ mv README.txt README
$ git rm README.txt
$ git add README

如此分開操作,Git 也會意識到這是一次改名,所以不管何種方式都一樣。當然,直接用 git mv 輕便得多,不過有時候用其他工具批處理改名的話,要記得在提交前刪除老的文件名,再添加新的文件名。

5. 查看提交歷史

在提交了若干更新之後,又或者克隆了某個項目,想回顧下提交歷史,可以使用 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 將每個提交放在一行顯示,這在提交數很大時非常有用。另外還有shortfullfuller 可以用,展示的信息或多或少有些不同,請自己動手實踐一下看看效果如何。

$ 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 列出了常用的格式佔位符寫法及其代表的意義。

選項	 說明
%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 命令支持的選項。表 2-2 還列出了一些其他常用的選項及其釋義。

選項 說明
-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(後跟指定格式)。

6. 限制輸出長度

除了定製輸出格式的選項之外,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 選項的最後指定它們的路徑。因爲是放在最後位置上的選項,所以用兩個短劃線(--)隔開之前的選項和後面限定的路徑名。

表 2-3 還列出了其他常用的類似選項。

選項 說明
-(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 條。


7.撤消操作

任何時候,你都有可能需要撤消剛纔所做的某些操作。接下來,我們會介紹一些基本的撤消操作相關的命令。請注意,有些操作並不總是可以撤消的,所以請務必謹慎小心,一旦失誤,就有可能丟失部分工作成果。

7.1 修改最後一次提交

有時候我們提交完了才發現漏掉了幾個文件沒有加,或者提交信息寫錯了。想要撤消剛纔的提交操作,可以使用 --amend 選項重新提交:

$ git commit --amend

此命令將使用當前的暫存區域快照提交。如果剛纔提交完沒有作任何改動,直接運行此命令的話,相當於有機會重新編輯提交說明,但將要提交的文件快照和之前的一樣。

啓動文本編輯器後,會看到上次提交時的說明,編輯它確認沒問題後保存退出,就會使用新的提交說明覆蓋剛纔失誤的提交。

如果剛纔提交時忘了暫存某些修改,可以先補上暫存操作,然後再運行 --amend 提交:

$ git commit -m 'initial commit'
$ git add forgotten_file
$ git commit --amend

上面的三條命令最終只是產生一個提交,第二個提交命令修正了第一個的提交內容。

7.2 取消已經暫存的文件

接下來的兩個小節將演示如何取消暫存區域中的文件,以及如何取消工作目錄中已修改的文件。不用擔心,查看文件狀態的時候就提示了該如何撤消,所以不需要死記硬背。來看下面的例子,有兩個修改過的文件,我們想要分開提交,但不小心用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 文件又回到了之前已修改未暫存的狀態。

7.3 取消對文件的修改

如果覺得剛纔對 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 來說它們就像從未存在過一樣。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章