git 代碼託管 學習筆記

配置

git config

  • Git 提供了一個叫做 git config 的工具,專門用來配置或讀取相應的工作環境變量。

  • /etc/gitconfig 文件:系統中對所有用戶都普遍適用的配置。若使用 git config 時用 --system選項,讀寫的就是這個文件。

  • ~/.gitconfig 文件:用戶目錄下的配置文件只適用於該用戶。若使用 git config 時用 --global選項,讀寫的就是這個文件。

  • 當前項目的 Git 目錄中的配置文件(也就是工作目錄中的 .git/config 文件):這裏的配置僅僅針對當前項目有效。每一個級別的配置都會覆蓋上層的相同配置,所以 .git/config 裏的配置會覆蓋 /etc/gitconfig 中的同名變量。

  • 在 Windows 系統上,Git 會找尋用戶主目錄下的 .gitconfig 文件。主目錄即 $HOME 變量指定的目錄,一般都是 C:\Documents and Settings$USER。此外,Git 還會嘗試找尋 /etc/gitconfig 文件,只不過看當初 Git 裝在什麼目錄,就以此作爲根目錄來定位。

配置用戶信息

配置個人的用戶名稱和電子郵件地址。每次 Git 提交時都會引用這兩條信息,說明是誰提交了更新,所以會隨更新內容一起被永久納入歷史記錄:

git config --global user.name "Your Name"	//設置用戶名
git config --global user.email "[email protected]"	//設置用戶郵箱

如果用了 --global 選項,那麼更改的配置文件就是位於你用戶主目錄下的那個,以後你所有的項目都會默認使用這裏配置的用戶信息。如果要在某個特定的項目中使用其他名字或者電郵,只要去掉 --global 選項重新配置即可,新的設定保存在當前項目的 .git/config 文件裏。

配置生成 SSH 公鑰

首先,你需要確認自己是否已經擁有密鑰。 默認情況下,用戶的 SSH 密鑰存儲在其 ~/.ssh 目錄下。 進入該目錄並列出其中內容,你便可以快速確認自己是否已擁有密鑰:

cd ~/.ssh
ls
authorized_keys2  id_dsa       known_hosts
config            id_dsa.pub

需要尋找一對以 id_dsa 或 id_rsa 命名的文件,其中一個帶有 .pub 擴展名。 .pub 文件是你的公鑰,另一個則是私鑰。 如果找不到這樣的文件(或者根本沒有 .ssh 目錄),你可以通過運行 ssh-keygen 程序來創建它們。在 Linux/Mac 系統中,ssh-keygen 隨 SSH 軟件包提供;在 Windows 上,該程序包含於 MSysGit 軟件包中。
打開Git Base Here然後輸入下面命令

1.輸入命令:cd ~

2.然後輸入:ssh-keygen.exe

然後按回車,再次按回車,在回車,按三次回車:
在這裏插入圖片描述

查看配置信息

檢查已有的配置信息

git config --list

有時候會看到重複的變量名,那就說明它們來自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig),不過最終 Git 實際採用的是最後一個。

也可以直接查閱某個環境變量的設定,只要把特定的名字跟在後面即可,像這樣:

git config user.name #查看用戶名

查看本機ssh公鑰

cat ~/.ssh/id_rsa.pub

或者\直接打開用戶(一般都是Administrator)下的.ssh文件夾,打開它裏面的id_rsa.pub 文件

幫助

閱讀使用幫助

git help <verb>
git <verb> --help
man git-<verb>
//要學習 config 命令可以怎麼用,運行:
git help config

初始化倉庫 git init

    git init

初始化後,在當前目錄下會出現一個名爲 .git 的目錄,所有 Git 需要的數據和資源都存放在這個目錄中。
如果當前目錄下有幾個文件想要納入版本控制,需要先用 git add 命令告訴 Git 開始對這些文件進行跟蹤,然後提交:

$ git add *.c
$ git add README
$ git commit -m '提交時附帶的消息'

倉庫克隆 git clone

克隆倉庫的命令格式爲git clone [url]。

url: https://baike.baidu.com/item/url/110640?fr=aladdin

比如,要克隆 Ruby 語言的 Git 代碼倉庫 Grit,可以用下面的命令:

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

這會在當前目錄下創建一個名爲grit的目錄,其中包含一個 .git 的目錄,用於保存下載下來的所有版本記錄,然後從中取出最新版本的文件拷貝。

如果希望在克隆的時候,自己定義要新建的項目目錄名稱,可以在上面的命令末尾指定新的名字:

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

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

Git 支持許多數據傳輸協議。之前的例子使用的是 git:// 協議,不過你也可以用 http(s):// 或者 user@server:/path.git 表示的 SSH 傳輸協議。

檢查當前文件狀態 git status

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

git status
On branch master
nothing to commit, working directory clean

說明所有已跟蹤文件在上次提交後都未被更改過。
上面的信息還表明,當前目錄下沒有出現任何處於未跟蹤的新文件,否則 Git 會在這裏列出來。
該命令還顯示了當前所在的分支是 master,這是默認的分支名稱,實際是可以修改的.

創建一個新文件 README,保存退出後運行 git status 會看到該文件出現在未跟蹤文件列表中:

vim README
git status
On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)

        README

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

