Git 屬性

2018-09-27 15:51 更新

一些設(shè)置項(xiàng)也能被運(yùn)用于特定的路徑中,這樣,Git 以對(duì)一個(gè)特定的子目錄或子文件集運(yùn)用那些設(shè)置項(xiàng)。這些設(shè)置項(xiàng)被稱為 Git 屬性,可以在你目錄中的.gitattributes文件內(nèi)進(jìn)行設(shè)置(通常是你項(xiàng)目的根目錄),也可以當(dāng)你不想讓這些屬性文件和項(xiàng)目文件一同提交時(shí),在.git/info/attributes進(jìn)行設(shè)置。

使用屬性,你可以對(duì)個(gè)別文件或目錄定義不同的合并策略,讓 Git 知道怎樣比較非文本文件,在你提交或簽出前讓 Git 過濾內(nèi)容。你將在這部分了解到能在自己的項(xiàng)目中使用的屬性,以及一些實(shí)例。

二進(jìn)制文件

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

識(shí)別二進(jìn)制文件

一些文件看起來像是文本文件,但其實(shí)是作為二進(jìn)制數(shù)據(jù)被對(duì)待。例如,在Mac上的Xcode項(xiàng)目含有一個(gè)以.pbxproj結(jié)尾的文件,它是由記錄設(shè)置項(xiàng)的IDE寫到磁盤的JSON數(shù)據(jù)集(純文本javascript數(shù)據(jù)類型)。雖然技術(shù)上看它是由ASCII字符組成的文本文件,但你并不認(rèn)為如此,因?yàn)樗_實(shí)是一個(gè)輕量級(jí)數(shù)據(jù)庫 — 如果有2人改變了它,你通常無法合并和比較內(nèi)容,只有機(jī)器才能進(jìn)行識(shí)別和操作,于是,你想把它當(dāng)成二進(jìn)制文件。

讓 Git 把所有pbxproj文件當(dāng)成二進(jìn)制文件,在.gitattributes文件中設(shè)置如下:

*.pbxproj -crlf -diff

現(xiàn)在,Git 會(huì)嘗試轉(zhuǎn)換和修正CRLF(回車換行)問題,也不會(huì)當(dāng)你在項(xiàng)目中運(yùn)行g(shù)it show或git diff時(shí),比較不同的內(nèi)容。在Git 1.6及之后的版本中,可以用一個(gè)宏代替-crlf -diff:

*.pbxproj binary

比較二進(jìn)制文件

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

下面,我們將會(huì)展示,如何使用轉(zhuǎn)化工具進(jìn)行二進(jìn)制文件的比較。

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

Word文檔

這個(gè)特性很酷,而且鮮為人知,因此我會(huì)結(jié)合實(shí)例來講解。首先,要解決的是最令人頭疼的問題:對(duì)Word文檔進(jìn)行版本控制。很多人對(duì)Word文檔又恨又愛,如果想對(duì)其進(jìn)行版本控制,你可以把文件加入到 Git 庫中,每次修改后提交即可。但這樣做沒有一點(diǎn)實(shí)際意義,因?yàn)檫\(yùn)行g(shù)it diff命令后,你只能得到如下的結(jié)果:

$ git diff
diff --git a/chapter1.doc b/chapter1.doc
index 88839c4..4afcb7c 100644
Binary files a/chapter1.doc and b/chapter1.doc differ

你不能直接比較兩個(gè)不同版本的Word文件,除非進(jìn)行手動(dòng)掃描,不是嗎? Git 屬性能很好地解決此問題,把下面的行加到.gitattributes文件:

*.doc diff=word

當(dāng)你要看比較結(jié)果時(shí),如果文件擴(kuò)展名是"doc",Git 調(diào)用"word"過濾器。什么是"word"過濾器呢?其實(shí)就是 Git 使用strings 程序,把Word文檔轉(zhuǎn)換成可讀的文本文件,之后再進(jìn)行比較:

$ git config diff.word.textconv catdoc

這個(gè)命令會(huì)在你的 .git/config 文件中增加一節(jié):

[diff "word"]
    textconv = catdoc

現(xiàn)在如果在兩個(gè)快照之間比較以.doc結(jié)尾的文件,Git 對(duì)這些文件運(yùn)用"word"過濾器,在比較前把Word文件轉(zhuǎn)換成文本文件。

