自定义 Git

《Pro Git》读书笔记

配置Git

Git使用一系列配置来保存你自定义的行为:

  • 它首先会查找 /etc/gitconfig 文件,该文件含有系统里每位用户及他们所拥有的仓库的配置值。如果你传递 –system 选项给 git config,它就会读写该文件
  • 接下来 Git 会查找每个用户的 ~/.gitconfig 文件(或者 ~/.config/git/config 文件)。你可以传递–global 选项让 Git 读写该文件。
  • 最后 Git 会查找你正在操作的版本库所对应的 Git 目录下的配置文件(.git/config)。这个文件中的值只对该版本库有效。

以上三个层次中每层的配置(系统、全局、本地)都会覆盖掉上一层次的配置,所以 .git/config 中的值会覆盖掉 /etc/gitconfig 中所对应的值。

客户端基本配置

  • core.editor

默认情况下,Git 会调用环境变量($VISUAL 或 $EDITOR)设置的任意文本编辑器,如果没有设置,会调用 vi来创建和编辑你的提交以及标签信息。你可以使用 core.editor 选项来修改默认的编辑器:

1
git config --global core.editor emacs

  • commit.tmplate

如果把此项指定为你的系统上某个文件的路径,当你提交的时候, Git 会使用该文件的内容作为提交的默认信息。

  • core.pager

该配置项指定 Git 运行诸如 log 和 diff 等命令所使用的分页器。你可以把它设置成用 more 或者任何你喜欢的分页器(默认用的是 less),当然也可以设置成空字符串,关闭该选项

  • user.signingkey

如果你要创建经签署的含附注的标签(正如 签署工作 所述),那么把你的 GPG 签署密钥设置为配置项会更好。

  • core.excludesfile

正如 忽略文件 所述,你可以在你的项目的 .gitignore 文件里面规定无需纳入 Git 管理的文件的模板,这样它们既不会出现在未跟踪列表,也不会在你运行 git add 后被暂存。不过有些时候,你想要在你所有的版本库中忽略掉某一类文件。

  • help.autocorrect

Git 会尝试猜测你的意图,但是它不会越俎代庖。如果你把 help.autocorrect 设置成 1,那么只要有一个命令被模糊匹配到了,Git 会自动运行该命令。

Git 中的着色

Git 充分支持对终端内容着色,对你凭肉眼简单、快速分析命令输出有很大帮助。你可以设置许多的相关选项来满足自己的偏好。

  • color.ui

Git 会自动着色大部分输出内容,但如果你不喜欢花花绿绿,也可以关掉。这个设置的默认值是 auto。

  • color.*

要想具体到哪些命令输出需要被着色以及怎样着色,你需要用到和具体命令有关的颜色配置选项。它们都能被置为 true、false 或 always:

格式化和多余空白字符

格式化与多余的空白字符是许多开发人员在协作时,特别是在跨平台情况下,不时会遇到的令人头疼的琐碎的问题。由于编辑器的不同或者文件行尾的换行符在 Windows 下被替换了,一些细微的空格变化会不经意地混入提交的补丁或其它协作成果中。不用怕,Git 提供了一些配置项来帮助你解决这些问题。

  • core.autocrlf

Git 可以在你提交时自动地把回车和换行转换成换行,而在检出代码时把换行转换成回车和换行。你可以用core.autocrlf 来打开此项功能。如果是在 Windows 系统上,把它设置成 true,这样在检出代码时,换行会被转换成回车和换行:

如果使用以换行作为行结束符的 Linux 或 Mac,你不需要 Git 在检出文件时进行自动的转换;然而当一个以回车加换行作为行结束符的文件不小心被引入时,你肯定想让 Git 修正。你可以把 core.autocrlf 设置成 input 来告诉 Git 在提交时把回车和换行转换成换行,检出时不转换:

  • core.whitespace

Git 预先设置了一些选项来探测和修正多余空白字符问题。它提供了六种处理多余空白字符的主要选项 —— 其中三个默认开启,另外三个默认关闭,不过你可以自由地设置它们。

默认被打开的三个选项是:blank-at-eol,查找行尾的空格;blank-at-eof,盯住文件底部的空行;space-before-tab,警惕行头 tab 前面的空格。

默认被关闭的三个选项是:indent-with-non-tab,揪出以空格而非 tab 开头的行(你可以用 tabwidth 选项控制它);tab-in-indent,监视在行头表示缩进的 tab;cr-at-eol,告诉 Git 忽略行尾的回车。

服务器端配置

  • receive.fsckObjects

Git 能够确认每个对象的有效性以及 SHA-1 检验和是否保持一致。但 Git 不会在每次推送时都这么做。设置 receive.fsckObjects 为 true 来强迫它这么做

  • receive.denyNonFastForwards

