Pro Git 7.2 Git 屬性
一些設置項(settings)也能被運用於特定的路徑中,這樣,Git 只對這個特定的子目錄或某些檔檔應用這些設置值。這些設置項被稱為 Git 屬性,可以在你目錄中的 .gitattributes 檔內進行設置(通常是你專案的根目錄),也可以當你不想讓這些屬性檔和專案檔案一同提交時,在 .git/info/attributes 進行設置。
使用屬性,你可以對個別檔或目錄定義不同的合併策略,讓 Git 知道怎樣比較非文字檔,在你提交或簽出(check out)前讓 Git 過濾內容。你將在這個章節裏瞭解到能在自己的專案中使用的屬性,以及一些實例。
二進位檔案
你可以用 Git 屬性讓其知道哪些是二進位檔案(以防 Git 沒有識別出來),以及指示怎樣處理這些檔,這點很酷。例如,一些文字檔是由機器產生的,而且無法比較,而一些二進位檔案可以比較 — 你將會瞭解到怎樣讓 Git 識別這些檔。
識別二進位檔案
一些檔看起來像是文字檔,但其實是作為二進位資料被對待。例如,在 Mac 上的 Xcode 專案含有一個以 .pbxproj 結尾的檔,它是由記錄設置項的 IDE 寫到磁片的 JSON 資料集(純文字 javascript 資料類型)。雖然技術上看它是由 ASCII 字元組成的文字檔,但你並不認為如此,因為它確實是一個羽量級資料庫 — 如果有兩個人改變了它,你通常無法合併和比較內容,只有機器才能進行識別和操作,於是,你想把它當成二進位檔案。
讓 Git 把所有 pbxproj 檔當成二進位檔案,在 .gitattributes 文件中設置如下:
*.pbxproj -crlf -diff
現在,Git 不會嘗試轉換和修正 CRLF(回車換行)問題;也不會當你在專案中運行 git show 或 git diff 時,比較不同的內容。在 Git 1.6 及之後的版本中,可以用一個巨集代替 -crlf -diff:
*.pbxproj binary
比較二進位檔案
在 Git 1.6 及以上版本中,你能利用 Git 屬性來有效地比較二進位檔案。可以設置 Git 把二進位資料轉換成文本格式,用通常的 diff 來比較。
這個特性很酷,而且鮮為人知,因此我會結合實例來講解。首先,要解決的是最令人頭疼的問題:對 Word 文檔進行版本控制。很多人對 Word 文檔又恨又愛,如果想對其進行版本控制,你可以把檔案加入到 Git 倉庫中,每次修改後提交即可。但這樣做沒有一點實際意義,因為運行 git diff 命令後,你只能得到如下的結果:
$ 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 文件:
*.doc diff=word
當你要看比較結果時,如果檔副檔名是 ”doc”,Git 會使用 ”word” 篩檢程式。什麼是 ”word” 篩檢程式呢?其實就是 Git 使用 strings 程式,把 Word 文檔轉換成可讀的文字檔,之後再進行比較:
$ git config diff.word.textconv strings
現在如果在兩個快照之間比較以 .doc 結尾的檔,Git 對這些檔運用 ”word” 篩檢程式,在比較前把 Word 檔轉換成文字檔。
下面展示了一個實例,我把此書的第一章納入 Git 管理,在一個段落中加入了一些文本後保存,之後運行 git diff 命令,得到結果如下:
$ 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 程式,可以用它參照中繼資料把圖像轉換成文本。比較的不同結果將會用文本向你展示:
$ echo '*.png diff=exif' >> .gitattributes
$ git config diff.exif.textconv exiftool
如果在專案中替換了一個影像檔,運行 git diff 命令的結果如下:
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 屬性提供了兩種方式來進行。
首先,你能夠把 blob 的 SHA-1 校驗和自動注入檔案的 $Id$ 欄位。如果在一個或多個檔案上設置了此欄位,當下次你簽出分支的時候,Git 用 blob 的 SHA-1 值替換那個欄位。注意,這不是提交對象的 SHA 校驗和,而是 blob 本身的校驗和:
$ echo '*.txt ident' >> .gitattributes
$ echo '$Id$' > test.txt
下次簽出文件時,Git 注入了 blob 的 SHA 值:
$ rm text.txt
$ git checkout -- text.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
然而,這樣的顯示結果沒有多大的實際意義。這個 SHA 的值相當地隨機,無法區分日期的前後,所以,如果你在 CVS 或 Subversion 中用過關鍵字替換,一定會包含一個日期值。
因此,你能寫自己的篩檢程式,在提交文件到暫存區或簽出文件時替換關鍵字。有兩種篩檢程式,”clean” 和 ”smudge”。在 .gitattributes 檔中,你能對特定的路徑設置一個篩檢程式,然後設置處理檔案的腳本,這些腳本會在檔案簽出前(”smudge”,見圖 7-2)和提交到暫存區前(”clean”,見圖7-3)被調用。這些篩檢程式能夠做各種有趣的事。
這裡舉一個簡單的例子:在暫存前,用 indent(縮進)程式過濾所有C原始程式碼。在. gitattributes 檔中設置 ”indent” 篩檢程式過濾 *.c 文件:
*.c filter=indent
然後,通過以下配置,讓 Git 知道 ”indent” 篩檢程式在遇到 ”smudge” 和 ”clean” 時分別該做什麼:
$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat
於是,當你暫存 *.c 檔時,indent程式會被觸發,在把它們簽出之前,cat程式會被觸發。但cat程式在這裡沒什麼實際作用。這樣的組合,使C原始程式碼在暫存前被indent程式過濾,非常有效。
另一個例子是類似 RCS 的 $Date$ 關鍵字擴展。為了演示,需要一個小腳本,接受檔案名參數,得到專案的最新提交日期,最後把日期寫入該檔。下麵用 Ruby 腳本來實現:
#! /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 清除之:
$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'
這個 Perl 小程式會刪除 $Date$ 字串裡多餘的字元,恢復 $Date$ 原貌。到目前為止,你的篩檢程式已經設置完畢,可以開始測試了。打開一個檔,在檔中輸入 $Date$ 關鍵字,然後設置 Git 屬性:
$ echo '# $Date$' > date_test.txt
$ echo 'date*.txt filter=dater' >> .gitattributes
如果暫存該檔,之後再簽出,你會發現關鍵字被替換了:
$ 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
當產生一個歸檔(archive)時,可以設置 Git 不匯出某些檔案和目錄。如果你不想在歸檔中包含一個子目錄或檔案,但想將他們納入專案的版本管理中,你能對應地設置 export-ignore 屬性。
例如,在 test/ 子目錄中有一些測試檔,在專案的壓縮包中包含他們是沒有意義的。因此,可以增加下面這行到 Git 屬性檔中:
test/ export-ignore
現在,當運行 git archive 來創建專案的壓縮包時,那個目錄不會在歸檔中出現。
export-subst
還能對歸檔做一些簡單的關鍵字替換。在第2章中已經可以看到,可以以 --pretty=format 形式的簡碼在任何檔中放入 $Format:$ 字串。例如,如果想在專案中包含一個叫作 LAST_COMMIT 的檔,當運行 git archive 時,最後提交日期自動地注入進該檔,可以這樣設置:
$ 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 後,打開該檔,會發現其內容如下:
$ cat LAST_COMMIT
Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$
合併策略
通過 Git 屬性,還能對專案中的特定檔使用不同的合併策略。一個非常有用的選項就是,當一些特定檔發生衝突,Git 不會嘗試合併他們,而使用你這邊的合併。
如果專案的一個分支有歧義或比較特別,但你想從該分支合併,而且需要忽略其中某些檔,這樣的合併策略是有用的。例如,你有一個資料庫設置檔 database.xml,在兩個分支中他們是不同的,你想合併一個分支到另一個,而不弄亂該資料庫檔,可以設置屬性如下:
database.xml merge=ours
如果合併到另一個分支,database.xml 檔不會有合併衝突,顯示如下:
$ git merge topic
Auto-merging database.xml
Merge made by recursive.
這樣,database.xml會保持原樣。