下面展示了一個(gè)實(shí)例,我把此書的第一章納入 Git 管理,在一個(gè)段落中加入了一些文本后保存,之后運(yùn)行g(shù)it diff命令,得到結(jié)果如下:

$ 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 成功且簡(jiǎn)潔地顯示出我增加的文本"(See Chapter 3)"。工作的很完美!

OpenDocument文本文檔

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

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

*.odt diff=odt

然后在.git/config 文件中設(shè)置odt過濾器:

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

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

! /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";

然后把它設(shè)為可執(zhí)行文件

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

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

圖像文件

你還能用這個(gè)方法比較圖像文件。當(dāng)比較時(shí),對(duì)JPEG文件運(yùn)用一個(gè)過濾器,它能提煉出EXIF信息 — 大部分圖像格式使用的元數(shù)據(jù)。如果你下載并安裝了exiftool程序,可以用它參照元數(shù)據(jù)把圖像轉(zhuǎn)換成文本。比較的不同結(jié)果將會(huì)用文本向你展示:

$ echo '*.png diff=exif' >> .gitattributes
$ git config diff.exif.textconv exiftool

如果在項(xiàng)目中替換了一個(gè)圖像文件,運(yùn)行g(shù)it diff命令的結(jié)果如下:

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

你會(huì)發(fā)現(xiàn)文件的尺寸大小發(fā)生了改變。

關(guān)鍵字?jǐn)U展

使用SVN或CVS的開發(fā)人員經(jīng)常要求關(guān)鍵字?jǐn)U展。在 Git 中,你無法在一個(gè)文件被提交后修改它,因?yàn)?Git 會(huì)先對(duì)該文件計(jì)算校驗(yàn)和。然而,你可以在簽出時(shí)注入文本,在提交前刪除它。 Git 屬性提供了2種方式這么做。

首先,你能夠把blob的SHA-1校驗(yàn)和自動(dòng)注入文件的$Id$字段。如果在一個(gè)或多個(gè)文件上設(shè)置了此字段,當(dāng)下次你簽出分支的時(shí)候,Git 用blob的SHA-1值替換那個(gè)字段。注意,這不是提交對(duì)象的SHA校驗(yàn)和,而是blob本身的校驗(yàn)和:

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

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

$ rm test.txt
$ git checkout -- test.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $

然而,這樣的顯示結(jié)果沒有多大的實(shí)際意義。這個(gè)SHA的值相當(dāng)?shù)仉S機(jī),無法區(qū)分日期的前后,所以,如果你在CVS或Subversion中用過關(guān)鍵字替換,一定會(huì)包含一個(gè)日期值。

因此,你能寫自己的過濾器,在提交文件到暫存區(qū)或簽出文件時(shí)替換關(guān)鍵字。有2種過濾器,"clean"和"smudge"。在 .gitattributes文件中,你能對(duì)特定的路徑設(shè)置一個(gè)過濾器,然后設(shè)置處理文件的腳本,這些腳本會(huì)在文件簽出前("smudge",見圖 7-2)和提交到暫存區(qū)前("clean",見圖7-3)被調(diào)用。這些過濾器能夠做各種有趣的事。

2015-05-19/555b038046c3a

圖7-2. 簽出時(shí),"smudge"過濾器被觸發(fā)。

2015-05-19/555b038c2a393

圖7-3. 提交到暫存區(qū)時(shí),"clean"過濾器被觸發(fā)。

這里舉一個(gè)簡(jiǎn)單的例子:在暫存前,用indent(縮進(jìn))程序過濾所有C源代碼。在.gitattributes文件中設(shè)置"indent"過濾器過濾*.c文件:

*.c filter=indent

然后,通過以下配置,讓 Git 知道"indent"過濾器在遇到"smudge"和"clean"時(shí)分別該做什么:

$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat

于是,當(dāng)你暫存*.c文件時(shí),indent程序會(huì)被觸發(fā),在把它們簽出之前,cat程序會(huì)被觸發(fā)。但cat程序在這里沒什么實(shí)際作用。這樣的組合,使C源代碼在暫存前被indent程序過濾,非常有效。

