到目前为止,我阐述了 Git 基本的运作机制和使用方式,介绍了 Git
提供的许多工具来帮助你简单且有效地使用它。 在本章,我将会介绍 Git 的一些重要的配置方法和钩子机制以满足自定义的要求。通过这些工具,它会和你和公司或团队配合得天衣无缝。
7.1 配置 Git
如第一章所言,用git config配置 Git,要做的第一件事就是设置名字和邮箱地址:
$ git config --global user.name "John Doe" $ git config --global user.email johndoe@example.com |
从现在开始,你会了解到一些类似以上但更为有趣的设置选项来自定义 Git。
先过一遍第一章中提到的 Git 配置细节。Git 使用一系列的配置文件来存储你定义的偏好,它首先会查找/etc/gitconfig文件,该文件含有
对系统上所有用户及他们所拥有的仓库都生效的配置值(译注:gitconfig是全局配置文件), 如果传递--system选项给git
config命令, Git 会读写这个文件。
接下来 Git 会查找每个用户的~/.gitconfig文件,你能传递--global选项让 Git读写该文件。
最后 Git 会查找由用户定义的各个库中 Git 目录下的配置文件(.git/config),该文件中的值只对属主库有效。
以上阐述的三层配置从一般到特殊层层推进,如果定义的值有冲突,以后面层中定义的为准,例如:在.git/config和/etc/gitconfig的较量中,.git/config取得了胜利。虽然你也可以直接手动编辑这些配置文件,但是运行git
config命令将会来得简单些。
客户端基本配置
Git 能够识别的配置项被分为了两大类:客户端和服务器端,其中大部分基于你个人工作偏好,属于客户端配置。尽管有数不尽的选项,但我只阐述
其中经常使用或者会对你的工作流产生巨大影响的选项,如果你想观察你当前的 Git 能识别的选项列表,请运行
git config的手册页(译注:以man命令的显示方式)非常细致地罗列了所有可用的配置项。
core.editor
Git默认会调用你的环境变量editor定义的值作为文本编辑器,如果没有定义的话,会调用Vi来创建和编辑提交以及标签信息,
你可以使用core.editor改变默认编辑器:
$ git config --global core.editor emacs |
现在无论你的环境变量editor被定义成什么,Git 都会调用Emacs编辑信息。
commit.template
如果把此项指定为你系统上的一个文件,当你提交的时候, Git 会默认使用该文件定义的内容。
例如:你创建了一个模板文件$HOME/.gitmessage.txt,它看起来像这样:
subject line what happened [ticket: X] |
设置commit.template,当运行git commit时, Git
会在你的编辑器中显示以上的内容, 设置commit.template如下:
$ git config --global commit.template $HOME/.gitmessage.txt $ git commit |
然后当你提交时,在编辑器中显示的提交信息如下:
subject line what happened [ticket: X]
#Please enter the commit message for your changes. Lines starting # with '#' will be |
如果你有特定的策略要运用在提交信息上,在系统上创建一个模板文件,设置 Git
默认使用它,这样当提交时,你的策略每次都会被运用。
core.pager
core.pager指定 Git 运行诸如log、diff等所使用的分页器,你能设置成用more或者任何你喜欢的分页器(默认用的是less),
当然你也可以什么都不用,设置空字符串:
$ git config --global core.pager '' |
这样不管命令的输出量多少,都会在一页显示所有内容。
user.signingkey
如果你要创建经签署的含附注的标签(正如第二章所述),那么把你的GPG签署密钥设置为配置项会更好,设置密钥ID如下:
$ git config --global user.signingkey <gpg-key-id> |
现在你能够签署标签,从而不必每次运行git tag命令时定义密钥:
core.excludesfile
正如第二章所述,你能在项目库的.gitignore文件里头用模式来定义那些无需纳入
Git 管理的文件,这样它们不会出现在未跟踪列表, 也不会在你运行git add后被暂存。然而,如果你想用项目库之外的文件来定义那些需被忽略的文件的话,用core.excludesfile
通知 Git 该文件所处的位置,文件内容和.gitignore类似。
help.autocorrect
该配置项只在 Git 1.6.1及以上版本有效,假如你在Git 1.6中错打了一条命令,会显示:
$ git com git: 'com' is not a git-command. See 'git --help'. Did you mean this? commit |
如果你把help.autocorrect设置成1(译注:启动自动修正),那么在只有一个命令被模糊匹配到的情况下,Git
会自动运行该命令。
Git中的着色
Git能够为输出到你终端的内容着色,以便你可以凭直观进行快速、简单地分析,有许多选项能供你使用以符合你的偏好。
color.ui
Git会按照你需要自动为大部分的输出加上颜色,你能明确地规定哪些需要着色以及怎样着色,设置color.ui为true来打开所有的默认终端着色。
$ git config --global color.ui true |
设置好以后,当输出到终端时,Git 会为之加上颜色。其他的参数还有false和always,false意味着不为输出着色,而always则表明在任何情况下都要着色,即使
Git 命令被重定向到文件或管道。Git 1.5.5版本引进了此项配置,如果你拥有的版本更老,你必须对颜色有关选项各自进行详细地设置。
你会很少用到color.ui = always,在大多数情况下,如果你想在被重定向的输出中插入颜色码,你能传递--color标志给
Git 命令来迫使它这么做,color.ui = true应该是你的首选。
color.*
想要具体到哪些命令输出需要被着色以及怎样着色或者 Git 的版本很老,你就要用到和具体命令有关的颜色配置选项,它们都能被置为true、false或always:
color.branch color.diff color.interactive color.status |
除此之外,以上每个选项都有子选项,可以被用来覆盖其父设置,以达到为输出的各个部分着色的目的。例如,让diff输出的改变信息以粗体、蓝色前景和黑色背景的形式显示:
$ git config --global color.diff.meta “blue black bold” |
你能设置的颜色值如:normal、black、red、green、yellow、blue、magenta、cyan、white,正如以上例子设置的粗体属性,想要设置字体属性的话,可以选择如:bold、dim、ul、blink、reverse。
如果你想配置子选项的话,可以参考git config帮助页。
外部的合并与比较工具
虽然 Git 自己实现了diff,而且到目前为止你一直在使用它,但你能够用一个外部的工具替代它,除此以外,你还能用一个图形化的工具来合并和解决冲突从而不必自己手动解决。有一个不错且免费的工具可以被用来做比较和合并工作,它就是P4Merge(译注:Perforce图形化合并工具),我会展示它的安装过程。
P4Merge可以在所有主流平台上运行,现在开始大胆尝试吧。对于向你展示的例子,在Mac和Linux系统上,我会使用路径名,在Windows上,/usr/local/bin应该被改为你环境中的可执行路径。
下载P4Merge:http://www.perforce.com/perforce/downloads/component.html
首先把你要运行的命令放入外部包装脚本中,我会使用Mac系统上的路径来指定该脚本的位置,在其他系统上,它应该被放置在二进制文件p4merge所在的目录中。创建一个merge包装脚本,名字叫作extMerge,让它带参数调用p4merge二进制文件:
$ cat /usr/local/bin/extMerge #!/bin/sh /Applications/p4merge.app/Contents/MacOS/p4merge $* |
diff包装脚本首先确定传递过来7个参数,随后把其中2个传递给merge包装脚本,默认情况下,
Git 传递以下参数给diff:
path old-file old-hex old-mode new-file new-hex new-mode |
由于你仅仅需要old-file和new-file参数,用diff包装脚本来传递它们吧。
$ cat /usr/local/bin/extDiff #!/bin/sh [ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5" |
确认这两个脚本是可执行的:
$ sudo chmod +x /usr/local/bin/extMerge $ sudo chmod +x /usr/local/bin/extDiff |
现在来配置使用你自定义的比较和合并工具吧。这需要许多自定义设置:merge.tool通知
Git 使用哪个合并工具;mergetool.*.cmd规定命令运行的方式;mergetool.trustExitCode会通知
Git 程序的退出是否指示合并操作成功;diff.external通知 Git 用什么命令做比较。因此,你能运行以下4条配置命令:
$ git config --global merge.tool extMerge $ git config --global
mergetool.extMerge.cmd \ 'extMerge "$BASE" "$LOCAL" "$REMOTE |
或者直接编辑~/.gitconfig文件如下:
[merge] tool = extMerge [mergetool "extMerge"] cmd = extMerge "$BASE"
"$LOCAL" "$REMOTE" "$MERGED" trustExitCode = false [dif |
设置完毕后,运行diff命令:
$ git diff 32d1776b1^ 32d1776b1 |
命令行居然没有发现diff命令的输出,其实,Git 调用了刚刚设置的P4Merge。
当你设法合并两个分支,结果却有冲突时,运行git mergetool,Git
会调用P4Merge让你通过图形界面来解决冲突。
设置包装脚本的好处是你能简单地改变diff和merge工具,例如把extDiff和extMerge改成KDiff3,要做的仅仅是编辑extMerge脚本文件:
$ cat /usr/local/bin/extMerge #!/bin/sh /Applications/kdiff3.app/Contents/MacOS/kdiff3 $* |
现在 Git 会使用KDiff3来做比较、合并和解决冲突。
Git预先设置了许多其他的合并和解决冲突的工具,而你不必设置cmd。可以把合并工具设置为:kdiff3、opendiff、tkdiff、
meld、xxdiff、emerge、vimdiff、gvimdiff。如果你不想用到KDiff3的所有功能,只是想用它来合并,那么kdiff3
正符合你的要求,运行:
$ git config --global merge.tool kdiff3 |
如果运行了以上命令,没有设置extMerge和extDiff文件,Git
会用KDiff3做合并,让通常内设的比较工具来做比较。
格式化与空白
格式化与空白是许多开发人员在协作时,特别是在跨平台情况下,遇到的令人头疼的细小问题。由于编辑器的不同或者Windows程序员在跨平台项目中的文件行尾加入了回车换行符,一些细微的空格变化会不经意地进入大家合作的工作或提交的补丁中。不用怕,Git
的一些配置选项会帮助你解决这些问题。
core.autocrlf
假如你正在Windows上写程序,又或者你正在和其他人合作,他们在Windows上编程,而你却在其他系统上,在这些情况下,你可能会遇到行尾结束符问题。这是因为Windows使用回车和换行两个字符来结束一行,而Mac和Linux只使用换行一个字符。虽然这是小问题,但它会极大地扰乱跨平台协作。
Git可以在你提交时自动地把行结束符CRLF转换成LF,而在签出代码时把LF转换成CRLF。用core.autocrlf来打开此项功能,如果是在Windows系统上,把它设置成true,这样当签出代码时,LF会被转换成CRLF:
$ git config --global core.autocrlf true |
Linux或Mac系统使用LF作为行结束符,因此你不想 Git 在签出文件时进行自动的转换;当一个以CRLF为行结束符的文件不小心被引入时你肯定想进行修正,把core.autocrlf设置成input来告诉
Git 在提交时把CRLF转换成LF,签出时不转换:
$ git config --global core.autocrlf input |
这样会在Windows系统上的签出文件中保留CRLF,会在Mac和Linux系统上,包括仓库中保留LF。
如果你是Windows程序员,且正在开发仅运行在Windows上的项目,可以设置false取消此功能,把回车符记录在库中:
$ git config --global core.autocrlf false |
core.whitespace
Git预先设置了一些选项来探测和修正空白问题,其4种主要选项中的2个默认被打开,另2个被关闭,你可以自由地打开或关闭它们。
默认被打开的2个选项是trailing-space和space-before-tab,trailing-space会查找每行结尾的空格,space-before-tab会查找每行开头的制表符前的空格。
默认被关闭的2个选项是indent-with-non-tab和cr-at-eol,indent-with-non-tab会查找8个以上空格(非制表符)开头的行,cr-at-eol让
Git 知道行尾回车符是合法的。
设置core.whitespace,按照你的意图来打开或关闭选项,选项以逗号分割。通过逗号分割的链中去掉选项或在选项前加-来关闭,例如,如果你想要打开除了cr-at-eol之外的所有选项:
$ git config --global core.whitespace \ trailing-space,space-before-tab,indent-with-non-tab |
当你运行git diff命令且为输出着色时,Git 探测到这些问题,因此你也许在提交前能修复它们,当你用git
apply打补丁时同样也会从中受益。如果正准备运用的补丁有特别的空白问题,你可以让 Git 发警告:
$ git apply --whitespace=warn <patch> |
或者让 Git 在打上补丁前自动修正此问题:
$ git apply --whitespace=fix <patch> |
这些选项也能运用于衍合。如果提交了有空白问题的文件但还没推送到上流,你可以运行带有--whitespace=fix选项的rebase来让Git在重写补丁时自动修正它们。
服务器端配置
Git服务器端的配置选项并不多,但仍有一些饶有生趣的选项值得你一看。
receive.fsckObjects
Git默认情况下不会在推送期间检查所有对象的一致性。虽然会确认每个对象的有效性以及是否仍然匹配SHA-1检验和,但
Git 不会在每次推送时都检查一致性。对于 Git 来说,库或推送的文件越大,这个操作代价就相对越高,每次推送会消耗更多时间,如果想在每次推送时
Git 都检查一致性,设置receive.fsckObjects 为true来强迫它这么做:
$ git config --system receive.fsckObjects true |
现在 Git 会在每次推送生效前检查库的完整性,确保有问题的客户端没有引入破坏性的数据。
receive.denyNonFastForwards
如果对已经被推送的提交历史做衍合,继而再推送,又或者以其它方式推送一个提交历史至远程分支,且该提交历史没在这个远程分支中,这样的推送会被拒绝。这通常是个很好的禁止策略,但有时你在做衍合并确定要更新远程分支,可以在push命令后加-f标志来强制更新。
要禁用这样的强制更新功能,可以设置receive.denyNonFastForwards:
$ git config --system receive.denyNonFastForwards true |
稍后你会看到,用服务器端的接收钩子也能达到同样的目的。这个方法可以做更细致的控制,例如:禁用特定的用户做强制更新。
receive.denyDeletes
规避denyNonFastForwards策略的方法之一就是用户删除分支,然后推回新的引用。在更新的
Git 版本中(从1.6.1版本开始),把receive.denyDeletes设置为true:
$ git config --system receive.denyDeletes true |
这样会在推送过程中阻止删除分支和标签 — 没有用户能够这么做。要删除远程分支,必须从服务器手动删除引用文件。通过用户访问控制列表也能这么做,在本章结尾将会介绍这些有趣的方式。
7.2 Git属性
一些设置项也能被运用于特定的路径中,这样,Git 以对一个特定的子目录或子文件集运用那些设置项。这些设置项被称为
Git 属性,可以在你目录中的.gitattributes文件内进行设置(通常是你项目的根目录),也可以当你不想让这些属性文件和项目文件一同提交时,在.git/info/attributes进行设置。
使用属性,你可以对个别文件或目录定义不同的合并策略,让 Git 知道怎样比较非文本文件,在你提交或签出前让
Git 过滤内容。你将在这部分了解到能在自己的项目中使用的属性,以及一些实例。
二进制文件
你可以用 Git 属性让其知道哪些是二进制文件(以防 Git 没有识别出来),以及指示怎样处理这些文件,这点很酷。例如,一些文本文件是由机器产生的,而且无法比较,而一些二进制文件可以比较
— 你将会了解到怎样让 Git 识别这些文件。
识别二进制文件
一些文件看起来像是文本文件,但其实是作为二进制数据被对待。例如,在Mac上的Xcode项目含有一个以.pbxproj结尾的文件,它是由记录设置项的IDE写到磁盘的JSON数据集(纯文本javascript数据类型)。虽然技术上看它是由ASCII字符组成的文本文件,但你并不认为如此,因为它确实是一个轻量级数据库
— 如果有2人改变了它,你通常无法合并和比较内容,只有机器才能进行识别和操作,于是,你想把它当成二进制文件。
让 Git 把所有pbxproj文件当成二进制文件,在.gitattributes文件中设置如下:
现在,Git 会尝试转换和修正CRLF(回车换行)问题,也不会当你在项目中运行git
show或git diff时,比较不同的内容。在Git 1.6及之后的版本中,可以用一个宏代替-crlf
-diff:
比较二进制文件
在Git 1.6及以上版本中,你能利用 Git 属性来有效地比较二进制文件。可以设置
Git 把二进制数据转换成文本格式,用通常的diff来比较。
这个特性很酷,而且鲜为人知,因此我会结合实例来讲解。首先,要解决的是最令人头疼的问题:对Word文档进行版本控制。很多人对Word文档又恨又爱,如果想对其进行版本控制,你可以把文件加入到
Git 库中,每次修改后提交即可。但这样做没有一点实际意义,因为运行git diff命令后,你只能得到如下的结果:
$ git diff diff --git a/chapter1.doc b/chapter1.doc index 88839c4..
4afcb7c 100644 Binary files a/chapter1.doc and b/chapter1 |
你不能直接比较两个不同版本的Word文件,除非进行手动扫描,不是吗? Git
属性能很好地解决此问题,把下面的行加到.gitattributes文件:
当你要看比较结果时,如果文件扩展名是”doc”,Git 调用”word”过滤器。什么是”word”过滤器呢?其实就是
Git 使用strings 程序,把Word文档转换成可读的文本文件,之后再进行比较:
$ git config diff.word.textconv strings |
现在如果在两个快照之间比较以.doc结尾的文件,Git 对这些文件运用”word”过滤器,在比较前把Word文件转换成文本文件。
下面展示了一个实例,我把此书的第一章纳入 Git 管理,在一个段落中加入了一些文本后保存,之后运行git
diff命令,得到结果如下:
$ git diff diff --git a/chapter1.doc b/chapter1.doc index c1c8a0a..b93c9e4 100644
--- a/chapter1.doc +++ b/chapter1.doc @@ -8 |
Git 成功且简洁地显示出我增加的文本”Let’s see if this
works”。虽然有些瑕疵,在末尾显示了一些随机的内容,但确实可以比较了。如果你能找到或自己写个Word到纯文本的转换器的话,效果可能会更好。strings可以在大部分Mac和Linux系统上运行,所以它是处理二进制格式的第一选择。
你还能用这个方法比较图像文件。当比较时,对JPEG文件运用一个过滤器,它能提炼出EXIF信息 — 大部分图像格式使用的元数据。如果你下载并安装了exiftool程序,可以用它参照元数据把图像转换成文本。比较的不同结果将会用文本向你展示:
$ echo '*.png diff=exif' >> .gitattributes $ git config diff.exif.textconv exiftool |
如果在项目中替换了一个图像文件,运行git diff命令的结果如下:
diff --git a/image.png b/image.png index 88839c4..4afcb7c 100644
--- a/image.png +++ b/image.png @@ -1,12 +1,12 @@ ExifTool |
你会发现文件的尺寸大小发生了改变。
关键字扩展
使用SVN或CVS的开发人员经常要求关键字扩展。在 Git 中,你无法在一个文件被提交后修改它,因为
Git 会先对该文件计算校验和。然而,你可以在签出时注入文本,在提交前删除它。 Git 属性提供了2种方式这么做。
首先,你能够把blob的SHA-1校验和自动注入文件的$Id$字段。如果在一个或多个文件上设置了此字段,当下次你签出分支的时候,Git
用blob的SHA-1值替换那个字段。注意,这不是提交对象的SHA校验和,而是blob本身的校验和:
$ echo '*.txt ident' >> .gitattributes $ echo '$Id$' > test.txt |
下次签出文件时,Git 入了blob的SHA值:
$ rm text.txt $ git checkout -- text.txt $ cat test.txt $Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $ |
然而,这样的显示结果没有多大的实际意义。这个SHA的值相当地随机,无法区分日期的前后,所以,如果你在CVS或Subversion中用过关键字替换,一定会包含一个日期值。
因此,你能写自己的过滤器,在提交文件到暂存区或签出文件时替换关键字。有2种过滤器,”clean”和”smudge”。在
.gitattributes文件中,你能对特定的路径设置一个过滤器,然后设置处理文件的脚本,这些脚本会在文件签出前和提交到暂存区前被调用。这些过滤器能够做各种有趣的事。
这里举一个简单的例子:在暂存前,用indent(缩进)程序过滤所有C源代码。在.gitattributes文件中设置”indent”过滤器过滤*.c文件:
然后,通过以下配置,让 Git 知道”indent”过滤器在遇到”smudge”和”clean”时分别该做什么:
$ git config --global filter.indent.clean indent $ git config --global filter.indent.smudge cat |
于是,当你暂存*.c文件时,indent程序会被触发,在把它们签出之前,cat程序会被触发。但cat程序在这里没什么实际作用。这样的组合,使C源代码在暂存前被indent程序过滤,非常有效。
另一个例子是类似RCS的$Date$关键字扩展。为了演示,需要一个小脚本,接受文件名参数,得到项目的最新提交日期,最后把日期写入该文件。下面用Ruby脚本来实现:
#! /usr/bin/env ruby data = STDIN.read last_date =
`git log --pretty=format:"%ad" -1` puts data.gsub('$Date$', '$Date: ' + l |
该脚本从git log命令中得到最新提交日期,找到文件中的所有$Date$字符串,最后把该日期填充到$Date$字符串中
— 此脚本很简单,你可以选择你喜欢的编程语言来实现。把该脚本命名为expand_date,放到正确的路径中,之后需要在
Git 中设置一个过滤器(dater),让它在签出文件时调用expand_date,在暂存文件时用Perl清除之:
$ git config filter.dater.smudge expand_date $ git config filter.dater.clean
'perl -pe "s/\\\$Date[^\\\$]*\\\$/\\\$Date\\\$/ |
这个Perl小程序会删除$Date$字符串里多余的字符,恢复$Date$原貌。到目前为止,你的过滤器已经设置完毕,可以开始测试了。打开一个文件,在文件中输入$Date$关键字,然后设置
Git 属性:
$ echo '# $Date$' > date_test.txt $ echo 'date*.txt filter=dater' >> .gitattributes |
如果暂存该文件,之后再签出,你会发现关键字被替换了:
$ git add date_test.txt .gitattributes $ git commit -m "Testing date expansion in Git"
$ rm date_test.txt $ git checkout date |
虽说这项技术对自定义应用来说很有用,但还是要小心,因为.gitattributes文件会随着项目一起提交,而过滤器(例如:dater)不会,所以,过滤器不会在所有地方都生效。当你在设计这些过滤器时要注意,即使它们无法正常工作,也要让整个项目运作下去。
导出仓库
Git属性在导出项目归档时也能发挥作用。
export-ignore
当产生一个归档时,可以设置 Git 不导出某些文件和目录。如果你不想在归档中包含一个子目录或文件,但想他们纳入项目的版本管理中,你能对应地设置export-ignore属性。
例如,在test/子目录中有一些测试文件,在项目的压缩包中包含他们是没有意义的。因此,可以增加下面这行到
Git 属性文件中:
现在,当运行git archive来创建项目的压缩包时,那个目录不会在归档中出现。
export-subst
还能对归档做一些简单的关键字替换。在第2章中已经可以看到,可以以--pretty=format形式的简码在任何文件中放入$Format:$
字符串。例如,如果想在项目中包含一个叫作LAST_COMMIT的文件,当运行git archive时,最后提交日期自动地注入进该文件,可以这样设置:
$ echo 'Last commit date: $Format:%cd$' > LAST_COMMIT $
echo "LAST_COMMIT export-subst" >> .gitattributes $ git add LAST_COMMI |
运行git archive后,打开该文件,会发现其内容如下:
$ cat LAST_COMMIT Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$ |
合并策略
通过 Git 属性,还能对项目中的特定文件使用不同的合并策略。一个非常有用的选项就是,当一些特定文件发生冲突,Git
会尝试合并他们,而使用你这边的合并。
如果项目的一个分支有歧义或比较特别,但你想从该分支合并,而且需要忽略其中某些文件,这样的合并策略是有用的。例如,你有一个数据库设置文件database.xml,在2个分支中他们是不同的,你想合并一个分支到另一个,而不弄乱该数据库文件,可以设置属性如下:
如果合并到另一个分支,database.xml文件不会有合并冲突,显示如下:
$ git merge topic Auto-merging database.xml Merge made by recursive. |
这样,database.xml会保持原样。
|