This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""提交信息规范校验(CI 侧权威闸门)。
|
||||
|
||||
规则与本机 ~/.githooks/commit-msg 完全一致,防止 --no-verify 绕过。
|
||||
放行:Merge / Revert " / fixup! / squash! / amend! 开头的自动生成提交。
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
TYPES = "feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert"
|
||||
RE = re.compile(r"^(?:" + TYPES + r")(?:\([a-zA-Z0-9._/-]+\))?!?: .{3,}$")
|
||||
ALLOW = re.compile(r'^(?:Merge |Revert "|fixup! |squash! |amend! )')
|
||||
|
||||
HELP = """
|
||||
\u274c 提交信息不合规,已拦截。
|
||||
|
||||
规范 : <类型>(<范围>): <简短描述>
|
||||
类型 : feat 新功能 | fix 修缺陷 | docs 文档 | style 格式(不改逻辑)
|
||||
refactor 重构 | perf 性能 | test 测试 | build 构建/依赖
|
||||
ci CI 配置 | chore 杂务 | revert 回滚
|
||||
示例 : feat(asset): 新增按歌曲生成应援序列
|
||||
fix(parser): 修复表格解析越界导致的 500
|
||||
docs: 补充 OSS 短签名接口说明
|
||||
|
||||
要点 : 类型小写、冒号后一个空格、描述 \u22653 字符
|
||||
标题 \u226472 字符、结尾不加句号
|
||||
破坏性变更类型后加 !,正文写 BREAKING CHANGE:
|
||||
详见 : docs/提交规范.md
|
||||
"""
|
||||
|
||||
|
||||
def collect():
|
||||
"""优先用 Gitea 事件负载里的本次推送提交,取不到则退回 git log。"""
|
||||
msgs = []
|
||||
ev = os.environ.get("GITHUB_EVENT_PATH") or ""
|
||||
if ev and os.path.exists(ev):
|
||||
try:
|
||||
with open(ev, encoding="utf-8") as fh:
|
||||
d = json.load(fh)
|
||||
for c in (d.get("commits") or []):
|
||||
m = (c.get("message") or "").strip()
|
||||
if m:
|
||||
msgs.append(m)
|
||||
except Exception as e:
|
||||
print(" 事件负载解析失败(%s),回退到 git log" % e)
|
||||
if not msgs:
|
||||
out = subprocess.run(["git", "log", "-n", "5", "--format=%B%x00"],
|
||||
capture_output=True, text=True).stdout
|
||||
msgs = [x.strip() for x in out.split("\x00") if x.strip()]
|
||||
return msgs
|
||||
|
||||
|
||||
def subject_of(m):
|
||||
for line in m.splitlines():
|
||||
s = line.strip()
|
||||
if not s or s.startswith("#"):
|
||||
continue
|
||||
return s
|
||||
return ""
|
||||
|
||||
|
||||
def judge(subj):
|
||||
if not subj:
|
||||
return "空提交信息"
|
||||
if ALLOW.match(subj):
|
||||
return ""
|
||||
if len(subj) > 72:
|
||||
return "标题 %d 字符 > 72" % len(subj)
|
||||
if not RE.match(subj):
|
||||
return "不符合 <类型>(<范围>): <描述>"
|
||||
if subj.endswith(".") or subj.endswith("\u3002"):
|
||||
return "标题结尾不要加句号"
|
||||
return ""
|
||||
|
||||
|
||||
def main():
|
||||
msgs = collect()
|
||||
bad = []
|
||||
print("待校验提交 %d 条:" % len(msgs))
|
||||
for m in msgs:
|
||||
subj = subject_of(m)
|
||||
why = judge(subj)
|
||||
if why:
|
||||
bad.append((subj, why))
|
||||
print(" \u274c [%s] %s" % (why, subj))
|
||||
else:
|
||||
print(" \u2705 %s" % subj)
|
||||
try:
|
||||
with open("/tmp/ci-gate-status", "w") as fh:
|
||||
fh.write("failure" if bad else "success")
|
||||
except Exception:
|
||||
pass
|
||||
if bad:
|
||||
print(HELP)
|
||||
return 1
|
||||
print("\u2705 提交信息全部合规")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user