fix(ci): run 块按点源语义加固——原生 stderr 变终止性错误曾致两 job 3 秒亡
run 44(本分支上一次推送)把上一版原生检出打回原形:L0 用了 3 s、L0.5 用了
2 s 双双失败,日志里是 error: No such remote 'origin' 被包成 NativeCommandError
后整步终止。根因在我上一版:runner 执行 run: 块的方式是 powershell -command
点源那个文件,会话继承的 $ErrorActionPreference 是 Stop,于是原生程序往 stderr
写的任意一行都会被 PS 5.1 包成终止性错误。我写的 git remote get-url origin
2>$null 正是这一类,而 2>$null 不算摊平,反而让它先把整步炸掉。
修法两件事一起做:块首显式 $ErrorActionPreference = Continue(不依赖继承);
所有 git 调用走同一个漏斗 Invoke-DtuGit,把 stderr 摊平成字符串再决定是否打印。
探针顺手换成 git config --get remote.origin.url:远端不存在时它只返回非零码、
不往 stderr 写,从源头不再触发这类错误。
更阴的一条教训是我的测法本身。第一版复现器用 -File 包一层点源,结果四条失败
路径全测成 rc=0。用最小矩阵把语义钉死后:exit 1 写在被点源的文件里时,-File
直跑 = 1;-File 包一层点源 = 0 且继续执行后续语句;-Command 点源(runner 的真
实用法)= 1。也就是说只要我一直用 -File 测,检出失败也会一路伪装成绿,这个洞
比上面那个 bug 更坏。现在统一用 runner 的真实语义测:powershell -NoProfile
-Command 点源块体文件,且前置 $ErrorActionPreference = Stop。
换测法后的实测(全部跑在从 ci.yml 抽出的那份字节上,不是另抄的副本):
- 点源 + Stop、全新工作区:ExitCode=0、checkout ok: c1e7e8c / commits=669、
dirty_files=0、150.9 s(修复前同一模式就是 run 44 的 3 秒亡);
- DTU_REPO_SHA 空 / DTU_REPO_URL 空 / sha 不在远端 / 坏令牌:ExitCode 均 1,
坏令牌 0.5 s 返回 unable to get password,不挂起、不烧 job 超时;
- 复用工作区:1.6 s,dirty_files=0,被忽略文件保留,未跟踪垃圾清除;
- 结构面:13 步、两 job 第 1 步逐字一致、uses: 计数 0、run 块非 ASCII 计数 0、
无 BOM 无 CR;check yaml Passed;check_doc_links --all 180 篇、无法判定 0。
文档同笔落地(教训要跟它救的那段代码一起进历史):指南 §5.3 补 run 44 实录与
退出码矩阵、实测表补 G/H 两行、§7 补 job 几秒就亡一行、两份变更记录各加一行;
remote-ci-ops skill §10 补 run: 块的正确测法。另记一个运维事实:推到已存在
的远端分支会正常建 run(run 44 即如此,推送后约 4 分钟入库,我先前把轮询窗口
取得太短误判成没建),以后验 workflow 不再需要手动 dispatch 与服务凭据。
下一轮可能红的两处我先标出来,红了正是我们要的证据:SYSTEM 下的 ui-golden
(历史上败在 32 位 MSBuild 与 VCTargetsPath)与 test-coverage(缺
DTU_MS_COVERAGE,走自动发现)。
This commit is contained in:
+166
-126
@@ -135,95 +135,115 @@ jobs:
|
||||
run: |
|
||||
# Pure ASCII only (see file header).
|
||||
#
|
||||
# WHY NOT `uses: actions/checkout`: the runner resolves every `uses:` by
|
||||
# contacting the actions URL during "Prepare all required actions", BEFORE
|
||||
# the first step runs. On this intranet runner that call intermittently dies
|
||||
# with a connect timeout -- runs 36 (10-21) / 41 (10-25) / 42 (10-26) each
|
||||
# burned ~45 s and produced ZERO hook output, while a manual curl to the same
|
||||
# URL minutes later returned 200. Manual reachability is not CI health: the
|
||||
# resolver runs as SYSTEM and the runner caches nothing across jobs (each job
|
||||
# re-resolves into its own .cache/act/<hash>). Gitea 1.25.4 also removed the
|
||||
# custom DEFAULT_ACTIONS_URL knob, so the only fix this repo owns is to stop
|
||||
# depending on the public resolver at all.
|
||||
# WHY NOT a "uses:" action: the runner resolves every uses: by contacting the
|
||||
# actions URL during "Prepare all required actions" -- BEFORE the first step
|
||||
# runs. On this intranet runner that call intermittently dies with a connect
|
||||
# timeout: runs 36 (10-21) / 41 (10-25) / 42 (10-26) each burned ~45 s and
|
||||
# produced ZERO hook output, while a manual curl to the same URL minutes later
|
||||
# returned 200. Manual reachability is not CI health: the resolver runs as
|
||||
# SYSTEM and the runner caches nothing across jobs. Gitea 1.25.4 also removed
|
||||
# the custom DEFAULT_ACTIONS_URL knob, so the only fix this repo owns is to
|
||||
# stop depending on the public resolver at all.
|
||||
#
|
||||
# KEEP IN SYNC with the same step in the other job. This logic deliberately
|
||||
# does NOT live in scripts/*.ps1 (unlike the Python / MSVC probes): the file
|
||||
# is not checked out yet at the moment it would be needed -- chicken and egg.
|
||||
# does NOT live in scripts/*.ps1 (unlike the Python / MSVC probes): the file is
|
||||
# not checked out yet at the moment it would be needed -- chicken and egg.
|
||||
#
|
||||
# NOTE on $ErrorActionPreference: left at Continue on purpose. Under Windows
|
||||
# PowerShell 5.1 a native command that writes to stderr while EAP is Stop
|
||||
# raises NativeCommandError -- git legitimately writes progress to stderr.
|
||||
# $ErrorActionPreference is set to Continue EXPLICITLY. The runner executes a
|
||||
# run: block by dot-sourcing it into a session, so an inherited Stop turns any
|
||||
# raw native stderr line into a TERMINATING NativeCommandError. Measured
|
||||
# 2026-10-27, run 44 / task-72: a benign "error: No such remote 'origin'" from
|
||||
# a probe killed the whole job 3 seconds in, before any gate could run.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Invoke-DtuGit {
|
||||
# Single funnel for git. Two reasons it must be a funnel, not bare calls:
|
||||
# 1) native stderr has to be flattened into plain strings (see above), and
|
||||
# 2) $LASTEXITCODE has to stay readable by the caller afterwards.
|
||||
# Returns Code + lines and lets the caller decide what to print.
|
||||
param([string[]]$G)
|
||||
$lines = & git @G 2>&1 | ForEach-Object { [string]$_ }
|
||||
return [pscustomobject]@{ Code = $LASTEXITCODE; Lines = @($lines) }
|
||||
}
|
||||
if (-not $env:DTU_REPO_SHA) { Write-Host '::error::DTU_REPO_SHA is empty'; exit 1 }
|
||||
if (-not $env:DTU_REPO_URL) { Write-Host '::error::DTU_REPO_URL is empty'; exit 1 }
|
||||
# Never let a fetch wait for a human: the runner has no console, and a
|
||||
# credential prompt would burn the whole job timeout while looking like a hung
|
||||
# CI. Anonymous fetch works here (the repository is public; measured HTTP 200
|
||||
# on /info/refs?service=git-upload-pack), so CI must not depend on a credential
|
||||
# store it cannot answer.
|
||||
$env:GIT_TERMINAL_PROMPT = '0'
|
||||
$ws = (Get-Location).Path
|
||||
Write-Host "workspace: $ws"
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $ws '.git'))) {
|
||||
git init -q .
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::git init failed'; exit 1 }
|
||||
$r = Invoke-DtuGit -G @('init', '-q', '.')
|
||||
if ($r.Code -ne 0) { Write-Host '::error::git init failed'; $r.Lines | ForEach-Object { Write-Host " $_" }; exit 1 }
|
||||
}
|
||||
$rem = git remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $rem) {
|
||||
git remote set-url origin $env:DTU_REPO_URL
|
||||
# "git remote get-url" writes to stderr when the remote is absent -- that is
|
||||
# the exact line that killed run 44. Ask the config instead: an absent key
|
||||
# prints nothing and only sets a non-zero code, so nothing escapes as an error.
|
||||
$probe = Invoke-DtuGit -G @('config', '--get', 'remote.origin.url')
|
||||
$url = ($probe.Lines -join '').Trim()
|
||||
if ($probe.Code -eq 0 -and $url) {
|
||||
$r = Invoke-DtuGit -G @('remote', 'set-url', 'origin', $env:DTU_REPO_URL)
|
||||
} else {
|
||||
git remote add origin $env:DTU_REPO_URL
|
||||
$r = Invoke-DtuGit -G @('remote', 'add', 'origin', $env:DTU_REPO_URL)
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::configuring remote origin failed'; exit 1 }
|
||||
if ($r.Code -ne 0) { Write-Host '::error::configuring remote origin failed'; $r.Lines | ForEach-Object { Write-Host " $_" }; exit 1 }
|
||||
# Full refspec, NOT --depth 1: the commit-message gate resolves base..head
|
||||
# ranges and several gates diff against history, so a shallow clone turns
|
||||
# them into "unknown revision" failures. Tags are skipped on purpose: this
|
||||
# repository has zero tags and no script uses `git describe --tags`
|
||||
# (measured 2026-10-27).
|
||||
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
|
||||
# Never let a fetch wait for a human: the runner has no console and a
|
||||
# credential prompt would burn the whole 30 m job timeout while looking like
|
||||
# a hung CI. Terminal prompts off + no credential helper means an auth
|
||||
# problem fails immediately with a message instead of blocking. Anonymous
|
||||
# fetch works here (the repository is public; measured HTTP 200 on
|
||||
# /info/refs?service=git-upload-pack), so CI must not start depending on a
|
||||
# credential store it cannot answer.
|
||||
$env:GIT_TERMINAL_PROMPT = '0'
|
||||
# ranges and several gates diff against history, so a shallow clone turns them
|
||||
# into "unknown revision" failures. Tags are skipped on purpose: this repository
|
||||
# has zero tags and no script uses "git describe --tags" (measured 2026-10-27).
|
||||
$r = Invoke-DtuGit -G @('config', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*')
|
||||
if ($r.Code -ne 0) { Write-Host '::error::setting remote.origin.fetch failed'; exit 1 }
|
||||
# The token, when the instance provides one, travels only as a per-invocation
|
||||
# -c override: it never lands in .git/config and is never echoed in logs.
|
||||
# Anonymous fetch works too (the repository is public), so CI does not start
|
||||
# depending on token availability.
|
||||
$auth = @('-c', 'credential.helper=', '-c', 'credential.interactive=false')
|
||||
if ($env:DTU_REPO_TOKEN) {
|
||||
# Same scheme actions/checkout uses against Gitea git-http: Basic with a
|
||||
# placeholder user and the token as the password. (Bearer is the GitHub
|
||||
# *API* style, not the git-http style.) Honest caveat: this branch CANNOT
|
||||
# be verified here -- the repository is public, so an anonymous fetch
|
||||
# succeeds and any token we fabricate locally only proves a bad one is
|
||||
# placeholder user and the token as the password. (Bearer is the GitHub API
|
||||
# style, not the git-http style.) Honest caveat: this branch CANNOT be
|
||||
# verified here -- the repository is public, so an anonymous fetch succeeds
|
||||
# and any token we fabricate locally only proves that a bad one is
|
||||
# rejected. If the repository ever goes private this is the first line to
|
||||
# check; the failure mode is a loud rc=1, never a silent fallback.
|
||||
$pair = 'x-access-token:' + $env:DTU_REPO_TOKEN
|
||||
$b64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
|
||||
$auth += @('-c', ('http.extraheader=AUTHORIZATION: Basic ' + $b64))
|
||||
}
|
||||
# PS 5.1 wraps each native stderr line into an ErrorRecord, turning two real
|
||||
# log lines into eight of CategoryInfo/FullyQualifiedErrorId noise. Flattening
|
||||
# with "$_" keeps the log readable; $LASTEXITCODE is unaffected.
|
||||
$fetchArgs = $auth + @('fetch', '--prune', '--no-tags', 'origin')
|
||||
& git @fetchArgs 2>&1 | ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "::error::git fetch origin failed (exit $LASTEXITCODE)"; exit 1 }
|
||||
# Some refs (PR merge commits, refs outside refs/heads) are not covered by the
|
||||
# branch refspec -- fall back to an explicit ref fetch, then INSIST the wanted
|
||||
# SHA is really present, rather than silently building the wrong tree.
|
||||
git rev-parse --verify -q ($env:DTU_REPO_SHA + '^{commit}') 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host 'sha not covered by the branch refspec -- fetching the event ref explicitly'
|
||||
$refArgs = $auth + @('fetch', '--prune', '--no-tags', 'origin', $env:DTU_REPO_REF)
|
||||
& git @refArgs 2>&1 | ForEach-Object { "$_" }
|
||||
$f = Invoke-DtuGit -G ($auth + @('fetch', '--prune', '--no-tags', 'origin'))
|
||||
if ($f.Code -ne 0) {
|
||||
$f.Lines | ForEach-Object { Write-Host " $_" }
|
||||
Write-Host ('::error::git fetch origin failed (exit ' + $f.Code + ')')
|
||||
exit 1
|
||||
}
|
||||
git rev-parse --verify -q ($env:DTU_REPO_SHA + '^{commit}') 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host ('::error::' + $env:DTU_REPO_SHA + ' is not present in the fetched history'); exit 1 }
|
||||
& git checkout -q --detach $env:DTU_REPO_SHA 2>&1 | ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::detached checkout failed'; exit 1 }
|
||||
git reset --hard -q $env:DTU_REPO_SHA
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::reset --hard failed'; exit 1 }
|
||||
# Some event refs (PR merge commits, refs outside refs/heads) are not covered
|
||||
# by the branch refspec -- fall back to an explicit ref fetch, then INSIST the
|
||||
# wanted SHA is really present rather than silently building the wrong tree.
|
||||
$want = $env:DTU_REPO_SHA + '^{commit}'
|
||||
$chk = Invoke-DtuGit -G @('cat-file', '-e', $want)
|
||||
if ($chk.Code -ne 0) {
|
||||
Write-Host 'sha not covered by the branch refspec -- fetching the event ref explicitly'
|
||||
$f2 = Invoke-DtuGit -G ($auth + @('fetch', '--prune', '--no-tags', 'origin', $env:DTU_REPO_REF))
|
||||
if ($f2.Code -ne 0) { $f2.Lines | ForEach-Object { Write-Host " $_" } }
|
||||
$chk = Invoke-DtuGit -G @('cat-file', '-e', $want)
|
||||
}
|
||||
if ($chk.Code -ne 0) {
|
||||
Write-Host ('::error::' + $env:DTU_REPO_SHA + ' is not present in the fetched history')
|
||||
Write-Host ' (refusing to run the gates against a tree we did not verify)'
|
||||
exit 1
|
||||
}
|
||||
$c = Invoke-DtuGit -G @('checkout', '-q', '--detach', $env:DTU_REPO_SHA)
|
||||
if ($c.Code -ne 0) { $c.Lines | ForEach-Object { Write-Host " $_" }; Write-Host '::error::detached checkout failed'; exit 1 }
|
||||
$rr = Invoke-DtuGit -G @('reset', '--hard', '-q', $env:DTU_REPO_SHA)
|
||||
if ($rr.Code -ne 0) { Write-Host '::error::reset --hard failed'; exit 1 }
|
||||
# -ffd, deliberately not -ffdx: -x would delete IGNORED files, and the runner
|
||||
# reuses those across runs (pip / pre-commit caches live in the workspace).
|
||||
git clean -ffd -q
|
||||
Write-Host ('checkout ok: ' + (git rev-parse --short HEAD) + ' | commits=' + (git rev-list --count HEAD) + ' | dirty_files=' + ((git status --porcelain | Measure-Object).Count))
|
||||
$cl = Invoke-DtuGit -G @('clean', '-ffd', '-q')
|
||||
if ($cl.Code -ne 0) { $cl.Lines | ForEach-Object { Write-Host " $_" }; Write-Host '::error::git clean failed'; exit 1 }
|
||||
$head = (Invoke-DtuGit -G @('rev-parse', '--short', 'HEAD')).Lines -join ''
|
||||
$ncommits = (Invoke-DtuGit -G @('rev-list', '--count', 'HEAD')).Lines -join ''
|
||||
$dirty = @(Invoke-DtuGit -G @('status', '--porcelain')).Lines.Count
|
||||
Write-Host ('checkout ok: ' + $head + ' | commits=' + $ncommits + ' | dirty_files=' + $dirty)
|
||||
- name: 环境自检(Python / cppcheck / pre-commit)
|
||||
shell: powershell
|
||||
run: |
|
||||
@@ -503,95 +523,115 @@ jobs:
|
||||
run: |
|
||||
# Pure ASCII only (see file header).
|
||||
#
|
||||
# WHY NOT `uses: actions/checkout`: the runner resolves every `uses:` by
|
||||
# contacting the actions URL during "Prepare all required actions", BEFORE
|
||||
# the first step runs. On this intranet runner that call intermittently dies
|
||||
# with a connect timeout -- runs 36 (10-21) / 41 (10-25) / 42 (10-26) each
|
||||
# burned ~45 s and produced ZERO hook output, while a manual curl to the same
|
||||
# URL minutes later returned 200. Manual reachability is not CI health: the
|
||||
# resolver runs as SYSTEM and the runner caches nothing across jobs (each job
|
||||
# re-resolves into its own .cache/act/<hash>). Gitea 1.25.4 also removed the
|
||||
# custom DEFAULT_ACTIONS_URL knob, so the only fix this repo owns is to stop
|
||||
# depending on the public resolver at all.
|
||||
# WHY NOT a "uses:" action: the runner resolves every uses: by contacting the
|
||||
# actions URL during "Prepare all required actions" -- BEFORE the first step
|
||||
# runs. On this intranet runner that call intermittently dies with a connect
|
||||
# timeout: runs 36 (10-21) / 41 (10-25) / 42 (10-26) each burned ~45 s and
|
||||
# produced ZERO hook output, while a manual curl to the same URL minutes later
|
||||
# returned 200. Manual reachability is not CI health: the resolver runs as
|
||||
# SYSTEM and the runner caches nothing across jobs. Gitea 1.25.4 also removed
|
||||
# the custom DEFAULT_ACTIONS_URL knob, so the only fix this repo owns is to
|
||||
# stop depending on the public resolver at all.
|
||||
#
|
||||
# KEEP IN SYNC with the same step in the other job. This logic deliberately
|
||||
# does NOT live in scripts/*.ps1 (unlike the Python / MSVC probes): the file
|
||||
# is not checked out yet at the moment it would be needed -- chicken and egg.
|
||||
# does NOT live in scripts/*.ps1 (unlike the Python / MSVC probes): the file is
|
||||
# not checked out yet at the moment it would be needed -- chicken and egg.
|
||||
#
|
||||
# NOTE on $ErrorActionPreference: left at Continue on purpose. Under Windows
|
||||
# PowerShell 5.1 a native command that writes to stderr while EAP is Stop
|
||||
# raises NativeCommandError -- git legitimately writes progress to stderr.
|
||||
# $ErrorActionPreference is set to Continue EXPLICITLY. The runner executes a
|
||||
# run: block by dot-sourcing it into a session, so an inherited Stop turns any
|
||||
# raw native stderr line into a TERMINATING NativeCommandError. Measured
|
||||
# 2026-10-27, run 44 / task-72: a benign "error: No such remote 'origin'" from
|
||||
# a probe killed the whole job 3 seconds in, before any gate could run.
|
||||
$ErrorActionPreference = 'Continue'
|
||||
function Invoke-DtuGit {
|
||||
# Single funnel for git. Two reasons it must be a funnel, not bare calls:
|
||||
# 1) native stderr has to be flattened into plain strings (see above), and
|
||||
# 2) $LASTEXITCODE has to stay readable by the caller afterwards.
|
||||
# Returns Code + lines and lets the caller decide what to print.
|
||||
param([string[]]$G)
|
||||
$lines = & git @G 2>&1 | ForEach-Object { [string]$_ }
|
||||
return [pscustomobject]@{ Code = $LASTEXITCODE; Lines = @($lines) }
|
||||
}
|
||||
if (-not $env:DTU_REPO_SHA) { Write-Host '::error::DTU_REPO_SHA is empty'; exit 1 }
|
||||
if (-not $env:DTU_REPO_URL) { Write-Host '::error::DTU_REPO_URL is empty'; exit 1 }
|
||||
# Never let a fetch wait for a human: the runner has no console, and a
|
||||
# credential prompt would burn the whole job timeout while looking like a hung
|
||||
# CI. Anonymous fetch works here (the repository is public; measured HTTP 200
|
||||
# on /info/refs?service=git-upload-pack), so CI must not depend on a credential
|
||||
# store it cannot answer.
|
||||
$env:GIT_TERMINAL_PROMPT = '0'
|
||||
$ws = (Get-Location).Path
|
||||
Write-Host "workspace: $ws"
|
||||
if (-not (Test-Path -LiteralPath (Join-Path $ws '.git'))) {
|
||||
git init -q .
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::git init failed'; exit 1 }
|
||||
$r = Invoke-DtuGit -G @('init', '-q', '.')
|
||||
if ($r.Code -ne 0) { Write-Host '::error::git init failed'; $r.Lines | ForEach-Object { Write-Host " $_" }; exit 1 }
|
||||
}
|
||||
$rem = git remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $rem) {
|
||||
git remote set-url origin $env:DTU_REPO_URL
|
||||
# "git remote get-url" writes to stderr when the remote is absent -- that is
|
||||
# the exact line that killed run 44. Ask the config instead: an absent key
|
||||
# prints nothing and only sets a non-zero code, so nothing escapes as an error.
|
||||
$probe = Invoke-DtuGit -G @('config', '--get', 'remote.origin.url')
|
||||
$url = ($probe.Lines -join '').Trim()
|
||||
if ($probe.Code -eq 0 -and $url) {
|
||||
$r = Invoke-DtuGit -G @('remote', 'set-url', 'origin', $env:DTU_REPO_URL)
|
||||
} else {
|
||||
git remote add origin $env:DTU_REPO_URL
|
||||
$r = Invoke-DtuGit -G @('remote', 'add', 'origin', $env:DTU_REPO_URL)
|
||||
}
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::configuring remote origin failed'; exit 1 }
|
||||
if ($r.Code -ne 0) { Write-Host '::error::configuring remote origin failed'; $r.Lines | ForEach-Object { Write-Host " $_" }; exit 1 }
|
||||
# Full refspec, NOT --depth 1: the commit-message gate resolves base..head
|
||||
# ranges and several gates diff against history, so a shallow clone turns
|
||||
# them into "unknown revision" failures. Tags are skipped on purpose: this
|
||||
# repository has zero tags and no script uses `git describe --tags`
|
||||
# (measured 2026-10-27).
|
||||
git config remote.origin.fetch '+refs/heads/*:refs/remotes/origin/*'
|
||||
# Never let a fetch wait for a human: the runner has no console and a
|
||||
# credential prompt would burn the whole 30 m job timeout while looking like
|
||||
# a hung CI. Terminal prompts off + no credential helper means an auth
|
||||
# problem fails immediately with a message instead of blocking. Anonymous
|
||||
# fetch works here (the repository is public; measured HTTP 200 on
|
||||
# /info/refs?service=git-upload-pack), so CI must not start depending on a
|
||||
# credential store it cannot answer.
|
||||
$env:GIT_TERMINAL_PROMPT = '0'
|
||||
# ranges and several gates diff against history, so a shallow clone turns them
|
||||
# into "unknown revision" failures. Tags are skipped on purpose: this repository
|
||||
# has zero tags and no script uses "git describe --tags" (measured 2026-10-27).
|
||||
$r = Invoke-DtuGit -G @('config', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*')
|
||||
if ($r.Code -ne 0) { Write-Host '::error::setting remote.origin.fetch failed'; exit 1 }
|
||||
# The token, when the instance provides one, travels only as a per-invocation
|
||||
# -c override: it never lands in .git/config and is never echoed in logs.
|
||||
# Anonymous fetch works too (the repository is public), so CI does not start
|
||||
# depending on token availability.
|
||||
$auth = @('-c', 'credential.helper=', '-c', 'credential.interactive=false')
|
||||
if ($env:DTU_REPO_TOKEN) {
|
||||
# Same scheme actions/checkout uses against Gitea git-http: Basic with a
|
||||
# placeholder user and the token as the password. (Bearer is the GitHub
|
||||
# *API* style, not the git-http style.) Honest caveat: this branch CANNOT
|
||||
# be verified here -- the repository is public, so an anonymous fetch
|
||||
# succeeds and any token we fabricate locally only proves a bad one is
|
||||
# placeholder user and the token as the password. (Bearer is the GitHub API
|
||||
# style, not the git-http style.) Honest caveat: this branch CANNOT be
|
||||
# verified here -- the repository is public, so an anonymous fetch succeeds
|
||||
# and any token we fabricate locally only proves that a bad one is
|
||||
# rejected. If the repository ever goes private this is the first line to
|
||||
# check; the failure mode is a loud rc=1, never a silent fallback.
|
||||
$pair = 'x-access-token:' + $env:DTU_REPO_TOKEN
|
||||
$b64 = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($pair))
|
||||
$auth += @('-c', ('http.extraheader=AUTHORIZATION: Basic ' + $b64))
|
||||
}
|
||||
# PS 5.1 wraps each native stderr line into an ErrorRecord, turning two real
|
||||
# log lines into eight of CategoryInfo/FullyQualifiedErrorId noise. Flattening
|
||||
# with "$_" keeps the log readable; $LASTEXITCODE is unaffected.
|
||||
$fetchArgs = $auth + @('fetch', '--prune', '--no-tags', 'origin')
|
||||
& git @fetchArgs 2>&1 | ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host "::error::git fetch origin failed (exit $LASTEXITCODE)"; exit 1 }
|
||||
# Some refs (PR merge commits, refs outside refs/heads) are not covered by the
|
||||
# branch refspec -- fall back to an explicit ref fetch, then INSIST the wanted
|
||||
# SHA is really present, rather than silently building the wrong tree.
|
||||
git rev-parse --verify -q ($env:DTU_REPO_SHA + '^{commit}') 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host 'sha not covered by the branch refspec -- fetching the event ref explicitly'
|
||||
$refArgs = $auth + @('fetch', '--prune', '--no-tags', 'origin', $env:DTU_REPO_REF)
|
||||
& git @refArgs 2>&1 | ForEach-Object { "$_" }
|
||||
$f = Invoke-DtuGit -G ($auth + @('fetch', '--prune', '--no-tags', 'origin'))
|
||||
if ($f.Code -ne 0) {
|
||||
$f.Lines | ForEach-Object { Write-Host " $_" }
|
||||
Write-Host ('::error::git fetch origin failed (exit ' + $f.Code + ')')
|
||||
exit 1
|
||||
}
|
||||
git rev-parse --verify -q ($env:DTU_REPO_SHA + '^{commit}') 2>$null | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host ('::error::' + $env:DTU_REPO_SHA + ' is not present in the fetched history'); exit 1 }
|
||||
& git checkout -q --detach $env:DTU_REPO_SHA 2>&1 | ForEach-Object { "$_" }
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::detached checkout failed'; exit 1 }
|
||||
git reset --hard -q $env:DTU_REPO_SHA
|
||||
if ($LASTEXITCODE -ne 0) { Write-Host '::error::reset --hard failed'; exit 1 }
|
||||
# Some event refs (PR merge commits, refs outside refs/heads) are not covered
|
||||
# by the branch refspec -- fall back to an explicit ref fetch, then INSIST the
|
||||
# wanted SHA is really present rather than silently building the wrong tree.
|
||||
$want = $env:DTU_REPO_SHA + '^{commit}'
|
||||
$chk = Invoke-DtuGit -G @('cat-file', '-e', $want)
|
||||
if ($chk.Code -ne 0) {
|
||||
Write-Host 'sha not covered by the branch refspec -- fetching the event ref explicitly'
|
||||
$f2 = Invoke-DtuGit -G ($auth + @('fetch', '--prune', '--no-tags', 'origin', $env:DTU_REPO_REF))
|
||||
if ($f2.Code -ne 0) { $f2.Lines | ForEach-Object { Write-Host " $_" } }
|
||||
$chk = Invoke-DtuGit -G @('cat-file', '-e', $want)
|
||||
}
|
||||
if ($chk.Code -ne 0) {
|
||||
Write-Host ('::error::' + $env:DTU_REPO_SHA + ' is not present in the fetched history')
|
||||
Write-Host ' (refusing to run the gates against a tree we did not verify)'
|
||||
exit 1
|
||||
}
|
||||
$c = Invoke-DtuGit -G @('checkout', '-q', '--detach', $env:DTU_REPO_SHA)
|
||||
if ($c.Code -ne 0) { $c.Lines | ForEach-Object { Write-Host " $_" }; Write-Host '::error::detached checkout failed'; exit 1 }
|
||||
$rr = Invoke-DtuGit -G @('reset', '--hard', '-q', $env:DTU_REPO_SHA)
|
||||
if ($rr.Code -ne 0) { Write-Host '::error::reset --hard failed'; exit 1 }
|
||||
# -ffd, deliberately not -ffdx: -x would delete IGNORED files, and the runner
|
||||
# reuses those across runs (pip / pre-commit caches live in the workspace).
|
||||
git clean -ffd -q
|
||||
Write-Host ('checkout ok: ' + (git rev-parse --short HEAD) + ' | commits=' + (git rev-list --count HEAD) + ' | dirty_files=' + ((git status --porcelain | Measure-Object).Count))
|
||||
$cl = Invoke-DtuGit -G @('clean', '-ffd', '-q')
|
||||
if ($cl.Code -ne 0) { $cl.Lines | ForEach-Object { Write-Host " $_" }; Write-Host '::error::git clean failed'; exit 1 }
|
||||
$head = (Invoke-DtuGit -G @('rev-parse', '--short', 'HEAD')).Lines -join ''
|
||||
$ncommits = (Invoke-DtuGit -G @('rev-list', '--count', 'HEAD')).Lines -join ''
|
||||
$dirty = @(Invoke-DtuGit -G @('status', '--porcelain')).Lines.Count
|
||||
Write-Host ('checkout ok: ' + $head + ' | commits=' + $ncommits + ' | dirty_files=' + $dirty)
|
||||
- name: 探测 MSVC(VS BuildTools / Community 等)
|
||||
id: msvc
|
||||
shell: powershell
|
||||
|
||||
Reference in New Issue
Block a user