Szhangbiao's blog

记录一些让自己可以回忆的东西

0%

Android Studio中统一Git Commit Message的格式

Git Hooks 是 Git 的一个重要特性,它让你可以在 Git 仓库中定义一些自动化的脚本,这些脚本可以在特定的 Git 事件(如提交代码、接收代码等)发生时被触发执行。它们是在 Git 仓库目录中的 .git/hooks/ 下的一组可执行文件。具体来说,每个 Git 仓库中都有一个名为 “.git/hooks” 的隐藏目录,该目录中存放了一些示例的 hook 脚本。这些脚本本质上就是可执行的程序,可以用任何你喜欢的脚本语言来编写(如 Bash、Python、Node.js 等),只要该语言在你的系统环境中可执行即可。

背景

国庆假期回来,项目的重构工作正在如火如荼的进行中,统一 git commit message 的格式首先由 iOS team 的大佬提出来并在群里分享了一个文件然后让我们放到项目根目录下的.git/hooks/里,按照他的说明完成这一系列的操作后去尝试 Commit 一下试试,发现并没有起作用,打开他分享的文件发现他使用 Python 语言写的,由于 Mac OS 的系统是默认支持 Python 的,所以他们 iOS 开发能使用,而我这边用的是 Windows 自然就没法运行,然后就开始了在 Android Studio 中统一 Git Commit Message 的格式的解决过程。

解决过程

在了解过什么是 Git Hooks 后,我就开始着手解决这个问题。打开项目根目录下的.git/hooks/文件下发现有很多 sample 后缀的文件。打开 commit-msg.sample 文件,发现里面的内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/sh
#
# An example hook script to check the commit log message.
# Called by "git commit" with one argument, the name of the file
# that has the commit message. The hook should exit with non-zero
# status after issuing an appropriate message if it wants to stop the
# commit. The hook is allowed to edit the commit message file.
#
# To enable this hook, rename this file to "commit-msg".

# Uncomment the below to add a Signed-off-by line to the message.
# Doing this in a hook is a bad idea in general, but the prepare-commit-msg
# hook is more suited to it.
#
# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p')
# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1"

# This example catches duplicate Signed-off-by lines.

test "" = "$(grep '^Signed-off-by: ' "$1" |
sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || {
echo >&2 Duplicate Signed-off-by lines.
exit 1
}

从这里可以看出,这个脚本是用 Bash 语言来编写的,而且只要把这个脚本重命名把后缀.sample去掉就能让这个脚本生效。到了这里解决方案已经很简单了,只要把之前大佬发的文件里的 Python 语言替换成 Bash 语言就可以了。
替换后的脚本如下:

1
2
3
4
5
6
7
8
#!/bin/sh
# Git commit message valid script
echo "Pre-Executing commit msg: `cat $1`"
COMMIT_MSG=`cat $1 | egrep "^\[ABCD \-[[:digit:]]+\].+"`
if [ "$COMMIT_MSG" = "" ]; then
echo "Invalid commit message format, e.g. '[ABCD -123] XYZ'"
exit 1
fi

注: 这里已经做了脱敏处理,校验的正则可以根据实际需求进行修改。

逻辑也很简单就是拿到 Git Commit 的 Message,然后用一个正则判断是否符合规范。不符合的话就给一个提示并退出。
到了这里只要把文件发给团队成员,让他们放到各自的.git/hooks/目录下就可以了。

然而事情到这里并没有结束,发到群里让每个人都去拷贝一份到各自的.git/hooks/目录下固然可以,但是这样操作上比较繁琐,考虑到后续的人员加入也要进行这一步,这时候就有必要把copy这步操作用脚本做成自动化的处理了。

最终方案

首先我们在项目根目录下的script文件夹下创建一个名为commit-msg.sample的文件,然后把上面的脚本复制过来即可。
然后再在项目的根目录下的script文件夹下创建一个名为copy-git-hooks.gradle的文件,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.security.MessageDigest

// Copy commit-msg.sample file to git hooks dir
// Mac need run command 'chmod +x .git/hooks/commit-msg'
final def rootPath = rootProject.rootDir.path
copyFile(rootProject.file("$rootPath/.git/hooks/commit-msg"), rootProject.file("$rootPath/script/commit-msg.sample"))

def copyFile(File oldFile, File newFile) {
if (!oldFile.exists() || !oldFile.isFile() || md5(oldFile) != md5(newFile)) {
copy {
from newFile
into oldFile.parent
rename {
oldFile.name
}
}
}
}

static def deleteFile(File file) {
if (file.exists() && file.isFile()) {
file.delete()
}
}

static md5(File file) {
return MessageDigest.getInstance("MD5").digest(file.bytes).encodeHex().toString().toLowerCase()
}

大致的逻辑就是判断.git/hooks/目录下的commit-msg是否存在,或者是否是一个文件,或者文件的 MD5 是否和commit-msg.sample文件的 MD5 相同,如果不是的话就把commit-msg.sample文件重命名后复制到.git/hooks/目录下。
然后在项目下的build.gradle文件中添加如下内容:

1
2
3
4
buildscript {
apply("${project.rootDir}/script/commit-msg.gradle")
...
}

在 Gradle 的编译阶段这个Copy的操作会自动的完成。

扩展

Git Hooks 里面有非常多的功能,比如后面我们也尝试把Ktlint的代码扫描放到pre-commit里执行,这样在每次 Commit Message 前都会进行一次代码格式的审查。