应选用远端的 post-receive hook,它在推送完成、refs 更新后执行,可获取 oldrev/newrev/refname 并准确识别分支合并;需解析合并提交消息提取源分支名,配合日志去重与 bot api 可靠发消息。

合并后触发脚本的 Git Hook 选 pre-receive 还是 post-merge?
不能用 pre-receive,它在远端服务器上运行、收不到本地分支名和提交信息;也不能用 post-merge,它只在本地执行、且仅响应 git pull 或 git merge 本地操作,不覆盖 git push 后的场景。真正可靠的是远端的 post-receive Hook —— 它在推送完成、refs 更新后立即执行,能拿到 oldrev newrev refname 三元组,准确判断是否为分支合并(比如 refs/heads/main 被更新)。
实操建议:
- 在 Git 服务端(如 Gitea/GitLab 自托管实例或 bare repo)的
hooks/post-receive文件里写逻辑,不是本地仓库 - 检查
$3是否匹配目标分支(如refs/heads/main),再用git log --merges $1..$2确认是否有合并提交 - 避免在 Hook 中调用交互式命令(如
git status),bare repo 没有工作区
怎么从合并提交中提取被合并的源分支名?
Git 合并提交默认有两个 parent(git show --pretty=%p -s HEAD),但仅靠 parent hash 无法直接还原分支名 —— 分支名是引用,可能已被删除或重命名。可靠方式是解析合并提交的 commit message:GitHub/GitLab 默认生成类似 Merge branch 'feat/login' into 'main' 的标题,而 Git CLI 手动合并时可用 --no-ff --edit 强制填写规范 message。
实操建议:
- 用
git log -1 --pretty=%B $2 | head -n1提取首行,再用sed -n 's/Merge branch .\(.*\). into .*/\1/p'抽取源分支 - 若匹配失败,fallback 到
git merge-base $1 $2找共同祖先,再用git branch --contains $ancestor --format='%(refname:short)' | grep -v $target_branch推测(但不准,慎用) - 不要依赖
git show-branch,它输出不稳定,不易解析
发消息到开发群该走 Webhook 还是 Bot API?
Webhook 更轻量、无需鉴权管理,但只支持少数平台(企业微信/钉钉/飞书)且字段受限;Bot API 功能全(可@人、传文件、格式化代码块),但需维护 access_token 和失效刷新逻辑。如果群聊平台明确,优先选 Bot API —— 比如飞书用 https://open.feishu.cn/open-apis/bot/v2/handle/xxx POST JSON,关键字段是 msg_type=interactive + card 结构。
实操建议:
- 消息体必须包含:源分支名、合并者(
git log -1 --pretty=%an $2)、合并时间(date +%Y-%m-%d@%H:%M)、变更 commit 数(git rev-list $1..$2 --count) - 敏感信息如 token 存在环境变量(
export FEISHU_BOT_TOKEN=xxx),绝不在脚本里硬编码 - 加
set -e和trap 'exit 1' ERR,任一环节失败就中断,避免静默丢消息
脚本里怎么避免重复发送或漏发?
重复发送通常因 Hook 被多次调用(如 GitLab CI 触发推送、或手动 push --force 覆盖后又推一次);漏发则多因网络超时或 token 过期未捕获。核心是把“已发送”状态落盘,而不是依赖 Git Hook 的原子性。
实操建议:
- 每次成功发送后,用
echo "$2 $(date -u +%s)" >> /var/log/git-merge-sent.log记录 commit hash 和时间戳 - 脚本开头先查日志:
grep -q "^$2 " /var/log/git-merge-sent.log && exit 0 - curl 发送加
--max-time 10 --retry 2,失败时 echo 到 syslog(logger -t git-notify "send failed: $?"),方便排查
真正麻烦的是跨平台分支命名不一致 —— 比如前端用 feat/*、后端用 feature/*,抽分支名的正则得按团队约定动态适配,这个得写进配置文件,别硬塞在脚本里。











