Git快速入門

假如只能閱讀一章來學習 Git,那麼本教程絕對是一個不二的選擇。本章內容涵蓋你在使用 Git 完成各種工作中將要使用的各種基本命令。 在學習完本章之後,你應該能夠配置並初始化一個倉庫(repository)、開始或停止跟蹤(track)文件、暫存(stage)或提交(commit)更改。 本章也將演示如何配置 Git 來忽略指定的文件和文件模式、如何迅速而簡單地撤銷錯誤操作、如何瀏覽項目的歷史版本以及不同提交(commits)間的差異、如何向遠程倉庫推送(push)以及如何從遠程倉庫拉取(pull)文件。

遠程倉庫是什麼?

Repository(倉庫)包含的內容 - Git的目標是管理一個工程,或者說是一些文件的集合,以跟蹤它們的變化。Git使用Repository來存儲這些信息。一個倉庫主要包含以下內容(也包括其他內容):

  • 許多commit objects
  • 到commit objects的指針,叫做heads
  • Git的倉庫和工程存儲在同一個目錄下,在一個叫做.git的子目錄中。

1.創建Repository(倉庫)

在使用Repository(倉庫)之前,我們首先需要創建倉庫,創建倉庫有很多種,這裏常見的有如下幾種:

爲了節省時間,這裏使用第三方託管平臺作爲講解,以 http://git.oschina.net 爲例,大概需要通過以下幾個步驟完成倉庫的創建。

  1. 註冊網站賬號
  2. 登錄帳後,創建倉庫

第一步:註冊網站的帳號

打開網址: http://git.oschina.net/signup ,寫入一些必填項,然後提交,如下圖所示 -

Git快速入門

提交完成後,登錄帳號還不能使用,還需要登錄註冊的郵箱驗證帳號。驗證郵箱驗證帳號後,登錄後默認的用戶面板界面如下所示 -

Git快速入門

點擊紅色箭頭指向的」**+**「號,以創建一個倉庫,如下所示 -

Git快速入門

這樣,一個公開的倉庫就創建完成了。要記住上面圖片創建的路徑:http://git.oschina.net/yiibai/git-start.git

2.獲取 Git 倉庫

有兩種取得 Git 項目倉庫的方法。第一種是從一個服務器克隆一個現有的 Git 倉庫。第二種是在現有項目或目錄下導入所有文件到 Git 中;

2.1 克隆現有的倉庫

如果你想獲得一份已經存在了的 Git 倉庫的拷貝,比如說,想爲某個開源項目貢獻自己的一份力,這時就要用到 git clone 命令。 如果你對其它的 VCS 系統(比如說Subversion)很熟悉,請留心一下這裏所使用的命令是」clone「而不是」checkout「。 這是 Git 區別於其它版本控制系統的一個重要特性,Git 克隆的是該 Git 倉庫服務器上的幾乎所有數據,而不是僅僅複製完成你的工作所需要文件。 當你執行 git clone 命令的時候,默認配置下遠程 Git 倉庫中的每一個文件的每一個版本都將被拉取下來。如果服務器的磁盤壞掉了,通常可以使用任何一個克隆下來的用戶端來重建服務器上的倉庫。

在安裝了Git 的 Windows系統上,在一個目錄(本示例是:F:\worksp)中,單擊右鍵,在彈出的菜單中選擇「Git Bash」,如下圖中所示 -

Git快速入門

克隆倉庫的命令格式是 git clone [url] 。 比如,要克隆 Git 的上面創建的倉庫 git-start.git,可以用下面的命令:

$ git clone http://git.oschina.net/yiibai/git-start.git

這會在當前目錄下創建一個名爲 「git-start.git」 的目錄,並在這個目錄下初始化一個 .git 文件夾,從遠程倉庫拉取下所有數據放入 .git 文件夾,然後從中讀取最新版本的文件的拷貝。上面命令執行後,輸出結果如下所示 -

Git快速入門

如果想在克隆遠程倉庫的時候,自定義本地倉庫的名字,可以使用如下命令:

$ git clone http://git.oschina.net/yiibai/git-start.git mygit-start

這將執行與上一個命令相同的操作,不過在本地創建的倉庫名字變爲 mygit-start

Git 支持多種數據傳輸協議。 上面的例子使用的是 http:// 協議,不過也可以使用 git:// 協議或者使用 SSH 傳輸協議,比如 user[@server_ip](https://github.com/server_ip "@server_ip")-or-host:path/to/repo.git 。在服務器上搭建 Git 將會介紹所有這些協議在服務器端如何配置使用,以及各種方式之間的利弊。

