1. 程式人生 > >git詳解二——git基礎

git詳解二——git基礎

轉載自:http://blog.jobbole.com/25808/

Git 基礎

讀完本章你就能上手使用 Git 了(伯樂線上注:如果你對Git還不瞭解,建議從本Git系列第一篇文章開始閱讀)。本章將介紹幾個最基本的,也是最常用的 Git 命令,以後絕大多數時間裡用到的也就是這幾個命令。讀完本章,你就能初始化一個新的程式碼倉庫,做一些適當配置;開始或停止跟蹤某些檔案;暫存或提交某些更 新。我們還會展示如何讓 Git 忽略某些檔案,或是名稱符合特定模式的檔案;如何既快且容易地撤消犯下的小錯誤;如何瀏覽專案的更新歷史,檢視某兩次更新之間的差異;以及如何從遠端倉庫 拉資料下來或者推資料上去。

2.1  取得專案的 Git 倉庫

有兩種取得 Git 專案倉庫的方法。第一種是在現存的目錄下,通過匯入所有檔案來建立新的 Git 倉庫。第二種是從已有的 Git 倉庫克隆出一個新的映象倉庫來。

在工作目錄中初始化新倉庫

要對現有的某個專案開始用 Git 管理,只需到此專案所在的目錄,執行:

Shell

 

1

$ git init

初始化後,在當前目錄下會出現一個名為 .git 的目錄,所有 Git 需要的資料和資源都存放在這個目錄中。不過目前,僅僅是按照既有的結構框架初始化好了裡邊所有的檔案和目錄,但我們還沒有開始跟蹤管理專案中的任何一個檔案。(在第九章我們會詳細說明剛才建立的.git

 目錄中究竟有哪些檔案,以及都起些什麼作用。)

如果當前目錄下有幾個檔案想要納入版本控制,需要先用 git add 命令告訴 Git 開始對這些檔案進行跟蹤,然後提交:

Shell

 

1

2

3

$ git add *.c

$ git add README

$ git commit -m 'initial project version'

稍後我們再逐一解釋每條命令的意思。不過現在,你已經得到了一個實際維護著若干檔案的 Git 倉庫。

從現有倉庫克隆

如果想對某個開源專案出一份力,可以先把該專案的 Git 倉庫複製一份出來,這就需要用到 git clone 命令。如果你熟悉其他的 VCS 比如 Subversion,你可能已經注意到這裡使用的是 clone 而不是 checkout。這是個非常重要的差別,Git 收取的是專案歷史的所有資料(每一個檔案的每一個版本),伺服器上有的資料克隆之後本地也都有了。實際上,即便伺服器的磁碟發生故障,用任何一個克隆出來 的客戶端都可以重建伺服器上的倉庫,回到當初克隆時的狀態(雖然可能會丟失某些伺服器端的掛鉤設定,但所有版本的資料仍舊還在,有關細節請參考第四章)。github

克隆倉庫的命令格式為 git clone [url]。比如,要克隆 Ruby 語言的 Git 程式碼倉庫 Grit,可以用下面的命令:

Shell

 

1

$ git clone git://github.com/schacon/grit.git

這會在當前目錄下建立一個名為“grit”的目錄,其中包含一個 .git 的目錄,用於儲存下載下來的所有版本記錄,然後從中取出最新版本的檔案拷貝。如果進入這個新建的grit 目錄,你會看到專案中的所有檔案已經在裡邊了,準備好後續的開發和使用。如果希望在克隆的時候,自己定義要新建的專案目錄名稱,可以在上面的命令末尾指定新的名字:

Shell

 

1

$ git clone git://github.com/schacon/grit.git mygrit

唯一的差別就是,現在新建的目錄成了 mygrit,其他的都和上邊的一樣。

Git 支援許多資料傳輸協議。之前的例子使用的是 git:// 協議,不過你也可以用 http(s):// 或者[email protected]:/path.git表示的 SSH 傳輸協議。我們會在第四章詳細介紹所有這些協議在伺服器端該如何配置使用,以及各種方式之間的利弊。

2.2  記錄每次更新到倉庫

現在我們手上已經有了一個真實專案的 Git 倉庫,並從這個倉庫中取出了所有檔案的工作拷貝。接下來,對這些檔案作些修改,在完成了一個階段的目標之後,提交本次更新到倉庫。

請記住,工作目錄下面的所有檔案都不外乎這兩種狀態:已跟蹤或未跟蹤。已跟蹤的檔案是指本來就被納入版本控制管理的檔案,在上次快照中有它們的記 錄,工作一段時間後,它們的狀態可能是未更新,已修改或者已放入暫存區。而所有其他檔案都屬於未跟蹤檔案。它們既沒有上次更新時的快照,也不在當前的暫存 區域。初次克隆某個倉庫時,工作目錄中的所有檔案都屬於已跟蹤檔案,且狀態為未修改。

