Android Git Hooks

Posted 麦田里的守望者-Jiang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Git Hooks相关的知识,希望对你有一定的参考价值。

前言

在项目开发过程中,如果想在提交代码前或提交代码后,或在执行其它特定的 Git 相关操作时,去做一些自动化的事情,这时就可以利用 Git 钩子。比如:本地执行 git commit 时,自动去检验 commitmessage 是否符合规范。

通常,Git 钩子分为 客户端钩子和服务端钩子。

客户端钩子

客户端钩子主要有:pre-commitcommit-msg

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

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

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

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

在这里,通常并不会在 pre-commit 钩子中,去做静态代码检测,原因主要有:

  • 使用 git commit --no-verify 可以绕过这个环节;
  • 当前快速迭代,每 commit 一次,就检测一次,造成时间开销大。

所以,静态代码检测会放在服务端的钩子中 或 .gitlab-ci.yml 配置中。

另外,commit-msg 钩子 是会被使用的一个钩子,它主要用来校验提交信息规范,方便回溯每一个commit。

其它客户端钩子还有:pre-rebasepost-rewritepre-pushpre-auto-gc

服务端钩子

服务端钩子主要有:pre-receiveupdatepost-receive

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

update 脚本和 pre-receive 脚本十分类似,不同之处在于它会为每一个准备更新的分支各运行一次。 假如推送者同时向多个分支推送内容,pre-receive 只运行一次,相比之下 update 则会为每一个被推送的分支各运行一次。 它不会从标准输入读取内容,而是接受三个参数:引用的名字(分支),推送前的引用指向的内容的 SHA-1 值,以及用户准备推送的内容的 SHA-1 值。 如果 update 脚本以非零值退出,只有相应的那一个引用会被拒绝;其余的依然会被更新。

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

提交信息校验

使用 git 来做项目版本管理,在 clone 项目到本地后,可以在项目下的文件路径: .git/hooks 下找到相关的 hook 脚本文件,通常.git 文件夹是隐藏的,可以在终端使用:open .git/hooks 打开文件夹,或切换到对应目录下查看:

wangjiang@wangjiangdeMacBook-Pro 项目路径 % cd .git/hooks
wangjiang@wangjiangdeMacBook-Pro hooks % ls
applypatch-msg.sample           post-update.sample              pre-merge-commit.sample         pre-receive.sample
commit-msg.sample               pre-applypatch.sample           pre-push.sample                 prepare-commit-msg.sample
fsmonitor-watchman.sample       pre-commit.sample               pre-rebase.sample               update.sample
wangjiang@wangjiangdeMacBook-Pro hooks % 

将文件的后缀 .sample 去掉,这些文件脚本就会在执行 git 相关操作的时候被启动执行。

文件脚本可以使用 RubyPythonShell 写。

但是,这些文件并没有放到 git 版本管理,所以需要自己在项目下建立一个可以 git 版本管理的文件夹来管理这些 hook 文件脚本,另外一方面主要是为了方便随时更新这些脚本。比如在项目下建立一个文件夹:项目路径/.githooks/,但是还需要把该文件夹下的脚本文件复制到 .git/hooks 文件夹下。

这里以 提交信息 为例子。

撰写脚本

客户单钩子:commit-msg

项目路径/.githooks/新建 commit-msg 文件,该文件内容:

#!/bin/bash

cecho()
    RED="\\033[0;31m"
    GREEN='\\033[0;32m'
    YELLOW='\\033[1;33m'`在这里插入代码片`
    # ... ADD MORE COLORS
    NC='\\033[0m' # No Color

    printf "$!1$2 $NC\\n"


showFailMsg()
	cecho "RED"  "The prefix can only be feat、docs、fix、style、refactor、test、chore. Don't forget the colon(:)"
	showPrefixMsg
	showExampleMsg


showPrefixMsg()
	echo "Allowed prefix:"
	echo "  feat 开发新需求,必须带上 xxx 信息"
	echo "  fix 修复 bug 使用,必须带上 xxx 信息"
	echo "  docs 修改文档"
	echo "  style 代码格式相关,低频使用"
	echo "  refactor 代码重构(体积优化等等)"
	echo "  test 修改非生产环境代码,如添加测试用例和修改测试代码"
	echo "  chore 修改 bazel 配置相关"
	echo "  coding 临时代码,为了出包,review 的时候禁止合入"