2.2. 在現有目錄中初始化倉庫

如果不克隆現有的倉庫,而是打算使用 Git 來對現有的項目進行管理。假設有一個項目的目錄是:D:\worksp\git_sample,只需要進入該項目的目錄並輸入:

$ git init

執行上面命令,輸出結果如下 -

Git快速入門

該命令將創建一個名爲 .git 的子目錄,這個子目錄含有初始化的 Git 倉庫中所有的必須文件,這些文件是 Git 倉庫的骨幹。 但是,在這個時候,我們僅僅是做了一個初始化的操作,項目裏的文件還沒有被跟蹤。

如果是在一個已經存在文件的文件夾(而不是空文件夾)中初始化 Git 倉庫來進行版本控制的話,應該開始跟蹤這些文件並提交。可通過 git add 命令來實現對指定文件的跟蹤,然後執行 git commit 提交,假設在目錄 F:\worksp\git-start.git 中有一些代碼需要跟蹤(版本控制),比如有一個 Python 代碼文件叫作:hello.py 內容如下:

#!/usr/bin/python3
#coding=utf-8

print ("This is my first Python Programming.")

可通過 git add 命令來實現對hello.py 文件的跟蹤 -

$ git add hello.py
$ git commit -m 'initial project version'

上面命令執行結果如下 -

Git快速入門

在之後的章節中,再逐一解釋每一條指令的意思。 現在,你已經得到了一個實際維護(或者說是跟蹤)着若干個文件的 Git 倉庫。

3. 更新提交到倉庫

3.1 記錄每次更新到倉庫

現在我們手上有了一個真實項目的 Git 倉庫(如上面 clone 下來的 git-start.git),並從這個倉庫中取出了所有文件的工作拷貝。 接下來,對這些文件做些修改,在完成了一個階段的目標之後,提交本次更新到倉庫。

工作目錄下的每一個文件都不外乎這兩種狀態:已跟蹤或未跟蹤。 已跟蹤的文件是指那些被納入了版本控制的文件,在上一次快照中有它們的記錄,在工作一段時間後,它們的狀態可能處於未修改,已修改或已放入暫存區。 工作目錄中除已跟蹤文件以外的所有其它文件都屬於未跟蹤文件,它們既不存在於上次快照的記錄中,也沒有放入暫存區。 初次克隆某個倉庫的時候,工作目錄中的所有文件都屬於已跟蹤文件,並處於未修改狀態。

編輯過某些文件之後,由於自上次提交後你對它們做了修改,Git 將它們標記爲已修改文件。 我們逐步將這些修改過的文件放入暫存區,然後提交所有暫存了的修改,如此反覆。所以使用 Git 時文件的生命週期如下:

Git快速入門

3.2 檢查當前文件狀態

要查看哪些文件處於什麼狀態,可以用 git status 命令。 如果在克隆倉庫後立即使用此命令,會看到類似這樣的輸出:

$ git status

上面命令執行結果如下 -

Git快速入門

這說明現在你的工作目錄相當乾淨。換句話說,所有已跟蹤文件在上次提交後都未被更改過。 此外,上面的信息還表明,當前目錄下沒有出現任何處於未跟蹤狀態的新文件,否則 Git 會在這裏列出來。 最後,該命令還顯示了當前所在分支,並告訴你這個分支同遠程服務器上對應的分支沒有偏離。現在,分支名是 「master」, 這是默認的分支名。

現在,在項目下創建一個新的 mytext.txt 文件。 如果之前並不存在這個文件,使用 git status 命令,將看到一個新的未跟蹤文件:

# 向 mytext.md 文件寫入一點內容
$ echo 'This is my first Git control file ' > mytext.txt
$ git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    mytext.txt

nothing added to commit but untracked files present (use "git add" to track)

上面命令執行結果如下 -

Git快速入門

在狀態報告中可以看到新建的 README 文件出現在 Untracked files 下面。 未跟蹤的文件意味着 Git 在之前的快照(提交)中沒有這些文件;Git 不會自動將之納入跟蹤範圍,除非你明明白白地告訴它「我需要跟蹤該文件」, 這樣的處理讓你不必擔心將生成的二進制文件或其它不想被跟蹤的文件包含進來。 不過現在的例子中,我們確實想要跟蹤管理 README 這個文件。

3.3 跟蹤新文件

使用命令 git add 開始跟蹤一個文件。 所以,要跟蹤 mytext.txt 文件,運行:

$ git add mytext.txt

此時再運行 git status 命令,會看到 mytext.txt 文件已被跟蹤,並處於暫存狀態:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   mytext.txt