在編輯過某些檔案之後,Git 將這些檔案標為已修改。我們逐步把這些修改過的檔案放到暫存區域,直到最後一次性提交所有這些暫存起來的檔案,如此重複。所以使用 Git 時的檔案狀態變化週期如圖 2-1 所示。

 

Git詳解之二:Git基礎

 

圖 2-1. 檔案的狀態變化週期

檢查當前檔案狀態

要確定哪些檔案當前處於什麼狀態,可以用 git status 命令。如果在克隆倉庫之後立即執行此命令,會看到類似這樣的輸出:

Shell

 

1

$ git status # On branch master nothing to commit (working directory clean)

這說明你現在的工作目錄相當乾淨。換句話說,當前沒有任何跟蹤著的檔案,也沒有任何檔案在上次提交後更改過。此外,上面的資訊還表明,當前目錄下沒 有出現任何處於未跟蹤的新檔案,否則 Git 會在這裡列出來。最後,該命令還顯示了當前所在的分支是 master,這是預設的分支名稱,實際是可以修改的,現在先不用考慮。下一章我們就會詳細討論分支和引用。

現在讓我們用 vim 編輯一個新檔案 README,儲存退出後執行 git status 會看到該檔案出現在未跟蹤檔案列表中:

Shell

 

1

2

3

4

5

6

7

8

9

10

$ 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 不會自動將之納入跟蹤範圍,除非你明明白白地告訴它“我需要跟蹤該檔案”,因而不用擔心把臨時檔案什麼的也歸入版本管理。不過現在的例子中,我們確實想要跟蹤管理 README 這個檔案。

跟蹤新檔案

使用命令 git add 開始跟蹤一個新檔案。所以,要跟蹤 README 檔案,執行:

Shell

 

1

$ git add README

此時再執行 git status 命令,會看到 README 檔案已被跟蹤,並處於暫存狀態:

Shell

 

1

2

3

4

5

6

7

8

9

$ git status

# On branch master

# Changes to be committed:

#   (use "git reset HEAD

 

     ..." to unstage)

#

# new file:   README

#

只要在 “Changes to be committed” 這行下面的,就說明是已暫存狀態。如果此時提交,那麼該檔案此時此刻的版本將被留存在歷史記錄中。你可能會想起之前我們使用git init 後就運行了 git add 命令,開始跟蹤當前目錄下的檔案。在 git add 後面可以指明要跟蹤的檔案或目錄路徑。如果是目錄的話,就說明要遞迴跟蹤該目錄下的所有檔案。(譯註:其實git add 的潛臺詞就是把目標檔案快照放入暫存區域,也就是 add file into staged area,同時未曾跟蹤過的檔案標記為需要跟蹤。這樣就好理解後續 add 操作的實際意義了。)

暫存已修改檔案

現在我們修改下之前已跟蹤過的檔案 benchmarks.rb,然後再次執行 status 命令,會看到這樣的狀態報告:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$ 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 出現在 “Changed but not updated” 這行下面,說明已跟蹤檔案的內容發生了變化,但還沒有放到暫存區。要暫存這次更新,需要執行git add 命令(這是個多功能命令,根據目標檔案的狀態不同,此命令的效果也不同:可以用它開始跟蹤新檔案,或者把已跟蹤的檔案放到暫存區,還能用於合併時把有衝突的檔案標記為已解決狀態等)。現在讓我們執行git add 將 benchmarks.rb 放到暫存區,然後再看看 git status 的輸出:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

$ 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

#

現在兩個檔案都已暫存,下次提交時就會一併記錄到倉庫。假設此時,你想要在 benchmarks.rb 裡再加條註釋,重新編輯存檔後,準備好提交。不過且慢,再執行git status 看看:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

$ 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 檔案出現了兩次!一次算未暫存,一次算已暫存,這怎麼可能呢?好吧,實際上 Git 只不過暫存了你執行 git add 命令時的版本,如果現在提交,那麼提交的是添加註釋前的版本,而非當前工作目錄中的版本。所以,運行了git add 之後又作了修訂的檔案,需要重新執行 git add 把最新版本重新暫存起來:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

$ 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

#

忽略某些檔案

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

Shell

 

1

$ 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 檔案的例子:

Shell

 

1

2

3

4

5

6

# 此為註釋 – 將被 Git 忽略

*.a       # 忽略所有 .a 結尾的檔案

!lib.a    # 但 lib.a 除外

/TODO     # 僅僅忽略專案根目錄下的 TODO 檔案,不包括 subdir/TODO

build/    # 忽略 build/ 目錄下的所有檔案

