58 lines
3.1 KiB
Bash
Executable File
58 lines
3.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
||
# 回执等待件——合回后等 CI 回执的后台轮询器(2026-11-02 用户裁;规则面=process-conventions §1 第 3 条「回执等待纪律」)。
|
||
# 用法: tools/ci_watch.sh <合并提交hash> [超时秒,默认 900]
|
||
# 调用面: 合回落地先向用户呈报本批总结,随即以 run_in_background 后台任务运行本件;命令结束时
|
||
# harness 唤醒会话——绿→删区收口;红→呈报红闸名单走修复批;超时→呈报「回执未落」(CI 可能崩溃)。
|
||
# 「本次回执已落」认领判据(三条全满足,防认错轮):
|
||
# ① 回执可整读——CI 写回执非原子,半截/读空静默重试;
|
||
# ② 回执 commit 为传入提交或其后代——另一会话合回推进尖端触发补跑覆盖时按后代认领(coalesce 语义见 ci_runner.sh);
|
||
# ③ 回执 timestamp 晚于合并提交时刻——排除合并前盘上的旧回执(旧尖端是合并提交的祖先,只查②会误领旧回执)。
|
||
# 退出码: 0=绿 1=红 2=超时/回执未落 3=参数错误
|
||
set -u
|
||
# 主工作区定位:CI 只在主工作区跑(post-merge 钩子限主区),从任务 worktree 内调用也要读主区回执——
|
||
# 经 git 公共目录(.git)的上级定位主仓根,主区调用与 worktree 调用同一读数
|
||
COMMON="$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)" \
|
||
|| { echo "不在 git 仓库内"; exit 3; }
|
||
REPO="$(dirname "$COMMON")"
|
||
STATUS="$REPO/game/tests/.gates/ci-status.json"
|
||
BASE="${1:-}"
|
||
TIMEOUT="${2:-900}"
|
||
INTERVAL=30
|
||
[ -n "$BASE" ] || { echo "用法: ci_watch.sh <合并提交hash> [超时秒]"; exit 3; }
|
||
BASE="$(git -C "$REPO" rev-parse --verify "${BASE}^{commit}")" || { echo "提交不存在: $BASE"; exit 3; }
|
||
BASE_CT="$(git -C "$REPO" log -1 --format=%ct "$BASE")"
|
||
_deadline=$(( $(date +%s) + TIMEOUT ))
|
||
while :; do
|
||
_read="$(python -I -c "
|
||
import json, sys, datetime
|
||
try:
|
||
d = json.load(open(sys.argv[1], encoding='utf-8'))
|
||
ts = int(datetime.datetime.fromisoformat(d['timestamp']).timestamp())
|
||
print(d.get('commit', ''), d.get('verdict', ''), ts)
|
||
except Exception:
|
||
pass
|
||
" "$STATUS")"
|
||
_com=""; _verdict=""; _ts=""
|
||
if [ -n "$_read" ]; then
|
||
set -- $_read
|
||
[ $# -eq 3 ] && { _com="$1"; _verdict="$2"; _ts="$3"; }
|
||
fi
|
||
if [ -n "$_com" ] && [ -n "$_verdict" ] && [ -n "$_ts" ] \
|
||
&& [ "$_ts" -gt "$BASE_CT" ] \
|
||
&& git -C "$REPO" merge-base --is-ancestor "$BASE" "$_com" 2>/dev/null; then
|
||
python -I -c "
|
||
import json, sys
|
||
d = json.load(open(sys.argv[1], encoding='utf-8'))
|
||
print('CI回执:', d.get('verdict'), '| commit', d.get('commit', '')[:8], '| 耗时', d.get('duration_s'), 's')
|
||
if d.get('note'): print('note:', d['note'])
|
||
for g in d.get('red_gates', []): print('红闸:', g)
|
||
for l in d.get('key_lines', []): print('key:', l)
|
||
" "$STATUS"
|
||
[ "$_verdict" = "green" ] && exit 0
|
||
exit 1
|
||
fi
|
||
_now=$(date +%s); _left=$((_deadline - _now))
|
||
[ "$_left" -le 0 ] && { echo "超时(${TIMEOUT}s)回执未落——CI 可能崩溃或未触发,查 game/tests/.gates/ 的 ci.lock 与 ci.log"; exit 2; }
|
||
sleep $(( _left < INTERVAL ? _left : INTERVAL ))
|
||
done
|