上面命令執行結果如下 -

Git快速入門

只要在 Changes to be committed 這行下面的,就說明是已暫存狀態。 如果此時提交,那麼該文件此時此刻的版本將被留存在歷史記錄中。git add 命令使用文件或目錄的路徑作爲參數;如果參數是目錄的路徑,該命令將遞歸地跟蹤該目錄下的所有文件。

3.4 暫存已修改文件

現在我們來修改一個已被跟蹤的文件。 如果修改了一個名爲 README.md 的已被跟蹤的文件,打開文件 README.md並編輯其中的內容,在文件的未尾加入一行內容:」這是暫存已修改文件示例」,然後運行 git status 命令,會看到下面內容:

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   mytext.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

執行上面命令得到以下結果 -

Git快速入門

文件 README.md 出現在 Changes not staged for commit 這行下面,說明已跟蹤文件的內容發生了變化,但還沒有放到暫存區。要暫存這次更新,需要運行 git add 命令。 這是個多功能命令:可以用它開始跟蹤新文件,或者把已跟蹤的文件放到暫存區,還能用於合併時把有衝突的文件標記爲已解決狀態等。 將這個命令理解爲「添加內容到下一次提交中」而不是「將一個文件添加到項目中」要更加合適。 現在讓我們運行 git add 將」README.md「放到暫存區,然後再看看 git status 的輸出:

$ git add README.md

Administrator@MY-PC /F/worksp/git-start (master)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   README.md
        new file:   mytext.txt


Administrator@MY-PC /F/worksp/git-start (master)
$

現在兩個文件都已暫存,下次提交時就會一併記錄到倉庫。 假設此時,想要在 README.md 裏再加條註釋, 重新編輯存盤後,準備好提交。不過且慢,先向 「README.md」 文件加入一點內容,再運行 git status ,如下所示 -

$ echo "Add new Line content 1002 " >> README.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   mytext.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

執行上面命令,輸出結果如下所示 -

Git快速入門

怎麼回事? 現在 README.md 文件同時出現在暫存區和非暫存區。 這怎麼可能呢? 好吧,實際上 Git 只不過暫存了運行 git add 命令時的版本, 如果現在提交,README.md 的版本是最後一次運行 git add 命令時的那個版本,而不是運行 git commit 時,在工作目錄中的當前版本。 所以,運行了 git add 之後又作了修訂的文件,需要重新運行 git add 把最新版本重新暫存起來:

$ git add README.md

Administrator@MY-PC /F/worksp/git-start (master)
$ git status
warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directory.
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   README.md
        new file:   mytext.txt


Administrator@MY-PC /F/worksp/git-start (master)
$

3.5 狀態簡覽

git status 命令的輸出十分詳細,但其用語有些繁瑣。 如果你使用 git status -s 命令或 git status --short 命令,將得到一種更爲緊湊的格式輸出。 運行 git status -s,狀態報告輸出如下:

$ git status -s
 M README.md
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

新添加的未跟蹤文件前面有 ?? 標記,新添加到暫存區中的文件前面有 A 標記,修改過的文件前面有 M 標記。 你可能注意到了 M 有兩個可以出現的位置,出現在右邊的 M 表示該文件被修改了但是還沒放入暫存區,出現在靠左邊的 M 表示該文件被修改了並放入了暫存區。 例如,上面的狀態報告顯示: README 文件在工作區被修改了但是還沒有將修改後的文件放入暫存區,lib/simplegit.rb 文件被修改了並將修改後的文件放入了暫存區。 而 Rakefile 在工作區被修改並提交到暫存區後又在工作區中被修改了,所以在暫存區和工作區都有該文件被修改了的記錄。

3.6 忽略文件

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

$ cat .gitignore
*.[oa]
*~

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

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

  • 所有空行或者以 開頭的行都會被 Git 忽略。
  • 可以使用標準的 glob 模式匹配。
  • 匹配模式可以以(/)開頭防止遞歸。
  • 匹配模式可以以(/)結尾指定目錄。
  • 要忽略指定模式以外的文件或目錄,可以在模式前加上驚歎號(!)取反。

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

下面再看一個 .gitignore 文件的例子:

# no .a files
*.a

# but do track lib.a, even though you're ignoring .a files above
!lib.a

# only ignore the TODO file in the current directory, not subdir/TODO
/TODO

# ignore all files in the build/ directory
build/

# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt

# ignore all .pdf files in the doc/ directory
doc/**/*.pdf

提示:GitHub 有一個十分詳細的針對數十種項目及編程語言的 .gitignore 文件列表,你可以在 http://github.com/github/gitignore 找到它。

