8.2 Git 屬性

2018-02-24 15:22 更新

Git 屬性

你也可以針對特定的路徑配置某些設(shè)置項(xiàng),這樣 Git 就只對特定的子目錄或子文件集運(yùn)用它們。 這些基于路徑的設(shè)置項(xiàng)被稱為 Git 屬性,可以在你的目錄下的?.gitattributes?文件內(nèi)進(jìn)行設(shè)置(通常是你的項(xiàng)目的根目錄)。如果不想讓這些屬性文件與其它文件一同提交,你也可以在.git/info/attributes?文件中進(jìn)行設(shè)置。

通過使用屬性,你可以對項(xiàng)目中的文件或目錄單獨(dú)定義不同的合并策略,讓 Git 知道怎樣比較非文本文件,或者讓 Git 在提交或檢出前過濾內(nèi)容。 在本節(jié),你將學(xué)習(xí)到一些能在自己的項(xiàng)目中用到的屬性,并看到幾個(gè)實(shí)際的例子。

二進(jìn)制文件

你可以用 Git 屬性讓 Git 知道哪些是二進(jìn)制文件(以防它沒有識別出來),并指示其如何處理這些文件。 例如,一些文本文件是由機(jī)器產(chǎn)生的,沒有辦法進(jìn)行比較,但是一些二進(jìn)制文件可以比較。 你將了解到怎樣讓 Git 區(qū)分這些文件。

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

有些文件表面上是文本文件,實(shí)質(zhì)上應(yīng)被作為二進(jìn)制文件處理。 例如,Mac 平臺(tái)上的 Xcode 項(xiàng)目會(huì)包含一個(gè)以?.pbxproj?結(jié)尾的文件,它通常是一個(gè)記錄項(xiàng)目構(gòu)建配置等信息的 JSON(純文本 Javascript 數(shù)據(jù)類型)數(shù)據(jù)集,由 IDE 寫入磁盤。 雖然技術(shù)上看它是由 UTF-8 編碼的文本文件,但你并不會(huì)希望將它當(dāng)作文本文件來處理,因?yàn)樗鋵?shí)是一個(gè)輕量級數(shù)據(jù)庫——如果有兩個(gè)人修改了它,你通常無法合并內(nèi)容,diff 的輸出也幫不上什么忙。 它本應(yīng)被機(jī)器處理。 因此,你想把它當(dāng)成二進(jìn)制文件。

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

*.pbxproj binary

現(xiàn)在,Git 不會(huì)嘗試轉(zhuǎn)換或修正回車換行(CRLF)問題,當(dāng)你在項(xiàng)目中運(yùn)行?git show?或?git diff?時(shí),Git 也不會(huì)比較或打印該文件的變化。

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

你也可以使用 Git 屬性來有效地比較兩個(gè)二進(jìn)制文件。 秘訣在于,告訴 Git 怎么把你的二進(jìn)制文件轉(zhuǎn)化為文本格式,從而能夠使用普通的 diff 方式進(jìn)行對比。

首先,讓我們嘗試用這個(gè)技術(shù)解決世人最頭疼的問題之一:對 Microsoft Word 文檔進(jìn)行版本控制。 大家都知道,Microsoft Word 幾乎是世上最難纏的編輯器,盡管如此,大家還是在用它。 如果想對 Word 文檔進(jìn)行版本控制,你可以把文件加入到 Git 庫中,每次修改后提交即可。但這樣做有什么實(shí)際意義呢? 畢竟運(yùn)行?git diff?命令后,你只能得到如下的結(jié)果:

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

除了檢出之后睜大眼睛逐行掃描,就真的沒有辦法直接比較兩個(gè)不同版本的 Word 文檔嗎? Git 屬性能很好地解決此問題。 把下面這行文本加到你的?.gitattributes?文件中:

*.docx diff=word

這告訴 Git 當(dāng)你嘗試查看包含變更的比較結(jié)果時(shí),所有匹配?.docx?模式的文件都應(yīng)該使用“word”過濾器。 “word”過濾器是什么? 我們現(xiàn)在就來設(shè)置它。 我們會(huì)對 Git 進(jìn)行配置,令其能夠借助?docx2txt?程序?qū)?Word 文檔轉(zhuǎn)為可讀文本文件,這樣不同的文件間就能夠正確比較了。

