Learn Git

Git 配置

如第一章所言,用 git config 配置 Git,要做的第一件事就是設置名字和郵箱地址:

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

從現在開始,你會瞭解到一些類似以上但更為有趣的設置選項來自訂 Git。

先過一遍第一章中提到的 Git 配置細節。Git 使用一系列的設定檔來儲存你定義的偏好,它首先會查找 /etc/gitconfig 檔,該檔所含的設定值對系統上所有使用者都有效,也對他們所擁有的倉庫都有效(譯注:gitconfig 是全域設定檔), 如果傳遞 --system 選項給 git config 命令, Git 會讀寫這個檔。

接下來 Git 會尋找每個用戶的 ~/.gitconfig 檔,它是針對個別使用者的,你可以傳遞 --global 選項讓 Git 讀寫該檔。

最後,Git 會尋找你目前使用中的倉庫 Git 目錄下的設定檔(.git/config),該文件中的設定值只對這個倉庫有效。以上闡述的三層配置從一般到特殊層層推進,如果定義的值有衝突,後面層級的設定會面寫前面層級的設定值,例如:在 .git/config/etc/gitconfig 的較量中, .git/config 取得了勝利。雖然你也可以直接手動編輯這些設定檔,但是執行 git config 命令將會來得簡單些。

用戶端基本配置

Git 能夠識別的配置項被分為了兩大類:用戶端和伺服器端,其中大部分屬於用戶端配置,這是基於你個人工作偏好所做的設定。儘管有數不盡的選項,但我只闡述其中經常使用、或者會對你的工作流程產生巨大影響的選項。許多選項只在極端的情況下有用,這裏就不多做介紹了。如果你想觀察你的 Git 版本能識別的選項清單,請執行

$ git config --help

git config 的手冊頁(譯注:以 man 命令的顯示方式)非常細緻地羅列了所有可用的配置項。

core.editor

預設情況下,Git 會使用你所設定的「預設文字編輯器」,否則會使用 Vi 來創建和編輯提交以及標籤資訊,你可以使用 core.editor 改變預設編輯器:

$ git config --global core.editor emacs

現在無論你的環境變數 editor 被定義成什麼,Git 都會觸發 Emacs 來編輯相關訊息。

commit.template

如果把這個項目指定為你系統上的一個檔,當你提交的時候,Git 會預設使用該檔定義的內容做為預設的提交訊息。例如:你創建了一個範本檔 $HOME/.gitmessage.txt,它看起來像這樣:

subject line

what happened

[ticket: X]

如下設定 commit.template 可以告訴 Git,把上列內容做為預設訊息,當你執行 git commit 的時候,在你的編輯器中顯示:

$ git config --global commit.template $HOME/.gitmessage.txt
$ git commit

然後當你提交時,在編輯器中顯示的提交資訊如下:

subject line

what happened

[ticket: X]
# 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 <file>..." to unstage)
#
# modified:   lib/test.rb
#
~
~
".git/COMMIT_EDITMSG" 14L, 297C

如果你對於提交訊息有特定的政策,那就在系統上創建一個範本檔,設定 Git 使用它做為預設值,這樣可以幫助提升你的政策經常被遵守的機會。

core.pager

core.pager 指定 Git 執行諸如 log、diff 等命令時所使用的分頁器,你可以設成用 more 或者任何你喜歡的分頁器(預設用的是 less), 當然你也可以不用分頁器,只要把它設成空字串:

$ git config --global core.pager ''

這樣不管命令的輸出量多少,都會在一頁顯示所有內容。

user.signingkey

如果你要創建經簽署的含附注的標籤(signed annotated tags)(正如第二章所述),那麼把你的 GPG 簽署金鑰設為配置項會更好,設置金鑰 ID 如下:

$ git config --global user.signingkey <gpg-key-id>

現在你能夠簽署標籤,從而不必每次執行 git tag 命令時定義金鑰:

$ git tag -s <tag-name>

core.excludesfile

