diff --git a/python/robomp/src/host_tools.py b/python/robomp/src/host_tools.py index d1a4d4a2a..a98a662c8 100644 --- a/python/robomp/src/host_tools.py +++ b/python/robomp/src/host_tools.py @@ -668,11 +668,24 @@ def _repair_commit_message_escapes(bindings: ToolBindings, args: Mapping[str, An Rebuilds ``origin/..HEAD`` with ``git commit-tree``, preserving every tree, parent topology, identity, and date — only messages change. Safe against already-pushed commits: the push transport uses - ``--force-with-lease``. Best effort: any git failure leaves the branch - untouched (the push proceeds with the ugly message rather than being - blocked on cosmetics), and the branch ref only moves via a compare-and- - swap ``update-ref`` at the very end. + ``--force-with-lease``. Once a broken message is detected the repair is + mandatory — a git failure mid-rewrite refuses the push (the branch ref + itself only ever moves via the compare-and-swap ``update-ref`` at the + very end, so a refusal never leaves partial state). """ + + def fail(step: str, proc: subprocess.CompletedProcess[str]) -> NoReturn: + err = (proc.stderr or proc.stdout).strip() or f"exit {proc.returncode}" + msg = ( + f"refusing to push: commit messages contain literal `\\n` escapes and the " + f"automatic repair failed at `{step}`: {err}\n" + "Reword the affected commits yourself (`git rebase -i origin/" + + bindings.repo.default_branch + + "`, real newlines via `git commit -F ` or multiple `-m` flags) and retry." + ) + _audit(bindings, tool_name, args, error=msg) + _raise_command(msg) + base = bindings.repo.default_branch rev_list = _run_repo_command(bindings, ["git", "rev-list", "--reverse", f"origin/{base}..HEAD"]) if rev_list.returncode != 0: @@ -685,6 +698,8 @@ def _repair_commit_message_escapes(bindings: ToolBindings, args: Mapping[str, An for sha in shas: show = _run_repo_command(bindings, ["git", "log", "-1", "--format=%B", sha]) if show.returncode != 0: + if repaired: + fail("git log", show) return message = show.stdout fixed = _repair_message_escapes(message) @@ -703,10 +718,10 @@ def _repair_commit_message_escapes(bindings: ToolBindings, args: Mapping[str, An ["git", "log", "-1", "--format=%T%x1f%P%x1f%an%x1f%ae%x1f%aI%x1f%cn%x1f%ce%x1f%cI", sha], ) if meta.returncode != 0: - return + fail("git log", meta) fields = meta.stdout.strip("\n").split("\x1f") if len(fields) != 8: - return + fail("git log", meta) tree, parents_raw, a_name, a_email, a_date, c_name, c_email, c_date = fields parents_old = parents_raw.split() parents_new = [rewritten.get(p, p) for p in parents_old] @@ -730,7 +745,7 @@ def _repair_commit_message_escapes(bindings: ToolBindings, args: Mapping[str, An }, ) if made.returncode != 0 or not made.stdout.strip(): - return + fail("git commit-tree", made) rewritten[sha] = made.stdout.strip() old_head, new_head = shas[-1], rewritten[shas[-1]] @@ -739,11 +754,7 @@ def _repair_commit_message_escapes(bindings: ToolBindings, args: Mapping[str, An ["git", "update-ref", "-m", "robomp: repaired commit message escapes", "HEAD", new_head, old_head], ) if update.returncode != 0: - log.warning( - "commit message escape repair failed at update-ref", - extra={"issue": bindings.issue_key, "err": (update.stderr or update.stdout).strip()}, - ) - return + fail("git update-ref", update) _audit(bindings, tool_name, args, result={"repaired_commit_messages": [sha[:12] for sha in repaired]}) log.info( "repaired commit message escapes",