showExampleMsg()
	cecho "GREEN"  "Usage example:"
	echo "	feat(danmaku): Add danmaku list xxx "
	echo "	fix(*): Fix base request timeout error xxx"
	echo "	docs: Add schema useage demo"


commit_msg=$(cat "$1:?Missing commit message file")
valid=0
if [[ $commit_msg =~ ^(((feat)|(docs)|(fix)|(style)|(refactor)|(test)|(chore)|(coding))(\\(.*\\))?:)1 ]]; then
	if [[ $commit_msg =~ ^(((feat)|(fix))(\\(.*\\))?:)1 ]]; then
		if [[ ! $commit_msg =~ ((--story=[0-9]+)|(--task=[0-9]+)|(--bug=[0-9]+))+ ]]; then
			cecho "RED"  "请带上 xxx 相关信息!!!!!!"
			showPrefixMsg
			showExampleMsg
			exit 1
		elif [[ ! $commit_msg =~ (--user=)1 ]]; then
			cecho "RED"   "请带上 xxx 相关信息 !!!!!!"
			showPrefixMsg
			showExampleMsg
			exit 1
		else
			exit 0
		fi
	else
		exit 0
	fi
else
	showFailMsg
	exit 1
fi

复制脚本

使用 gradle task 将 文件夹 项目路径/.githooks/ 下的文件复制到 .git/hooks 文件夹 下:

task copyGitHookScriptToLocalDir(type: Copy) 
    from '../.githooks/'
    into '../.git/hooks'

copyGitHookScriptToLocalDir.dependsOn assemble

这里在每次执行 assemble task 的时候,会执行 copyGitHookScriptToLocalDir task。当然,为了避免每次都复制,可以选择其它的任务依赖。

也可以通过设置 git config 中 core.hooksPath 来指定 hooks 的文件夹,在项目终端命令行执行:

git config core.hooksPath .githooks

或者编写自动化 gradle 脚本:

project.afterEvaluate 
    exec 
        ExecSpec execSpec ->
            executable 'bash'
            args '-c', 'git config core.hooksPath', '.githooks', '&& chmod 700', '.githooks/*'
    

commit message 校验

wangjiang@wangjiangdeMacBook-Pro xxx % git add README.MD
wangjiang@wangjiangdeMacBook-Pro bilibiliStudio % git commit -s -m "xxxx"              
The prefix can only be feat、docs、fix、style、refactor、test、chore. Don't forget the colon(:) 
Allowed prefix:
  feat 开发新需求,必须带上 xxx 这样的 tapd 信息
  fix 修复 bug 使用,必须带上 xxx 这样的 tapd 信息
  docs 修改文档
  style 代码格式相关,低频使用
  refactor 代码重构(体积优化等等)
  test 修改非生产环境代码,如添加测试用例和修改测试代码
  chore 修改 bazel 配置相关
  coding 临时代码,为了出包,review 的时候禁止合入
Usage example: 
        feat(danmaku): Add danmaku list xxx
        fix(*): Fix base request timeout error xxx
        docs: Add schema useage demo
wangjiang@wangjiangdeMacBook-Pro xxx % git commit -s -m "docs: Add schema useage demo"
[xxx/feature/wangjiang_lint 24a650c6c] docs: Add schema useage demo

到这里,一个完整的客户端钩子就完成。如果感兴趣,可以尝试在钩子中执行其它操作,如:./gradlew lint等。

参考

  1. Shell 教程
  2. 自定义 Git - Git 钩子

以上是关于Android Git Hooks的主要内容,如果未能解决你的问题,请参考以下文章

Android Git Hooks

Git钩子:'.git/hooks/pre-commit':不允许操作

在Git项目中使用pre-commit统一管理hooks

Taro (VUE style) 项目增加lint以及git hooks

定义全局Git Hooks和自定义Git Hooks

git hooks 简介与使用