修 gitlint 门禁在 CI 空转:runner 无 git 身份致 254 被误报成信息违规
task 31(首次真跑出的 L0)失败与提交信息无关:actions/checkout 把 HOME
指向每次任务的临时目录,SYSTEM 账号没有全局 git 身份,gitlint 在读到任何
规则之前就以 Missing git configuration: please set user.name 退出——254
是配置错误,不是规则违规。旧脚本还把失败原因写死成"标题超 100 字符",加
上该步 exit 1,后面 26 个钩子一个都没跑。
改三处:① 先探测身份,缺失则用 GIT_CONFIG_COUNT/KEY/VALUE 注入一次性身
份,只进子进程环境、不写任何文件,开发者机器不受影响;② 254 与真实违规
分开报(2 = 门禁没跑起来,1 = 信息确实违规),不再替 gitlint 猜规则;
③ 钩子输出改走 ForEach-Object { "$_" },避免 PS 5.1 把原生 stderr 包成
ErrorRecord 污染日志。
实测:空 HOME 下同一条命令改前 254、改后 0 failed;正常环境 10 条提交全
过;脚本纯 ASCII、解析 0 错。同批文档:§7 排障表新增本坑与"新建分支首
次 push 不触发 run",两份变更记录各加一行。
This commit is contained in:
@@ -6,12 +6,13 @@
|
||||
.DESCRIPTION
|
||||
Why this file exists (2026-10-11 CI audit):
|
||||
|
||||
.pre-commit-config.yaml defines 17 hooks, but CI reported 16
|
||||
(passed=15 failed=0 skipped=1). The missing one was `gitlint`: its hook
|
||||
definition declares `stages: [commit-msg]`, while `pre-commit run
|
||||
--all-files` only runs the `pre-commit` stage. So the commit-message rule
|
||||
from AGENTS.md (single-line Chinese title < 100 chars) was enforced on
|
||||
developer machines and NEVER in CI -- a silent gate gap, not a failure.
|
||||
CI always reports ONE hook fewer than .pre-commit-config.yaml defines
|
||||
(measured 2026-10-12: master 18 defined / 17 reported, v2-on-ui 26 / 25).
|
||||
The missing one is `gitlint`: its hook definition declares
|
||||
`stages: [commit-msg]`, while `pre-commit run --all-files` only runs the
|
||||
`pre-commit` stage. So the commit-message rule from AGENTS.md (title
|
||||
< 100 chars, body lines <= 80) was enforced on developer machines and
|
||||
NEVER in CI -- a silent gate gap, not a failure.
|
||||
|
||||
pre-commit cannot check "the commits of this push" on its own, so this
|
||||
script feeds each commit message to the hook through
|
||||
@@ -78,6 +79,35 @@ try {
|
||||
$PythonExe = "$PythonExe".Trim()
|
||||
Write-Host "gitlint: using python $PythonExe"
|
||||
|
||||
# --- git identity guard ---------------------------------------------
|
||||
# Measured on CI 2026-10-12 (task 31): actions/checkout points HOME at
|
||||
# a per-job temp dir and the SYSTEM account has no global identity, so
|
||||
# gitlint aborted with "Missing git configuration: please set
|
||||
# user.name" and exit code 254 BEFORE reading a single rule. Every push
|
||||
# failed this gate no matter how good the message was, and the log made
|
||||
# it look like a message violation.
|
||||
# Inject a throwaway identity through GIT_CONFIG_* (git >= 2.31): it is
|
||||
# inherited by the child git processes only, so NO file is written and
|
||||
# a developer machine keeps its own identity untouched. gitlint needs
|
||||
# an identity merely to start; it does not judge the author.
|
||||
$savedPref = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$idName = git config user.name 2>$null
|
||||
$idMail = git config user.email 2>$null
|
||||
$ErrorActionPreference = $savedPref
|
||||
if (-not "$idName".Trim() -or -not "$idMail".Trim()) {
|
||||
if ($env:GIT_CONFIG_COUNT) {
|
||||
Write-Host '::warning::gitlint: GIT_CONFIG_* already set by the caller -- leaving it alone'
|
||||
} else {
|
||||
$env:GIT_CONFIG_COUNT = '2'
|
||||
$env:GIT_CONFIG_KEY_0 = 'user.name'
|
||||
$env:GIT_CONFIG_VALUE_0 = 'dtu-ci'
|
||||
$env:GIT_CONFIG_KEY_1 = 'user.email'
|
||||
$env:GIT_CONFIG_VALUE_1 = 'dtu-ci@invalid.local'
|
||||
Write-Host '::warning::gitlint: no git identity here -- injected a throwaway one via GIT_CONFIG_* (needed only so gitlint can start)'
|
||||
}
|
||||
}
|
||||
|
||||
# --- resolve the commit range ---------------------------------------
|
||||
$range = ''
|
||||
$cleanBase = "$BaseSha".Trim()
|
||||
@@ -115,6 +145,7 @@ try {
|
||||
|
||||
$checked = 0
|
||||
$failed = 0
|
||||
$gateBroken = $false
|
||||
foreach ($sha in $shas) {
|
||||
$sha = "$sha".Trim()
|
||||
$checked++
|
||||
@@ -134,12 +165,22 @@ try {
|
||||
|
||||
$savedPref = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
& $PythonExe -s -m pre_commit run --hook-stage commit-msg `
|
||||
--commit-msg-filename $msgPath gitlint 2>&1
|
||||
# ForEach-Object { "$_" } unwraps the ErrorRecords that PS 5.1's
|
||||
# 2>&1 builds for every native stderr line, without disturbing
|
||||
# $LASTEXITCODE (docs/CI guide, section 7.3).
|
||||
$hookOut = & $PythonExe -s -m pre_commit run --hook-stage commit-msg `
|
||||
--commit-msg-filename $msgPath gitlint 2>&1 | ForEach-Object { "$_" }
|
||||
$rc = $LASTEXITCODE
|
||||
$ErrorActionPreference = $savedPref
|
||||
$hookOut | ForEach-Object { Write-Host $_ }
|
||||
|
||||
if ($rc -ne 0) {
|
||||
if ($rc -eq 254 -or ($hookOut -match 'Missing git configuration')) {
|
||||
# The gate itself could not run. Never report that as a
|
||||
# message violation -- it is a runner/environment fault and
|
||||
# needs a different fix (see the identity guard above).
|
||||
Write-Host "::error::gitlint: GATE BROKEN (exit $rc, git config error) at $($sha.Substring(0, 8)) -- NOT a message violation"
|
||||
$gateBroken = $true
|
||||
} elseif ($rc -ne 0) {
|
||||
$failed++
|
||||
Write-Host "::error::gitlint: commit $($sha.Substring(0, 8)) rejected -- `"$titleLine`""
|
||||
}
|
||||
@@ -152,8 +193,12 @@ try {
|
||||
Add-Content -Path $env:GITHUB_STEP_SUMMARY -Value $summary -Encoding utf8
|
||||
}
|
||||
|
||||
if ($gateBroken) {
|
||||
Write-Host '::error::commit message gate could not RUN -- fix the runner git identity; do NOT read this as a message violation'
|
||||
exit 2
|
||||
}
|
||||
if ($failed -gt 0) {
|
||||
Write-Host '::error::commit message gate failed -- rule: single-line Chinese title, < 100 chars (see docs/ pre-commit usage guide + .gitlint)'
|
||||
Write-Host '::error::commit message gate failed -- see the gitlint output above for the exact rule id (rules in .gitlint; AGENTS.md: title <= 100 chars, body lines <= 80)'
|
||||
exit 1
|
||||
}
|
||||
exit 0
|
||||
|
||||
Reference in New Issue
Block a user