3.7 查看已暫存和未暫存的修改

如果 git status 命令的輸出對於你來說過於模糊,你想知道具體修改了什麼地方,可以用 git diff 命令。 稍後我們會詳細介紹 git diff,可能通常會用它來回答這兩個問題:當前做的哪些更新還沒有暫存? 有哪些更新已經暫存起來準備好了下次提交? 儘管 git status 已經通過在相應欄下列出文件名的方式回答了這個問題,git diff 將通過文件補丁的格式顯示具體哪些行發生了改變。

假如再次修改 README.md 文件後暫存,然後編輯 READ.md 文件並在文件的最後追加一行內容:」this is another line 1003「 之後先不暫存, 運行 git status 命令將會看到:

$ echo "this is another line 1003 " >> README.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   README.md
        new file:   mytext.txt

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   README.md


Administrator@MY-PC /F/worksp/git-start (master)
$

要查看尚未暫存的文件更新了哪些部分,不加參數直接輸入 git diff

$ git diff
diff --git a/README.md b/README.md
index ea161e2..6679481 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
 Add new Line content 1001
 Add new Line content 1002
+this is another line 1003
warning: LF will be replaced by CRLF in README.md.
The file will have its original line endings in your working directo(END)

執行上面命令,得到以下輸出結果 -

Git快速入門

上面輸出顯示有加一行「+this is another line 1003」,前面帶有一個加號:「+」。

請注意,git diff 本身只顯示尚未暫存的改動,而不是自上次提交以來所做的所有改動。 所以有時候你一下子暫存了所有更新過的文件後,運行 git diff 後卻什麼也沒有,就是這個原因。

然後用 git diff --cached 查看已經暫存起來的變化:(--staged--cached 是同義詞)

$ git diff --cached
diff --git a/README.md b/README.md
index 2f88ca7..ea161e2 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,2 @@
-#git-start
-這是一個 Git 學習使用的Git倉庫。
\ No newline at end of file
+Add new Line content 1001
+Add new Line content 1002
diff --git a/mytext.txt b/mytext.txt
new file mode 100644
index 0000000..1820ae1
--- /dev/null
+++ b/mytext.txt
@@ -0,0 +1 @@
+This is my first Git control file
warning: LF will be replaced by CRLF in mytext.txt.
The file will have its original line endings in your working directory.

Administrator@MY-PC /F/worksp/git-start (master)
$

執行上面命令,得到如下輸出結果 -

Git快速入門

如上圖中所示,分別對比了兩個文件:README.mdmytext.txt,其中綠色的內容表示添加,紅色的內容表示刪除。

注意:git diff 的插件版本,在本教程中,我們使用 git diff 來分析文件差異。 但是,如果你喜歡通過圖形化的方式或其它格式輸出方式的話,可以使用 git difftool 命令來用 Araxisemergevimdiff 等軟件輸出 diff 分析結果。 使用 git difftool --tool-help 命令來看你的系統支持哪些 Git Diff 插件。

3.8 提交更新

現在的暫存區域已經準備妥當可以提交了。 在此之前,請一定要確認還有什麼修改過的或新建的文件還沒有 git add 過,否則提交的時候不會記錄這些還沒暫存起來的變化。 這些修改過的文件只保留在本地磁盤。 所以,每次準備提交前,先用 git status 看下,是不是都已暫存起來了,如果沒有暫存起來則要先使用命令:git add .將所有文件暫存起來, 然後再運行提交命令 git commit

$ git status
$ git add .
$ git commit

這種方式會啓動文本編輯器以便輸入本次提交的說明。 (默認會啓用 shell 的環境變量 $EDITOR 所指定的軟件,一般都是 vimemacs。使用 git config --global core.editor 命令設定你喜歡的編輯軟件。)