在狀態報告中可以看到新建的README文件出現在“Untracked files”下面。未跟蹤的文件意味着Git在之前的快照(提交)中沒有這些文件;Git 不會自動將之納入跟蹤範圍,除非你明明白白地告訴它“我需要跟蹤該文件”,因而不用擔心把臨時文件什麼的也歸入版本管理。

跟蹤新文件 git add

命令 git add 開始跟蹤一個新文件。
要跟蹤 README 文件,運行:

git add README

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

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

        new file:   README
       

暫存已修改文件

修改 README 文件然後再次運行 git status 命令。

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  

文件 README 出現在 “Changes not staged for commit” 這行下面,說明已跟蹤文件的內容發生了變化,但還沒有放到暫存區。要暫存這次更新,需要運行 git add 命令(這是個多功能命令,根據目標文件的狀態不同,此命令的效果也不同:可以用它開始跟蹤新文件,或者把已跟蹤的文件放到暫存區,還能用於合併時把有衝突的文件標記爲已解決狀態等)。
運行 git add 將 README 放到暫存區,然後再看看 git status 的輸出。

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

        new file:   README

現在README 文件已暫存,下次提交時就會記錄到倉庫。假設此時,你想要在 README 裏再加條註釋,重新編輯存盤後,準備好提交。不過且慢,再運行 git status 看看:

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

        new file:   README

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

README 文件出現了兩次!一次算未暫存,一次算已暫存。
實際上 Git 只不過暫存了你運行 git add 命令時的版本,如果現在提交,那麼提交的是添加註釋前的版本,而非當前工作目錄中的版本。所以,運行了 git add 之後又作了修訂的文件,需要重新運行 git add 把最新版本重新暫存起來:

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

        new file:   README

忽略某些文件 .gitignore

