原文:《Pro Git》javascript
自定義 Githtml
到目前爲止,我闡述了 Git 基本的運做機制和使用方式,介紹了 Git 提供的許多工具來幫助你簡單且有效地使用它。 在本章,我將會介紹 Git 的一些重要的配置方法和鉤子機制以知足自定義的要求。經過這些工具,它會和你和公司或團隊配合得完美無缺。(伯樂在線注:若是你對Git還不瞭解,建議從本Git系列第一篇文章開始閱讀)java
7.1 配置 Gitgit
如第一章所言,用git config
配置 Git,要作的第一件事就是設置名字和郵箱地址:程序員
1
2
|
$ 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
命令將會來得簡單些。正則表達式
客戶端基本配置shell
Git 可以識別的配置項被分爲了兩大類:客戶端和服務器端,其中大部分基於你我的工做偏好,屬於客戶端配置。儘管有數不盡的選項,但我只闡述 其中常用或者會對你的工做流產生巨大影響的選項,若是你想觀察你當前的 Git 能識別的選項列表,請運行數據庫
1
|
$ git config --help
|
git config
的手冊頁(譯註:以man命令的顯示方式)很是細緻地羅列了全部可用的配置項。編程
Git默認會調用你的環境變量editor定義的值做爲文本編輯器,若是沒有定義的話,會調用Vi來建立和編輯提交以及標籤信息, 你可使用core.editor
改變默認編輯器:小程序
1
|
$ git config --global core.editor emacs
|
如今不管你的環境變量editor被定義成什麼,Git 都會調用Emacs編輯信息。
若是把此項指定爲你係統上的一個文件,當你提交的時候, Git 會默認使用該文件定義的內容。 例如:你建立了一個模板文件$HOME/.gitmessage.txt
,它看起來像這樣:
1
2
3
4
5
|
subject line
what happened
[ticket: X]
|
設置commit.template
,當運行git commit
時, Git 會在你的編輯器中顯示以上的內容, 設置commit.template
以下:
1
2
|
$ git config --global commit.template $HOME/.gitmessage.txt
$ git commit
|
而後當你提交時,在編輯器中顯示的提交信息以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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
), 固然你也能夠什麼都不用,設置空字符串:
1
|
$ git config --global core.pager
''
|
這樣無論命令的輸出量多少,都會在一頁顯示全部內容。
user.signingkey
若是你要建立經簽署的含附註的標籤(正如第二章所述),那麼把你的GPG簽署密鑰設置爲配置項會更好,設置密鑰ID以下:
1
|
$ git config --global user.signingkey <gpg-key-
id
>
|
如今你可以簽署標籤,從而沒必要每次運行git tag
命令時定義密鑰:
1
|
$ git tag -s <tag-name>
|
core.excludesfile
正如第二章所述,你能在項目庫的.gitignore
文件裏頭用模式來定義那些無需歸入 Git 管理的文件,這樣它們不會出如今未跟蹤列表, 也不會在你運行git add
後被暫存。然而,若是你想用項目庫以外的文件來定義那些需被忽略的文件的話,用core.excludesfile
通知 Git 該文件所處的位置,文件內容和.gitignore
相似。
help.autocorrect
該配置項只在 Git 1.6.1及以上版本有效,假如你在Git 1.6中錯打了一條命令,會顯示:
1
2
3
4
5
|
$ git com
git:
'com'
is
not a git-command. See
'git --help'
.
Did you mean
this
?
commit
|
若是你把help.autocorrect
設置成1(譯註:啓動自動修正),那麼在只有一個命令被模糊匹配到的狀況下,Git 會自動運行該命令。
Git中的着色
Git可以爲輸出到你終端的內容着色,以便你能夠憑直觀進行快速、簡單地分析,有許多選項能供你使用以符合你的偏好。
color.ui
Git會按照你須要自動爲大部分的輸出加上顏色,你能明確地規定哪些須要着色以及怎樣着色,設置color.ui
爲true來打開全部的默認終端着色。
1
|
$ git config --global color.ui
true
|
設置好之後,當輸出到終端時,Git 會爲之加上顏色。其餘的參數還有false和always,false意味着不爲輸出着色,而always則代表在任何狀況下都要着色,即便 Git 命令被重定向到文件或管道。Git 1.5.5版本引進了此項配置,若是你擁有的版本更老,你必須對顏色有關選項各自進行詳細地設置。 你會不多用到color.ui = always
,在大多數狀況下,若是你想在被重定向的輸出中插入顏色碼,你能傳遞--color
標誌給 Git 命令來迫使它這麼作,color.ui = true
應該是你的首選。
color.*
想要具體到哪些命令輸出須要被着色以及怎樣着色或者 Git 的版本很老,你就要用到和具體命令有關的顏色配置選項,它們都能被置爲true
、false
或always
:
1
2
3
4
|
color.branch
color.
diff
color.interactive
color.status
|
除此以外,以上每一個選項都有子選項,能夠被用來覆蓋其父設置,以達到爲輸出的各個部分着色的目的。例如,讓diff輸出的改變信息以粗體、藍色前景和黑色背景的形式顯示:
1
|
$ 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:
1
|
http:
//www
.perforce.com
/perforce/downloads/component
.html
|
首先把你要運行的命令放入外部包裝腳本中,我會使用Mac系統上的路徑來指定該腳本的位置,在其餘系統上,它應該被放置在二進制文件p4merge
所在的目錄中。建立一個merge包裝腳本,名字叫做extMerge
,讓它帶參數調用p4merge
二進制文件:
1
2
3
|
$
cat
/usr/local/bin/extMerge
#!/bin/sh
/Applications/p4merge
.app
/Contents/MacOS/p4merge
$*
|
diff包裝腳本首先肯定傳遞過來7個參數,隨後把其中2個傳遞給merge包裝腳本,默認狀況下, Git 傳遞如下參數給diff:
1
|
path old-
file
old-hex old-mode new-
file
new-hex new-mode
|
因爲你僅僅須要old-file
和new-file
參數,用diff包裝腳原本傳遞它們吧。
1
2
3
|
$
cat
/usr/local/bin/extDiff
#!/bin/sh
[ $
# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"
|
確認這兩個腳本是可執行的:
1
2
|
$
sudo
chmod
+x
/usr/local/bin/extMerge
$
sudo
chmod
+x
/usr/local/bin/extDiff
|
如今來配置使用你自定義的比較和合並工具吧。這須要許多自定義設置:merge.tool
通知 Git 使用哪一個合併工具;mergetool.*.cmd
規定命令運行的方式;mergetool.trustExitCode
會通知 Git 程序的退出是否指示合併操做成功;diff.external
通知 Git 用什麼命令作比較。所以,你能運行如下4條配置命令:
1
2
3
4
5
|
$ 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
文件以下:
1
2
3
4
5
6
7
|
[merge]
tool = extMerge
[mergetool
"extMerge"
]
cmd = extMerge
"$BASE"
"$LOCAL"
"$REMOTE"
"$MERGED"
trustExitCode =
false
[
diff
]
external = extDiff
|
設置完畢後,運行diff命令:
1
|
$ git
diff
32d1776b1^ 32d1776b1
|
命令行竟然沒有發現diff命令的輸出,其實,Git 調用了剛剛設置的P4Merge,它看起來像圖7-1這樣:
Figure 7-1. P4Merge.
當你設法合併兩個分支,結果卻有衝突時,運行git mergetool
,Git 會調用P4Merge讓你經過圖形界面來解決衝突。 設置包裝腳本的好處是你能簡單地改變diff和merge工具,例如把extDiff
和extMerge
改爲KDiff3,要作的僅僅是編輯extMerge
腳本文件:
1
2
3
|
$
cat
/usr/local/bin/extMerge
#!/bin/sh
/Applications/kdiff3
.app
/Contents/MacOS/kdiff3
$*
|
如今 Git 會使用KDiff3來作比較、合併和解決衝突。 Git預先設置了許多其餘的合併和解決衝突的工具,而你沒必要設置cmd。能夠把合併工具設置爲:kdiff三、opendiff、tkdiff、 meld、xxdiff、emerge、vimdiff、gvimdiff。若是你不想用到KDiff3的全部功能,只是想用它來合併,那麼kdiff3 正符合你的要求,運行:
1
|
$ git config --global merge.tool kdiff3
|
若是運行了以上命令,沒有設置extMerge
和extDiff
文件,Git 會用KDiff3作合併,讓一般內設的比較工具來作比較。
格式化與空白
格式化與空白是許多開發人員在協做時,特別是在跨平臺狀況下,遇到的使人頭疼的細小問題。因爲編輯器的不一樣或者Windows程序員在跨平臺項目中的文件行尾加入了回車換行符,一些細微的空格變化會不經意地進入你們合做的工做或提交的補丁中。不用怕,Git 的一些配置選項會幫助你解決這些問題。
core.autocrlf
假如你正在Windows上寫程序,又或者你正在和其餘人合做,他們在Windows上編程,而你卻在其餘系統上,在這些狀況下,你可能會遇到行尾結束符問題。這是由於Windows使用回車和換行兩個字符來結束一行,而Mac和Linux只使用換行一個字符。雖然這是小問題,但它會極大地擾亂跨平臺協做。 Git能夠在你提交時自動地把行結束符CRLF轉換成LF,而在簽出代碼時把LF轉換成CRLF。用core.autocrlf
來打開此項功能,若是是在Windows系統上,把它設置成true
,這樣當簽出代碼時,LF會被轉換成CRLF:
1
|
$ git config --global core.autocrlf
true
|
Linux或Mac系統使用LF做爲行結束符,所以你不想 Git 在簽出文件時進行自動的轉換;當一個以CRLF爲行結束符的文件不當心被引入時你確定想進行修正,把core.autocrlf
設置成input來告訴 Git 在提交時把CRLF轉換成LF,簽出時不轉換:
1
|
$ git config --global core.autocrlf input
|
這樣會在Windows系統上的簽出文件中保留CRLF,會在Mac和Linux系統上,包括倉庫中保留LF。 若是你是Windows程序員,且正在開發僅運行在Windows上的項目,能夠設置false
取消此功能,把回車符記錄在庫中:
1
|
$ git config --global core.autocrlf
false
|
core.whitespace
Git預先設置了一些選項來探測和修正空白問題,其4種主要選項中的2個默認被打開,另2個被關閉,你能夠自由地打開或關閉它們。 默認被打開的2個選項是trailing-space
和space-before-tab
,trailing-space
會查找每行結尾的空格,space-before-tab
會查找每行開頭的製表符前的空格。 默認被關閉的2個選項是indent-with-non-tab
和cr-at-eol
,indent-with-non-tab
會查找8個以上空格(非製表符)開頭的行,cr-at-eol
讓 Git 知道行尾回車符是合法的。 設置core.whitespace
,按照你的意圖來打開或關閉選項,選項以逗號分割。經過逗號分割的鏈中去掉選項或在選項前加-
來關閉,例如,若是你想要打開除了cr-at-eol
以外的全部選項:
1
2
|
$ git config --global core.whitespace \
trailing-space,space-before-tab,indent-with-non-tab
|
當你運行git diff
命令且爲輸出着色時,Git 探測到這些問題,所以你也許在提交前能修復它們,當你用git apply
打補丁時一樣也會從中受益。若是正準備運用的補丁有特別的空白問題,你可讓 Git 發警告:
1
|
$ git apply --whitespace=warn <patch>
|
或者讓 Git 在打上補丁前自動修正此問題:
1
|
$ git apply --whitespace=warn <patch>
|
這些選項也能運用於衍合。若是提交了有空白問題的文件但還沒推送到上流,你能夠運行帶有--whitespace=fix
選項的rebase
來讓Git在重寫補丁時自動修正它們。
服務器端配置
Git服務器端的配置選項並很少,但仍有一些饒有生趣的選項值得你一看。
receive.fsckObjects
Git默認狀況下不會在推送期間檢查全部對象的一致性。雖然會確認每一個對象的有效性以及是否仍然匹配SHA-1檢驗和,但 Git 不會在每次推送時都檢查一致性。對於 Git 來講,庫或推送的文件越大,這個操做代價就相對越高,每次推送會消耗更多時間,若是想在每次推送時 Git 都檢查一致性,設置receive.fsckObjects
爲true來強迫它這麼作:
1
|
$ git config --system receive.fsckObjects
true
|
如今 Git 會在每次推送生效前檢查庫的完整性,確保有問題的客戶端沒有引入破壞性的數據。
receive.denyNonFastForwards
若是對已經被推送的提交歷史作衍合,繼而再推送,又或者以其它方式推送一個提交歷史至遠程分支,且該提交歷史沒在這個遠程分支中,這樣的推送會被拒絕。這一般是個很好的禁止策略,但有時你在作衍合併肯定要更新遠程分支,能夠在push命令後加-f
標誌來強制更新。 要禁用這樣的強制更新功能,能夠設置receive.denyNonFastForwards
:
1
|
$ git config --system receive.denyNonFastForwards
true
|
稍後你會看到,用服務器端的接收鉤子也能達到一樣的目的。這個方法能夠作更細緻的控制,例如:禁用特定的用戶作強制更新。
receive.denyDeletes
規避denyNonFastForwards
策略的方法之一就是用戶刪除分支,而後推回新的引用。在更新的 Git 版本中(從1.6.1版本開始),把receive.denyDeletes
設置爲true:
1
|
$ git config --system receive.denyDeletes
true
|
這樣會在推送過程當中阻止刪除分支和標籤 — 沒有用戶可以這麼作。要刪除遠程分支,必須從服務器手動刪除引用文件。經過用戶訪問控制列表也能這麼作,在本章結尾將會介紹這些有趣的方式。
7.2 Git屬性
一些設置項也能被運用於特定的路徑中,這樣,Git 以對一個特定的子目錄或子文件集運用那些設置項。這些設置項被稱爲 Git 屬性,能夠在你目錄中的.gitattributes
文件內進行設置(一般是你項目的根目錄),也能夠當你不想讓這些屬性文件和項目文件一同提交時,在.git/info/attributes
進行設置。 使用屬性,你能夠對個別文件或目錄定義不一樣的合併策略,讓 Git 知道怎樣比較非文本文件,在你提交或簽出前讓 Git 過濾內容。你將在這部分了解到能在本身的項目中使用的屬性,以及一些實例。
二進制文件
你能夠用 Git 屬性讓其知道哪些是二進制文件(以防 Git 沒有識別出來),以及指示怎樣處理這些文件,這點很酷。例如,一些文本文件是由機器產生的,並且沒法比較,而一些二進制文件能夠比較 — 你將會了解到怎樣讓 Git 識別這些文件。
識別二進制文件
一些文件看起來像是文本文件,但實際上是做爲二進制數據被對待。例如,在Mac上的Xcode項目含有一個以.pbxproj
結尾的文件,它是由記錄設置項的IDE寫到磁盤的JSON數據集(純文本javascript數據類型)。雖然技術上看它是由ASCII字符組成的文本文件,但你並不認爲如此,由於它確實是一個輕量級數據庫 — 若是有2人改變了它,你一般沒法合併和比較內容,只有機器才能進行識別和操做,因而,你想把它當成二進制文件。 讓 Git 把全部pbxproj
文件當成二進制文件,在.gitattributes
文件中設置以下:
1
|
*.pbxproj -crlf -
diff
|
如今,Git 會嘗試轉換和修正CRLF(回車換行)問題,也不會當你在項目中運行git show或git diff時,比較不一樣的內容。在Git 1.6及以後的版本中,能夠用一個宏代替-crlf -diff
:
1
|
*.pbxproj binary
|
比較二進制文件
在Git 1.6及以上版本中,你能利用 Git 屬性來有效地比較二進制文件。能夠設置 Git 把二進制數據轉換成文本格式,用一般的diff來比較。 這個特性很酷,並且不爲人知,所以我會結合實例來說解。首先,要解決的是最使人頭疼的問題:對Word文檔進行版本控制。不少人對Word文檔又恨又愛,若是想對其進行版本控制,你能夠把文件加入到 Git 庫中,每次修改後提交便可。但這樣作沒有一點實際意義,由於運行git diff
命令後,你只能獲得以下的結果:
1
2
3
4
|
$ git
diff
diff
--git a
/chapter1
.doc b
/chapter1
.doc
index 88839c4..4afcb7c 100644
Binary files a
/chapter1
.doc and b
/chapter1
.doc differ
|
你不能直接比較兩個不一樣版本的Word文件,除非進行手動掃描,不是嗎? Git 屬性能很好地解決此問題,把下面的行加到.gitattributes
文件:
1
|
*.doc
diff
=word
|
當你要看比較結果時,若是文件擴展名是」doc」,Git 調用」word」過濾器。什麼是」word」過濾器呢?其實就是 Git 使用strings
程序,把Word文檔轉換成可讀的文本文件,以後再進行比較:
1
|
$ git config
diff
.word.textconv strings
|
如今若是在兩個快照之間比較以.doc
結尾的文件,Git 對這些文件運用」word」過濾器,在比較前把Word文件轉換成文本文件。 下面展現了一個實例,我把此書的第一章歸入 Git 管理,在一個段落中加入了一些文本後保存,以後運行git diff
命令,獲得結果以下:
1
2
3
4
5
6
7
8
9
10
11
12
|
$ git
diff
diff
--git a
/chapter1
.doc b
/chapter1
.doc
index c1c8a0a..b93c9e4 100644
--- a
/chapter1
.doc
+++ b
/chapter1
.doc
@@ -8,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics
re going to cover how to get it and
set
it up
for
the first
time
if
you don
t already have it on your system.
In Chapter Two we will go over basic Git usage - how to use Git
for
the 80%
-s going on, modify stuff and contribute changes. If the book spontaneously
+s going on, modify stuff and contribute changes. If the book spontaneously
+Let's see
if
this works.
|
Git 成功且簡潔地顯示出我增長的文本」Let’s see if this works」。雖然有些瑕疵,在末尾顯示了一些隨機的內容,但確實能夠比較了。若是你能找到或本身寫個Word到純文本的轉換器的話,效果可能會更好。strings
能夠在大部分Mac和Linux系統上運行,因此它是處理二進制格式的第一選擇。 你還能用這個方法比較圖像文件。當比較時,對JPEG文件運用一個過濾器,它能提煉出EXIF信息 — 大部分圖像格式使用的元數據。若是你下載並安裝了exiftool
程序,能夠用它參照元數據把圖像轉換成文本。比較的不一樣結果將會用文本向你展現:
1
2
|
$
echo
'*.png diff=exif'
>> .gitattributes
$ git config
diff
.exif.textconv exiftool
|
若是在項目中替換了一個圖像文件,運行git diff
命令的結果以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
diff
--git a
/image
.png b
/image
.png
index 88839c4..4afcb7c 100644
--- a
/image
.png
+++ b
/image
.png
@@ -1,12 +1,12 @@
ExifTool Version Number : 7.74
-File Size : 70 kB
-File Modification Date
/Time
: 2009:04:21 07:02:45-07:00
+File Size : 94 kB
+File Modification Date
/Time
: 2009:04:21 07:02:43-07:00
File Type : PNG
MIME Type : image
/png
-Image Width : 1058
-Image Height : 889
+Image Width : 1056
+Image Height : 827
Bit Depth : 8
Color Type : RGB with Alpha
|
你會發現文件的尺寸大小發生了改變。
關鍵字擴展
使用SVN或CVS的開發人員常常要求關鍵字擴展。在 Git 中,你沒法在一個文件被提交後修改它,由於 Git 會先對該文件計算校驗和。然而,你能夠在簽出時注入文本,在提交前刪除它。 Git 屬性提供了2種方式這麼作。 首先,你可以把blob的SHA-1校驗和自動注入文件的$Id$
字段。若是在一個或多個文件上設置了此字段,當下次你簽出分支的時候,Git 用blob的SHA-1值替換那個字段。注意,這不是提交對象的SHA校驗和,而是blob自己的校驗和:
1
2
|
$
echo
'*.txt ident'
>> .gitattributes
$
echo
'$Id$'
>
test
.txt
|
下次簽出文件時,Git 入了blob的SHA值:
1
2
3
4
|
$
rm
text.txt
$ git checkout -- text.txt
$
cat
test
.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
|
然而,這樣的顯示結果沒有多大的實際意義。這個SHA的值至關地隨機,沒法區分日期的先後,因此,若是你在CVS或Subversion中用過關鍵字替換,必定會包含一個日期值。 所以,你能寫本身的過濾器,在提交文件到暫存區或簽出文件時替換關鍵字。有2種過濾器,」clean」和」smudge」。在.gitattributes
文件中,你能對特定的路徑設置一個過濾器,而後設置處理文件的腳本,這些腳本會在文件簽出前(」smudge」,見圖 7-2)和提交到暫存區前(」clean」,見圖7-3)被調用。這些過濾器可以作各類有趣的事。
圖7-2. 簽出時,「smudge」過濾器被觸發。
圖7-3. 提交到暫存區時,「clean」過濾器被觸發。
這裏舉一個簡單的例子:在暫存前,用indent
(縮進)程序過濾全部C源代碼。在.gitattributes
文件中設置」indent」過濾器過濾*.c
文件:
1
|
*.c filter=indent
|
而後,經過如下配置,讓 Git 知道」indent」過濾器在遇到」smudge」和」clean」時分別該作什麼:
1
2
|
$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge
cat
|
因而,當你暫存*.c
文件時,indent
程序會被觸發,在把它們簽出以前,cat
程序會被觸發。但cat
程序在這裏沒什麼實際做用。這樣的組合,使C源代碼在暫存前被indent
程序過濾,很是有效。 另外一個例子是相似RCS的$Date$
關鍵字擴展。爲了演示,須要一個小腳本,接受文件名參數,獲得項目的最新提交日期,最後把日期寫入該文件。下面用Ruby腳原本實現:
1
2
3
4
|
#! /usr/bin/env ruby
data = STDIN.
read
last_date = `git log --pretty=
format
:
"%ad"
-1`
puts data.gsub(
'$Date$'
,
'$Date: '
+ last_date.to_s +
'$'
)
|
該腳本從git log
命令中獲得最新提交日期,找到文件中的全部$Date$
字符串,最後把該日期填充到$Date$
字符串中 — 此腳本很簡單,你能夠選擇你喜歡的編程語言來實現。把該腳本命名爲expand_date
,放到正確的路徑中,以後須要在 Git 中設置一個過濾器(dater
),讓它在簽出文件時調用expand_date
,在暫存文件時用Perl清除之:
1
2
|
$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean
'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'
|
這個Perl小程序會刪除$Date$
字符串裏多餘的字符,恢復$Date$
原貌。到目前爲止,你的過濾器已經設置完畢,能夠開始測試了。打開一個文件,在文件中輸入$Date$
關鍵字,而後設置 Git 屬性:
1
2
|
$
echo
'# $Date$'
> date_test.txt
$
echo
'date*.txt filter=dater'
>> .gitattributes
|
若是暫存該文件,以後再簽出,你會發現關鍵字被替換了:
1
2
3
4
5
6
|
$ git add date_test.txt .gitattributes
$ git commit -m
"Testing date expansion in Git"
$
rm
date_test.txt
$ git checkout date_test.txt
$
cat
date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$
|
雖然說這項技術對自定義應用來講頗有用,但仍是要當心,由於.gitattributes
文件會隨着項目一塊兒提交,而過濾器(例如:dater
)不會,因此,過濾器不會在全部地方都生效。當你在設計這些過濾器時要注意,即便它們沒法正常工做,也要讓整個項目運做下去。
導出倉庫
Git屬性在導出項目歸檔時也能發揮做用。
export-ignore
當產生一個歸檔時,能夠設置 Git 不導出某些文件和目錄。若是你不想在歸檔中包含一個子目錄或文件,但想他們歸入項目的版本管理中,你能對應地設置export-ignore
屬性。 例如,在test/
子目錄中有一些測試文件,在項目的壓縮包中包含他們是沒有意義的。所以,能夠增長下面這行到 Git 屬性文件中:
1
|
test
/
export
-ignore
|
如今,當運行git archive來建立項目的壓縮包時,那個目錄不會在歸檔中出現。
export-subst
還能對歸檔作一些簡單的關鍵字替換。在第2章中已經能夠看到,能夠以--pretty=format
形式的簡碼在任何文件中放入$Format:$
字符串。例如,若是想在項目中包含一個叫做LAST_COMMIT
的文件,當運行git archive
時,最後提交日期自動地注入進該文件,能夠這樣設置:
1
2
3
4
|
$
echo
'Last commit date: $Format:%cd$'
> LAST_COMMIT
$
echo
"LAST_COMMIT export-subst"
>> .gitattributes
$ git add LAST_COMMIT .gitattributes
$ git commit -am
'adding LAST_COMMIT file for archives'
|
運行git archive
後,打開該文件,會發現其內容以下:
1
2
|
$
cat
LAST_COMMIT
Last commit
date
: $Format:Tue Apr 21 08:38:48 2009 -0700$
|
合併策略
經過 Git 屬性,還能對項目中的特定文件使用不一樣的合併策略。一個很是有用的選項就是,當一些特定文件發生衝突,Git 會嘗試合併他們,而使用你這邊的合併。 若是項目的一個分支有歧義或比較特別,但你想從該分支合併,並且須要忽略其中某些文件,這樣的合併策略是有用的。例如,你有一個數據庫設置文件database.xml,在2個分支中他們是不一樣的,你想合併一個分支到另外一個,而不弄亂該數據庫文件,能夠設置屬性以下:
1
|
database.xml merge=ours
|
若是合併到另外一個分支,database.xml文件不會有合併衝突,顯示以下:
1
2
3
|
$ git merge topic
Auto-merging database.xml
Merge made by recursive.
|
這樣,database.xml會保持原樣。
7.3 Git掛鉤
和其餘版本控制系統同樣,當某些重要事件發生時,Git 以調用自定義腳本。有兩組掛鉤:客戶端和服務器端。客戶端掛鉤用於客戶端的操做,如提交和合並。服務器端掛鉤用於 Git 服務器端的操做,如接收被推送的提交。你能夠隨意地使用這些掛鉤,下面會講解其中一些。
安裝一個掛鉤
掛鉤都被存儲在 Git 目錄下的hooks
子目錄中,即大部分項目中的.git/hooks
。 Git 默認會放置一些腳本樣本在這個目錄中,除了能夠做爲掛鉤使用,這些樣本自己是能夠獨立使用的。全部的樣本都是shell腳本,其中一些還包含了Perl的腳本,不過,任何正確命名的可執行腳本均可以正常使用 — 能夠用Ruby或Python,或其餘。在Git 1.6版本以後,這些樣本名都是以.sample結尾,所以,你必須從新命名。在Git 1.6版本以前,這些樣本名都是正確的,但這些樣本不是可執行文件。 把一個正確命名且可執行的文件放入 Git 目錄下的hooks
子目錄中,能夠激活該掛鉤腳本,所以,以後他一直會被 Git 調用。隨後會講解主要的掛鉤腳本。
客戶端掛鉤
有許多客戶端掛鉤,如下把他們分爲:提交工做流掛鉤、電子郵件工做流掛鉤及其餘客戶端掛鉤。
提交工做流掛鉤
有 4個掛鉤被用來處理提交的過程。pre-commit
掛鉤在鍵入提交信息前運行,被用來檢查即將提交的快照,例如,檢查是否有東西被遺漏,確認測試是否運行,以及檢查代碼。當從該掛鉤返回非零值時,Git 放棄這次提交,但能夠用git commit --no-verify
來忽略。該掛鉤能夠被用來檢查代碼錯誤(運行相似lint的程序),檢查尾部空白(默認掛鉤是這麼作的),檢查新方法(譯註:程序的函數)的說明。 prepare-commit-msg
掛鉤在提交信息編輯器顯示以前,默認信息被建立以後運行。所以,能夠有機會在提交做者看到默認信息前進行編輯。該掛鉤接收一些選項:擁有提交信息的文件路徑,提交類型,若是是一次修訂的話,提交的SHA-1校驗和。該掛鉤對一般的提交來講不是頗有用,只在自動產生的默認提交信息的狀況下有做用,如提交信息模板、合併、壓縮和修訂提交等。能夠和提交模板配合使用,以編程的方式插入信息。 commit-msg
掛鉤接收一個參數,此參數是包含最近提交信息的臨時文件的路徑。若是該掛鉤腳本以非零退出,Git 放棄提交,所以,能夠用來在提交經過前驗證項目狀態或提交信息。本章上一小節已經展現了使用該掛鉤覈對提交信息是否符合特定的模式。 post-commit
掛鉤在整個提交過程完成後運行,他不會接收任何參數,但能夠運行git log -1 HEAD
來得到最後的提交信息。總之,該掛鉤是做爲通知之類使用的。 提交工做流的客戶端掛鉤腳本能夠在任何工做流中使用,他們常常被用來實施某些策略,但值得注意的是,這些腳本在clone期間不會被傳送。能夠在服務器端實施策略來拒毫不符合某些策略的推送,但這徹底取決於開發者在客戶端使用這些腳本的狀況。因此,這些腳本對開發者是有用的,由他們本身設置和維護,並且在任什麼時候候均可以覆蓋或修改這些腳本。
E-mail工做流掛鉤
有3個可用的客戶端掛鉤用於e-mail工做流。當運行git am
命令時,會調用他們,所以,若是你沒有在工做流中用到此命令,能夠跳過本節。若是你經過e-mail接收由git format-patch
產生的補丁,這些掛鉤也許對你有用。 首先運行的是applypatch-msg
掛鉤,他接收一個參數:包含被建議提交信息的臨時文件名。若是該腳本非零退出,Git 放棄此補丁。可使用這個腳本確認提交信息是否被正確格式化,或讓腳本編輯信息以達到標準化。 下一個在git am
運行期間調用是pre-applypatch
掛鉤。該掛鉤不接收參數,在補丁被運用以後運行,所以,能夠被用來在提交前檢查快照。你能用此腳本運行測試,檢查工做樹。若是有些什麼遺漏,或測試沒經過,腳本會以非零退出,放棄這次git am
的運行,補丁不會被提交。 最後在git am
運行期間調用的是post-applypatch
掛鉤。你能夠用他來通知一個小組或獲取的補丁的做者,但沒法阻止打補丁的過程。
其餘客戶端掛鉤
pre- rebase
掛鉤在衍合前運行,腳本以非零退出能夠停止衍合的過程。你可使用這個掛鉤來禁止衍合已經推送的提交對象,Git pre- rebase
掛鉤樣本就是這麼作的。該樣本假定next是你定義的分支名,所以,你可能要修改樣本,把next改爲你定義過且穩定的分支名。 在git checkout
成功運行後,post-checkout
掛鉤會被調用。他能夠用來爲你的項目環境設置合適的工做目錄。例如:放入大的二進制文件、自動產生的文檔或其餘一切你不想歸入版本控制的文件。 最後,在merge
命令成功執行後,post-merge
掛鉤會被調用。他能夠用來在 Git 沒法跟蹤的工做樹中恢復數據,諸如權限數據。該掛鉤一樣可以驗證在 Git 控制以外的文件是否存在,所以,當工做樹改變時,你想這些文件能夠被複制。
服務器端掛鉤
除了客戶端掛鉤,做爲系統管理員,你還可使用兩個服務器端的掛鉤對項目實施各類類型的策略。這些掛鉤腳本能夠在提交對象推送到服務器前被調用,也能夠在推送到服務器後被調用。推送到服務器前調用的掛鉤能夠在任什麼時候候以非零退出,拒絕推送,返回錯誤消息給客戶端,還能夠如你所願設置足夠複雜的推送策略。
pre-receive 和 post-receive
處理來自客戶端的推送(push)操做時最早執行的腳本就是 pre-receive
。它從標準輸入(stdin)獲取被推送引用的列表;若是它退出時的返回值不是0,全部推送內容都不會被接受。利用此掛鉤腳本能夠實現相似保證最新的索引中不包含非fast-forward類型的這類效果;抑或檢查執行推送操做的用戶擁有建立,刪除或者推送的權限或者他是否對將要修改的每個文件都有訪問權限。 post-receive
掛鉤在整個過程完結之後運行,能夠用來更新其餘系統服務或者通知用戶。它接受與 pre-receive
相同的標準輸入數據。應用實例包括給某郵件列表發信,通知實時整合數據的服務器,或者更新軟件項目的問題追蹤系統 —— 甚至能夠經過分析提交信息來決定某個問題是否應該被開啓,修改或者關閉。該腳本沒法組織推送進程,不過客戶端在它完成運行以前將保持鏈接狀態;因此在用它做一些消耗時間的操做以前請三思。
update
update 腳本和 pre-receive
腳本十分相似。不一樣之處在於它會爲推送者更新的每個分支運行一次。假如推送者同時向多個分支推送內容,pre-receive
只運行一次,相比之下 update 則會爲每個更新的分支運行一次。它不會從標準輸入讀取內容,而是接受三個參數:索引的名字(分支),推送前索引指向的內容的 SHA-1 值,以及用戶試圖推送內容的 SHA-1 值。若是 update 腳本以退出時返回非零值,只有相應的那一個索引會被拒絕;其他的依然會獲得更新。
7.4 Git 強制策略實例
在本節中,咱們應用前面學到的知識創建這樣一個Git 工做流程:檢查提交信息的格式,只接受純fast-forward內容的推送,而且指定用戶只能修改項目中的特定子目錄。咱們將寫一個客戶端角原本提示開發人員他們推送的內容是否會被拒絕,以及一個服務端腳原本實際執行這些策略。 這些腳本使用 Ruby 寫成,一半因爲它是做者傾向的腳本語言,另外做者以爲它是最接近僞代碼的腳本語言;於是即使你不使用 Ruby 也能大體看懂。不過任何其餘語言也同樣適用。全部 Git 自帶的樣例腳本都是用 Perl 或 Bash 寫的。因此從這些腳本中能找到至關多的這兩種語言的掛鉤樣例。
服務端掛鉤
全部服務端的工做都在hooks(掛鉤)目錄的 update(更新)腳本中制定。update 腳本爲每個獲得推送的分支運行一次;它接受推送目標的索引,該分支原來指向的位置,以及被推送的新內容。若是推送是經過 SSH 進行的,還能夠獲取發出這次操做的用戶。若是設定全部操做都經過公匙受權的單一賬號(好比"git")進行,就有必要經過一個 shell 包裝依據公匙來判斷用戶的身份,而且設定環境變量來表示該用戶的身份。下面假設嘗試鏈接的用戶儲存在$USER
環境變量裏,咱們的 update 腳本首先蒐集一切須要的信息:
1
2
3
4
5
6
7
8
|
#!/usr/bin/env ruby
$refname = ARGV[0]
$oldrev = ARGV[1]
$newrev = ARGV[2]
$user = ENV[
'USER'
]
puts
"Enforcing Policies... \n(#{$refname}) (#{$oldrev[0,6]}) (#{$newrev[0,6]})"
|
沒錯,我在用全局變量。別鄙視我——這樣比較利於演示過程。
指定特殊的提交信息格式
咱們的第一項任務是指定每一條提交信息都必須遵循某種特殊的格式。做爲演示,假定每一條信息必須包含一條形似 「ref: 1234」 這樣的字符串,由於咱們須要把每一次提交和項目的問題追蹤系統。咱們要逐一檢查每一條推送上來的提交內容,看看提交信息是否包含這麼一個字符串,而後,若是該提交裏不包含這個字符串,以非零返回值退出從而拒絕這次推送。 把 $newrev
和 $oldrev
變量的值傳給一個叫作 git rev-list
的 Git plumbing 命令能夠獲取全部提交內容的 SHA-1 值列表。git rev-list
基本相似git log
命令,但它默認只輸出 SHA-1 值而已,沒有其餘信息。因此要獲取由 SHA 值表示的從一次提交到另外一次提交之間的全部 SHA 值,能夠運行:
1
2
3
4
5
6
|
$ git rev-list 538c33..d14fc7
d14fc7c847ab946ec39590d87783c69b031bdfb7
9f585da4401b0a3999e84113824d15245c13f0be
234071a1be950e2a8d078e6141f5cd20c1e61ad3
dfa04c9ef3d5197182f13fb5b9b1fb7717d2222a
17716ec0f1ff5c77eff40b7fe912f9f6cfd0e475
|
截取這些輸出內容,循環遍歷其中每個 SHA 值,找出與之對應的提交信息,而後用正則表達式來測試該信息包含的格式話的內容。 下面要搞定如何從全部的提交內容中提取出提交信息。使用另外一個叫作 git cat-file
的 Git plumbing 工具能夠得到原始的提交數據。咱們將在第九章瞭解到這些 plumbing 工具的細節;如今暫時先看一下這條命令的輸出:
1
2
3
4
5
6
7
|
$ git
cat
-
file
commit ca82a6
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@ gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@ gmail.com> 1240030591 -0700
changed the version number
|
經過 SHA-1 值得到提交內容中的提交信息的一個簡單辦法是找到提交的第一行,而後取從它日後的全部內容。可使用 Unix 系統的 sed
命令來實現該效果:
1
2
|
$ git
cat
-
file
commit ca82a6 |
sed
'1,/^$/d'
changed the version number
|
這條咒語從每個待提交內容裏提取提交信息,而且會在提取信息不符合要求的狀況下退出。爲了退出腳本和拒絕這次推送,返回一個非零值。整個腳本大體以下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$regex = /<p style=
"text-align:center;"
><span class=
"MathJax_Preview"
>\[ref: (\d+)\]<
/span
><script
type
=
"math/tex; mode=display"
>ref: (\d+)<
/script
><
/p
>/
# 指定提交信息格式
def check_message_format
missed_revs = `git rev-list
#{$oldrev}..#{$newrev}`.split("\n")
missed_revs.each
do
|rev|
message = `git
cat
-
file
commit
#{rev} | sed '1,/^$/d'`
if
!$regex.match(message)
puts
"[POLICY] Your message is not formatted correctly"
exit
1
end
end
end
check_message_format
|
把這一段放在 update
腳本里,全部包含不符合指定規則的提交都會遭到拒絕。
實現基於用戶的訪問權限控制列表(ACL)系統
假設你須要添加一個使用訪問權限控制列表的機制來指定哪些用戶對項目的哪些部分有推送權限。某些用戶具備所有的訪問權,其餘人只對某些子目錄或者特定的文件具備推送權限。要搞定這一點,全部的規則將被寫入一個位於服務器的原始 Git 倉庫的acl
文件。咱們讓 update
掛鉤檢閱這些規則,審視推送的提交內容中須要修改的全部文件,而後決定執行推送的用戶是否對全部這些文件都有權限。 咱們首先要建立這個列表。這裏使用的格式和 CVS 的 ACL 機制十分相似:它由若干行構成,第一項內容是 avail
或者unavail
,接着是逗號分隔的規則生效用戶列表,最後一項是規則生效的目錄(空白表示開放訪問)。這些項目由 |
字符隔開。 下例中,咱們指定幾個管理員,幾個對 doc
目錄具備權限的文檔做者,以及一個對 lib
和 tests
目錄具備權限的開發人員,相應的 ACL 文件以下:
1
2
3
4
|
avail|nickh,pjhyett,defunkt,tpw
avail|usinclair,cdickens,ebronte|doc
avail|schacon|lib
avail|schacon|tests
|
首先把這些數據讀入你編寫的數據結構。本例中,爲保持簡潔,咱們暫時只實現 avail
的規則(譯註:也就是省略了unavail
部分)。下面這個方法生成一個關聯數組,它的主鍵是用戶名,值是一個該用戶有寫權限的全部目錄組成的數組:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
def get_acl_access_data(acl_file)
# read in ACL data
acl_file = File.
read
(acl_file).
split
(
"\n"
).reject { |line| line ==
''
}
access = {}
acl_file.each
do
|line|
avail,
users
, path = line.
split
(
'|'
)
next unless avail ==
'avail'
users
.
split
(
','
).each
do
|user|
access[user] ||= []
access[user] << path
end
end
access
end
|
針對以前給出的 ACL 規則文件,這個 get_acl_access_data
方法返回的數據結構以下:
1
2
3
4
5
6
7
8
|
{
"defunkt"
=>[nil],
"tpw"
=>[nil],
"nickh"
=>[nil],
"pjhyett"
=>[nil],
"schacon"
=>[
"lib"
,
"tests"
],
"cdickens"
=>[
"doc"
],
"usinclair"
=>[
"doc"
],
"ebronte"
=>[
"doc"
]}
|
搞定了用戶權限的數據,下面須要找出哪些位置將要被提交的內容修改,從而確保試圖推送的用戶對這些位置有所有的權限。 使用 git log
的 --name-only
選項(在第二章裏簡單的提過)咱們能夠垂手可得的找出一次提交裏修改的文件:
1
2
3
4
|
$ git log -1 --name-only --pretty=
format
:
''
9f585d
README
lib
/test
.rb
|
使用 get_acl_access_data
返回的 ACL 結構來一一覈對每一次提交修改的文件列表,就能找出該用戶是否有權限推送全部的提交內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 僅容許特定用戶修改項目中的特定子目錄
def check_directory_perms
access = get_acl_access_data(
'acl'
)
# 檢查是否有人在向他沒有權限的地方推送內容
new_commits = `git rev-list
#{$oldrev}..#{$newrev}`.split("\n")
new_commits.each
do
|rev|
files_modified = `git log -1 --name-only --pretty=
format
:
''
#{rev}`.split("\n")
files_modified.each
do
|path|
next
if
path.size == 0
has_file_access =
false
access[$user].each
do
|access_path|
if
!access_path
# 用戶擁有徹底訪問權限
|| (path.index(access_path) == 0)
# 或者對此位置有訪問權限
has_file_access =
true
end
end
if
!has_file_access
puts
"[POLICY] You do not have access to push to #{path}"
exit
1
end
end
end
end
check_directory_perms
|
以上的大部份內容應該都比較容易理解。經過 git rev-list
獲取推送到服務器內容的提交列表。而後,針對其中每一項,找出它試圖修改的文件而後確保執行推送的用戶對這些文件具備權限。一個不太容易理解的 Ruby 技巧石path.index(access_path) ==0
這句,它的返回真值若是路徑以 access_path
開頭——這是爲了確保access_path
並非只在容許的路徑之一,而是全部准許全選的目錄都在該目錄之下。 如今你的用戶無法推送帶有不正確的提交信息的內容,也不能在准許他們訪問範圍以外的位置作出修改。
只容許 Fast-Forward 類型的推送
剩下的最後一項任務是指定只接受 fast-forward 的推送。在 Git 1.6 或者更新版本里,只須要設定 receive.denyDeletes
和receive.denyNonFastForwards
選項就能夠了。可是經過掛鉤的實現能夠在舊版本的 Git 上工做,而且經過必定的修改它它能夠作到只針對某些用戶執行,或者更多之後可能用的到的規則。 檢查這一項的邏輯是看看提交裏是否包含從舊版本里能找到但在新版本里卻找不到的內容。若是沒有,那這是一次純 fast-forward 的推送;若是有,那咱們拒絕這次推送:
1
2
3
4
5
6
7
8
9
10
11
|
# 只容許純 fast-forward 推送
def check_fast_forward
missed_refs = `git rev-list
#{$newrev}..#{$oldrev}`
missed_ref_count = missed_refs.
split
(
"\n"
).size
if
missed_ref_count > 0
puts
"[POLICY] Cannot push a non fast-forward reference"
exit
1
end
end
check_fast_forward
|
一切都設定好了。若是如今運行 chmod u+x .git/hooks/update
—— 修改包含以上內容文件的權限,而後嘗試推送一個包含非 fast-forward 類型的索引,會獲得一下提示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
$ git push -f origin master
Counting objects: 5,
done
.
Compressing objects: 100% (3
/3
),
done
.
Writing objects: 100% (3
/3
), 323 bytes,
done
.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
Enforcing Policies...
(refs
/heads/master
) (8338c5) (c5b616)
[POLICY] Cannot push a non-fast-forward reference
error: hooks
/update
exited with error code 1
error: hook declined to update refs
/heads/master
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to
'git@gitserver:project.git'
|
這裏有幾個有趣的信息。首先,咱們能夠看到掛鉤運行的起點:
1
2
|
Enforcing Policies...
(refs
/heads/master
) (fb8c72) (c56860)
|
注意這是從 update 腳本開頭輸出到標準你輸出的。全部從腳本輸出的提示都會發送到客戶端,這點很重要。 下一個值得注意的部分是錯誤信息。