正如第二章所述,你能在專案倉庫的 .gitignore 檔裡頭用模式(pattern)來定義那些無需納入 Git 管理的檔案,這樣它們不會出現在未追蹤列表,也不會在你執行 git add 後被暫存。然而,如果你想用專案倉庫之外的檔案來定義那些需被忽略的檔的話,可以用 core.excludesfile 來通知 Git 該檔所在的位置,檔案內容則和 .gitignore 類似。

help.autocorrect

這個選置項只在 Git 1.6.1 以上(含)版本有效,假如你在 Git 1.6 中錯打了一條命令,它會像這樣顯示:

$ git com
git: 'com' is not a git-command. See 'git --help'.

Did you mean this?
     commit

如果你把 help.autocorrect 設置成1(譯注:啟動自動修正),那麼在只有一個命令符合的情況下,Git 會自動執行該命令。

Git 中的著色

Git 能夠為輸出到你終端(terminal)的內容著色,以便你可以憑直觀進行快速、簡單地分析,有許多選項能幫你將顏色調成你喜好的。

color.ui

Git 會按照你的需要,自動為大部分的輸出加上顏色。你能明確地規定哪些需要著色、以及怎樣著色,設置 color.ui 為 true 來打開所有的預設終端著色:

$ git config --global color.ui true

設置好以後,當輸出到終端時,Git 會為之加上顏色。其他的參數還有 false 和 always,false 意味著不為輸出著色,而 always 則表示在任何情況下都要著色 -- 即使 Git 命令被重定向到文件或 pipe 到另一個命令。Git 1.5.5 版本引進了此項配置,如果你的版本更舊,你必須對顏色有關選項各自進行詳細地設置。

你會很少用到 color.ui = always,在大多數情況下,如果你想在被重定向的輸出中插入顏色碼,你可以傳遞 --color 旗標給 Git 命令來迫使它這麼做,color.ui = true 應該是你的首選。

color.*

想要具體指定哪些命令輸出需要被著色,以及怎樣著色,或者 Git 的版本很舊,你就要用到和具體命令有關的顏色配置選項,它們都能被設為 true、false 或 always:

color.branch
color.diff
color.interactive
color.status

除此之外,以上每個選項都有子選項,可以被用來覆蓋其父設置,以達到為輸出的各個部分著色的目的。例如,要讓 diff 輸出的改變資訊 (meta information) 以粗體、藍色前景和黑色背景的形式顯示,你可以執行:

$ git config --global color.diff.meta "blue black bold"

你能設置的顏色值如下:normal、black、red、green、yellow、blue、magenta、cyan、white。正如以上例子設置的粗體屬性,想要設置字體屬性的話,你的選擇有:bold、dim、ul、blink、reverse。

如果你想配置子選項的話,可以參考 git config 幫助頁。

外部的合併與比較工具

雖然 Git 自己實做了 diff,而且到目前為止你一直在使用它,但你能夠設定一個外部的工具來替代它。你還可以設定用一個圖形化的工具來合併和解決衝突,而不必自己手動解決。有一個不錯且免費的工具可以被用來做比較和合併工作,它就是 P4Merge(譯注:Perforce 圖形化合併工具),我會展示它的安裝過程。

所以如果你想試試看的話,因為 P4Merge 可以在所有主流平臺上運行,所以你應該可以嘗試看看。對於向你展示的例子,在 Mac 和 Linux 系統上,我會使用路徑名;在 Windows 上,/usr/local/bin 應該被改為你環境中的可執行路徑。

你可以在這裏下載 P4Merge:

http://www.perforce.com/perforce/downloads/component.html

首先,你要設定一個外部包裝腳本(external wrapper scripts)來執行你要的命令,我會使用 Mac 系統上的路徑來指定該腳本的位置;在其他系統上,它應該被放置在二進位檔案 p4merge 所在的目錄中。創建一個 merge 包裝腳本,名字叫作 extMerge,讓它附帶所有參數呼叫 p4merge 二進位檔案:

$ cat /usr/local/bin/extMerge
#!/bin/sh
/Applications/p4merge.app/Contents/MacOS/p4merge $*

diff 包裝腳本首先確定傳遞過來7個參數,隨後把其中2個傳遞給你的 merge 包裝腳本,預設情況下,Git 會傳遞以下參數給 diff:

path old-file old-hex old-mode new-file new-hex new-mode