如果你变基已经被推送的提交,继而再推送,又或者推送一个提交到远程分支,而这个远程分支当前指向的提交不在该提交的历史中,这样的推送会被拒绝。这通常是个很好的策略,但有时在变基的过程中,你确信自己需要更新远程分支,可以在 push 命令后加 -f 标志来强制更新(force-update)。

要禁用这样的强制更新推送(force-pushes),可以设置 receive.denyNonFastForwards:

1
$ git config --system receive.denyNonFastForwards true

  • receive.denyDeletes

有一些方法可以绕过 denyNonFastForwards 策略。其中一种是先删除某个分支,再连同新的引用一起推送回该分支。把 receive.denyDeletes 设置为 true 可以把这个漏洞补上:

Git 属性

你也可以针对特定的路径配置某些设置项,这样 Git 就只对特定的子目录或子文件集运用它们。这些基于路径的设置项被称为 Git 属性,可以在你的目录下的 .gitattributes 文件内进行设置(通常是你的项目的根目录)。如果不想让这些属性文件与其它文件一同提交,你也可以在 .git/info/attributes 文件中进行设置。

通过使用属性,你可以对项目中的文件或目录单独定义不同的合并策略,让 Git 知道怎样比较非文本文件,或者让 Git 在提交或检出前过滤内容。

二进制文件

你可以用 Git 属性让 Git 知道哪些是二进制文件(以防它没有识别出来),并指示其如何处理这些文件。

识别二进制文件

要让 Git 把所有 pbxproj 文件当成二进制文件,在 .gitattributes 文件中如下设置:

1
*.pbxproj binary

现在,Git 不会尝试转换或修正回车换行(CRLF)问题,当你在项目中运行 git show 或 git diff 时,Git 也不会比较或打印该文件的变化

比较二进制文件

可以使用 Git 属性来有效地比较两个二进制文件。秘诀在于,告诉 Git 怎么把你的二进制文件转化为文本格式,从而能够使用普通的 diff 方式进行对比。

把下面这行文本加到你的 .gitattributes 文件中:

1
*.docx diff=word

这告诉 Git 当你尝试查看包含变更的比较结果时,所有匹配 .docx 模式的文件都应该使用“word”过滤器。“word”过滤器是什么?我们现在就来设置它。我们会对 Git 进行配置,令其能够借助 docx2txt 程序将Word 文档转为可读文本文件,这样不同的文件间就能够正确比较了。

关键字展开

Git 属性提供了另一种方法:我们可以编写自己的过滤器来实现文件提交或检出时的关键字替换。

导出版本库

当归档的时候,可以设置 Git 不导出某些文件和目录。如果你不想在归档中包含某个子目录或文件,但想把它们纳入项目的版本管理中,你可以在 export-ignore 属性中指定它们。

例如,假设你在 test/ 子目录下有一些测试文件,不希望它们被包含在项目导出的压缩包(tarball)中。你可以增加下面这行到 Git 属性文件中:

1
test/ export-ignore

合并策略

你还能对项目中的特定文件指定不同的合并策略。一个非常有用的选项就是,告诉 Git 当特定文件发生冲突时不要尝试合并它们,而是直接使用你这边的内容

Git 钩子

Git 能在特定的重要动作发生时触发自定义脚本。有两组这样的钩子:客户端的和服务器端的。客户端钩子由诸如提交和合并这样的操作所调用,而服务器端钩子作用于诸如接收被推送的提交这样的联网操作。

安装一个钩子

钩子都被存储在 Git 目录下的 hooks 子目录中。也即绝大部分项目中的 .git/hooks 。当你用 git init 初始化一个新版本库时,Git 默认会在这个目录中放置一些示例脚本。这些脚本除了本身可以被调用外,它们还透露了被触发时所传入的参数。所有的示例都是 shell 脚本,其中一些还混杂了 Perl 代码,不过,任何正确命名的可执行脚本都可以正常使用 —— 你可以用 Ruby 或 Python,或其它语言编写它们。这些示例的名字都是以.sample 结尾,如果你想启用它们,得先移除这个后缀

把一个正确命名且可执行的文件放入 Git 目录下的 hooks 子目录中,即可激活该钩子脚本。

客户端钩子

克隆某个版本库时,它的客户端钩子 并不 随同复制。如果需要靠这些脚本来强制维持某种策略,建议你在服务器端实现这一功能。

提交工作流钩子

pre-commit 钩子在键入提交信息前运行。它用于检查即将提交的快照,例如,检查是否有所遗漏,确保测试运行,以及核查代码。如果该钩子以非零值退出,Git 将放弃此次提交,不过你可以用 git commit –no-verify 来绕过这个环节。你可以利用该钩子,来检查代码风格是否一致(运行类似 lint 的程序)、尾随空白字符是否存在(自带的钩子就是这么做的),或新方法的文档是否适当。

