8.2 Git 屬性

2018-02-24 15:22 更新

Git 屬性

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

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

二進制文件

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

識別二進制文件

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

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

*.pbxproj binary

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

比較二進制文件

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

首先,讓我們嘗試用這個技術(shù)解決世人最頭疼的問題之一:對 Microsoft Word 文檔進行版本控制。 大家都知道,Microsoft Word 幾乎是世上最難纏的編輯器,盡管如此,大家還是在用它。 如果想對 Word 文檔進行版本控制,你可以把文件加入到 Git 庫中,每次修改后提交即可。但這樣做有什么實際意義呢? 畢竟運行?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

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

*.docx diff=word

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

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

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

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

$ git config diff.word.textconv docx2txt

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

作為例子,我把本書的第一章另存為 Word 文件,并提交到 Git 版本庫。 接著,往其中加入一個新的段落。 運行?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.”,一字不差。 還算不上完美——格式上的變動顯示不出來——但已經(jīng)足夠了。

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

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

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

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

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

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

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

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

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

在(Git 源碼中)實現(xiàn)這個特性的原始提交信息里給出了一個簡單的例子:在提交前,用?indent程序過濾所有 C 源碼。 你可以在?.gitattributes?文件中對 filter 屬性設置“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?程序會先被觸發(fā);在把它們檢出回硬盤時,cat程序會先被觸發(fā)。?cat?在這里沒什么實際作用:它僅僅把輸入的數(shù)據(jù)重新輸出。 這樣的組合可以有效地在暫存前用?indent?過濾所有的 C 源碼。

另一個有趣的例子是實現(xiàn) RCS 風格的?$Date$?關(guān)鍵字展開。 要想演示這個例子,我們需要實現(xiàn)這樣的一個小腳本:接受文件名參數(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 + '$')

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

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

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

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

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

導出版本庫

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

export-ignore

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

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

test/ export-ignore

現(xiàn)在,當你運行?git archive?來創(chuàng)建項目的壓縮包時,那個目錄不會被包括在歸檔中。

export-subst

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

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

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

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

$ 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 注解進行替換,并且 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:` 和 `$`
標記。
'
$ 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:` 和 `$` 標記。

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

合并策略

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

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

database.xml merge=ours

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

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

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

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

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

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

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號