首先,你需要安裝?docx2txt;它可以從??下載。 按照INSTALL?文件的說明,把它放到你的可執(zhí)行路徑下。 接下來,你還需要寫一個(gè)腳本把輸出結(jié)果包裝成 Git 支持的格式。 在你的可執(zhí)行路徑下創(chuàng)建一個(gè)叫?docx2txt?文件,添加這些內(nèi)容:

#!/bin/bash
docx2txt.pl $1 -

別忘了用?chmod a+x?給這個(gè)文件加上可執(zhí)行權(quán)限。 最后,你需要配置 Git 來使用這個(gè)腳本:

$ git config diff.word.textconv docx2txt

現(xiàn)在如果在兩個(gè)快照之間進(jìn)行比較,Git 就會(huì)對那些以?.docx?結(jié)尾的文件應(yīng)用“word”過濾器,即docx2txt。 這樣你的 Word 文件就能被高效地轉(zhuǎn)換成文本文件并進(jìn)行比較了。

作為例子,我把本書的第一章另存為 Word 文件,并提交到 Git 版本庫。 接著,往其中加入一個(gè)新的段落。 運(yùn)行?git diff,輸出如下:

$ git diff
diff --git a/chapter1.docx b/chapter1.docx
index 0b013ca..ba25db5 100644
--- a/chapter1.docx
+++ b/chapter1.docx
@@ -2,6 +2,7 @@
 This chapter will be about getting started with Git. We will begin at the beginning by explaining some background on version control tools, then move on to how to get Git running on your system and finally how to get it setup to start working with. At the end of this chapter you should understand why Git is around, why you should use it and you should be all setup to do so.
 1.1. About Version Control
 What is "version control", and why should you care? Version control is a system that records changes to a file or set of files over time so that you can recall specific versions later. For the examples in this book you will use software source code as the files being version controlled, though in reality you can do this with nearly any type of file on a computer.
+Testing: 1, 2, 3.
 If you are a graphic or web designer and want to keep every version of an image or layout (which you would most certainly want to), a Version Control System (VCS) is a very wise thing to use. It allows you to revert files back to a previous state, revert the entire project back to a previous state, compare changes over time, see who last modified something that might be causing a problem, who introduced an issue and when, and more. Using a VCS also generally means that if you screw things up or lose files, you can easily recover. In addition, you get all this for very little overhead.
 1.1.1. Local Version Control Systems
 Many people's version-control method of choice is to copy files into another directory (perhaps a time-stamped directory, if they're clever). This approach is very common because it is so simple, but it is also incredibly error prone. It is easy to forget which directory you're in and accidentally write to the wrong file or copy over files you don't mean to.

Git 成功地挑出了我們添加的那句話“Testing: 1, 2, 3.”,一字不差。 還算不上完美——格式上的變動(dòng)顯示不出來——但已經(jīng)足夠了。

你還能用這個(gè)方法比較圖像文件。 其中一個(gè)辦法是,在比較時(shí)對圖像文件運(yùn)用一個(gè)過濾器,提煉出 EXIF 信息——這是在大部分圖像格式中都有記錄的一種元數(shù)據(jù)。 如果你下載并安裝了?exiftool程序,可以利用它將圖像轉(zhuǎn)換為關(guān)于元數(shù)據(jù)的文本信息,這樣比較時(shí)至少能以文本的形式顯示發(fā)生過的變動(dòng):

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

如果在項(xiàng)目中替換了一個(gè)圖像文件,運(yùn)行?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: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

你一眼就能看出文件大小和圖像尺寸發(fā)生了變化。

關(guān)鍵字展開

SVN 或 CVS 風(fēng)格的關(guān)鍵字展開(keyword expansion)功能經(jīng)常會(huì)被習(xí)慣于上述系統(tǒng)的開發(fā)者使用到。 在 Git 中,這項(xiàng)功能有一個(gè)主要問題,就是你無法利用它往文件中加入其關(guān)聯(lián)提交的相關(guān)信息,因?yàn)?Git 總是先對文件做校驗(yàn)和運(yùn)算(譯者注:Git 中提交對象的校驗(yàn)依賴于文件的校驗(yàn)和,而 Git 屬性針對特定文件或路徑,因此基于 Git 屬性的關(guān)鍵字展開無法僅根據(jù)文件反推出對應(yīng)的提交)。 不過,我們可以在檢出某個(gè)文件后對其注入文本,并在再次提交前刪除這些文本。 Git 屬性提供了兩種方法來達(dá)到這一目的。