編輯器會顯示類似下面的文本信息(本例選用 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:
#    new file:   README
#    modified:   CONTRIBUTING.md
#
this is my commit info note.
~
~
".git/COMMIT_EDITMSG" 9L, 283C

可以看到,默認的提交消息包含最後一次運行 git status 的輸出,放在註釋行裏,另外開頭還有一空行,供你輸入提交說明。完全可以去掉這些註釋行,不過留着也沒關係,多少能幫你回想起這次更新的內容有哪些。 (如果想要更詳細的對修改了哪些內容的提示,可以用 -v 選項,這會將你所做的改變的 diff 輸出放到編輯器中從而使你知道本次提交具體做了哪些修改。) 退出編輯器時,Git 會丟掉註釋行,用輸入提交附帶信息生成一次提交。如上面示例中,提交的備註信息是:「this is my commit info note.」。

另外,也可以在 commit 命令後添加 -m 選項,將提交信息與命令放在同一行,如下所示:

$ git commit -m "this is my commit info note."
[master 463dc4f] Story 182: Fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README.md

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

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

3.9 跳過使用暫存區域

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

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
 1 file changed, 5 insertions(+), 0 deletions(-)

看到了嗎?提交之前不再需要 git add 文件「README.md」了。

3.10 移除文件

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

如果只是簡單地從工作目錄中手工刪除文件,運行 git status 時就會在 「Changes not staged for commit」 部分(也就是 未暫存清單)看到:

$ rm mytext.txt
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    mytext.txt

no changes added to commit (use "git add" and/or "git commit -a")

Administrator@MY-PC /F/worksp/git-start (master)
$

下一次提交時,該文件就不再納入版本管理了。 如果刪除之前修改過並且已經放到暫存區域的話,則必須要用強制刪除選項 -f(注:即 force 的首字母)。 這是一種安全特性,用於防止誤刪還沒有添加到快照的數據,這樣的數據不能被 Git 恢復。

另外一種情況是,我們想把文件從 Git 倉庫中刪除(亦即從暫存區域移除),但仍然希望保留在當前工作目錄中。 換句話說,你想讓文件保留在磁盤,但是並不想讓 Git 繼續跟蹤。 當你忘記添加 .gitignore 文件,不小心把一個很大的日誌文件或一堆 .a 這樣的編譯生成文件添加到暫存區時,這一做法尤其有用。 爲達到這一目的,使用 --cached 選項:

$ git rm --cached mytext.txt
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        deleted:    mytext.txt

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

$ git rm log/\*.log

注意到星號 * 之前的反斜槓 \, 因爲 Git 有它自己的文件模式擴展匹配方式,所以我們不用 shell 來幫忙展開。 此命令刪除 log/ 目錄下擴展名爲 .log 的所有文件。 類似的比如:

$ git rm \*~

該命令爲刪除以 ~ 結尾的所有文件。

3.11 移動文件

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

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

$ git mv file_from file_to

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

$ git mv README.md README
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

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

$ mv README.md README
$ git rm README.md
$ git add README

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

4 查看提交歷史

在提交了若干更新,又或者克隆了某個項目之後,你也許想回顧下提交歷史。 完成這個任務最簡單而又有效的工具是 git log 命令。

接下來的例子會用我專門用於演示的 simplegit 項目, 運行下面的命令獲取該項目源代碼:

$ git clone http://github.com/yiibai/simplegit-progit

然後在此項目中運行 git log,應該會看到下面的輸出:

$ git log
commit 0e72e2c0ab0c5bfbe34603e5fcca91a0b5c381ff
Author: your_name <[email protected]>
Date:   Thu Jul 6 23:49:46 2017 +0800

    this is my comment

commit 85090b865d5cd7213e41a948e9f6f7466a950dbe
Author: Maxsu <[email protected]>
Date:   Thu Jul 6 17:34:41 2017 +0800

    Initial commit

Administrator@MY-PC /F/worksp/git-start (master)
$

默認不用任何參數的話,git log 會按提交時間列出所有的更新,最近的更新排在最上面。 正如你所看到的,這個命令會列出每個提交的 SHA-1 校驗和、作者的名字和電子郵件地址、提交時間以及提交說明。

git log 有許多選項可以幫助你搜尋你所要找的提交, 接下來我們介紹些最常用的。

一個常用的選項是 -p,用來顯示每次提交的內容差異。 你也可以加上 -2 來僅顯示最近兩次提交:

$ git log -p -2
commit 0e72e2c0ab0c5bfbe34603e5fcca91a0b5c381ff
Author: your_name <[email protected]>
Date:   Thu Jul 6 23:49:46 2017 +0800

    this is my comment

diff --git a/README.md b/README.md
index 2f88ca7..6679481 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@
commit 0e72e2c0ab0c5bfbe34603e5fcca91a0b5c381ff
Author: your_name <[email protected]>
Date:   Thu Jul 6 23:49:46 2017 +0800

    this is my comment

diff --git a/README.md b/README.md
index 2f88ca7..6679481 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,3 @@

該選項除了顯示基本信息之外,還附帶了每次 commit 的變化。 當進行代碼審查,或者快速瀏覽某個搭檔提交的 commit 所帶來的變化的時候,這個參數就非常有用了。 你也可以爲 git log 附帶一系列的總結性選項。 比如說,如果你想看到每次提交的簡略的統計信息,可以使用 --stat 選項:

$ git log --stat
commit 0e72e2c0ab0c5bfbe34603e5fcca91a0b5c381ff
Author: your_name <[email protected]>
Date:   Thu Jul 6 23:49:46 2017 +0800

    this is my comment

 README.md  | 5 +++--
 mytext.txt | 1 +
 2 files changed, 4 insertions(+), 2 deletions(-)

commit 85090b865d5cd7213e41a948e9f6f7466a950dbe
Author: Maxsu <[email protected]>
Date:   Thu Jul 6 17:34:41 2017 +0800

    Initial commit

 README.md | 2 ++
 1 file changed, 2 insertions(+)

Administrator@MY-PC /F/worksp/git-start (master)
$

正如你所看到的,--stat 選項在每次提交的下面列出額所有被修改過的文件、有多少文件被修改了以及被修改過的文件的哪些行被移除或是添加了。 在每次提交的最後還有一個總結。

另外一個常用的選項是 --pretty。 這個選項可以指定使用不同於默認格式的方式展示提交歷史。 這個選項有一些內建的子選項供你使用。 比如用 oneline 將每個提交放在一行顯示,查看的提交數很大時非常有用。 另外還有 shortfullfuller 可以用,展示的信息或多或少有些不同,請自己動手實踐一下看看效果如何。

$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit

但最有意思的是 format,可以定製要顯示的記錄格式。 這樣的輸出對後期提取分析格外有用 — 因爲你知道輸出的格式不會隨着 Git 的更新而發生改變:

$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit

git log --pretty=format 常用的選項 列出了常用的格式佔位符寫法及其代表的意義。

你一定感到奇怪 作者提交者 之間究竟有何差別, 其實作者指的是實際作出修改的人,提交者指的是最後將此工作成果提交到倉庫的人。 所以,當你爲某個項目發佈補丁,然後某個核心成員將你的補丁併入項目時,你就是作者,而那個核心成員就是提交者。 我們會在 分佈式 Git 再詳細介紹兩者之間的細微差別。

onelineformat 與另一個 log 選項 --graph 結合使用時尤其有用。 這個選項添加了一些ASCII字符串來形象地展示你的分支、合併歷史:

$ 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

5 撤消操作

在任何一個階段,你都有可能想要撤消某些操作。 這裏,我們將會學習幾個撤消你所做修改的基本工具。 注意,有些撤消操作是不可逆的。 這是在使用 Git 的過程中,會因爲操作失誤而導致之前的工作丟失的少有的幾個地方之一。

有時候我們提交完了才發現漏掉了幾個文件沒有添加,或者提交信息寫錯了。 此時,可以運行帶有 --amend 選項的提交命令嘗試重新提交:

$ git commit --amend

這個命令會將暫存區中的文件提交。 如果自上次提交以來你還未做任何修改(例如,在上次提交後馬上執行了此命令),那麼快照會保持不變,而你所修改的只是提交信息。

文本編輯器啓動後,可以看到之前的提交信息。 編輯後保存會覆蓋原來的提交信息。

例如,提交後發現忘記了暫存某些需要的修改,可以像下面這樣操作:

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

最終你只會有一個提交 - 第二次提交將代替第一次提交的結果。

5.1 取消暫存的文件

接下來的兩個小節演示如何操作暫存區域與工作目錄中已修改的文件。 這些命令在修改文件狀態的同時,也會提示如何撤消操作。 例如,你已經修改了兩個文件並且想要將它們作爲兩次獨立的修改提交,但是卻意外地輸入了 git add * 暫存了它們兩個。 如何只取消暫存兩個中的一個呢? git status 命令提示:

$ git add *
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    README.md -> README
        deleted:    mytext.txt

在 「Changes to be committed」 文字正下方,提示使用 git reset HEAD <file>... 來取消暫存。 所以,我們可以這樣來取消暫存 mytext.txt 文件:

$ git reset HEAD mytext.txt
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    README.md -> README

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    mytext.txt

5.2 撤消對文件的修改

如果並不想保留對 mytext.txt 文件的修改怎麼辦? 該如何方便地撤消修改 - 將它還原成上次提交時的樣子(或者剛克隆完的樣子,或者剛把它放入工作目錄時的樣子)? 幸運的是,git status 也告訴了你應該如何做。 在最後一個例子中,未暫存區域是這樣:

$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    README.md -> README

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    mytext.txt

它非常清楚地告訴瞭如何撤消之前所做的修改。讓我們來按照提示執行:

$ git checkout -- mytext.txt

Administrator@MY-PC /F/worksp/git-start (master)
$ git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    README.md -> README


Administrator@MY-PC /F/worksp/git-start (master)
$ ls
README  mytext.txt

可以看到,mytext.txt文件又回來了。

如果仍然想保留對那個文件做出的修改,但是現在仍然需要撤消,我們將會在 Git 分支介紹保存進度與分支;這些通常是更好的做法。

記住,在 Git 中任何已提交的東西幾乎總是可以恢復的。甚至那些被刪除的分支中的提交或使用 --amend 選項覆蓋的提交也可以恢復。然而,任何你未提交的東西丟失後很可能再也找不到了。

6 程倉庫的使用

前面所有講解的內容都是一個人「自娛自樂」, Git這東西自己玩也沒有多大意思,沒有發揮出來Git最牛逼的地方。要使用Git在項目上多人協作那纔有意思。

爲了能在任意 Git 項目上協作,需要知道如何管理自己的遠程倉庫。遠程倉庫是指託管在因特網或其他網絡中的你的項目的版本庫。可以有好幾個遠程倉庫,通常有些倉庫對你只讀,有些則可以讀寫。 與他人協作涉及管理遠程倉庫以及根據需要推送或拉取數據。 管理遠程倉庫包括瞭解如何添加遠程倉庫、移除無效的遠程倉庫、管理不同的遠程分支並定義它們是否被跟蹤等等。 在本節中,我們將介紹一部分遠程管理的技能。

6.1 查看遠程倉庫

如果想查看你已經配置的遠程倉庫服務器,可以運行 git remote 命令。 它會列出你指定的每一個遠程服務器的簡寫。 如果已經克隆了自己的倉庫,那麼至少應該能看到 origin - 這是 Git 給你克隆的倉庫服務器的默認名字:

$ git clone http://git.oschina.net/yiibai/git-start.git
Cloning into 'ticgit'...
remote: Reusing existing pack: 1857, done.
remote: Total 157 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (1857/1857), 74.35 KiB | 168.00 KiB/s, done.
Resolving deltas: 100% (772/772), done.
Checking connectivity... done.


$ cd git-start
$ git remote
origin

也可以指定選項 -v,會顯示需要讀寫遠程倉庫使用的 Git 保存的簡寫與其對應的 URL。

$ git remote -v
origin  http://git.oschina.net/yiibai/git-start.git (fetch)
origin  http://git.oschina.net/yiibai/git-start.git (push)

如果遠程倉庫不止一個,該命令會將它們全部列出。 例如,與幾個協作者合作的,擁有多個遠程倉庫的倉庫看起來像下面這樣:

$ cd git-start
$ git remote -v
mydoor  http://git.oschina.net/yiibai/git-start.git (fetch)
mydoor  http://git.oschina.net/yiibai/git-start.git (push)
curry     http://git.oschina.net/yiibai/git-start.git (fetch)
curry     http://git.oschina.net/yiibai/git-start.git (push)
deepfun   http://git.oschina.net/yiibai/git-start.git (fetch)
deepfun   http://git.oschina.net/yiibai/git-start.git (push)
koke      http://git.oschina.net/yiibai/git-start.git (fetch)
koke      http://git.oschina.net/yiibai/git-start.git (push)

這樣可以輕鬆拉取其中任何一個用戶的貢獻。 此外,大概還會有某些遠程倉庫的推送權限,雖然目前還不會在此介紹。

6.2 添加遠程倉庫

我在之前的章節中已經提到並展示瞭如何添加遠程倉庫的示例,不過這裏將演示如何明確地做到這一點。 運行 git remote add <shortname> <url> 添加一個新的遠程 Git 倉庫,同時指定一個可以輕鬆引用的簡寫:

$ git remote
origin

$ git remote add gs http://git.oschina.net/yiibai/git-start.git
$ git remote -v
gs      http://git.oschina.net/yiibai/git-start.git (fetch)
gs      http://git.oschina.net/yiibai/git-start.git (push)
origin  http://git.oschina.net/yiibai/git-start.git (fetch)
origin  http://git.oschina.net/yiibai/git-start.git (push)

現在你可以在命令行中使用字符串 gs 來代替整個 URL。 例如,如果想拉取倉庫中有但你沒有的信息,可以運行git fetch gs

$ git fetch gs
From http://git.oschina.net/yiibai/git-start
 * [new branch]      master     -> gs/master

現在 master 分支可以在本地通過 gs/master 訪問到 - 可以將它合併到自己的某個分支中,或者如果你想要查看它的話,可以檢出一個指向該點的本地分支。

6.2 從遠程倉庫中抓取與拉取

就如剛纔所見,從遠程倉庫中獲得數據,可以執行:

$ git fetch [remote-name]

這個命令會訪問遠程倉庫,從中拉取所有還沒有的數據。執行完成後,將會擁有那個遠程倉庫中所有分支的引用,可以隨時合併或查看。

如果使用 clone 命令克隆了一個倉庫,命令會自動將其添加爲遠程倉庫並默認以 「origin」 爲簡寫。 所以,git fetch origin 會抓取克隆(或上一次抓取)後新推送的所有工作。 必須注意 git fetch 命令會將數據拉取到本地倉庫 - 它並不會自動合併或修改當前的工作。 當準備好時必須手動將其合併入你的工作區。

如果你有一個分支設置爲跟蹤一個遠程分支,可以使用 git pull 命令來自動的抓取然後合併遠程分支到當前分支。 這對你來說可能是一個更簡單或更舒服的工作流程;默認情況下,git clone 命令會自動設置本地 master 分支跟蹤克隆的遠程倉庫的 master 分支(或不管是什麼名字的默認分支)。 運行 git pull 通常會從最初克隆的服務器上抓取數據並自動嘗試合併到當前所在的分支。

6.3 推送到遠程倉庫

當想分享你的項目時,必須將其推送到上游。 這個命令很簡單:git push [remote-name] [branch-name]。 當你想要將 master 分支推送到 origin 服務器時(再次說明,克隆時通常會自動幫你設置好那兩個名字),那麼運行這個命令就可以將所做的備份到服務器:

$ git push origin master

只有當你有所克隆服務器的寫入權限,並且之前沒有人推送過時,這條命令才能生效。 當你和其他人在同一時間克隆,他們先推送到上游然後你再推送到上游,你的推送就會毫無疑問地被拒絕。 你必須先將他們的工作拉取下來並將其合併進你的工作後才能推送。

現在我們要把前所有添加和修改的內容添加到遠程倉庫,以便其協同的開發人員也可以獲取到我們提交的內容。執行以下命令時,會要求我們輸入在 http://git.oschina.net/ 註冊的用戶名和密碼。

$ git push origin master

執行過程如下圖中所示 -

Git快速入門

現在登錄 http://git.oschina.net/ 查看提交是成功(與本地電腦上的內容是否一致),如下所示 -

Git快速入門

此時文件:*http://git.oschina.net/yiibai/git-start/blob/master/mytext.txt* 的內容應該與 F:\worksp\git-start\mytext.txt 的內容完全一樣。

6.4 查看遠程倉庫

如果想要查看某一個遠程倉庫的更多信息,可以使用 git remote show [remote-name] 命令。 如果想以一個特定的縮寫名運行這個命令,例如 origin,會得到像下面類似的信息:

$ git remote show origin
* remote origin
  Fetch URL: http://git.oschina.net/yiibai/git-start.git
  Push  URL: http://git.oschina.net/yiibai/git-start.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)

它同樣會列出遠程倉庫的 URL 與跟蹤分支的信息。 這些信息非常有用,它告訴你正處於 master 分支,並且如果運行 git pull,就會抓取所有的遠程引用,然後將遠程 master 分支合併到本地 master 分支。 它也會列出拉取到的所有遠程引用。

這是一個經常遇到的簡單例子。 如果你是 Git 的重度使用者,那麼還可以通過 git remote show 看到更多的信息。

$ git remote show origin
* remote origin
  Fetch URL: http://git.oschina.net/yiibai/git-start.git
  Push  URL: http://git.oschina.net/yiibai/git-start.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local branch configured for 'git pull':
    master merges with remote master
  Local ref configured for 'git push':
    master pushes to master (fast-forwardable)

這個命令列出了當你在特定的分支上執行 git push 會自動地推送到哪一個遠程分支。 它也同樣地列出了哪些遠程分支不在你的本地,哪些遠程分支已經從服務器上移除了,還有當你執行 git pull 時哪些分支會自動合併。

6.5 遠程倉庫的移除與重命名

如果想要重命名引用的名字可以運行 git remote rename 去修改一個遠程倉庫的簡寫名。 例如,想要將 gs 重命名爲 newgs,可以用 git remote rename 這樣做:

$ git remote rename gs newgs
$ git remote
origin
newgs

值得注意的是這同樣也會修改你的遠程分支名字。 那些過去引用 gs/master 的現在會引用 newgs/master

如果因爲一些原因想要移除一個遠程倉庫 - 你已經從服務器上搬走了或不再想使用某一個特定的鏡像了,又或者某一個貢獻者不再貢獻了 - 可以使用 git remote rm

$ git remote rm newgs
$ git remote
origin

注:先寫到這裏,以後適當補充。