.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