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 ,寫入一些必填項,然後提交,如下圖所示 -

提交完成後,登入帳號還不能使用,還需要登入註冊的郵箱驗證帳號。驗證郵箱驗證帳號後,登入後預設的使用者面板介面如下所示 -

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

這樣,一個公開的倉庫就建立完成了。要記住上面圖片建立的路徑: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 clone [url] 。 比如,要克隆 Git 的上面建立的倉庫 git-start.git,可以用下面的命令:

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

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

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

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

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

Git 支援多種資料傳輸協定。 上面的例子使用的是 http:// 協定,不過也可以使用 git:// 協定或者使用 SSH 傳輸協定,比如 user@server_ip-or-host:path/to/repo.git 。在伺服器上搭建 Git 將會介紹所有這些協定在伺服器端如何組態使用,以及各種方式之間的利弊。

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

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

$ git init

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

該命令將建立一個名為 .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 倉庫。

3. 更新提交到倉庫

3.1 記錄每次更新到倉庫

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

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

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

3.2 檢查當前檔案狀態

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

$ git status

上面命令執行結果如下 -

這說明現在你的工作目錄相當乾淨。換句話說,所有已跟蹤檔案在上次提交後都未被更改過。 此外,上面的資訊還表明,當前目錄下沒有出現任何處於未跟蹤狀態的新檔案,否則 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)

上面命令執行結果如下 -

在狀態報告中可以看到新建的 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

上面命令執行結果如下 -

只要在 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

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

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

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

怎麼回事? 現在 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)

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

上面輸出顯示有加一行「+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)
$

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

如上圖中所示,分別對比了兩個檔案: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

執行過程如下圖中所示 -

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

此時檔案: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

註:先寫到這裡,以後適當補充。


以下是糾正/補充內容:

- "6 程倉庫的使用">>>>>>>>>>>>>> "6 [遠]程倉庫的使用"  提交時間:2019-09-18