doc/*.txt # 會忽略 doc/notes.txt 但不包括 doc/server/arch.txt

 

檢視已暫存和未暫存的更新

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

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

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$ 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

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$ 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,效果是相同的,但更好記些。)來看看實際的效果:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

$ 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 會看到暫存前後的兩個版本:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

$ 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 看暫存前後的變化:

Shell

 

1

2

3

4

5

6

7

8

9

10

$ 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 檢視已經暫存起來的變化:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

$ 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

 

提交更新

現在的暫存區域已經準備妥當可以提交了。在此之前,請一定要確認還有什麼修改過的或新建的檔案還沒有 git add 過,否則提交的時候不會記錄這些還沒暫存起來的變化。所以,每次準備提交前,先用git status 看下,是不是都已暫存起來了,然後再執行提交命令 git commit

Shell

 

1

$ git commit

這種方式會啟動文字編輯器以便輸入本次提交的說明。(預設會啟用 shell 的環境變數 $EDITOR 所指定的軟體,一般都是 vim 或 emacs。當然也可以按照第一章介紹的方式,使用git config --global core.editor 命令設定你喜歡的編輯軟體。)

編輯器會顯示類似下面的文字資訊(本例選用 Vim 的屏顯方式展示):

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

# 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

可以看到,預設的提交訊息包含最後一次執行 git status 的輸出,放在註釋行裡,另外開頭還有一空行,供你輸入提交說明。你完全可以去掉這些註釋行,不過留著也沒關係,多少能幫你回想起這次更新的內容有哪些。(如果覺得這還不夠,可以用-v 選項將修改差異的每一行都包含到註釋中來。)退出編輯器時,Git 會丟掉註釋行,將說明內容和本次更新提交到倉庫。

另外也可以用 -m 引數後跟提交說明的方式,在一行命令中提交更新:

Shell

 

1

2

3

4

$ 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

好,現在你已經建立了第一個提交!可以看到,提交後它會告訴你,當前是在哪個分支(master)提交的,本次提交的完整 SHA-1 校驗和是什麼(463dc4f),以及在本次提交中,有多少檔案修訂過,多少行添改和刪改過。

記住,提交時記錄的是放在暫存區域的快照,任何還未暫存的仍然保持已修改狀態,可以在下次提交時納入版本管理。每一次執行提交操作,都是對你專案作一次快照,以後可以回到這個狀態,或者進行比較。

跳過使用暫存區域

儘管使用暫存區域的方式可以精心準備要提交的細節,但有時候這麼做略顯繁瑣。Git 提供了一個跳過使用暫存區域的方式,只要在提交的時候,給 git commit 加上-a 選項,Git 就會自動把所有已經跟蹤過的檔案暫存起來一併提交,從而跳過 git add 步驟:

Shell

 

1

2

3

4

5

6

7

8

9

10

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

如果只是簡單地從工作目錄中手工刪除檔案,執行 git status 時就會在 “Changed but not updated” 部分(也就是_未暫存_清單)看到:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

$ 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 記錄此次移除檔案的操作:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

$ 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 選項即可:

Shell

 

1

$ git rm --cached readme.txt

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

Shell

 

1

$ git rm log/\*.log

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

Shell

 

1

$ git rm \*~

會遞迴刪除當前目錄及其子目錄中所有 ~ 結尾的檔案。

移動檔案

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

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

Shell

 

1

$ git mv file_from file_to

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

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

$ 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 就相當於運行了下面三條命令:

Shell

 

1

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

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

2.3  檢視提交歷史

在提交了若干更新之後,又或者克隆了某個專案,想回顧下提交歷史,可以使用 git log 命令檢視。

接下來的例子會用我專門用於演示的 simplegit 專案,執行下面的命令獲取該專案原始碼:

Shell

 

1

git clone git://github.com/schacon/simplegit-progit.git

然後在此專案中執行 git log,應該會看到下面的輸出:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

$ 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 則僅顯示最近的兩次更新:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

$ 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,僅顯示簡要的增改行數統計:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

$ 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 將每個提交放在一行顯示,這在提交數很大時非常有用。另外還有 shortfull 和fuller 可以用,展示的資訊或多或少有些不同,請自己動手實踐一下看看效果如何。

Shell

 

1

2

3

4

$ git log --pretty=oneline

ca82a6dff817ec66f44342007202690a93763949 changed the version number

085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test code

a11bef06a3f659402fe7563abf99ad00de2209e6 first commit

但最有意思的是 format,可以定製要顯示的記錄格式,這樣的輸出便於後期程式設計提取分析,像這樣:

Shell

 

1

2

3

4

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

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

選項 說明

%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 專案倉庫中可以看到:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

$ 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 還列出了一些其他常用的選項及其釋義。

Shell

 

1

2

3

4

5

6

7

8

9

10

選項 說明

-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。下面的命令列出所有最近兩週內的提交:

Shell

 

1

$ git log --since=2.weeks

你可以給出各種時間格式,比如說具體的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。

還可以給出若干搜尋條件,列出符合的提交。用 --author 選項顯示指定作者的提交,用 --grep 選項搜尋提交說明中的關鍵字。(請注意,如果要得到同時滿足這兩個選項搜尋條件的提交,就必須用--all-match 選項。)

如果只關心某些檔案或者目錄的歷史提交,可以在 git log 選項的最後指定它們的路徑。因為是放在最後位置上的選項,所以用兩個短劃線(--)隔開之前的選項和後面限定的路徑名。

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

Shell

 

1

2

3

4

5

6

選項 說明

-(n) 僅顯示最近的 n 條提交

--since, --after 僅顯示指定時間之後的提交。

--until, --before 僅顯示指定時間之前的提交。

--author 僅顯示指定作者相關的提交。

--committer 僅顯示指定提交者相關的提交。

來看一個實際的例子,如果要檢視 Git 倉庫中,2008 年 10 月期間,Junio Hamano 提交的但未合併的測試指令碼(位於專案的 t/ 目錄下的檔案),可以用下面的查詢命令:

Shell

 

1

2

3

4

5

6

7

8

$ 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 所示的介面。

 

Git詳解之二:Git基礎

 

圖 2-2. gitk 的圖形介面

上半個視窗顯示的是歷次提交的分支祖先圖譜,下半個視窗顯示當前點選的提交對應的具體差異。

2.4  撤消操作

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

修改最後一次提交

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

Shell

 

1

$ git commit --amend

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

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

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

Shell

 

1

2

3

$ git commit -m 'initial commit'

$ git add forgotten_file

$ git commit --amend

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

取消已經暫存的檔案

接下來的兩個小節將演示如何取消暫存區域中的檔案,以及如何取消工作目錄中已修改的檔案。不用擔心,檢視檔案狀態的時候就提示了該如何撤消,所以不需要死記硬背。來看下面的例子,有兩個修改過的檔案,我們想要分開提交,但不小心用git add . 全加到了暫存區域。該如何撤消暫存其中的一個檔案呢?其實,git status 的命令輸出已經告訴了我們該怎麼做:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

$ 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 檔案:

Shell

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

$ 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 同樣提示了具體的撤消方法,接著上面的例子,現在未暫存區域看起來像這樣:

Shell

 

1

2

3

4

5

6

7

8

9

10

# 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 以及更高版本中會這樣提示,如果你還在用老版本,我們強烈建議你升級,以獲取最佳的使用者體驗),讓我們試試看:

Shell

 

1

2

3

4

5

6

7

8

9

10

$ 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 預設使用這個名字來標識你所克隆的原始倉庫:

Shell

 

1

2

3

4

5

6

7

8

9

10

$ 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 的簡寫,取首字母),顯示對應的克隆地址:

Shell

 

1

2

$ git remote -v

origin git://github.com/schacon/ticgit.git

如果有多個遠端倉庫,此命令將全部列出。比如在我的 Grit 專案中,可以看到:

Shell

 

1

2

3

4

5

6

7

$ 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    [email protected] github.com:mojombo/grit.git

這樣一來,我就可以非常輕鬆地從這些使用者的倉庫中,拉取他們的提交到本地。請注意,上面列出的地址只有 origin 用的是 SSH URL 連結,所以也只有這個倉庫我能推送資料上去(我們會在第四章解釋原因)。

新增遠端倉庫

要新增一個新的遠端倉庫,可以指定一個簡單的名字,以便將來引用,執行 git remote add [shortname] [url]

Shell

 

1

2

3

4

5

6

$ 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

Shell

 

1

2

3

4

5

6

7

8

$ 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,你可以將它合併到自己的某個分支,或者切換到這個分支,看看有些什麼有趣的更新。

從遠端倉庫抓取資料

正如之前所看到的,可以用下面的命令從遠端倉庫抓取資料到本地:

Shell

 

1

$ 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 名字),可以執行下面的命令:

Shell

 

1

$ git push origin master

只有在所克隆的伺服器上有寫許可權,或者同一時刻沒有其他人在推資料,這條命令才會如期完成任務。如果在你推資料前,已經有其他人推送了若干更新,那 你的推送操作就會被駁回。你必須先把他們的更新抓取到本地,合併到自己的專案中,然後才可以再次推送。有關推送資料到遠端倉庫的詳細內容見第三章。

檢視遠端倉庫資訊

我們可以通過命令 git remote show [remote-name] 檢視某個遠端倉庫的詳細資訊,比如要看所克隆的 origin 倉庫,可以執行:

Shell