prepare-commit-msg钩子在启动提交信息编辑器之前,默认信息被创建之后运行。它允许你编辑提交者所看到的默认信息。该钩子接收一些选项:存有当前提交信息的文件的路径、提交类型和修补提交的提交的 SHA-1校验。它对一般的提交来说并没有什么用;然而对那些会自动产生默认信息的提交,如提交信息模板、合并提交、压缩提交和修订提交等非常实用。你可以结合提交模板来使用它,动态地插入信息。

commit-msg 钩子接收一个参数,此参数即上文提到的,存有当前提交信息的临时文件的路径。如果该钩子脚本以非零值退出,Git 将放弃提交,因此,可以用来在提交通过前验证项目状态或提交信息。在本章的最后一节,我们将展示如何使用该钩子来核对提交信息是否遵循指定的模板。

post-commit 钩子在整个提交过程完成后运行。它不接收任何参数,但你可以很容易地通过运行 git log -1HEAD 来获得最后一次的提交信息。该钩子一般用于通知之类的事情。

电子邮件工作流钩子

可以给电子邮件工作流设置三个客户端钩子。它们都是由 git am 命令调用的

第一个运行的钩子是 applypatch-msg 。它接收单个参数:包含请求合并信息的临时文件的名字。如果脚本返回非零值,Git 将放弃该补丁。你可以用该脚本来确保提交信息符合格式,或直接用脚本修正格式错误。

下一个在 git am 运行期间被调用的是 pre-applypatch 。有些难以理解的是,它正好运行于应用补丁 之后,产生提交之前,所以你可以用它在提交前检查快照。你可以用这个脚本运行测试或检查工作区。如果有什么遗漏,或测试未能通过,脚本会以非零值退出,中断 git am 的运行,这样补丁就不会被提交。

post-applypatch 运行于提交产生之后,是在 git am 运行期间最后被调用的钩子。你可以用它把结果通知给一个小组或所拉取的补丁的作者。但你没办法用它停止打补丁的过程。

其他钩子

pre-rebase 钩子运行于变基之前,以非零值退出可以中止变基的过程。你可以使用这个钩子来禁止对已经推送的提交变基。Git 自带的 pre-rebase 钩子示例就是这么做的,

post-rewrite 钩子被那些会替换提交记录的命令调用,比如 git commit –amend 和 git rebase(不过不包括 git filter-branch)。它唯一的参数是触发重写的命令名,同时从标准输入中接受一系列重写的提交记录。这个钩子的用途很大程度上跟 post-checkout 和 post-merge 差不多。

在 git checkout 成功运行后,post-checkout 钩子会被调用。你可以根据你的项目环境用它调整你的工作目录。其中包括放入大的二进制文件、自动生成文档或进行其他类似这样的操作。

在 git merge 成功运行后,post-merge 钩子会被调用。你可以用它恢复 Git 无法跟踪的工作区数据,比如权限数据。这个钩子也可以用来验证某些在 Git 控制之外的文件是否存在,这样你就能在工作区改变时,把这些文件复制进来。

pre-push 钩子会在 git push 运行期间, 更新了远程引用但尚未传送对象时被调用。它接受远程分支的名字和位置作为参数,同时从标准输入中读取一系列待更新的引用。你可以在推送开始之前,用它验证对引用的更新操作(一个非零的退出码将终止推送过程)。

Git 的一些日常操作在运行时,偶尔会调用 git gc –auto 进行垃圾回收。pre-auto-gc 钩子会在垃圾回收开始之前被调用,可以用它来提醒你现在要回收垃圾了,或者依情形判断是否要中断回收。

服务端钩子

  • pre-receive

处理来自客户端的推送操作时,最先被调用的脚本是 pre-receive。它从标准输入获取一系列被推送的引用。如果它以非零值退出,所有的推送内容都不会被接受。你可以用这个钩子阻止对引用进行非快进(non-fastforward)的更新,或者对该推送所修改的所有引用和文件进行访问控制。

  • update

update 脚本和 pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。

  • post-receive

post-receive 挂钩在整个过程完结以后运行,可以用来更新其他系统服务或者通知用户。它接受与 prereceive 相同的标准输入数据。它的用途包括给某个邮件列表发信,通知持续集成(continous integration)的服务器,或者更新问题追踪系统(ticket-tracking system) —— 甚至可以通过分析提交信息来决定某个问题(ticket)是否应该被开启,修改或者关闭。该脚本无法终止推送进程,不过客户端在它结束运行之前将保持连接状态,所以如果你想做其他操作需谨慎使用它,因为它将耗费你很长的一段时间。