由於你僅僅需要 old-filenew-file 參數,用 diff 包裝腳本來傳遞它們吧。

$ cat /usr/local/bin/extDiff
#!/bin/sh
[ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"

你還需要確認一下這兩個腳本是可執行的:

$ sudo chmod +x /usr/local/bin/extMerge
$ sudo chmod +x /usr/local/bin/extDiff

現在來設定使用你自訂的比較和合併工具吧。這需要許多自訂設置:merge.tool 通知 Git 使用哪個合併工具;mergetool.*.cmd 規定命令如何執行;mergetool.trustExitCode 會通知 Git 該程式的退出碼(exit code)是否指示合併操作成功;diff.external 通知 Git 用什麼命令做比較。因此,你可以執行以下4條配置命令:

$ git config --global merge.tool extMerge
$ git config --global mergetool.extMerge.cmd \
    'extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
$ git config --global mergetool.trustExitCode false
$ git config --global diff.external extDiff

或者直接編輯 ~/.gitconfig 文件如下:

[merge]
  tool = extMerge
[mergetool "extMerge"]
  cmd = extMerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
  trustExitCode = false
[diff]
  external = extDiff

設置完畢後,如果你像這樣執行 diff 命令:

$ git diff 32d1776b1^ 32d1776b1

不同於在命令列得到 diff 命令的輸出,Git 觸發了剛剛設置的 P4Merge,它看起來像圖7-1這樣:

Figure 7-1. P4Merge.

當你設法合併兩個分支,結果卻有衝突時,執行 git mergetool,Git 會啟用 P4Merge 讓你通過圖形介面來解決衝突。

設置包裝腳本的好處是你能簡單地改變 diff 和 merge 工具,例如把 extDiffextMerge 改成 KDiff3,要做的僅僅是編輯 extMerge 指令檔:

$ cat /usr/local/bin/extMerge
#!/bin/sh
/Applications/kdiff3.app/Contents/MacOS/kdiff3 $*

現在 Git 會使用 KDiff3 來做比較、合併和解決衝突。

Git 預先設置了許多其他的合併和解決衝突的工具,而你不必設置 cmd。可以把合併工具設置為:kdiff3、opendiff、tkdiff、meld、xxdiff、emerge、vimdiff、gvimdiff。如果你不想用 KDiff3 來做 diff,只是想用它來合併,而且 kdiff3 命令也在你的路徑裏,那麼你可以執行:

$ git config --global merge.tool kdiff3

如果執行了以上命令,沒有設置 extMergeextDiff 檔,Git 會用 KDiff3 做合併,讓平常內建的比較工具來做比較。

格式化與空格

格式化與空格是許多開發人員在協同工作時,特別是在跨平臺情況下,遇到的令人頭疼的細小問題。在一些大家合作的工作或提交的補丁中,很容易因為編輯器安靜無聲地加入一些小空格,或者 Windows 程式師在跨平臺專案中的檔案行尾加入了回車分行符號(carriage return)。Git 的一些配置選項可以幫助解決這些問題。

core.autocrlf

如果你在 Windows 上寫程式,或者你不是用 Windows,但和其他在 Windows 上寫程式的人合作,在這些情況下,你可能會遇到換行符號的問題。這是因為 Windows 使用回車(carriage-return)和換行(linefeed)兩個字元來結束一行,而 Mac 和 Linux 只使用一個換行字元。雖然這是小問題,但它會極大地擾亂跨平臺協作。

Git 可以在你提交時自動地把換行符號 CRLF 轉換成 LF,而在簽出代碼時把 LF 轉換成 CRLF。用 core.autocrlf 來打開此項功能,如果是在Windows 系統上,把它設置成 true,這樣當 check out 程式的時候,LF 會被轉換成 CRLF:

$ git config --global core.autocrlf true

Linux 或 Mac 系統使用 LF 作為行結束符,因此你不希望 Git 在 check out 檔案時進行自動的轉換;但是,當一個以 CRLF 做為換行符號的檔案不小心被引入時,你肯定希望 Git 可以修正它。你可以把 core.autocrlf 設置成 input 來告訴 Git 在提交時把 CRLF 轉換成 LF,check out 時不轉換:

$ git config --global core.autocrlf input

這樣會在 Windows 系統上的 check out 檔案中保留 CRLF,而在 Mac 和 Linux 系統上,以及倉庫中保留 LF。

如果你是 Windows 程式師,且正在開發僅運行在 Windows 上的專案,可以設置 false 取消此功能,把 carriage returns 記錄在倉庫中:

$ git config --global core.autocrlf false

core.whitespace

Git 預先設置了一些選項來探測和修正空格問題,其中有四個主要選項,有2個預設開啟,2個預設關閉,你可以自由地打開或關閉它們。

預設開啟的2個選項是:trailing-space 會查找每行結尾的空格,space-before-tab 會查找每行開頭的定位字元前的空格。

預設關閉的2個選項是:indent-with-non-tab 會查找8個以上空格(非定位字元)開頭的行,cr-at-eol 告訴 Git carriage returns 是合法的。

設置 core.whitespace,按照你的需要來打開或關閉選項,設定值之間以逗號分隔。從設定字串裏把設定值去掉,就可以關閉這個設定,或是在設定值前面加上減號 - 也可以。例如,如果你想要打開除了 cr-at-eol 之外的所有選項,你可以這麼做:

$ git config --global core.whitespace \
    trailing-space,space-before-tab,indent-with-non-tab

當你執行 git diff 命令且為輸出著色時,Git 會偵測這些問題,因此你有可能在提交前修復它們。當你用 git apply 打補丁時,它也同樣會使用這些設定值來幫助你。你可以要 Git 警告你,如果正準備運用的補丁有特別的空白問題:

$ git apply --whitespace=warn <patch>

或者讓 Git 在打上補丁嘗試自動修正此問題:

$ git apply --whitespace=fix <patch>

這些選項也能運用於衍合。如果提交了有空格問題的檔但還沒推送到上游,你可以執行帶有 --whitespace=fix 選項的 rebase 來讓 Git 在重寫補丁時自動修正它們。

伺服器端配置

Git 伺服器端的配置選項並不多,但仍有一些有趣的選項值得你一看。

receive.fsckObjects

Git 預設情況下不會在推送期間檢查所有物件的一致性。Git 雖然會檢查確認每個物件仍然符合它的 SHA-1 checksum,所指向的物件也都是有效的,但是預設 Git 不會在每次推送時都做這種檢查。對於 Git 來說,倉庫或推送的檔越大,這個操作代價就相對越高,每次推送會消耗更多時間。如果想讓 Git 在每次推送時都檢查物件一致性,可以設定 receive.fsckObjects 為 true 來強迫它這麼做:

$ git config --system receive.fsckObjects true

現在 Git 會在每次推送被接受前檢查庫的完整性,確保有問題的用戶端沒有引入破壞性的資料。

receive.denyNonFastForwards

如果對已經被推送的提交歷史做衍合,繼而再推送;或是要將某個提交推送到遠端分支,而該提交歷史未包含這個遠端分支目前指向的 commit,這樣的推送會被拒絕。這通常是個很好的禁止策略,但有時你在做衍合的時候,你可能很確定自己在做什麼,那就可以在 push 命令後加 -f 旗標來強制更新遠端分支。

要禁用這樣的強制更新遠端分支 non-fast-forward references 的功能,可以如下設定 receive.denyNonFastForwards

$ git config --system receive.denyNonFastForwards true

稍後你會看到,用伺服器端的 receive hooks 也能達到同樣的目的。這個方法可以做更細緻的控制,例如:拒絕某些特定的使用者強制更新 non-fast-forwards。

receive.denyDeletes

避開 denyNonFastForwards 策略的方法之一就是使用者刪除分支,然後推回新的引用(reference)。在更新的 Git 版本中(從1.6.1版本開始),你可以把 receive.denyDeletes 設置為 true:

$ git config --system receive.denyDeletes true

這樣會在推送過程中阻止刪除分支和標籤 — 沒有使用者能夠這麼做。要刪除遠端分支,必須從伺服器手動刪除引用檔(ref files)。通過用戶存取控制清單也能這麼做,在本章結尾將會介紹這些有趣的方式。