7.2 自定義 Git - Git屬性

Git屬性

一些設置項也能被運用於特定的路徑中,這樣,Git 以對一個特定的子目錄或子文件集運用那些設置項。這些設置項被稱爲 Git 屬性,可以在你目錄中的.gitattributes文件內進行設置(通常是你項目的根目錄),也可以當你不想讓這些屬性文件和項目文件一同提交時,在.git/info/attributes進行設置。

使用屬性,你可以對個別文件或目錄定義不同的合併策略,讓 Git 知道怎樣比較非文本文件,在你提交或簽出前讓 Git 過濾內容。你將在這部分瞭解到能在自己的項目中使用的屬性,以及一些實例。

二進制文件

你可以用 Git 屬性讓其知道哪些是二進制文件(以防 Git 沒有識別出來),以及指示怎樣處理這些文件,這點很酷。例如,一些文本文件是由機器產生的,而且無法比較,而一些二進制文件可以比較 — 你將會瞭解到怎樣讓 Git 識別這些文件。

識別二進制文件

一些文件看起來像是文本文件,但其實是作爲二進制數據被對待。例如,在Mac上的Xcode項目含有一個以.pbxproj結尾的文件,它是由記錄設置項的IDE寫到磁盤的JSON數據集(純文本javascript數據類型)。雖然技術上看它是由ASCII字符組成的文本文件,但你並不認爲如此,因爲它確實是一個輕量級數據庫 — 如果有2人改變了它,你通常無法合併和比較內容,只有機器才能進行識別和操作,於是,你想把它當成二進制文件。

讓 Git 把所有pbxproj文件當成二進制文件,在.gitattributes文件中設置如下:

*.pbxproj -crlf -diff

現在,Git 會嘗試轉換和修正CRLF(回車換行)問題,也不會當你在項目中運行git showgit diff時,比較不同的內容。在Git 1.6及之後的版本中,可以用一個宏代替-crlf -diff

*.pbxproj binary

比較二進制文件

你可以使用 Git 屬性來有效地比較兩個二進制文件(binary files,譯註:指非文本文件)。那麼第一步要做的是,告訴 Git 怎麼把你的二進制文件轉化爲純文本格式,從而讓普通的 diff 命令可以進行文本對比。但是,我們怎麼把二進制文件轉化爲文本呢?最好的解決方法是找到一個轉換工具幫助我們進行轉化。但是,大部分的二進制文件不能表示爲可讀的文本,例如語音文件就很難轉化爲文本文件。如果你遇到這些情況,比較簡單的解決方法是從這些二進制文件中獲取元數據。雖然這些元數據並不能完全描述一個二進制文件,但大多數情況下,都是能夠概括文件情況的。

下面,我們將會展示,如何使用轉化工具進行二進制文件的比較。

邊注:有一些二進制文件雖然包含文字,但是卻難以轉換。(譯註:例如 Word 文檔。)在這些情況,你可以嘗試使用 strings 工具來獲取其中的文字。但如果當這些文檔包含 UTF-16 編碼,或者其他代碼頁(codepages),strings 也可能無補於事。strings 在大部分的 Mac 和 Linux 下都有安裝。當遇到有二進制文件需要轉換的時候,你可以試試這個工具。

Word文檔

這個特性很酷,而且鮮爲人知,因此我會結合實例來講解。首先,要解決的是最令人頭疼的問題:對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 catdoc

這個命令會在你的 .git/config 文件中增加一節:

[diff "word"]
    textconv = catdoc

現在如果在兩個快照之間比較以.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
@@ -128,7 +128,7 @@ and data size)
 Since its birth in 2005, Git has evolved and matured to be easy to use
 and yet retain these initial qualities. It’s incredibly fast, it’s
 very efficient with large projects, and it has an incredible branching
-system for non-linear development.
+system for non-linear development (See Chapter 3).

Git 成功且簡潔地顯示出我增加的文本"(See Chapter 3)"。工作的很完美!

OpenDocument文本文檔

我們用於處理Word文檔(*.doc)的方法同樣適用於處理OpenOffice.org創建的OpenDocument文本文檔(*.odt)。

把下面這行添加到.gitattributes文件:

*.odt diff=odt

然後在.git/config 文件中設置odt過濾器:

[diff "odt"]
    binary = true
    textconv = /usr/local/bin/odt-to-txt

OpenDocument文檔實際上是多個文件(包括一個XML文件和表格、圖片等文件)的壓縮包。我們需要寫一個腳本來提取其中純文本格式的內容。創建一個文件/usr/local/bin/odt-to-txt(你也可以放到其他目錄下),寫入下面內容:

#! /usr/bin/env perl
# Simplistic OpenDocument Text (.odt) to plain text converter.
# Author: Philipp Kempgen

if (! defined($ARGV[0])) {
    print STDERR "No filename given!\n";
    print STDERR "Usage: $0 filename\n";
    exit 1;
}

my $content = '';
open my $fh, '-|', 'unzip', '-qq', '-p', $ARGV[0], 'content.xml' or die $!;
{
    local $/ = undef;  # slurp mode
    $content = <$fh>;
}
close $fh;
$_ = $content;
s/<text:span\b[^>]*>//g;           # remove spans
s/<text:h\b[^>]*>/\n\n*****  /g;   # headers
s/<text:list-item\b[^>]*>\s*<text:p\b[^>]*>/\n    --  /g;  # list items
s/<text:list\b[^>]*>/\n\n/g;       # lists
s/<text:p\b[^>]*>/\n  /g;          # paragraphs
s/<[^>]+>//g;                      # remove all XML tags
s/\n{2,}/\n\n/g;                   # remove multiple blank lines
s/\A\n+//;                         # remove leading blank lines
print "\n", $_, "\n\n";

然後把它設爲可執行文件

chmod +x /usr/local/bin/odt-to-txt

現在git diff命令就可以顯示.odt文件的變更了。

圖像文件

你還能用這個方法比較圖像文件。當比較時,對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:17 10:12:35-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本身的校驗和:

$ echo '*.txt ident' >> .gitattributes
$ echo '$Id$' > test.txt

下次簽出文件時,Git 入了blob的SHA值:

$ rm test.txt
$ git checkout -- test.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文件:

*.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

當產生一個歸檔時,可以設置 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,在2個分支中他們是不同的,你想合併一個分支到另一個,而不弄亂該數據庫文件,可以設置屬性如下:

database.xml merge=ours

如果合併到另一個分支,database.xml文件不會有合併衝突,顯示如下:

$ git merge topic
Auto-merging database.xml
Merge made by recursive.

這樣,database.xml會保持原樣。


本文來自 http://git-scm.com/ 保存下來,方便自己查閱。


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