一種方法是,你可以把文件所對應(yīng)數(shù)據(jù)對象的 SHA-1 校驗(yàn)和自動(dòng)注入到文件中的?$Id$?字段。 如果在一個(gè)或多個(gè)文件上設(shè)置了該屬性,下次當(dāng)你檢出相關(guān)分支的時(shí)候,Git 會(huì)用相應(yīng)數(shù)據(jù)對象的 SHA-1 值替換上述字段。 注意,這不是提交對象的 SHA-1 校驗(yàn)和,而是數(shù)據(jù)對象本身的校驗(yàn)和:

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

當(dāng)你下次檢出文件時(shí),Git 將注入數(shù)據(jù)對象的 SHA-1 校驗(yàn)和:

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

然而,這個(gè)結(jié)果的用途比較有限。 如果用過 CVS 或 Subversion 的關(guān)鍵字替換功能,我們會(huì)想加上一個(gè)時(shí)間戳信息——光有 SHA-1 校驗(yàn)和用途不大,它僅僅是個(gè)隨機(jī)字符串,你無法憑字面值來區(qū)分不同 SHA-1 時(shí)間上的先后。

因此 Git 屬性提供了另一種方法:我們可以編寫自己的過濾器來實(shí)現(xiàn)文件提交或檢出時(shí)的關(guān)鍵字替換。 一個(gè)過濾器由“clean”和“smudge”兩個(gè)子過濾器組成。 在?.gitattributes?文件中,你能對特定的路徑設(shè)置一個(gè)過濾器,然后設(shè)置文件檢出前的處理腳本(“smudge”,見?Figure?8-2)和文件暫存前的處理腳本(“clean”,見?Figure?8-3)。 這兩個(gè)過濾器能夠被用來做各種有趣的事。

Figure 8-3.?“clean”過濾器會(huì)在文件被暫存時(shí)觸發(fā)

在(Git 源碼中)實(shí)現(xiàn)這個(gè)特性的原始提交信息里給出了一個(gè)簡單的例子:在提交前,用?indent程序過濾所有 C 源碼。 你可以在?.gitattributes?文件中對 filter 屬性設(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

在這個(gè)例子中,當(dāng)你暫存?*.c?文件時(shí),indent?程序會(huì)先被觸發(fā);在把它們檢出回硬盤時(shí),cat程序會(huì)先被觸發(fā)。?cat?在這里沒什么實(shí)際作用:它僅僅把輸入的數(shù)據(jù)重新輸出。 這樣的組合可以有效地在暫存前用?indent?過濾所有的 C 源碼。

另一個(gè)有趣的例子是實(shí)現(xiàn) RCS 風(fēng)格的?$Date$?關(guān)鍵字展開。 要想演示這個(gè)例子,我們需要實(shí)現(xiàn)這樣的一個(gè)小腳本:接受文件名參數(shù),得到項(xiàng)目的最新提交日期,并把日期寫入該文件。 下面是一個(gè)實(shí)現(xiàn)了該功能的 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 + '$')

這個(gè)腳本從?git log?中得到最新提交日期,將其注入所有輸入文件的?$Date$?字段,并輸出結(jié)果——你可以使用最順手的語言輕松實(shí)現(xiàn)一個(gè)類似的腳本。 把該腳本命名為?expand_date,放到你的可執(zhí)行路徑中。 現(xiàn)在,你需要在 Git 中設(shè)置一個(gè)過濾器(就叫它?dater?吧),讓它在檢出文件時(shí)調(diào)用你的?expand_date?來注入時(shí)間戳,完成 smudge 操作。 暫存文件時(shí)的 clean 操作則是用一行 Perl 表達(dá)式清除注入的內(nèi)容:

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

這段 Perl 代碼會(huì)刪除?$Date$?后面注入的內(nèi)容,恢復(fù)它的原貌。 過濾器終于準(zhǔn)備完成了,是時(shí)候測試一下。創(chuàng)建一個(gè)帶有?$Date$?關(guān)鍵字的文件,然后給它設(shè)置一個(gè) Git 屬性,關(guān)聯(lián)我們的新過濾器:

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

