最近同事总是进行不规范的合并操作并且推送到了代码托管平台,导致重复开发,极大增加工作量,想寻找一下有没有非人工的避免方法。应该是可以通过一个客户端hooks解决的。
git hooks是git在hooks下的一系列固定命名的脚本,用于在某个特点阶段执行。
目标规则
- 只允许master和同源 feature 分支合并到feature分支。
- 只允许master和 feature 分支合并到release分支。
事故场景
- 更新开发代码时pull拉取了另一个feature分支并合并提交。
- 将release分支merge合并到了feature分支并提交。
因此我们大概需要编写pre-merge-commit这个hook,对合并分支的名称进行检测。
Git合并类型
快进合并(Fast-forward Merges)是 Git 在合并两个没有分叉的分支时的默认行为,将 Some Feature 分支合并进 Main 分支,Git 只需要将 Main 分支的指向移动到最后一个 commit 节点上。
- 快进合并(Fast-forward Merges)和显示合并(Explicit Merges)都会触发
post-merge
钩子。
- 合并冲突时会触发
prepare-commit-msg
和 commit-msg
钩子。然而,commit-msg
钩子无法获取到合并进来的分支名称,因此只能使用 prepare-commit-msg
钩子。
hook脚本
git rev-parse --abbrev-ref HEAD
可得到当前分支名称
post-merge 钩子
本地pull远程分支完成时触发 post-merge
钩子,由git merge调用。接受一个参数:a status flag specifying whether or not the merge being done was a squash merge。
- 如果当前分支是受保护的分支,并且合并进来的分支是禁止合并的分支,则撤销合并。
- 当
post-merge
钩子被触发时,分支已经完成合并,并且 reflog 已经更新,因此我们可以通过 $GIT_REFLOG_ACTION
获取到合并进来的分支的信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #!/usr/bin/env sh
echo 'this is post-merge hook...' # 当前分支的名称 CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
# 受保护的分支 PROTECTED_BRANCH_NAME="feature-"
# 禁止合并的分支 FORBIDDEN_BRANCH_NAME="feature-"
# 如果当前分支为feature分支 if [[ "$CURRENT_BRANCH_NAME" == "$PROTECTED_BRANCH_NAME"* ]]; then # 合并分支不为同源feature分支&&不为master if [[ "$GIT_REFLOG_ACTION" != "$CURRENT_BRANCH_NAME" ]] && [["$GIT_REFLOG_ACTION" != *master*]]; then echo "检测到非法合并: ${GIT_REFLOG_ACTION//merge / } ==into==> $CURRENT_BRANCH_NAME" echo "撤销合并中..." $(git reset --merge HEAD@{1}) echo "已撤销合并 done" exit 1 fi fi
|
post-merge经过实验后似乎在上述两种情况下皆不被调用,或者说还没到调用阶段。
pre-merge-commit 钩子
这个钩子在合并完成,获得提交消息之前由git merge调用,可以使用--no-verify
选项绕过它。它不接受任何参数,并在合并成功执行之后和获取建议的提交日志消息以进行提交之前调用。从这个脚本中退出非零状态会导致Git合并命令在创建提交之前中止。
如果启用了pre-merge-commit
hook,则默认的预合并提交挂钩将运行pre-commit
hook。
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
| #!/usr/bin/env sh
echo 'this is pre-merge-commit hook...'
# 检查合并的来源分支是否为release CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref --symbolic-full-name @{u}) echo 'CURRENT_BRANCH_NAME: $CURRENT_BRANCH_NAME...'
# 受保护的分支 PROTECTED_BRANCH_NAME="feature-"
# 禁止合并的分支 FORBIDDEN_BRANCH_NAME="feature-"
# 如果当前分支为feature分支 if [[ "$CURRENT_BRANCH_NAME" == "$PROTECTED_BRANCH_NAME"* ]]; then # 合并分支不为同源feature分支&&不为master if [[ "$GIT_REFLOG_ACTION" != "$CURRENT_BRANCH_NAME" ]] && [["$GIT_REFLOG_ACTION" != *master*]]; then echo "检测到非法合并: ${GIT_REFLOG_ACTION//merge / } ==into==> $CURRENT_BRANCH_NAME" echo "撤销合并中..." $(git reset --merge HEAD@{1}) echo "已撤销合并 done" exit 1 fi fi
|
prepare-commit-msg(合并前提交)钩子
当处于merge状态时,git会在.git目录中生成一个MERGE_HEAD
文件,不是merge状态则没有,所以可以判断这个文件是否存在来解决此问题。
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 29 30 31 32 33 34 35
| #!/usr/bin/env sh
echo 'this is prepare-commit-msg hook...' if [ -e $MERGE_HEAD ]; then echo 'skip pre-commit hook...' exit 0 fi # 当前分支的名称 CURRENT_BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) echo CURRENT_BRANCH_NAME: "$CURRENT_BRANCH_NAME" # 受保护的分支 PROTECTED_BRANCH_NAME="feature-"
# 禁止合并的分支 FORBIDDEN_BRANCH_NAME="feature-"
if [[ -e .git/MERGE_HEAD ]]; then MERGE_HEAD=`cat .git/MERGE_HEAD` echo MERGE_HEAD: "$MERGE_HEAD" # 合并进来的分支名称 MERGE_BRANCH_NAME=$(git name-rev $MERGE_HEAD) # 如果当前分支为受保护的分支 if [[ "$CURRENT_BRANCH_NAME" == "$PROTECTED_BRANCH_NAME"* ]]; then ## 合并进来的分支不为同源feature分支&&不为master if [[ "$MERGE_BRANCH_NAME" != *"$CURRENT_BRANCH_NAME"* ]] && [[ "$MERGE_BRANCH_NAME" != *master* ]]; then echo "检测到非法合并: ${MERGE_BRANCH_NAME//$MERGE_HEAD / } ==into==> $CURRENT_BRANCH_NAME" echo "撤销合并中..." $(git reset --keep HEAD~1) echo "已撤销合并 done" exit 1 fi fi fi
echo 'prepare-commit-msg hook done...'
|
上面这个prepare-commit-msg
脚本经实验确实是正常工作的,不过会将正常5s左右的合并拖慢至30s,可根据实际需要使用。
参考博客
[Git Hooks] 阻止某个分支合并到另一个分支
7.8 Git 工具 - 高级合并-
githooks - Hooks used by Git
Environment Variables
一文带你彻底学会 Git Hooks 配置
Git高级操作: Git钩子
Nov 20, 2021 - Prevent merge from a specific branch using Git Hooks,这个人编写了一个pre-merge-commit hook
How to get origin merge branch name with git hook pre-merge-commit,使用环境变量GIT_REFLOG_ACTION获得要合并的分支