Skip to content

Explain ruleset and branch protection push rejections - #73

Draft
aiolibsbot wants to merge 3 commits into
sanitizers:masterfrom
aiolibsbot:koan/explain-push-rejections
Draft

aiolibsbot wants to merge 3 commits into
sanitizers:masterfrom
aiolibsbot:koan/explain-push-rejections

Conversation

@aiolibsbot

@aiolibsbot aiolibsbot commented Sep 16, 2026 •

Copy link
Copy Markdown

Depends on #72 — branched off koan/expose-cherry-pick-conflicts, which
introduces patchback/backport.py and the test suite this builds on. Review
that one first; the diff here is the last two commits only.

What: Diagnose why GitHub rejected the backport branch push instead of
always blaming the App Installation permissions.

Why: Rulesets and classic branch protection turn down pushes the
installation is perfectly entitled to make. The current message sends the
maintainer to the App settings page, where there is nothing to fix — see the
ansible-builder case in #48.

How: explain_push_rejection() matches the remote's side-band output
(GH013 / GH006 / the workflows refusal) and renders the matching
remediation — add Patchback to the ruleset Bypass list, or stop the branch
protection pattern from matching patchback/*. Classic branch protection has
no App bypass list at all, so that hint says so rather than pretending. The
existing permissions text stays as the fallback. Pure function, no new deps.

Testing: python -m pytest — 21 passed. Six new tests: one per rejection
class, plus an end-to-end one that installs a rejecting pre-receive hook on
the fixture remote and asserts the advice reaches the raised PermissionError.

Refs #48


Quality Report

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

Code scan: 1 issue(s) found

  • patchback/backport.py:23 — 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.
A push that GitHub turns down was always reported as a missing GitHub
App Installation permission. Rulesets and classic branch protection
reject pushes the installation is otherwise entitled to make, and each
needs the maintainer to act somewhere else entirely -- the app settings
advice sends them looking in the wrong place.

Classify the rejection from what the remote sent back and render the
matching remediation: adding Patchback to the ruleset bypass list, or
moving the backport branches out of the branch protection pattern.

Refs sanitizers#48

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