gitattributes中的filter


.gitattributes文件就是一個簡單的text文本文件,它的作用是gives attributes to pathnames.

該文件中的一些配置可以為某些特定目錄或者文件來設置,這樣Git就僅僅對一個子目錄或者文件子集來應用規則。這些path-specific配置被稱為Git atttributes並且要么在你的project root目錄中的.gitattributes文件來配置或者如果你不想把.gitattributes文件commit到項目中的話,那么也可以放在.git/info/attributes文件中。

該文件中的每一行都有如下格式:

pattern    attr1 attr2 ...

當pattern匹配了目錄時,那么在本行中所列出的attributes將會被賦予該目錄

每一個屬性對於特定的目錄可以是以下狀態:

Set
The path has the attribute with special value "true"; this is specified by listing only the name of the attribute in the attribute list.

Unset
The path has the attribute with special value "false"; this is specified by listing the name of the attribute prefixed with a dash - in the attribute list.

Set to a value
The path has the attribute with specified string value; this is specified by listing the name of the attribute followed by an equal sign = and its value in the attribute list.

Unspecified
No pattern matches the path, and nothing says if the path has or does not have the attribute, the attribute for the path is said to be Unspecified.

 

使用attributes,你可以做以下事情:比如對不同的文件集或者項目中的不同目錄指定不同的merge strategy,告訴git如何來diff非text文件,或者Git在你check in/check out時 git如何來過濾文件的內容。

binary文件

你能使用git attributes的功能來實現的一個很cool的事情是:告訴git哪些文件是binary二進制文件並且給Git關於如何處理這些文件的特定的指令。比如,一些text文件有可能是由機器產生的,而不能diff出來,然而有些binary文件卻可以被diff.你將需要告訴Git誰是誰。

一些文件看起來像是text文件,但是可能其目的卻是被用作binary data.比如,Xcode項目包含一個文件,以.pbxroj為擴展名,這時一耳光簡單的JSON文件,它記錄了你的build配置等信息。雖然技術上說,它是一個文本文(因為它都是UTF-8編碼的),但是實際上它確實一個輕量級的數據庫,因此它並不是一個真正的文本文件,因為他的內容是不能簡單的merge或者diff的。文件本身主要是由機器來使用的,簡單的說,你應該把它當做binary文件來對待。

為了告訴git將pbxproj文件都以binray data來處理,需要增加以下行到.gitattributes文件中:

*.pbxproj binary

現在Git將不會試圖變換或者fix CRLF的問題;也不會當你運行git show或者git diff時試圖計算或者打印一一些changes。

Diffing Binary Files

你可以使用Git attributes的功能有效地diff二進制文件。你可以通過告訴git如何轉換你的二進制文件為可以被git diff來比較的通用text格式來實現二進制文件的比較功能。

首先,使用這個技術來解決令人煩惱的word文件變更比較吧。如果你對word文檔做版本控制,你會發現一旦執行git diff命令,只有如下毫無意義的信息輸出:

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

你希望比較二者的不同,只能把兩個版本拿下來人工比較。而借助git attributes特性,你可以實現像文本文件一樣來比較不同。在.gitattributes文件中增加:

*.docx diff=word

這將告訴Git,任何匹配擴展名為.docx的文檔當使用git diff命令查看變更時將使用"word" filter命令。那么什么是word filter呢?你必須要配置它。這里你可以配置git使用docx2txt程序來轉換word文檔為一個可讀的text文件,這樣就能輕松實現diff功能了。

首先,你要安裝docx2txt程序,隨后你需要寫一個wrapper腳本來變換輸出為git期望的格式。創建一個docx2txt的腳本:

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

將上述腳本chmod a+x,以便可以執行。最后,配置git來使用這個腳本:

$ git config diff.word.textconv docx2txt

現在git知道如果試圖執行兩個快照之間的diff時,任何以.docx為擴展名的文件,它都應該執行word filter(被定義為docx2txt程序)這將有效地在試圖比較他們之前轉換word文件為普通的text文件以便比較。

下面是一個輸出的例子:

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

另外一個你可以通過這種模式來解決的二進制文件比較問題的是:image文件比較。一種方法是通過一個能夠抽取image文件的EXIF信息(metadata信息)的filter來執行image files比較。你可以下載並且安裝exiftool程序,你使用它來轉換image文件為你所需要的關於metadata的text文件。

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

keyword expansion

SVN-/CVS-Style keyword expansion經常被開發人員提出需求。在git中這個keyword expansion功能的主要問題是:你不能修改commit的任何信息,雖然注入text到一個文件中不被允許,因為git使用checksum機制來確保文件安全。然而,你可以在checkout時注入,而在放到commit時刪除一段text來規避這種情況。

首先你可以自動注入一個blob的SHA-1 checksum到一個$Id$域中。如果你在一個文件或一組文件中設置這個attribute,那么下一次你checkout那個branch時,git會自動更換使用那個blob的SHA-1信息來更換那個$Id$域。需要注意的是那個id不是commit的sha-1而是blob的sha-1 checksum:

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

下一次你checkout這個文件時,git將注入blob的sha-1:

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

然而,那個結果卻只能限制使用。如果你使用過CVS的keyword substitution功能,你可以包含一個datastamp信息。

如何實現呢?你可以通過寫一個你自己的filter來實現黨文件commit/checkout時自動替換這個信息。這些功能需要通過"clean"和"smudge" filter來實現。在.gitattributes文件中,你可以為一些特定路徑來設置一個filter,隨后設置當他們被checkout(smudge)時需要執行的處理該文件的腳本程序,以及當他們將被stage時(“clean"filter).這些filter可以被用來執行許多有趣的事情:

當checkout時執行上述smudge filter;

當文件被staged(commit)時執行clean filter

前面介紹過通過一個indent filter program來執行C源代碼在commit之前自動indent的功能。回顧一下:你通過設置.gitattributes中的filter屬性來配置所有*.c文件將使用indent filter

*.c filter=indent

然后,告訴git,indent filter在smudge和clean時,該indent filter應該做什么:

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

在上面這個例子中,當你commit *.c文件時,git將在commit之前執行indent程序,在checkout之前執行cat程序。這個組合便有效地在commit之前filter了所有c源程序代碼。

另外一個有趣的例子是:獲取$Date$keyword expansion.為了有效實現它,你需要一個小的腳本,該腳本帶一個文件名,指出最近的commit日期,並且插入到文件中。下面是一耳光ruby script:

#! /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命令中獲取最新的commit日期,並把該信息放到任何它在stdin中發現的$Date$ string,並且打印結果。你可以將該文件命名為expand_date並且放到你的path中。現在你需要設置一耳光filter(我們就叫他dater filter),並且告訴git使用expand_date filter來smudge files on checkout。你需要一個perl expression 在commit時清除掉:

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

這個perl腳本strips out anything it sees in a $Date$ string。現在你的filter已經就緒,你可以通過創建一個包含$Date$關鍵詞的文件然后創建filter:

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

如果你commit那些變更並且checkout文件,你可以看到關鍵詞已經被替換:

$ 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文件被commit了,並且被隨着項目repo來傳播,然而由於driver(這里是dater)並不會隨着project repo自然就ready,所以有可能在一些情況下並不能工作。當你設計這些filter時,他們可能會優雅地失敗,但項目本身仍然能夠正常工作。

Exporting your repository

Git attribute data也允許你在exporting項目的archive時做一些有趣的事情。

export-ignore

你可以告訴git不要在生成一個archive時,不要export一些文件或者目錄。如果有一些子目錄或者文件你不希望放到archive文件中但是你又需要放到項目checkout的working directory中去時,你可以通過export-ignore這個attribute來指定。

例如,你有一些test文件在test/目錄下,它本身對於export你的項目作為一個歸檔並沒有意義,你可以增加下面的行在git attributes文件中:

test/ export-ignore

現在當你執行git archive來創建project的tarball時,那個目錄並不會放入

注意:下面這個git archive命令工作的前提是.gitattributes(包含export-ignore屬性指示)已經commit到庫中了!~

git archive --format=zip -o test233.zip HEAD

export-subst

當輸出文件以便部署時,你可以應用git log的格式和keyword-expansion來選擇那些被標示為export-subst屬性的子文件集。

例如,如果你希望包含一個命名為LAST_COMMIT的文件在你的項目中,並且關於最后的commit的metadata信息在git archive命令運行時自動注入那個文件中,你可以設置下面的信息:

$ 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時,那么achived file將由以下內容:

$ 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

被替換的信息可以包含commit message和任何git notes以及git log:

$ echo '$Format:Last commit: %h by %aN at %cd%n%+w(76,6,9)%B$' > LAST_COMMIT
$ git commit -am 'export-subst uses git log's custom formatter

git archive uses git log's `pretty=format:` processor
directly, and strips the surrounding `$Format:` and `$`
markup from the output.
'
$ git archive @ | tar xfO - LAST_COMMIT
Last commit: 312ccc8 by Jim Hill at Fri May 8 09:14:04 2015 -0700
       export-subst uses git log's custom formatter

         git archive uses git log's `pretty=format:` processor directly, and
         strips the surrounding `$Format:` and `$` markup from the output.

產生的archive適合於deployment,但是像任何exported archive一樣,它並不適合做任何繼續的開發。

//注意所有git archive 支持的placeholder都在git help log under the --pretty-format section可以看到

merge strategies:

你可以使用git attributes來告訴git對特定的項目文件使用不同的merge strategies。一個非常有用的選項是告訴git當發生沖突時不要試圖merge特定的文件,而是使用你優選的沖突方來作為最后merge的結果。

比如你有一個database.xml文件包含了數據庫的配置信息,該文件在兩個branch中是不同的,而你希望merge in your other branch without messing up the database file.你可以通過設置一個屬性如下:

database.xml merge=ours

然后頂一個一個dummy ours merge strategy with:

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

 

如果你merge in the other branch,不會有database.xml文件的merge conflicts,將會繼續保留你在原本branch上的文件內容,你看到如下內容:

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

在這種情況下,database.xml將停留在branch to merge into的分支上的原來版本上。也就是說你在master branch上做merge topic,那么將保留master上的database.xml文件,不會有任何沖突

 

$: mkdir gitest
$: cd gittest
$: git init
$: echo "setup merge=ours" >> .gitattributes 
$: echo "master" >> setup
$: git add setup .gitattributes
$: git commit -a -m ...
$: git branch test
$: git checkout test
$: echo "test" >> setup
$: git commit -a -m ...
$: git checkout master
$: git merge test
Expected result: setup contains the word "master", instead git performs a ff merge and setup is "test'.)

上面的snippet可以通過在~/.gitconfig文件或者.git/config文件中增加以下section來實現:

[merge "ours"]
    name = "Keep ours merge"
    driver = true

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM