A contributor fixes a three-line null check in a parser, then ships the same commit with a reformatted switch statement and two renamed locals. Six days later the pull request is still open, and the maintainer asks the question that stalls most external patches: which parts of this diff are actually required? CI is green, but a green branch only proves the suite still passes with the patch applied. It says nothing about whether every changed line earns its place in the review.
This article describes a small harness that answers that question hunk by hunk. For each hunk in a patch, it reverse-applies only that hunk and reruns the pinned failing test, then records whether the test breaks again. The output is a three-value verdict per hunk, produced by the test runner rather than by opinion, and it turns a subjective "this diff feels large" comment into a table a maintainer can act on.
Why per-hunk evidence beats a narrated patch
Maintainers triage far more patches than they can read closely, so they optimize for review cost. A diff that mixes a fix with unrelated cleanup forces them to do three jobs at once: find the actual fix, verify it, and audit everything else for hidden behavior change.
Three failure modes show up again and again in review threads.
- Dead weight. A hunk that no test depends on, usually a drive-by refactor, gets reverted during review or blocks the merge entirely.
- False confidence. A patch passes because the suite is weak, not because the change is correct, and nobody can tell which lines carried the fix.
- Split requests. A maintainer asks for the cleanup to move to a separate pull request, which costs the contributor another full review cycle.
A hunk necessity run front-loads that work. The contributor discovers the dead hunks before a human has to point them out, and the final patch arrives with machine-checkable evidence attached.
Gate zero: the pinned test must fail before the patch
The harness assumes a single test command that reproduces the reported bug and exits non-zero on the unfixed revision. Without that gate, the whole run is noise: if the test already passes on HEAD, then every hunk will look "dead" and the table will be meaningless.
Two preconditions therefore run before any hunk is touched. The patch must make the test pass, and reverting the patch must make it fail again. A contributor who cannot satisfy both conditions has a test-quality problem to solve first, not a patch-splitting problem.
# gate 0, abbreviated
git apply "$PATCH" && eval "$TEST_CMD" # must succeed
git apply -R "$PATCH" && eval "$TEST_CMD" # must fail
The harness: split, reverse-apply, rerun
The script below runs inside a detached worktree so the reset loop cannot touch uncommitted work in the main checkout. It splits a unified diff into one file per hunk, keeps the file headers attached to each piece, then evaluates every hunk independently.
#!/usr/bin/env bash
# hunk-necessity.sh <patch-file> "<test-command>"
# Requires: git >= 2.30, a clean worktree, a test command that exits non-zero on the pinned bug.
set -euo pipefail
PATCH="$(realpath "${1:?patch file required}")"
TEST_CMD="${2:?test command required}"
ROOT="$(git rev-parse --show-toplevel)"
TMP="$(mktemp -d)"
HUNKS="$TMP/hunks"
RESULTS="$TMP/results.tsv"
mkdir -p "$HUNKS"
trap 'git -C "$ROOT" worktree remove --force "$TMP/wt" 2>/dev/null || true; rm -rf "$TMP"' EXIT
git -C "$ROOT" worktree add -q --detach "$TMP/wt" HEAD
cd "$TMP/wt"
# --- gate 0 ---
git apply "$PATCH"
eval "$TEST_CMD" >/dev/null 2>&1 || { echo "gate0: patch does not fix the pinned test" >&2; exit 2; }
git apply -R "$PATCH"
if eval "$TEST_CMD" >/dev/null 2>&1; then
echo "gate0: pinned test already passes on HEAD; it does not pin the bug" >&2; exit 3
fi
# --- split the diff into one patch per hunk, headers included ---
awk -v out="$HUNKS" '
/^diff --git / { if (hunk != "") close(hunk); hunk = ""; header = $0 "\n"; next }
/^(index |--- |\+\+\+ |new file|deleted file|similarity|rename|old mode|new mode)/ {
header = header $0 "\n"; next }
/^@@/ { if (hunk != "") close(hunk);
hunk = sprintf("%s/hunk-%03d.patch", out, ++n);
printf "%s", header > hunk }
hunk != "" { print >> hunk }
' "$PATCH"
printf 'hunk\tverdict\n' > "$RESULTS"
for piece in "$HUNKS"/hunk-*.patch; do
git checkout -q -- .
git apply "$PATCH"
id="$(basename "$piece" .patch)"
if ! git apply -R "$piece" 2>/dev/null; then
printf '%s\tentangled\n' "$id" >> "$RESULTS"
elif eval "$TEST_CMD" >/dev/null 2>&1; then
printf '%s\tdead\n' "$id" >> "$RESULTS"
else
printf '%s\tload-bearing\n' "$id" >> "$RESULTS"
fi
done
column -t "$RESULTS"
The harness is a starting point rather than a released tool, so treat the reset logic as reviewable code and run it on a scratch clone first. Every verdict comes from an exit code, which keeps the classification reproducible on any machine that can run the suite.
Reading the verdict table
| Verdict | How it is produced | What to do next |
|---|---|---|
load-bearing |
Reverse-applying this hunk alone makes the pinned test fail | Keep it in the fix commit, one hunk at a time |
dead |
The test still passes with the hunk removed | Move it to a separate cleanup pull request, or drop it |
entangled |
The hunk cannot be reverse-applied on its own | Inspect manually; often a rename, a move, or half of a two-part fix |
A patch whose hunks are mostly dead is not wrong, it is simply over-scoped, and the table gives the contributor a defensible reason to split it before review starts.
Where a free model actually helps, and where it must not decide
Once the table exists, the remaining work is interpretation: explaining why a hunk came back entangled, grouping dead hunks into a coherent follow-up commit, and drafting split-commit messages that a maintainer can read in one pass. That is language work over structured evidence, which is a reasonable fit for a hosted model in a browser session.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode is relevant here for one narrow reason: it advertises free model access and a free server option, both operator-supplied claims, which means a contributor can run the interpretation pass and the harness itself without paying for a subscription or provisioning a machine. The free plan page advertises a ten-million-token allowance at the time of writing, and quotas, model names, and machine specifications change often, so verify the current terms before depending on any specific number.
The division of authority matters more than the tooling. The model proposes a hypothesis about an entangled hunk or a commit split; the test runner remains the only thing allowed to issue a verdict. Anything the model claims about a hunk's necessity is unverified until the harness reruns and reproduces it.
Running the loop on a free server
A long suite multiplies awkwardly: with n hunks, the harness performs roughly n + 2 full test runs. A ten-minute suite and twelve hunks is about two hours of wall-clock time, which is a poor fit for a laptop needed for other work. The free server option moves that loop off the contributor's machine and keeps the local checkout free for editing, which is the practical case for it here rather than any claim about speed.
Limitations and who should not use this
The method has real boundaries, and a contributor who ignores them will produce a confident table that is wrong.
-
Interacting hunks. Two halves of one fix can each look
deadin isolation; a pairwise or grouped pass is required before deleting anything. - Skipped changes. Header-only and mode-only changes, binary files, and pure renames fall outside the splitter and never appear in the table.
-
Flaky tests. A flaky suite converts
deadintoload-bearingat random, so rerun borderline verdicts or quarantine the unstable test first. - Cost. Runtime scales linearly with hunk count, which makes the approach expensive for a fifty-hunk diff and cheap for a five-hunk one.
-
Not a correctness proof. A
load-bearingverdict means the suite noticed the hunk, not that the fix is right or complete.
Skipping the harness is reasonable for a one-file, one-hunk patch, for repositories whose tests need privileged infrastructure the contributor cannot reproduce, and for projects whose contribution guidelines require a single squashed commit regardless of scope.
A pre-push checklist
- Confirm the pinned test fails on
HEADand passes with the full patch. - Split the diff into hunks and record a verdict for each one.
- Move every
deadhunk into a separate pull request, or delete it. - Inspect every
entangledhunk by hand and note the reason in the patch description. - Attach the verdict table to the pull request so the reviewer starts from evidence.
None of this replaces a maintainer's judgment about design, and it is not meant to. It simply removes the cheapest objection from the review thread before anyone has to type it, which is often the difference between a patch merged this week and one reopened next month. If the loop above fits how you already work, MonkeyCode's free tier is one place to run it without a paid subscription.
Top comments (0)