Skip to content

Report git output and conflicting hunks on backport failure - #72

Draft
aiolibsbot wants to merge 2 commits into
sanitizers:masterfrom
aiolibsbot:koan/expose-cherry-pick-conflicts
Draft

aiolibsbot wants to merge 2 commits into
sanitizers:masterfrom
aiolibsbot:koan/expose-cherry-pick-conflicts

Conversation

@aiolibsbot

@aiolibsbot aiolibsbot commented Sep 15, 2026 •

Copy link
Copy Markdown

What

Capture the output of every git invocation the backport runs, and report it —
conflicting hunks included — in the check run and the progress comment.

Why

spawn_proc was check_call(), so no output was ever captured. Two visible
consequences:

How

  • Extract the git plumbing into patchback/backport.py, importing stdlib only.
    event_handlers.py goes 505 → 351 lines; the plumbing becomes reachable
    without octomachinery in the picture, which is what makes it testable.
  • run_proc() replaces spawn_proc: check_output with stderr merged in and
    errors='replace', since git emits non-UTF-8 freely. Failures land in
    CalledProcessError.output.
  • Every raised exception — fetch, checkout, conflict, push — now carries the
    sanitized command log. The conflict also reports
    git diff --diff-filter=U in a <details> block; git already names each
    conflicting path in its own output, so no extra call is needed for the file
    list.
  • On Expose the cherry-pick application conflict in the status updates #54's "what are the limits": check-run output.* fields and comment
    bodies both cap at 65535 characters, so each captured block is clipped at
    20000 with an explicit marker. Both interfaces get the same detail — the
    reporter feeds one payload to both.
  • sanitize_secret() is module-level and applied to everything rendered, so
    the installation token cannot reach a report through the new paths.

Testing

tests/ is new — the repo had none. 14 tests drive backport_pr_sync against
real local repositories (bare remote, clean target branch, diverging one), so
the clean cherry-pick, the merge-commit --mainline 1 path, and each failure
report are exercised for real. No network, no third-party imports beyond
pytest. .github/workflows/tests.yml runs them on 3.9/3.13/3.14.

Left out deliberately: #48's rulesets/branch-protection wording in the
privilege message (separate issue, separate PR), and #18's retry/--force
discussion — this only closes its reporting half.


Quality Report

Changes: 5 files changed, 498 insertions(+), 154 deletions(-)

Code scan: 1 issue(s) found

  • patchback/backport.py:22 — TODO comment

Tests: failed (command not found)

Branch hygiene: clean

Generated by Kōan

The bot spawned every git invocation through `check_call()`, which lets
the child inherit the parent's stdout and stderr. Nothing was captured,
so `CalledProcessError.stdout` was always `None` and the push-failure
report rendered an empty `[OUTPUT]` block — the one place the template
promised details. A cherry-pick conflict was even thinner: a bare
`Failed to cleanly apply ... on top of ...`, with the conflicting paths
and hunks only discoverable by replaying the backport by hand.

Capture the output instead — stderr merged into stdout, decoded
leniently because git happily emits non-UTF-8 — and carry it into every
exception the backport raises: the fetch and checkout lookups, the
conflict, and the push. The conflict additionally reports
`git diff --diff-filter=U` in a collapsed section, which is where the
actual collision lives. Everything is clipped to stay inside GitHub's
65535-character cap on check-run output and comment bodies, and passed
through the token mask first.

The plumbing moves to `patchback.backport`, which imports nothing but
the stdlib. That keeps it reachable without a web framework in the way,
and drops `event_handlers` from 505 to 351 lines.

Refs sanitizers#18, sanitizers#54
The repo had no tests and no CI job running them, for a bot whose core
operation force-pushes branches into other people's repositories.

Cover `patchback.backport` against real local repositories — a bare
remote, a clean target branch and a diverging one — so the cherry-pick,
the merge-commit mainline handling and each failure report are exercised
for real rather than mocked. No network and no third-party imports
beyond pytest are involved, which is what makes the split in the
previous commit worth having.

Test dependencies are installed unpinned in CI on purpose: the suite
needs only pytest, and threading it through the hash-pinned pip-tools
lock buys nothing until it grows real dependencies.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant