git config高级篇
Git 能够识别的配置项被分为了两大类:客户端和服务器端,其中大部分基于你个人工作偏好,属于客户端配置。尽管有数不尽的选项,但我只阐述 其中经常使用或者会对你的工作流产生巨大影响的选项,如果你想观察你当前的 Git 能识别的选项列表,请运行
$ git config --help
git config
的手册页(译注:以man命令的显示方式)非常细致地罗列了所有可用的配置项。
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" "$MERGED"'
$ git config --global mergetool.trustExitCode false
$ git config --global diff.external extDiff
或者直接编辑~/.gitconfig
文件如下:
[merge]
tool = extMerge
[mergetool "extMerge"]
cmd = extMerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
trustExitCode = false
[diff]
external = extDiff
设置完毕后,运行diff命令:
$ git diff 32d1776b1^ 32d1776b1
命令行居然没有发现diff命令的输出,其实,Git 调用了刚刚设置的P4Merge,它看起来像图1这样:
Figure 1. 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在重写补丁时自动修正它们。
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 ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: lib/test.rb
#
~
~
".git/COMMIT_EDITMSG" 14L, 297C
如果你有特定的策略要运用在提交信息上,在系统上创建一个模板文件,设置 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
命令时定义密钥:
$ git tag -s <tag-name>
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服务器端的配置选项并不多,但仍有一些饶有生趣的选项值得你一看。
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
这样会在推送过程中阻止删除分支和标签 — 没有用户能够这么做。要删除远程分支,必须从服务器手动删除引用文件。通过用户访问控制列表也能这么做,在本章结尾将会介绍这些有趣的方式。
++Git属性
一些设置项也能被运用于特定的路径中,这样,Git 以对一个特定的子目录或子文件集运用那些设置项。这些设置项被称为 Git 属性,可以在你目录中的.gitattributes
文件内进行设置(通常是你项目的根目录),也可以当你不想让这些属性文件和项目文件一同提交时,在.git/info/attributes
进行设置。
使用属性,你可以对个别文件或目录定义不同的合并策略,让 Git 知道怎样比较非文本文件,在你提交或签出前让 Git 过滤内容。你将在这部分了解到能在自己的项目中使用的属性,以及一些实例。
+++二进制文件
你可以用 Git 属性让其知道哪些是二进制文件(以防 Git 没有识别出来),以及指示怎样处理这些文件,这点很酷。例如,一些文本文件是由机器产生的,而且无法比较,而一些二进制文件可以比较 — 你将会了解到怎样让 Git 识别这些文件。
识别二进制文件
一些文件看起来像是文本文件,但其实是作为二进制数据被对待。例如,在Mac上的Xcode项目含有一个以.pbxproj
结尾的文件,它是由记录设置项的IDE写到磁盘的JSON数据集(纯文本javascript数据类型)。虽然技术上看它是由ASCII字符组成的文本文件,但你并不认为如此,因为它确实是一个轻量级数据库 — 如果有2人改变了它,你通常无法合并和比较内容,只有机器才能进行识别和操作,于是,你想把它当成二进制文件。
让 Git 把所有pbxproj
文件当成二进制文件,在.gitattributes
文件中设置如下:
*.pbxproj -crlf -diff
现在,Git 会尝试转换和修正CRLF(回车换行)问题,也不会当你在项目中运行git show或git diff时,比较不同的内容。在Git 1.6及之后的版本中,可以用一个宏代替-crlf -diff
:
*.pbxproj binary
比较二进制文件
在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.doc differ
你不能直接比较两个不同版本的Word文件,除非进行手动扫描,不是吗? Git 属性能很好地解决此问题,把下面的行加到.gitattributes
文件:
*.doc diff=word
当你要看比较结果时,如果文件扩展名是"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,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics
re going to cover how to get it and set it up for the first time if you don
t already have it on your system.
In Chapter Two we will go over basic Git usage - how to use Git for the 80%
-s going on, modify stuff and contribute changes. If the book spontaneously
+s going on, modify stuff and contribute changes. If the book spontaneously
+Let's see if this works.
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 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
你会发现文件的尺寸大小发生了改变。
+++关键字扩展
使用SVN或CVS的开发人员经常要求关键字扩展。在 Git 中,你无法在一个文件被提交后修改它,因为 Git 会先对该文件计算校验和。然而,你可以在签出时注入文本,在提交前删除它。 Git 属性提供了2种方式这么做。
首先,你能够把blob的SHA-1校验和自动注入文件的$Id$
字段。如果在一个或多个文件上设置了此字段,当下次你签出分支的时候,Git 用blob的SHA-1值替换那个字段。注意,这不是提交对象的SHA校验和,而是blob本身的校验和:
$ echo '*.png diff=exif' >> .gitattributes $ git config diff.exif.textconv exiftool
下次签出文件时,Git 入了blob的SHA值:
$ rm text.txt $ git checkout -- text.txt $ cat test.txt $Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
然而,这样的显示结果没有多大的实际意义。这个SHA的值相当地随机,无法区分日期的前后,所以,如果你在CVS或Subversion中用过关键字替换,一定会包含一个日期值。
因此,你能写自己的过滤器,在提交文件到暂存区或签出文件时替换关键字。有2种过滤器,"clean"和"smudge"。在 .gitattributes
文件中,你能对特定的路径设置一个过滤器,然后设置处理文件的脚本,这些脚本会在文件签出前("smudge",见图 2)和提交到暂存区前("clean",见图3)被调用。这些过滤器能够做各种有趣的事。
图2. 签出时,“smudge”过滤器被触发。
图3. 提交到暂存区时,“clean”过滤器被触发。
这里举一个简单的例子:在暂存前,用indent
(缩进)程序过滤所有C源代码。在.gitattributes
文件中设置"indent"过滤器过滤*.c
文件:
*.c filter=indent
然后,通过以下配置,让 Git 知道"indent"过滤器在遇到"smudge"和"clean"时分别该做什么:
$ git config --global filter.indent.clean indent
$ git config --global filter.indent.smudge cat
另一个例子是类似RCS的$Date$
关键字扩展。为了演示,需要一个小脚本,接受文件名参数,得到项目的最新提交日期,最后把日期写入该文件。下面用Ruby脚本来实现:于是,当你暂存
*.c
文件时,
indent
程序会被触发,在把它们签出之前,
cat
程序会被触发。但
cat
程序在这里没什么实际作用。这样的组合,使C源代码在暂存前被
indent
程序过滤,非常有效。
#! /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$
字符串,最后把该日期填充到$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_test.txt
$ cat date_test.txt
# $Date: Tue Apr 21 07:26:52 2009 -0700$
虽说这项技术对自定义应用来说很有用,但还是要小心,因为.gitattributes
文件会随着项目一起提交,而过滤器(例如:dater
)不会,所以,过滤器不会在所有地方都生效。当你在设计这些过滤器时要注意,即使它们无法正常工作,也要让整个项目运作下去。