Git屬性

2018-07-10 17:27 更新

Git屬性

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

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

二進(jìn)制文件

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

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

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

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

*.pbxproj -crlf -diff

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

*.pbxproj binary

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

在 Git 中,你能利用 Git 屬性來有效地比較二進(jìn)制文件??梢栽O(shè)置 Git 把二進(jìn)制數(shù)據(jù)轉(zhuǎn)換成文本格式,用通常的diff來比較。

We'll make use of the both described approaches to get usable diffs for some widely used binary formats.

Side note: There are different kinds of binary formats with a text content, which are hard to find usable converter for. In such a case you could try to extract a text from your file with the strings program. Some of these files may use an UTF-16 encoding or other "codepages" and strings won’t find anything useful in there. Your mileage may vary. However, strings is available on most Mac and Linux systems, so it may be a good first try to do this with many binary formats.

MS Word files

這個特性很酷,而且鮮為人知,因此我會結(jié)合實例來講解。首先,要解決的是最令人頭疼的問題:對Word文檔進(jìn)行版本控制。很多人對Word文檔又恨又愛,如果想對其進(jìn)行版本控制,你可以把文件加入到 Git 庫中,每次修改后提交即可。但這樣做沒有一點實際意義,因為運行git 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

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

*.doc diff=word

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

$ git config diff.word.textconv catdoc

This command adds a section to your .git/config that looks like this:

[diff "word"]
    textconv = catdoc

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

下面展示了一個實例,我把此書的第一章納入 Git 管理,在一個段落中加入了一些文本后保存,之后運行git 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 成功且簡潔地顯示出我增加的文本"Let’s see if this works"。雖然有些瑕疵,在末尾顯示了一些隨機的內(nèi)容,但確實可以比較了。如果你能找到或自己寫個Word到純文本的轉(zhuǎn)換器的話,效果可能會更好。 strings可以在大部分Mac和Linux系統(tǒng)上運行,所以它是處理二進(jìn)制格式的第一選擇。

OpenDocument Text files

The same approach that we used for MS Word files (*.doc) can be used for OpenDocument Text files (*.odt) created by OpenOffice.org.

Add the following line to your .gitattributes file:

*.odt diff=odt

Now set up the odt diff filter in .git/config:

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

OpenDocument files are actually zip’ped directories containing multiple files (the content in an XML format, stylesheets, images, etc.). We’ll need to write a script to extract the content and return it as plain text. Create a file /usr/local/bin/odt-to-txt (you are free to put it into a different directory) with the following content:

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

And make it executable

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

Now git diff will be able to tell you what changed in .odt files.

Image files

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

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

如果在項目中替換了一個圖像文件,運行git 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

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

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

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

首先,你能夠把blob的SHA-1校驗和自動注入文件的$Id$字段。如果在一個或多個文件上設(shè)置了此字段,當(dāng)下次你簽出分支的時候,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 $

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

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

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

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

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

*.c     filter=indent

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

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

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

另一個例子是類似RCS的$Date$關(guān)鍵字?jǐn)U展。為了演示,需要一個小腳本,接受文件名參數(shù),得到項目的最新提交日期,最后把日期寫入該文件。下面用Ruby腳本來實現(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$字符串中 — 此腳本很簡單,你可以選擇你喜歡的編程語言來實現(xiàn)。把該腳本命名為expand_date,放到正確的路徑中,之后需要在 Git 中設(shè)置一個過濾器(dater),讓它在簽出文件時調(diào)用expand_date,在暫存文件時用Perl清除之:

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

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

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

如果暫存該文件,之后再簽出,你會發(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$

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

導(dǎo)出倉庫

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

export-ignore

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

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

test/ export-ignore

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

export-subst

還能對歸檔做一些簡單的關(guān)鍵字替換。在第2章中已經(jīng)可以看到,可以以--pretty=format形式的簡碼在任何文件中放入$Format:$ 字符串。例如,如果想在項目中包含一個叫作LAST_COMMIT的文件,當(dāng)運行git archive時,最后提交日期自動地注入進(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'

運行git archive后,打開該文件,會發(fā)現(xiàn)其內(nèi)容如下:

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

合并策略

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

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

database.xml merge=ours

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

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

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


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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號