另一個(gè)例子是類似RCS的$Date$關(guān)鍵字?jǐn)U展。為了演示,需要一個(gè)小腳本,接受文件名參數(shù),得到項(xiàng)目的最新提交日期,最后把日期寫入該文件。下面用Ruby腳本來實(shí)現(xiàn):

! /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$字符串中 — 此腳本很簡(jiǎn)單,你可以選擇你喜歡的編程語言來實(shí)現(xiàn)。把該腳本命名為expand_date,放到正確的路徑中,之后需要在 Git 中設(shè)置一個(gè)過濾器(dater),讓它在簽出文件時(shí)調(diào)用expand_date,在暫存文件時(shí)用Perl清除之:

$ git config filter.dater.smudge expand_date
$ git config filter.dater.clean 'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/"'

這個(gè)Perl小程序會(huì)刪除$Date$字符串里多余的字符,恢復(fù)$Date$原貌。到目前為止,你的過濾器已經(jīng)設(shè)置完畢,可以開始測(cè)試了。打開一個(gè)文件,在文件中輸入$Date$關(guān)鍵字,然后設(shè)置 Git 屬性:

$ echo '# $Date$' > date_test.txt
$ echo 'date*.txt filter=dater' >> .gitattributes

如果暫存該文件,之后再簽出,你會(huì)發(fā)現(xiàn)關(guān)鍵字被替換了:

$ 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$

雖說這項(xiàng)技術(shù)對(duì)自定義應(yīng)用來說很有用,但還是要小心,因?yàn)?gitattributes文件會(huì)隨著項(xiàng)目一起提交,而過濾器(例如:dater)不會(huì),所以,過濾器不會(huì)在所有地方都生效。當(dāng)你在設(shè)計(jì)這些過濾器時(shí)要注意,即使它們無法正常工作,也要讓整個(gè)項(xiàng)目運(yùn)作下去。

導(dǎo)出倉庫

Git屬性在導(dǎo)出項(xiàng)目歸檔時(shí)也能發(fā)揮作用。

export-ignore

當(dāng)產(chǎn)生一個(gè)歸檔時(shí),可以設(shè)置 Git 不導(dǎo)出某些文件和目錄。如果你不想在歸檔中包含一個(gè)子目錄或文件,但想他們納入項(xiàng)目的版本管理中,你能對(duì)應(yīng)地設(shè)置export-ignore屬性。

例如,在test/子目錄中有一些測(cè)試文件,在項(xiàng)目的壓縮包中包含他們是沒有意義的。因此,可以增加下面這行到 Git 屬性文件中:

test/ export-ignore

現(xiàn)在,當(dāng)運(yùn)行 git archive 來創(chuàng)建項(xiàng)目的壓縮包時(shí),那個(gè)目錄不會(huì)在歸檔中出現(xiàn)。

export-subst

還能對(duì)歸檔做一些簡(jiǎn)單的關(guān)鍵字替換。在第2章中已經(jīng)可以看到,可以以--pretty=format形式的簡(jiǎn)碼在任何文件中放入$Format:$ 字符串。例如,如果想在項(xiàng)目中包含一個(gè)叫作LAST_COMMIT的文件,當(dāng)運(yùn)行g(shù)it archive時(shí),最后提交日期自動(dòng)地注入進(jìn)該文件,可以這樣設(shè)置:

$ 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'

運(yùn)行g(shù)it archive后,打開該文件,會(huì)發(fā)現(xiàn)其內(nèi)容如下:

$ cat LAST_COMMIT
Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$

合并策略

通過 Git 屬性,還能對(duì)項(xiàng)目中的特定文件使用不同的合并策略。一個(gè)非常有用的選項(xiàng)就是,當(dāng)一些特定文件發(fā)生沖突,Git 會(huì)嘗試合并他們,而使用你這邊的合并。

如果項(xiàng)目的一個(gè)分支有歧義或比較特別,但你想從該分支合并,而且需要忽略其中某些文件,這樣的合并策略是有用的。例如,你有一個(gè)數(shù)據(jù)庫設(shè)置文件database.xml,在2個(gè)分支中他們是不同的,你想合并一個(gè)分支到另一個(gè),而不弄亂該數(shù)據(jù)庫文件,可以設(shè)置屬性如下:

database.xml merge=ours

如果合并到另一個(gè)分支,database.xml文件不會(huì)有合并沖突,顯示如下:

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

這樣,database.xml會(huì)保持原樣。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)