一般我們總會有些文件無需納入 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 文件,不包括 subdir/TODO
/TODO
# 忽略 build/ 目錄下的所有文件
build/
# 會忽略 doc/notes.txt 但不包括 doc/server/arch.txt
doc/*.txt
# 忽略 doc/ 目錄下所有擴展名爲 txt 的文件
doc/**/*.txt
# 忽略倉庫中的所有擴展名爲 htm 的文件
**/*.htm

.gitignore規則不生效

.gitignore只能忽略那些原來沒有被track的文件,如果某些文件已經被納入了版本管理中,則修改.gitignore是無效的。

解決方法就是先把本地緩存刪除(改變成未track狀態),然後再提交:

git rm -r --cached .
git add .
git commit -m 'update .gitignore'

查看已暫存和未暫存的更新 git diff

實際上 git status 的顯示比較簡單,僅僅是列出了修改過的文件,如果要查看具體修改了什麼地方,可以用 git diff 命令。
git add 能回答我們

  • 當前做的哪些更新還沒有暫存?
  • 有哪些更新已經暫存起來準備好了下次提交?

git diff 會使用文件補丁的格式顯示具體添加和刪除的行。
假如再次修改 README 文件但不使用git add暫存,運行 status 命令將會看到:

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 

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

git diff
diff --git a/README  b/README 
index 535d2b0..0719398 100644
--- a/README 
+++ b/README 
@@ -6,3 +6,4 @@
 6
 7
 8
+9
index 535d2b0..0719398 100644

535d2b0表示暫存區的哈希值,100644表示對象模式,100表示普通文件,644代表一個權限。

--- a/README 
+++ b/README 

—表示變化前的版本。
+++表示變化後的版本。

@@ -6,3 +6,4 @@

標識內容變動發生的區域:
“-6,3”:減號表示變化前的文件,6表示第六行,3表示連續三行,也就是從第六行(包括)開始,連續3行。
“+6,4”:加號表示變化後的文件,6表示第六行,4表示連續四行,也就是從第六行(包括)開始,連續4行。
位置:如何確定3和7這個位置,原則是除去當前文件變動的一行外,上下各取3行。

  • 空格開頭標識行行內容無變化。
  • 減號(-)開頭標識原來版本文件刪除的行。
  • 加號(+)開頭標識修改後版本文件添加的行。

git diff命令比較的是工作目錄中當前文件和暫存區域快照之間的差異,也就是修改之後還沒有暫存起來的變化內容。
若要看已經暫存起來的文件和上次提交時的快照之間的差異,可以用 git diff --cached 命令。(Git 1.6.1 及更高版本還允許使用 git diff --staged,效果是相同的,但更好記些。)
緩存區與工作區的差異

git diff --cached
diff --git a/git_diff_test b/git_diff_test
index 0719398..f00c965 100644
--- a/git_diff_test
+++ b/git_diff_test
@@ -7,3 +7,4 @@
 7
 8
 9
+10

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

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

        modified:   README 

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 

現在運行 git diff 看暫存前後的變化:

git diff
diff --git a/README b/README 
index f00c965..ba70242 100644
--- a/README 
+++ b/README 
@@ -8,3 +8,4 @@
 8
 9
 10
+11

然後用 git diff --cached 查看已經暫存起來的變化:

git diff --cached
diff --git a/README b/README 
index 0719398..f00c965 100644
--- a/README 
+++ b/README 
@@ -7,3 +7,4 @@
 7
 8
 9
+10

提交更新 git commit

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

git commit

這種方式會啓動文本編輯器以便輸入本次提交的說明。(默認會啓用 shell 的環境變量 $EDITOR 所指定的軟件,一般都是 vim 或 emacs。當然也可以按照第一章介紹的方式,使用 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
#
~
~
~

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

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

git commit -m "git commit test"
[master 29c5b19] git commit test
 1 file changed, 2 insertions(+), 1 deletion(-)

提交後它會告訴你,當前是在哪個分支(master)提交的,本次提交的完整 SHA-1 校驗和是什麼(29c5b19),以及在本次提交中,有多少文件修訂過,多少行添改和刪改過。
提示信息顯示一個文件被修改,2行填改,1行刪改。

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

跳過使用暫存區域 git commit -am

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

git commit -m -a "git commit -m -a test"
[master 83e38c7] git commit -m -a test
 1 files changed, 5 insertions(+)

這樣提交之前就不需要git add 文件了。
也可以寫成 git commit -am "git commit -m -a test

對上一次提交進行修改 git commit --amend --no-edit

當進行一次git commit 之後對代碼進行修改,但又不想更新日誌時可以使用git commit --amend --no-edit

git commit --amend --no-edit
[master d610cf4] create new project
Date: Sun Aug 11 21:30:27 2019 +0800
1 file changed, 1 insertion(+)
create mode 100644 README.md

移除文件 git rm

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

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

git status
On branch master
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:    git_rm_test

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

然後再運行 git rm 記錄此次移除文件的操作:

git rm git_rm_test
rm 'git_rm_test'

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

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

git rm --cached git_rm_test

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

git rm log/\*.log

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

git rm \*~

會遞歸刪除當前目錄及其子目錄中所有 ~ 結尾的文件。
要刪除暫存區和工作區的文件可以使用:

git rm -f git_rm_test

移動文件 git mv

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

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

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

        renamed:    git_mv_test -> new_git_mv_test

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

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

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

查看歷史 git log

查看提交歷史

在提交了若干更新之後,又或者克隆了某個項目,想回顧下提交歷史,可以使用 git log 命令查看。
接下來的例子會用專門用於演示的 simplegit 項目,運行下面的命令獲取該項目源代碼:

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

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

git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test code

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
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 <schacon@gee-mail.com>
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,5 +5,5 @@ require 'rake/gempackagetask'
 spec = Gem::Specification.new do |s|
     s.name      =   "simplegit"
-    s.version   =   "0.1.0"
+    s.version   =   "0.1.1"
     s.author    =   "Scott Chacon"
     s.email     =   "schacon@gee-mail.com

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
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

git log 還提供了許多摘要選項可以用,比如 --stat,僅顯示簡要的增改行數統計:

git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number

 Rakefile |    2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 16:40:33 2008 -0700

    removed unnecessary test code

 lib/simplegit.rb |    5 -----
 1 file changed, 5 deletions(-)

commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Sat Mar 15 10:31:28 2008 -0700

    first commit

 README           |    6 ++++++
 Rakefile         |   23 +++++++++++++++++++++++
 lib/simplegit.rb |   25 +++++++++++++++++++++++++
 3 files changed, 54 insertions(+)

每個提交都列出了修改過的文件,以及其中添加和移除的行數,並在最後列出所有增減行數小計。

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

git log --pretty=
email     format:   full      fuller    medium    oneline   raw       short
git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date:   Mon Mar 17 21:52:11 2008 -0700

    changed the version number
git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test code
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit
git log --pretty=email
From fcae1c11810b70206afdceebd3632f694bf93717 Mon Sep 17 00:00:00 2001
From: schacon<schacon@gee-mail.com>
Date: Mon, 18 Mar 2019 14:28:02 +0800
Subject: [PATCH] git mv test
git log --pretty=short
commit fcae1c11810b70206afdceebd3632f694bf93717 (HEAD -> master)
Author: schacon<schacon@gee-mail.com>
    git mv test
 git log --pretty=fuller
commit fcae1c11810b70206afdceebd3632f694bf93717 (HEAD -> master)
Author:     schacon<schacon@gee-mail.com>
AuthorDate: Mon Mar 18 14:28:02 2019 +0800
Commit:     schacon<schacon@gee-mail.com>
CommitDate: Mon Mar 18 14:28:02 2019 +0800

    git mv test
git log --pretty=medium
commit fcae1c11810b70206afdceebd3632f694bf93717 (HEAD -> master)
Author: schacon<schacon@gee-mail.com>
Date:   Mon Mar 18 14:28:02 2019 +0800

    git mv test
git log --pretty=raw
commit fcae1c11810b70206afdceebd3632f694bf93717
tree 1521844698491d1e101cb66b3cbb2754a977e21d
parent 827d78b856f4b12a517fb474a87f3f16d0d0b0e4
author liaoxutao <liao.xt@afuiot.com> 1552890482 +0800
committer liaoxutao <liao.xt@afuiot.com> 1552890482 +0800

    git mv test

最有意思的是 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

列出了常用的格式佔位符寫法及其代表的意義。

選項 說明
%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 字符串表示的簡單圖形,形象地展示了每個提交所在的分支及其分化衍合情況。

git log --pretty=short --graph
* commit 88bd35df740abdad3ee05eb101bf19e0f8a81d29
| Author: liaoxutao <liao.xt@afuiot.com>
|
|     git commit -a -m test
|
* commit bb2ef8aab9dbfbd591e53d9639212fbf2b452996
| Author: liaoxutao <liao.xt@afuiot.com>
|

以上只是簡單介紹了一些 git log 命令支持的選項。
還列出了一些其他常用的選項及其釋義。

選項 說明
-p 按補丁格式顯示每個更新之間的差異。
–word-diff 按 word diff 格式顯示差異。
–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(後跟指定格式)。
–oneline –pretty=oneline --abbrev-commit 的簡化用法。

限制輸出長度

除了定製輸出格式的選項之外,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選項是路徑(path),如果只關心某些文件或者目錄的歷史提交,可以在 git log 選項的最後指定它們的路徑。因爲是放在最後位置上的選項,所以用兩個短劃線(–)隔開之前的選項和後面限定的路徑名。

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

撤銷操作

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

修改最後一次提交

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

git commit --amend

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

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

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

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

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

取消已經暫存的文件

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

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

        modified:   README.txt
        modified:   benchmarks.rb

就在 “Changes to be committed” 下面,括號中有提示,可以使用 git reset HEAD <file>... 的方式取消暫存。好吧,我們來試試取消暫存 benchmarks.rb 文件:

git reset HEAD benchmarks.rb
Unstaged changes after reset:
M       benchmarks.rb
git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        modified:   README.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:   benchmarks.rb

取消對文件的修改 git checkout – file 危險

如果覺得剛纔對 benchmarks.rb 的修改完全沒有必要,該如何取消修改,回到之前的狀態(也就是修改之前的版本)呢?git status 同樣提示了具體的撤消方法,接着上面的例子,現在未暫存區域看起來像這樣:

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:   benchmarks.rb

在第二個括號中,我們看到了拋棄文件修改的命令。

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

        modified:   README.txt

可以看到,該文件已經恢復到修改前的版本。你可能已經意識到了,這條命令有些危險,所有對文件的修改都沒有了,因爲我們剛剛把之前版本的文件複製過來重寫了此文件。所以在用這條命令前,請務必確定真的不再需要保留剛纔的修改。如果只是想回退版本,同時保留剛纔的修改以便將來繼續工作,可以用 stashing 和分支來處理,應該會更好些。

記住,任何已經提交到 Git 的都可以被恢復。即便在已經刪除的分支中的提交,或者用 --amend 重新改寫的提交,都可以被恢復(關於數據恢復的內容見第九章)。所以,你可能失去的數據,僅限於沒有提交過的,對 Git 來說它們就像從未存在過一樣。

遠程倉庫

查看當前的遠程庫 git remote

要查看當前配置有哪些遠程倉庫,可以用 git remote 命令,它會列出每個遠程庫的簡短名字。在克隆完某個項目後,至少可以看到一個名爲 origin 的遠程庫,Git 默認使用這個名字來標識你所克隆的原始倉庫:

git remote
origin

也可以加上 -v 選項(譯註:此爲 --verbose 的簡寫,取首字母),顯示對應的克隆地址:

git remote -v
origin  git://github.com/schacon/ticgit.git (fetch)
origin  git://github.com/schacon/ticgit.git (push)

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

git remote -v
bakkdoor  git://github.com/bakkdoor/xx1.git
cho45     git://github.com/cho45/xx2.git
defunkt   git://github.com/defunkt/xx3.git
koke      git://github.com/koke/xx4.git
origin    git@github.com:mojombo/xx5.git

這樣一來,就可以非常輕鬆地從這些用戶的倉庫中,拉取他們的提交到本地。請注意,上面列出的地址只有 origin 用的是 SSH URL 鏈接,所以也只有這個倉庫個人能推送數據上去

添加遠程倉庫 git remote add [shortname] [url]

要添加一個新的遠程倉庫,可以指定一個簡單的名字,以便將來引用,
運行 git remote add [shortname] [url]:

git remote add test_origin git@github.com:SnowyNighting/git-learn.git
git remote -v
test_origin     git@github.com:SnowyNighting/git-learn.git (fetch)
test_origin     git@github.com:SnowyNighting/git-learn.git (push)

現在可以用字符串 test_origin 指代對應的倉庫地址了。比如說,要抓取所有 Paul 有的,但本地倉庫沒有的信息,可以運行 git fetch test_origin
現在,Paul 的主幹分支(master)已經完全可以在本地訪問了,對應的名字是 pb/master,你可以將它合併到自己的某個分支,或者切換到這個分支。

從遠程倉庫抓取數據 git fetch [remote-name]

可以用下面的命令從遠程倉庫抓取數據到本地:

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]

項目進行到一個階段,要同別人分享目前的成果,可以將本地倉庫中的數據推送到遠程倉庫。實現這個任務的命令很簡單: git push [remote-name] [branch-name]。如果要把本地的 master 分支推送到 origin 服務器上(再次說明下,克隆操作會自動使用默認的 master 和 origin 名字),可以運行下面的命令:

 git push origin master

只有在所克隆的服務器上有寫權限,或者同一時刻沒有其他人在推數據,這條命令纔會如期完成任務。如果在你推數據前,已經有其他人推送了若干更新,那你的推送操作就會被駁回。你必須先把他們的更新抓取到本地,合併到自己的項目中,然後纔可以再次推送。

強制推送數據到遠程倉庫 git push -f

當遠程倉庫和本地數不一致時,因爲懶得解決衝突,在確保數據完全正確的情況下可以使用以下命令強制推送

git push -f

查看遠程倉庫信息 git remote show [remote-name]

我們可以通過命令 git remote show [remote-name] 查看某個遠程倉庫的詳細信息,比如要看所克隆的 test_origin 倉庫,可以運行:

$ git remote show test_origin
* remote test_origin
  Fetch URL: git@github.com:SnowyNighting/git-learn.git
  Push  URL: git@github.com:SnowyNighting/git-learn.git
  HEAD branch: master
  Remote branch:
    master tracked
  Local ref configured for 'git push':
    master pushes to master (up to date)
$ 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

除了對應的克隆地址外,它還給出了許多額外的信息。它友善地告訴你如果是在 master 分支,就可以用 git pull 命令抓取數據合併到本地。另外還列出了所有處於跟蹤狀態中的遠端分支。

上面的例子非常簡單,而隨着使用 Git 的深入,git remote show 給出的信息可能會像這樣:

$ 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

它告訴我們,運行 git push 時缺省推送的分支是什麼(譯註:最後兩行)。它還顯示了有哪些遠端分支還沒有同步到本地(譯註:第六行的 caching 分支),哪些已同步到本地的遠端分支在遠端服務器上已被刪除(譯註:Stale tracking branches 下面的兩個分支),以及運行 git pull 時將自動合併哪些分支(譯註:前四行中列出的 issues 和 master 分支)。

遠程倉庫的刪除和重命名

Git 中可以用 git remote rename 命令修改某個遠程倉庫在本地的簡稱,比如想把 test_origin改成 my_origin,可以這麼運行:

$ git remote rename test_origin my_origin
$ git remote
origin
paul

注意,對遠程倉庫的重命名,也會使對應的分支名稱發生變化,原來的 pb/master 分支現在成了 paul/master。

碰到遠端倉庫服務器遷移,或者原來的克隆鏡像不再使用,又或者某個參與者不再貢獻代碼,那麼需要移除對應的遠端倉庫,可以運行 git remote rm 命令:

git remote rm my_origin

標籤

同大多數 VCS 一樣,Git 也可以對某一時間點上的版本打上標籤。人們在發佈某個軟件版本(比如 v1.0 等等)的時候,經常這麼做。

顯示已有的標籤 git tag

列出現有標籤的命令非常簡單,直接運行 git tag 即可:

git tag
v0.1
v1.3

顯示的標籤按字母順序排列,所以標籤的先後並不表示重要程度的輕重。

我們可以用特定的搜索模式列出符合條件的標籤。在 Git 自身項目倉庫中,有着超過 240 個標籤,如果你只對 1.4.2 系列的版本感興趣,可以運行下面的命令:

git tag -l 'v1.4.2.*'
v1.4.2.1
v1.4.2.2
v1.4.2.3
v1.4.2.4

新建標籤

Git 使用的標籤有兩種類型:輕量級的(lightweight)和含附註的(annotated)。輕量級標籤就像是個不會變化的分支,實際上它就是個指向特定提交對象的引用。而含附註標籤,實際上是存儲在倉庫中的一個獨立對象,它有自身的校驗和信息,包含着標籤的名字,電子郵件地址和日期,以及標籤說明,標籤本身也允許使用 GNU Privacy Guard (GPG) 來簽署或驗證。一般我們都建議使用含附註型的標籤,以便保留相關信息;當然,如果只是臨時性加註標籤,或者不需要旁註額外信息,用輕量級標籤也沒問題。

含附註的標籤 git tag -a

創建一個含附註類型的標籤非常簡單,用 -a (譯註:取 annotated 的首字母)指定標籤名字即可:

git tag -a v1.0.0 -m "含標註的標籤"
git tag
v1.0.0

-m 選項則指定了對應的標籤說明,Git 會將此說明一同保存在標籤對象中。如果沒有給出該選項,Git 會啓動文本編輯軟件供你輸入標籤說明。

可以使用 git show 命令查看相應標籤的版本信息,並連同顯示打標籤時的提交對象。

git show v1.0.0
tag v1.0.0
Tagger: liaoxutao <liao.xt@afuiot.com>
Date:   Mon Mar 25 14:24:20 2019 +0800

含標註的標籤

commit b62a6ec18ffdfe8e5897161da80b5c516c89bc96 (HEAD -> master, tag: v1.0.0)
Author: liaoxutao <liao.xt@afuiot.com>
Date:   Mon Mar 25 12:07:27 2019 +0800

    第一次推送到遠程倉庫

diff --git a/ignore_test.txt b/ignore_test.txt
new file mode 100644
index 0000000..e69de29
diff --git a/ignore_test/ignore_test2/ignore_test2.txt b/ignore_test/ignore_test2/ignore_test2.txt
new file mode 100644
index 0000000..e69de29

我們可以看到在提交對象信息上面,列出了此標籤的提交者和提交時間,以及相應的標籤說明。

簽署標籤 git tag -s

如果你有自己的私鑰,還可以用 GPG 來簽署標籤,只需要把之前的 -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 <[email protected]>"
1024-bit DSA key, ID F721C45A, created 2009-02-09

現在再運行 git show 會看到對應的 GPG 簽名也附在其內:

git show v1.5
tag v1.5
Tagger: Scott Chacon <schacon@gee-mail.com>
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 <schacon@gee-mail.com>
Date:   Sun Feb 8 19:02:46 2009 -0800

    Merge branch 'experiment'

GPG是一種加密算法,現在github支持commit使用GPG加密,從而保證提交的commit在傳輸的過程中沒有被篡改。
默認沒有安裝GPG,需要自己安裝
https://www.cnblogs.com/xueweihan/p/5430451.html

驗證標籤 git tag -v

可以使用 git tag -v [tag-name] (譯註:取 verify 的首字母)的方式驗證已經簽署的標籤。此命令會調用 GPG 來驗證簽名,所以你需要有簽署者的公鑰,存放在 keyring 中,才能驗證:

git tag -v v1.4.2.1
object 883653babd8ee7ea23e6a5c392bb739348b1eb61
type commit
tag v1.4.2.1
tagger Junio C Hamano <junkio@cox.net> 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 <[email protected]>"
gpg:                 aka "[jpeg image of size 1513]"
Primary key fingerprint: 3565 2A26 2040 E066 C9A7  4A7D C0C6 D9A4 F311 9B9A

若是沒有簽署者的公鑰,會報告類似下面這樣的錯誤:

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'

輕量級標籤 git tag name

輕量級標籤實際上就是一個保存着對應提交對象的校驗和信息的文件。要創建這樣的標籤,一個 -a-s-m 選項都不用,直接給出標籤名字即可:

git tag v1.0.2
git tag
v1.0.0
v1.0.1
v1.0.2
git show v1.0.2
commit b62a6ec18ffdfe8e5897161da80b5c516c89bc96 (HEAD -> master, tag: v1.0.2, tag: v1.0.1, tag: v1.0.0)
Author: liaoxutao <liao.xt@afuiot.com>
Date:   Mon Mar 25 12:07:27 2019 +0800

    第一次推送到遠程倉庫

後期加註標籤

你甚至可以在後期對早先的某次提交加註標籤。比如在下面展示的提交歷史中:

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” 後爲此項目打上版本號 v1.2,沒關係,現在也能做。只要在打標籤的時候跟上對應提交對象的校驗和(或前幾位字符)即可:

git tag -a v1.2 9fceb02

可以看到我們已經補上了標籤:

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 <schacon@gee-mail.com>
Date:   Mon Feb 9 15:32:16 2009 -0800

version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date:   Sun Apr 27 20:43:35 2008 -0700

    updated rakefile
...

分享標籤

默認情況下,git push 並不會把標籤傳送到遠端服務器上,只有通過顯式命令才能分享標籤到遠端倉庫。其命令格式如同推送分支,運行 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

如果要一次推送所有本地新增的標籤上去,可以使用 --tags 選項:

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

現在,其他人克隆共享倉庫或拉取數據同步後,也會看到這些標籤。

Git命令別名 git config --global alias.newname order

Git 並不會推斷你輸入的幾個字符將會是哪條命令,不過如果想偷懶,少敲幾個命令的字符,可以用 git config 爲命令設置別名。

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

現在,如果要輸入 git commit 只需鍵入 git ci 即可。而隨着 Git 使用的深入,會有很多經常要用到的命令,遇到這種情況,不妨建個別名提高效率。

使用這種技術還可以創造出新的命令,比方說取消暫存文件時的輸入比較繁瑣,可以自己設置一下:

git config --global alias.unstage 'reset HEAD --'

這樣一來,下面的兩條命令完全等同:

git unstage fileA
git reset HEAD fileA

顯然,使用別名的方式看起來更清楚。另外,我們還經常設置 last 命令:

git config --global alias.last 'log -1 HEAD'

然後要看最後一次的提交信息,就變得簡單多了:

git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date:   Tue Aug 26 19:48:51 2008 +0800

    test for current head

    Signed-off-by: Scott Chacon <schacon@example.com>

可以看出,實際上 Git 只是簡單地在命令中替換了你設置的別名。
不過有時候我們希望運行某個外部命令,而非 Git 的子命令,這個好辦,只需要在命令前加上 ! 就行。如果你自己寫了些處理 Git 倉庫信息的腳本的話,就可以用這種技術包裝起來。
作爲演示,我們可以設置用 git visual 啓動 gitk:

git config --global alias.visual '!gitk'

Git 分支

現在讓我們來看一個簡單的分支與合併的例子,實際工作中大體也會用到這樣的工作流程:

  1. 開發某個網站。
  2. 爲實現某個新的需求,創建一個分支。
  3. 在這個分支上開展工作。

假設此時,你突然接到一個電話說有個很嚴重的問題需要緊急修補,那麼可以按照下面的方式處理:

  1. 返回到原先已經發布到生產服務器上的分支。
  2. 爲這次緊急修補建立一個新分支,並在其中修復問題。
  3. 通過測試後,回到生產服務器所在的分支,將修補分支合併進來,然後再推送到生產服務器上。
  4. 切換到之前實現新需求的分支,繼續工作。

新建分支 git checkout name

首先,我們假設你正在項目中愉快地工作,並且已經提交了幾次更新
在這裏插入圖片描述
新建分支 git checkout
要新建並切換到該分支,運行 git checkout 並加上 -b 參數

現在,你決定要修補問題追蹤系統上的 #53 問題。順帶說明下,Git 並不同任何特定的問題追蹤系統打交道。這裏爲了說明要解決的問題,才把新建的分支取名爲 iss53。要新建並切換到該分支,運行 git checkout 並加上 -b 參數:

git checkout -b iss53
Switched to a new branch 'iss53'

這相當於執行下面這兩條命令:

git branch iss53
git checkout iss53

執行完成後創建了一個新分支的指針
在這裏插入圖片描述
接着你開始嘗試修復問題,在提交了若干次更新後,iss53 分支的指針也會隨着向前推進,因爲它就是當前分支,換句話說,當前的 HEAD 指針正指向 iss53

vim index.html
git commit -a -m 'added a new footer [issue 53]'

分支隨工作進展向前推進
在這裏插入圖片描述
現在你就接到了那個網站問題的緊急電話,需要馬上修補。有了 Git ,我們就不需要同時發佈這個補丁和 iss53 裏作出的修改,也不需要在創建和發佈該補丁到服務器之前花費大力氣來複原這些修改。唯一需要的僅僅是切換回 master 分支。

切換分支 git checkout master

不過在此之前,留心你的暫存區或者工作目錄裏,那些還沒有提交的修改,它會和你即將檢出的分支產生衝突從而阻止 Git 爲你切換分支。切換分支的時候最好保持一個清潔的工作區域。稍後會介紹幾個繞過這種問題的辦法(分別叫做 stashing 和 commit amending)。目前已經提交了所有的修改,所以接下來可以正常轉換到 master 分支

git checkout master
Switched to branch 'master'

此時工作目錄中的內容和你在解決問題 #53 之前一模一樣,你可以集中精力進行緊急修補。這一點值得牢記:Git 會把工作目錄的內容恢復爲檢出某分支時它所指向的那個提交對象的快照。它會自動添加、刪除和修改文件以確保目錄的內容和你當時提交時完全一樣。

接下來,你得進行緊急修補。我們創建一個緊急修補分支 hotfix 來開展工作,直到搞定。

git checkout -b hotfix
Switched to a new branch 'hotfix'
$ vim index.html
$ git commit -a -m 'fixed the broken email address'
[hotfix 3a0874c] fixed the broken email address
 1 files changed, 1 deletion(-)

hotfix 分支是從 master 分支所在點分化出來的
在這裏插入圖片描述

分支合併1 git merge branchname

有必要作些測試,確保修補是成功的,然後回到 master 分支並把它合併進來,然後發佈到生產服務器。用 git merge 命令來進行合併:

git checkout master
git merge hotfix
Updating f42c576..3a0874c
Fast-forward
 README | 1 -
 1 file changed, 1 deletion(-)

請注意,合併時出現了“Fast forward”的提示。由於當前 master 分支所在的提交對象是要併入的 hotfix 分支的直接上游,Git 只需把 master 分支指針直接右移。換句話說,如果順着一個分支走下去可以到達另一個分支的話,那麼 Git 在合併兩者時,只會簡單地把指針右移,因爲這種單線的歷史分支不存在任何需要解決的分歧,所以這種合併過程可以稱爲快進(Fast forward)。

現在最新的修改已經在當前 master 分支所指向的提交對象中了,可以部署到生產服務器上去了。
合併之後,master 分支和 hotfix 分支指向同一位置。
在這裏插入圖片描述

刪除分支 git branch -d branchname

在那個超級重要的修補發佈以後,你想要回到被打擾之前的工作。由於當前 hotfix 分支和 master 都指向相同的提交對象,所以 hotfix 已經完成了歷史使命,可以刪掉了。使用 git branch-d 選項執行刪除操作:

git branch -d hotfix
Deleted branch hotfix (was 3a0874c).

現在回到之前未完成的 #53 問題修復分支上繼續工作

git checkout iss53
Switched to branch 'iss53'
vim index.html
git commit -a -m 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
 1 file changed, 1 insertion(+)

iss53分支可以不受影響繼續推進。
在這裏插入圖片描述
值得注意的是之前 hotfix 分支的修改內容尚未包含到 iss53 中來。如果需要納入此次修補,
可以用 git merge master 把 master 分支合併到 iss53;或者等 iss53 完成之後,再將 iss53 分支中的更新併入 master。

分支的合併2

在問題 #53 相關的工作完成之後,可以合併回 master 分支。實際操作同前面合併 hotfix 分支差不多,只需回到 master 分支,運行 git merge 命令指定要合併進來的分支:

git checkout master
git merge iss53
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 1 +
 1 file changed, 1 insertion(+)

請注意,這次合併操作的底層實現,並不同於之前 hotfix 的併入方式。因爲這次你的開發歷史是從更早的地方開始分叉的。由於當前 master 分支所指向的提交對象(C4)並不是 iss53 分支的直接祖先,Git 不得不進行一些額外處理。就此例而言,Git 會用兩個分支的末端(C4 和 C5)以及它們的共同祖先(C2)進行一次簡單的三方合併計算。圖 用紅框標出了 Git 用於合併的三個提交對象:
Git 爲分支合併自動識別出最佳的同源合併點。
在這裏插入圖片描述
這次,Git 沒有簡單地把分支指針右移,而是對三方合併後的結果重新做一個新的快照,並自動創建一個指向它的提交對象(C6)(見圖 3-17)。這個提交對象比較特殊,它有兩個祖先(C4 和 C5)。

值得一提的是 Git 可以自己裁決哪個共同祖先纔是最佳合併基礎;這和 CVS 或 Subversion(1.5 以後的版本)不同,它們需要開發者手工指定合併基礎。所以此特性讓 Git 的合併操作比其他系統都要簡單不少。
Git 自動創建了一個包含了合併結果的提交對象。
在這裏插入圖片描述
既然之前的工作成果已經合併到 master 了,那麼 iss53 也就沒用了。你可以就此刪除它,並在問題追蹤系統裏關閉該問題。

git branch -d iss53

遇到衝突時的分支合併

有時候合併操作並不會如此順利。如果在不同的分支中都修改了同一個文件的同一部分,Git 就無法乾淨地把兩者合到一起(譯註:邏輯上說,這種問題只能由人來裁決。)。如果你在解決問題 #53 的過程中修改了 hotfix 中修改的部分,將得到類似下面的結果:

git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git 作了合併,但沒有提交,它會停下來等你解決衝突。要看看哪些文件在合併時發生衝突,可以用 git status 查閱:

git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

        both modified:      index.html

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

任何包含未解決衝突的文件都會以未合併(unmerged)的狀態列出。Git 會在有衝突的文件里加入標準的衝突解決標記,可以通過它們來手工定位並解決這些衝突。可以看到此文件包含類似下面這樣的部分:

<<<<<<< HEAD
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
  please contact us at support@github.com
</div>
>>>>>>> iss53

其中HEAD表示當前分支
可以看到 ======= 隔開的上半部分,是 HEAD(即 master 分支,在運行 merge 命令時所切換到的分支)中的內容,下半部分是在 iss53 分支中的內容。解決衝突的辦法無非是二者選其一或者由你親自整合到一起。比如你可以通過把這段內容替換爲下面這樣來解決:

<div id="footer">
please contact us at email.support@github.com
</div>

這個解決方案各採納了兩個分支中的一部分內容,而且我還刪除了 <<<<<<<,======= 和 >>>>>>> 這些行。在解決了所有文件裏的所有衝突後,運行 git add 將把它們標記爲已解決狀態(譯註:實際上就是來一次快照保存到暫存區域。)。因爲一旦暫存,就表示衝突已經解決。如果你想用一個有圖形界面的工具來解決這些問題,不妨運行 git mergetool,它會調用一個可視化的合併工具並引導你解決所有衝突:

git mergetool

This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html

Normal merge conflict for 'index.html':
  {local}: modified file
  {remote}: modified file
Hit return to start merge resolution tool (opendiff):

如果不想用默認的合併工具(Git 爲我默認選擇了 opendiff,因爲我在 Mac 上運行了該命令),你可以在上方"merge tool candidates"裏找到可用的合併工具列表,輸入你想用的工具名。
退出合併工具以後,Git 會詢問你合併是否成功。如果回答是,它會爲你把相關文件暫存起來,以表明狀態爲已解決。
再運行一次 git status 來確認所有衝突都已解決

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

        modified:   index.html

如果覺得滿意了,並且確認所有衝突都已解決,也就是進入了暫存區,就可以用 git commit 來完成這次合併提交。提交的記錄差不多是這樣:

Merge branch 'iss53'

Conflicts:
  index.html
#
# It looks like you may be committing a merge.
# If this is not correct, please remove the file
#       .git/MERGE_HEAD
# and try again.
#

如果想給將來看這次合併的人一些方便,可以修改該信息,提供更多合併細節。比如你都作了哪些改動,以及這麼做的原因。有時候裁決衝突的理由並不直接或明顯,有必要略加註解。

分支管理

git branch 命令不僅僅能創建和刪除分支,如果不加任何參數,它會給出當前所有分支的清單:

git branch
  iss53
* master
  testing

注意看 master 分支前的 * 字符:它表示當前所在的分支。也就是說,如果現在提交更新,master 分支將隨着開發進度前移。若要查看各個分支最後一個提交對象的信息,運行 git branch -v:

git branch -v
  iss53   93b412c fix javascript issue
* master  7a98805 Merge branch 'iss53'
  testing 782fd34 add scott to the author list in the readmes

要從該清單中篩選出你已經(或尚未)與當前分支合併的分支,可以用 --merged 和 --no-merged 選項(Git 1.5.6 以上版本)。比如用 git branch --merged 查看哪些分支已被併入當前分支(譯註:也就是說哪些分支是當前分支的直接上游。):

git branch --merged
  iss53
* master

之前我們已經合併了 iss53,所以在這裏會看到它。一般來說,列表中沒有 * 的分支通常都可以用 git branch -d 來刪掉。原因很簡單,既然已經把它們所包含的工作整合到了其他分支,刪掉也不會損失什麼。

另外可以用 git branch --no-merged 查看尚未合併的工作:

git branch --no-merged
testing

它會顯示還未合併進來的分支。由於這些分支中還包含着尚未合併進來的工作成果,所以簡單地用 git branch -d 刪除該分支會提示錯誤,因爲那樣做會丟失數據:

git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.

不過,如果你確實想要刪除該分支上的改動,可以用大寫的刪除選項 -D 強制執行,就像上面提示信息中給出的那樣。

參考文獻

https://git-scm.com/

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