自定義過濾器真的很強(qiáng)大。 不過你需要注意的是,因?yàn)?.gitattributes?文件會(huì)隨著項(xiàng)目一起提交,而過濾器(例如這里的?dater)不會(huì),所以過濾器有可能會(huì)失效。 當(dāng)你在設(shè)計(jì)這些過濾器時(shí),要注重容錯(cuò)性——它們在出錯(cuò)時(shí)應(yīng)該能優(yōu)雅地退出,從而不至于影響項(xiàng)目的正常運(yùn)行。

導(dǎo)出版本庫

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

export-ignore

當(dāng)歸檔的時(shí)候,可以設(shè)置 Git 不導(dǎo)出某些文件和目錄。 如果你不想在歸檔中包含某個(gè)子目錄或文件,但想把它們納入項(xiàng)目的版本管理中,你可以在?export-ignore?屬性中指定它們。

例如,假設(shè)你在?test/?子目錄下有一些測試文件,不希望它們被包含在項(xiàng)目導(dǎo)出的壓縮包(tarball)中。 你可以增加下面這行到 Git 屬性文件中:

test/ export-ignore

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

export-subst

在導(dǎo)出文件進(jìn)行部署的時(shí)候,你可以將?git log?的格式化和關(guān)鍵字展開處理應(yīng)用于被?export-subst?屬性標(biāo)記的部分文件。

舉個(gè)例子,如果你想在項(xiàng)目中包含一個(gè)叫做?LAST_COMMIT?的文件,并在運(yùn)行?git archive?的時(shí)候自動(dòng)向它注入最新提交的元數(shù)據(jù),可以像這樣設(shè)置該文件:

$ echo 'Last commit date: $Format:%cd by %aN$' > LAST_COMMIT
$ echo "LAST_COMMIT export-subst" >> .gitattributes
$ git add LAST_COMMIT .gitattributes
$ git commit -am 'adding LAST_COMMIT file for archives'

運(yùn)行?git archive?之后,該文件被歸檔后的內(nèi)容會(huì)被替換成這樣:

$ git archive HEAD | tar xCf ../deployment-testing -
$ cat ../deployment-testing/LAST_COMMIT
Last commit date: Tue Apr 21 08:38:48 2009 -0700 by Scott Chacon

你也可以用諸如提交信息或者任意的 git 注解進(jìn)行替換,并且 git log 還能做簡單的字詞包裝:

$ echo '$Format:Last commit: %h by %aN at %cd%n%+w(76,6,9)%B$' > LAST_COMMIT
$ git commit -am 'export-subst 使用 git log 的自定義格式化工具

git archive 直接使用 git log 的 `pretty=format:`
處理器,并在輸出中移除兩側(cè)的 `$Format:` 和 `$`
標(biāo)記。
'
$ git archive @ | tar xfO - LAST_COMMIT
Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14:04 2015 -0700
       export-subst 使用 git log 的自定義格式化工具

         git archive 直接使用 git log 的 `pretty=format:` 處理器,并
         在輸出中移除兩側(cè)的 `$Format:` 和 `$` 標(biāo)記。

由此得到的歸檔適用于(當(dāng)前的)部署工作。然而和其他的導(dǎo)出歸檔一樣,它并不適用于后繼的部署工作。

合并策略

通過 Git 屬性,你還能對項(xiàng)目中的特定文件指定不同的合并策略。 一個(gè)非常有用的選項(xiàng)就是,告訴 Git 當(dāng)特定文件發(fā)生沖突時(shí)不要嘗試合并它們,而是直接使用你這邊的內(nèi)容。

考慮如下場景:項(xiàng)目中有一個(gè)分叉的或者定制過的特性分支,你希望該分支上的更改能合并回你的主干分支,同時(shí)需要忽略其中某些文件。此時(shí)這個(gè)合并策略就能派上用場。 假設(shè)你有一個(gè)數(shù)據(jù)庫設(shè)置文件database.xml,在兩個(gè)分支中它是不同的,而你想合并另一個(gè)分支到你的分支上,又不想弄亂該數(shù)據(jù)庫文件。 你可以設(shè)置屬性如下:

database.xml merge=ours

然后定義一個(gè)虛擬的合并策略,叫做?ours

$ git config --global merge.ours.driver true

如果你合并了另一個(gè)分支,database.xml?文件不會(huì)有合并沖突,相反會(huì)顯示如下信息:

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

這里,database.xml?保持了主干分支中的原始版本。

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號