merge: dsh-1741f418-0c10 合入 master(task-worktree 10项改进)
This commit is contained in:
@@ -50,7 +50,7 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr
|
||||
| `postmerge` | 主树合并后补绿:体积凭证**自动诊断**(新鲜跳过;红→先短链 `--update`,只读复检真漂了才升全链)+ `check_doc_links --all` + 列待提交文件,**不代提交** |
|
||||
|
||||
- **退出码** `0` 成功 / `1` 操作失败 / `2` 前置或环境错误 / `3` 用法——**只有不接管道才读得到**(→ 读输出)。
|
||||
- `new`:`--base`(默认 `v2-on-ui`)· `--needs-bench`(无台架 PASS 则 `done` 拒合)· `--lean`(跳 LFS smudge,只省磁盘不省时间)· `--no-bootstrap`(不建联接)· `--force`(**只跳过并发上限检查**,不解决谁让位 → A)
|
||||
- `new`:`--base`(默认 `master`)· `--needs-bench`(无台架 PASS 则 `done` 拒合)· `--lean`(跳 LFS smudge,只省磁盘不省时间)· `--no-bootstrap`(不建联接)· `--force`(**只跳过并发上限检查**,不解决谁让位 → A)
|
||||
- `done`:`--dry-run` · `--skip-build` · `--skip-gates` · `--force`。⚠ `--dry-run` **只保护预演段、不保护清场段**(实测:对已合入的分支它照样摘联接、删分支、清台账;且它的前置也要抢 `build` 锁)→ 用完复验 `git worktree list`,细节 → 陷阱清单。
|
||||
- `lock`:`--task`(acquire 必填)· `--wait <秒>` · `--ttl <小时>`(默认 `build`=1h / `bench`=6h,超期视为陈旧、下次 acquire 自动接管)· `--note` · `--bench-pass` · `--force`。⚠ `--note` 只在 **acquire** 写进 payload,**release 时被忽略** → 验收证据写主干提交说明或文档。
|
||||
- 环境变量:`DTU_WT_ROOT`(默认仓库同级 `_wt/`)· `DTU_TASK_BASE`(默认基线)· `DTU_INTEGRATION_WORKTREE`(集成点,用于隔离演练)。
|
||||
@@ -95,23 +95,23 @@ PY=".venv/Scripts/python.exe"; TW=".dsh/skills/task-worktree/scripts/task_worktr
|
||||
1. **合并前确认属主提交完**:`$TW status <ID>` 看未提交与领先数,跨会话再问属主一句。实测教训:属主还剩「2/2」没提交就 `done` → 只含 1/2 的分支进了主干,只能另开一棵树补后半(自己的时序错,不是分支问题)。
|
||||
2. 先 `--dry-run` 且**读全量**。判据两行:`[ok] 前置检查通过(领先 <base> 共 N 个提交)` + `[ok] 无冲突,预演结果树 <sha>`;**N 必须等于属主应交的提交数**,对不上回第 1 步。
|
||||
3. 正式 `$TW done <ID>`——**必须在主工作树**(铁律 1)。
|
||||
4. 见 `!! 门禁未全绿:工作树与分支**保留**,未清场` 时,紧跟的 `主干已含合并提交 <sha>` 说明**合并已落主干**;先定位哪条钩子红(`[SUMMARY] static: pre-commit hooks -- passed=N failed=M`),最常态是 `.map` 滞后 → 第 5 步。
|
||||
- ✅ 走提示 a):主工作树修正后追加提交。
|
||||
- ❌ **绝不用 b) 的 `reset --hard <sha>`**——那是丢弃已入库的合并提交,且 `reset --hard` 是项目级 Never(连未提交工作一起丢)。提示 b 是列出的可能性,不是建议。
|
||||
5. 补绿固定链(顺序即因果,全在主工作树,逐条不接管道看退出码):
|
||||
4. 见 `!! 门禁未全绿` 时,`done` 现在会**自动调 `postmerge` 补绿**(体积凭证 + RAM 台账 + 文档全库)。补绿成功且有回填文件时列出待提交清单;补绿失败才保留工作树等你手动排查。紧跟的 `主干已含合并提交 <sha>` 说明**合并已落主干**。
|
||||
- ✅ 自动补绿成功后按提示提交回填文件,再重跑 `done` 完成清场。
|
||||
- ❌ **绝不用 `reset --hard <sha>`**——那是丢弃已入库的合并提交,且 `reset --hard` 是项目级 Never(连未提交工作一起丢)。
|
||||
5. **`done` 现在自动完成整条补绿链**:regen(任务表 + 内存快照 + RAM 台账)→ build → ci → postmerge(凭证诊断 + 文档全库)。门禁通过后若 postmerge 产生回填文件,会列出待提交清单等你确认后再清场。
|
||||
手工补绿链仍保留为权威兜底(`postmerge` 内部逻辑等价于下面的步骤):
|
||||
|
||||
```bash
|
||||
"$PY" scripts/check_mem_numbers.py --write # 台账受管区 ↔ 新 .map 对齐
|
||||
"$PY" scripts/ram_baseline.py --write # RAM 快照 / 92% 预算台账
|
||||
"$PY" scripts/ram_baseline.py --update # 趋势基线追加一行
|
||||
"$PY" scripts/ram_baseline.py --write # RAM 快照 / 92% 预算台账
|
||||
"$PY" scripts/check_test_coverage.py --update-baseline # 覆盖率基线(新单测 / 新套件时必刷)
|
||||
"$PY" scripts/check_doc_links.py --all # 文档链接与章节引用
|
||||
git add <上面刷出来的基线/快照文件> # 显式列文件,别 -A 把别人在飞的改动一起提交
|
||||
git commit -F <UTF-8 无 BOM 的正文文件> # 提交属 Ask first(AGENTS.md 边界)
|
||||
```
|
||||
|
||||
为什么必然要补 → 陷阱「`done` 的门禁跑在 `/keil-build` 之前」。合并解过冲突时,**合并提交期 pre-commit 只查冲突文件**(→ G 节第 3 条)→ 落主干后必须另跑全量。
|
||||
**注释/文档-only 但碰了 .c/.h 的短链**:先比 `Program Size` 与合并前是否逐项相同——相同就只 `ram_baseline.py --update` 重写体积凭证(`scripts/size_attestation.json` 跟着补提交笔走),**别 `--write` 动受管区**(等于给在飞分支造冲突面);数字变了才走上面全链。**已命令化**:`"$PY" "$TW" postmerge`——新鲜直接跳过;红则先短链,再用只读 `check_mem_numbers` 复检裁定是否升全链;末尾跑 `--all` 并列出待提交文件(不代提交)。上面的手工全链仍是权威兜底。
|
||||
**`--dry-run` 新增台账碰撞预检**:双方都改过的已知生成文件(`ram_baseline.tsv` / `size_attestation.json` / RAM 台账 md)会在预演阶段提前警告,让你知道合并后需要 postmerge 重算。
|
||||
6. **重跑 `done` 前主树必须干净**,否则 `失败:主工作树不干净(…)——合并会把未提交改动卷进来` → 第 5 步产物先提交。分支已并时重跑打印 `[i] task/<ID>-<slug> 已合入 <base>,跳过合并,直接清场`——**该路径在门禁之前就返回,不会替你复绿**:上一次门禁没跑到的尾巴(主树 `ci_local.py` + `check_doc_links.py --all`;体积/文档那半可 `postmerge` 一把跑)必须自己补。主树脏且那份脏不是你的 → **C2**。
|
||||
7. 清场后再敲一次 `done <ID>` 得 `前置/环境错误:任务 <ID> 不存在(缺台账 …json)`(exit 2)——**不是失败**:清场最后一步 unlink 了台账。**判完成以 `git log` 为准**。
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ def _main_worktree_of(start: Path):
|
||||
_SCRIPT_ROOT = Path(__file__).resolve().parents[4]
|
||||
REPO_ROOT = _main_worktree_of(_SCRIPT_ROOT) or _SCRIPT_ROOT
|
||||
WT_ROOT = Path(os.environ.get("DTU_WT_ROOT") or (REPO_ROOT.parent / "_wt"))
|
||||
BASE_BRANCH = os.environ.get("DTU_TASK_BASE", "v2-on-ui")
|
||||
BASE_BRANCH = os.environ.get("DTU_TASK_BASE", "master")
|
||||
|
||||
STATE_DIR = WT_ROOT / ".state"
|
||||
LOCKS_DIR = STATE_DIR / "locks"
|
||||
@@ -86,11 +86,21 @@ LOCKS_DIR = STATE_DIR / "locks"
|
||||
# /worktree)把任务树建在**仓库内**这个命名空间下,与本 skill 的仓外 _wt/ 刻意分开。
|
||||
# .gitignore 里预置同名规则,正是为了让插件的 ensureIgnored() 见到就跳过、
|
||||
# 不在会话中途往这份受控文件里追加一行。
|
||||
PLUGIN_ROOT = REPO_ROOT / ".dsh-worktrees"
|
||||
PLUGIN_NAMESPACE = "worktree" # 插件侧 WORKTREE_NAMESPACE:name 即分支尾段
|
||||
PLUGIN_SUBDIR = PLUGIN_ROOT / PLUGIN_NAMESPACE
|
||||
# DSH worktree 插件实际使用两个位置(历史演进):
|
||||
# 旧版 dsh-task-worktree → .dsh-worktrees/worktree/
|
||||
# 新版内置 create_worktree → .agents/worktrees/
|
||||
# plugin_trees() 同时扫描两处,list 统一展示。
|
||||
PLUGIN_ROOT_LEGACY = REPO_ROOT / ".dsh-worktrees"
|
||||
PLUGIN_NAMESPACE_LEGACY = "worktree"
|
||||
PLUGIN_SUBDIR_LEGACY = PLUGIN_ROOT_LEGACY / PLUGIN_NAMESPACE_LEGACY
|
||||
|
||||
PLUGIN_ROOT_AGENTS = REPO_ROOT / ".agents" / "worktrees"
|
||||
|
||||
PLUGIN_LOCK_PREFIX = "pw" # 插件树在锁 payload 里的持有者前缀
|
||||
|
||||
# 兼容旧引用:外部若直接读 PLUGIN_SUBDIR 仍指向 legacy 路径
|
||||
PLUGIN_SUBDIR = PLUGIN_SUBDIR_LEGACY
|
||||
|
||||
|
||||
# --------------------------------------------------------------------- 基础工具
|
||||
|
||||
@@ -472,29 +482,50 @@ def junction_label(st) -> str:
|
||||
|
||||
|
||||
def plugin_trees():
|
||||
""".dsh-worktrees/worktree/ 下的工作树(事实源 = git worktree list,不读插件 manifest)。
|
||||
"""扫描两处 DSH 插件工作树(事实源 = git worktree list,不读插件 manifest)。
|
||||
|
||||
位置:
|
||||
- .dsh-worktrees/worktree/ (旧版 dsh-task-worktree 插件)
|
||||
- .agents/worktrees/ (新版内置 create_worktree 工具)
|
||||
|
||||
manifest.json 是插件私有格式(MANIFEST_VERSION 1,字段随版本变),而 git 的注册表
|
||||
同时覆盖「插件正常建的树」和「manifest 丢了 / 树被手工删」的残留——桥接命令要看的
|
||||
是真实存在的东西。
|
||||
"""
|
||||
try:
|
||||
base = PLUGIN_SUBDIR.resolve()
|
||||
except OSError:
|
||||
bases = []
|
||||
for candidate in (PLUGIN_SUBDIR_LEGACY, PLUGIN_ROOT_AGENTS):
|
||||
try:
|
||||
resolved = candidate.resolve()
|
||||
if resolved.is_dir():
|
||||
bases.append(resolved)
|
||||
except OSError:
|
||||
continue
|
||||
if not bases:
|
||||
return []
|
||||
trees = []
|
||||
seen_paths = set()
|
||||
for ent in worktree_entries():
|
||||
p = ent["path"]
|
||||
try:
|
||||
rp = p.resolve()
|
||||
except OSError:
|
||||
continue
|
||||
if rp == base or base not in rp.parents:
|
||||
matched_base = None
|
||||
for base in bases:
|
||||
if rp == base or base in rp.parents:
|
||||
matched_base = base
|
||||
break
|
||||
if matched_base is None:
|
||||
continue
|
||||
# 去重(同一棵树不会出现在两个 base 下,但防御性检查)
|
||||
path_key = str(rp)
|
||||
if path_key in seen_paths:
|
||||
continue
|
||||
seen_paths.add(path_key)
|
||||
dirty = (len(git_out("status", "--porcelain", cwd=p, check=False).splitlines())
|
||||
if p.is_dir() else -1)
|
||||
trees.append({
|
||||
"name": rp.relative_to(base).as_posix(),
|
||||
"name": rp.relative_to(matched_base).as_posix(),
|
||||
"path": str(p),
|
||||
"branch": ent.get("branch") or "(detached)",
|
||||
"dirty": dirty,
|
||||
@@ -605,30 +636,37 @@ def regen(cwd: Path) -> None:
|
||||
run_script("scripts/gen_task_table.py", ["--write"], cwd, "重生成 FreeRTOS 任务表")
|
||||
if list((Path(cwd) / "MDK-ARM").glob("**/*.map")):
|
||||
run_script("scripts/check_mem_numbers.py", ["--write"], cwd, "同步内存/Flash 快照")
|
||||
run_script("scripts/ram_baseline.py", ["--update"], cwd, "追加 RAM 趋势基线行")
|
||||
run_script("scripts/ram_baseline.py", ["--write"], cwd, "同步 RAM 预算台账受管区")
|
||||
else:
|
||||
print("[skip] 未找到 .map,跳过内存快照同步(需先 /keil-build)")
|
||||
print("[skip] 未找到 .map,跳过内存快照与 RAM 台账同步(需先 /keil-build)")
|
||||
|
||||
|
||||
def run_gates(cwd: Path, skip_build: bool) -> bool:
|
||||
ok = run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI")
|
||||
if skip_build:
|
||||
# 先构建再跑门禁:ci_local.py 含 mem-budget 钩子,需要新鲜 .map 才能正确比对台账。
|
||||
# 旧版先 ci 后 build 导致合并后 .map 仍是旧的 → mem-budget 必红。
|
||||
ok = True
|
||||
if not skip_build:
|
||||
got, info = lock_acquire("build", "__done__", wait=900)
|
||||
if not got:
|
||||
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 /keil-build".format(info.get("task")))
|
||||
return False
|
||||
# 告知 /keil-build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
|
||||
# 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**,
|
||||
# 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。
|
||||
os.environ["DTU_LOCK_HELD"] = "build"
|
||||
try:
|
||||
if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd,
|
||||
"/keil-build(0 错 0 警)"):
|
||||
ok = False
|
||||
finally:
|
||||
os.environ.pop("DTU_LOCK_HELD", None)
|
||||
lock_release("build", "__done__", force=True)
|
||||
else:
|
||||
print("[skip] 按 --skip-build 跳过 /keil-build")
|
||||
return ok
|
||||
got, info = lock_acquire("build", "__done__", wait=900)
|
||||
if not got:
|
||||
print("!! build 锁被 {} 占用(UV4 不能并发构建),跳过 /keil-build".format(info.get("task")))
|
||||
return False
|
||||
# 告知 /keil-build「调用方已持锁」:新版 keil_build.py 自持自释(见其 docstring),
|
||||
# 读到本变量即跳过,避免自我死锁;**旧版根本不看此变量、也不会自锁**,
|
||||
# 故此处对「工作树里还是旧脚本」的在途任务同样安全(向后兼容)。
|
||||
os.environ["DTU_LOCK_HELD"] = "build"
|
||||
try:
|
||||
if not run_script(".dsh/skills/keil-build/scripts/keil_build.py", [], cwd,
|
||||
"/keil-build(0 错 0 警)"):
|
||||
ok = False
|
||||
finally:
|
||||
os.environ.pop("DTU_LOCK_HELD", None)
|
||||
lock_release("build", "__done__", force=True)
|
||||
# 构建完成(或跳过后)再跑静态门禁 + 单测,此时 .map 是新鲜的
|
||||
if not run_script("scripts/ci_local.py", [], cwd, "静态门禁 + 主机侧纯逻辑单测 本地等价 CI"):
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
|
||||
@@ -668,45 +706,75 @@ def audit_residue(base_before: str, branch: str, cwd: Path) -> None:
|
||||
print(" - " + f)
|
||||
if len(both) > 30:
|
||||
print(" …(其余 {} 个略)".format(len(both) - 30))
|
||||
hits = []
|
||||
hits_dup = []
|
||||
hits_marker = []
|
||||
conflict_markers = ("<<<<<<<", "=======", ">>>>>>>")
|
||||
structured_exts = (".json", ".tsv", ".csv")
|
||||
for f in both:
|
||||
p = cwd / f
|
||||
if not p.is_file():
|
||||
continue
|
||||
try:
|
||||
lines = p.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
content = p.read_text(encoding="utf-8", errors="replace")
|
||||
lines = content.splitlines()
|
||||
except OSError:
|
||||
continue
|
||||
# 检查 git 冲突标记(结构化文件尤其危险,会静默损坏 JSON/TSV)
|
||||
ext = os.path.splitext(f)[1].lower()
|
||||
if ext in structured_exts or any(m in content for m in conflict_markers):
|
||||
marker_lines = [i + 1 for i, line in enumerate(lines)
|
||||
if any(line.strip().startswith(m) for m in conflict_markers)]
|
||||
if marker_lines:
|
||||
hits_marker.append((f, marker_lines))
|
||||
# 检查连续重复行(并集合并的经典症状)
|
||||
dups = [i + 1 for i in range(1, len(lines))
|
||||
if lines[i] == lines[i - 1] and lines[i].strip()]
|
||||
if dups:
|
||||
hits.append((f, dups))
|
||||
if hits:
|
||||
hits_dup.append((f, dups))
|
||||
if hits_marker:
|
||||
print()
|
||||
print("!! 发现 git 冲突标记(结构化文件损坏风险极高):")
|
||||
for f, lns in hits_marker:
|
||||
print(" - {} 第 {} 行".format(f, ", ".join(str(l) for l in lns[:8])))
|
||||
if hits_dup:
|
||||
print()
|
||||
print("!! 连续重复行(并集合并残留嫌疑):")
|
||||
for f, dups in hits:
|
||||
for f, dups in hits_dup:
|
||||
print(" - {} 第 {} 行".format(f, ", ".join(str(d) for d in dups[:8])))
|
||||
else:
|
||||
print("[ok] 未检测到连续重复行")
|
||||
if not hits_marker and not hits_dup:
|
||||
print("[ok] 未检测到冲突标记或连续重复行")
|
||||
print("提示:权威判据是 base / 两个 tip 三方对比(git show <ref>:<file>),别只看结果文件。")
|
||||
|
||||
|
||||
def harvest_memory(st: dict) -> None:
|
||||
"""任务工作树会被删除,先把它自己的记忆归并回主工作树,避免丢上下文"""
|
||||
src = Path(st["path"]) / ".workbuddy-ai" / "memory"
|
||||
if not src.is_dir():
|
||||
"""任务工作树会被删除,先把它的 dsh-mnemon 记忆归并回主工作树,避免丢上下文。
|
||||
|
||||
只处理 .mnemon/ 目录(dsh-mnemon 插件)。目录级合并:只拷贝目标不存在的子项,
|
||||
不覆盖已有内容。若无 .mnemon/ 则静默跳过。
|
||||
"""
|
||||
wt = Path(st["path"])
|
||||
src_mn = wt / ".mnemon"
|
||||
if not src_mn.is_dir():
|
||||
return
|
||||
dst_dir = REPO_ROOT / ".workbuddy-ai" / "memory"
|
||||
dst_dir.mkdir(parents=True, exist_ok=True)
|
||||
for f in sorted(src.glob("*.md")):
|
||||
text = f.read_text(encoding="utf-8", errors="replace").strip()
|
||||
if not text:
|
||||
continue
|
||||
dst = dst_dir / f.name
|
||||
marker = "## [task-worktree 归并自任务 {} / {}]".format(st["id"], st["slug"])
|
||||
with dst.open("a", encoding="utf-8", newline="\n") as fh:
|
||||
fh.write("\n" + marker + "\n\n" + text + "\n")
|
||||
print("[ok] 记忆归并 {} -> {}".format(f.name, dst))
|
||||
dst_mn = REPO_ROOT / ".mnemon"
|
||||
dst_mn.mkdir(parents=True, exist_ok=True)
|
||||
import shutil
|
||||
count = 0
|
||||
for item in sorted(src_mn.iterdir()):
|
||||
target = dst_mn / item.name
|
||||
try:
|
||||
if item.is_dir():
|
||||
if not target.exists():
|
||||
shutil.copytree(str(item), str(target))
|
||||
count += 1
|
||||
else:
|
||||
if not target.exists():
|
||||
shutil.copy2(str(item), str(target))
|
||||
count += 1
|
||||
except OSError as e:
|
||||
print("[warn] 记忆拷贝失败 {}: {}".format(item.name, e))
|
||||
if count:
|
||||
print("[ok] 记忆归并(mnemon) {} 个条目 -> {}".format(count, dst_mn))
|
||||
|
||||
|
||||
def teardown_worktree(st: dict, force: bool = False, base: str = None) -> None:
|
||||
@@ -989,6 +1057,25 @@ def cmd_done(a) -> int:
|
||||
raise Fail("预演发现冲突:到任务工作树里 `git merge {}` 解决并提交后重跑 done".format(base))
|
||||
tree = (r.stdout.splitlines() or ["?"])[0].strip()
|
||||
print("[ok] 无冲突,预演结果树 {}".format(tree))
|
||||
|
||||
# 台账类生成物碰撞预检:双方都改过的已知生成文件在合并后大概率需要重算
|
||||
ledger_files = (
|
||||
"scripts/ram_baseline.tsv",
|
||||
"scripts/size_attestation.json",
|
||||
"docs/内存管理/RAM预算台账.md",
|
||||
"docs/内存管理/RAM使用分析与优化方案.md",
|
||||
)
|
||||
mb = git_out("merge-base", base, branch, cwd=main, check=False)
|
||||
task_changed = set(filter(None, git_out("diff", "--name-only", mb, branch, cwd=main, check=False).splitlines()))
|
||||
base_changed = set(filter(None, git_out("diff", "--name-only", mb, base, cwd=main, check=False).splitlines()))
|
||||
collided = sorted(f for f in ledger_files if f in task_changed and f in base_changed)
|
||||
if collided:
|
||||
print()
|
||||
print("[warn] 以下 {} 个台账/凭证文件双方都改过,合并后需 postmerge 重算:".format(len(collided)))
|
||||
for f in collided:
|
||||
print(" - " + f)
|
||||
print(" (done 会自动调 postmerge 补绿;若 --dry-run 请留意此提示)")
|
||||
|
||||
if a.dry_run:
|
||||
print()
|
||||
print("--dry-run:到此为止,未做任何改动。")
|
||||
@@ -1007,6 +1094,8 @@ def cmd_done(a) -> int:
|
||||
print("=== 合并残留审计 ===")
|
||||
audit_residue(base_before, branch, cwd=main)
|
||||
|
||||
# 统一补绿链:regen + build + ci + postmerge 收敛到一条路径。
|
||||
# regen 先跑(任务表/内存快照),然后 build+ci,最后 postmerge 兜底凭证与文档。
|
||||
print("=== 重生成受管产物 ===")
|
||||
regen(main)
|
||||
|
||||
@@ -1017,13 +1106,46 @@ def cmd_done(a) -> int:
|
||||
print("=== 门禁 ===")
|
||||
gates_ok = run_gates(main, skip_build=a.skip_build)
|
||||
|
||||
if not gates_ok:
|
||||
# 无论门禁是否通过,都跑一次 postmerge 确保凭证/文档全库一致。
|
||||
# 门禁已通过时 postmerge 检测到新鲜凭证会直接跳过(零开销)。
|
||||
print("=== postmerge 补绿(凭证 + 文档全库)===")
|
||||
pm_args = argparse.Namespace(force=False, skip_mem=False, coverage=False, skip_docs=False)
|
||||
try:
|
||||
pm_rc = cmd_postmerge(pm_args)
|
||||
except Exception as e:
|
||||
print("[warn] postmerge 异常:{}".format(e))
|
||||
pm_rc = EXIT_FAIL
|
||||
|
||||
# postmerge 完成后检查是否有回填产物需要提交
|
||||
dirty = git_out("diff", "--name-only", "HEAD", cwd=main, check=False).strip()
|
||||
if dirty and not gates_ok:
|
||||
print()
|
||||
print("!! 门禁未全绿:工作树与分支**保留**,未清场。")
|
||||
print("!! 门禁未全绿且 postmerge 产生了回填文件,工作树与分支**保留**。")
|
||||
print(" 主干已含合并提交 {}".format(base_after[:12]))
|
||||
print(" 可选:a) 在主工作树修正后追加提交(推荐)")
|
||||
print(" ——体积凭证/台账红就用 `postmerge` 自动诊断回填,别手抄补链")
|
||||
print(" b) 回退本次合并:git -C \"{}\" reset --hard {}".format(main, base_before[:12]))
|
||||
print("[i] 以下文件待提交(属 Ask first):")
|
||||
for line in dirty.splitlines():
|
||||
if line.strip():
|
||||
print(" - " + line.strip())
|
||||
print(" 建议:git add <上面列的文件> && git commit -F <正文文件>")
|
||||
print(" 提交后重跑 `done {}` 完成清场。".format(a.task_id))
|
||||
return EXIT_FAIL
|
||||
elif not gates_ok:
|
||||
print()
|
||||
print("!! 门禁未全绿,工作树与分支**保留**,未清场。")
|
||||
print(" 主干已含合并提交 {}".format(base_after[:12]))
|
||||
print(" 请手动排查后重跑 `done {}`。".format(a.task_id))
|
||||
print(" 或回退本次合并:git -C \"{}\" reset --hard {}".format(main, base_before[:12]))
|
||||
return EXIT_FAIL
|
||||
|
||||
# 门禁通过 + postmerge 完成 → 若有回填文件则提示提交后再清场
|
||||
if dirty:
|
||||
print()
|
||||
print("[i] postmerge 已回填,以下文件待提交(属 Ask first):")
|
||||
for line in dirty.splitlines():
|
||||
if line.strip():
|
||||
print(" - " + line.strip())
|
||||
print(" 建议:git add <上面列的文件> && git commit -F <正文文件>")
|
||||
print(" 提交后重跑 `done {}` 完成清场。".format(a.task_id))
|
||||
return EXIT_FAIL
|
||||
|
||||
print("=== 清场 ===")
|
||||
|
||||
Reference in New Issue
Block a user