7.3 在提交消息中应用外部信息
关闭提交注释编辑器时,会触发 commit-msg
钩子,可用于处理提交注释或完成一个自动检测工作。
本节示例需要先禁用 prepare-commit-msg
钩子,同时启用 commit-msg
钩子:
# init hooks
$ git checkout -b commit-msg-example
Switched to a new branch 'commit-msg-example'
$ mv .git/hooks/prepare-commit-msg .git/hooks/prepare-commit-msg.example
$ cp .git/hooks/commit-msg.sample .git/hooks/commit-msg
示例一:禁止提交
commit-msg
脚本修改如下:
#!/bin/bash
echo "you are not allowed to commit"
exit 1
验证:
$ echo "Frogs, scallops, and coco shell" >> fishtank.txt
$ git add fishtank.txt
# no matter what you input into the editor, you got result like this:
$ git commit
you are not allowed to commit
$ git log -1
commit 64b780b3d4918e5a56ddca5662431be4fd0e3789 (HEAD -> commit-msg-example, master)
Author: SafeWinter <zandong_19@aliyun.com>
Date: Sun Dec 19 02:41:48 2021 +0800
did everything
可见,commit-msg
钩子是在关闭编辑器时触发,而 prepare-commit-msg
钩子是在弹出编辑器之前。
示例二:与 Jenkins-CI 组合使用
本例将演示的场景:在提交时必须输入一个有效的 Jenkins ID
,关闭提交注释编辑器后,commit-msg
钩子读取该 ID 并请求第三方网站,如果查询成功,则将查询结果补录到提交注释内,否则中止提交。
修改 commit-msg
钩子:
#!/bin/bash
JIRA_ID=$(cat $1 | grep jenkins | sed 's/jenkins //g')
ISSUE_INFO=$(curl -g "https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}")
if [ -z "${ISSUE_INFO}" ]; then
echo "Jenkins issue ${JIRA_ID} does not exist"
echo "Please try again"
exit 1
else
TITLE=$(curl -g "https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}" | grep -E "<title>.*</title>")
echo "Jenkins issue ${JIRA_ID}"
echo "${TITLE}"
exit 0
fi
注意:由于时间间隔过久,原请求地址已经改为 https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}
。
这里用到了 CURL
来获取网页内容,分别用一个无效 ID(jenkins 384895
)与有效 ID (jenkins 3157
)进行测试:
$ echo "more water" >> fishtank.txt
$ git add fishtank.txt
$ git commit
此时,在弹出编辑器的首行,即 subject
位置,输入 Feature cascading ...
,然后在第三行,即 body
位置,输入 jenkins 384895
,保存,关闭编辑器,返回命令行将看到如下结果:
很明显,无效 ID 无法顺利提交,可以用 git status
验证:
再来看看有效 ID 的情况。在提交注释编辑器中输入如下内容:
Feature: Cascading...
jenkins 51444
得到结果:
有效 ID 顺利提交,查看提交日志:
$ git log -1
commit 2fc2c1711cb86c731f9a2e94b7dee95b873acfa9 (HEAD -> commit-msg-example)
Author: SafeWinter <zandong_19@aliyun.com>
Date: Sun Dec 19 17:47:19 2021 +0800
Feature: Cascading...
jenkins 51444
这里与原书不一致,按书中的原意,title标签的内容应该加到提交注释中,因此 commit-msg
钩子漏了一个写入的操作:(第 11 行)
#!/bin/bash
JIRA_ID=$(cat $1 | grep jenkins | sed 's/jenkins //g')
ISSUE_INFO=$(curl -g "https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}")
if [ -z "${ISSUE_INFO}" ]; then
echo "Jenkins issue ${JIRA_ID} does not exist"
echo "Please try again"
exit 1
else
TITLE=$(curl -g "https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}" | grep -E "<title>.*</title>")
echo "Jenkins issue ${JIRA_ID}"
echo "${TITLE}" >> $1
exit 0
fi
再次验证(与书中一致):
如果需要去掉查询结果中的 <title></title>
标签,可以对 commit-msg
脚本调整如下:
#!/bin/bash
JIRA_ID=$(cat $1 | grep jenkins | sed 's/jenkins //g')
ISSUE_INFO=$(curl -g "https://issues.jenkins.io/browse/JENKINS-${JIRA_ID}")
if [ -z "${ISSUE_INFO}" ]; then
echo "Jenkins issue ${JIRA_ID} does not exist"
echo "Please try again"
exit 1
else
TITLE=$(curl https://issues.jenkins.io/browse/JENKINS-${JIRA_ID} | grep -E "<title>.*</title>")
TITLE=$(echo ${TITLE} | sed 's/^<title>//' | sed 's/<\/title>$//')
echo "Jenkins issue ${JIRA_ID}"
echo "${TITLE}" >> $1
exit 0
fi
新增一次提交,验证结果如下:
本例中,提交注释如果填入无效的 ID,会直接丢弃已经写好的注释内容,实际工作中不应如此简单粗暴。一个更合理的方案是结合 prepare-commit-msg
钩子实现提交注释内容的“缓存”:如果提交中断,则将已经写好的注释内容临时保存到一个位置,以便下次提交时,用 prepare-commit-msg
钩子提前读取。
7.4 阻止特定版本推送到远程库
本节介绍 pre-push
钩子。顾名思义,就是在执行 git push
命令时、实际推送前触发的一段 shell
脚本。如果不满足某个自定义推送条件,就可以阻止推送。
以 jgit
为例,演示如何按自定义的条件控制推送。由于执行 git push
命令时,相关 hook 钩子在执行脚本前会先通过 HTTPS
协议校验身份,因此只能以本地仓库为远程库:
# prepare remote repo locally
$ git clone https://git.eclipse.org/r/jgit/jgit chapter7.1
$ git clone --branch master ./chapter7.1/ chapter7.2
$ cd chapter7.2
$ git branch
* master
示例一:若提交注释包含
nopush
字样,则禁止推送
# checkout a new branch
$ git checkout -b prepushHook --track origin/master
# Create a commit
$ git reset --hard 2e0d178
$ sed -i '' 's/2.9.1/3.0.0/g' pom.xml
$ git add pom.xml
$ git commit -m "Please nopush"
$ git log -1
# Edit the hook
$ cp .git/hooks/pre-push.sample .git/hooks/pre-push
在 pre-push
钩子写入如下脚本:
#!/bin/bash
echo "You are not allowed to push"
exit 1
测试推送:
$ git push origin HEAD:refs/heads/master
$ git log --grep "nopush"
观察结果:
与预期一致。接下来加入判定逻辑:
#!/bin/bash
COMMITS=$(git log --grep "nopush")
if [ "$COMMITS" ]; then
echo "You have commit(s) with nopush message"
echo "aborting push"
exit 1
fi
注意:第 2 行 不能 在等号两边加空格(COMMITS = $(git log --grep "nopush")
)
验证效果:
$ git push origin HEAD:refs/heads/master
发散
让用户决定是否继续推送(提供交互式逻辑):检测提交注释中是否包含 nopush
,如果有,则提示用户是否继续推送——如果输入 n
,则直接中止推送;如果输入 y
,则继续推送:
pre-push
调整为如下内容:
#!/bin/bash
COMMITS=$(git log --grep "nopush" --format=format:%H)
if [ "$COMMITS" ]; then
exitmaybe=1
fi
if [ $exitmaybe -eq 1 ]; then
while true
do
clear
for commit in $COMMITS
do
echo "$commit has no push in the message"
done
echo "Are you sure you want to push the commit(s)?(yY/nN)"
read -r REPLY <&1
case $REPLY in
[Yy]* ) break;;
[Nn]* ) exit 1;;
* ) echo "Please answer yes or no.";;
esac
done
fi
测试1:输入 n
:
$ git push origin HEAD:refs/heads/master
测试2:输入 y
:
$ git push origin HEAD:refs/heads/master
小结
pre-push
钩子可以有效避免由于用户误操作或不小心误触命令导致的意外推送。配合 shell
脚本的强大功能,可以更灵活地控制推送细节。