ADR-037: hunks can be named in advance¶
Status: accepted
Relates to: ADR-020, ADR-022, ADR-023
Amends ADR-023's "-p without a terminal is refused, not ignored".
-p is still refused there, for the same reason.
This ADR adds the request that can be made there instead.
Context¶
ADR-023 gave backport hunk selection, and made it interactive only.
Under --json, in a pipe, or with tpl.interactive false, -p fails with tpl::backport::not_interactive, and
the one non-interactive way to narrow a backport is a pathspec — which selects whole files.
That leaves a caller with no terminal — a script, CI, an agent working on someone's behalf — unable to send one change out of a file that holds two. It is a real gap, because the unit that belongs upstream is a change, and one change is often a few hunks of one file, or one hunk each of several. The alternatives the caller is left with are all bad:
- Send the whole file, and ship a change nobody decided to send.
- Edit the emitted mailbox by hand, filtering hunks out of a patch that ADR-020's proof was made against. The proof no longer describes what is sent, which is the exact failure ADR-023 exists to prevent.
- Stage the change as a separate commit and diff against it, which
backportdoes not read: it measures the working tree.
The decision -p could not take for the user cannot be taken by git-tpl for a script either.
But a script can make it, if it can name what it is deciding about.
Decision¶
A hunk can be named, so that the choice is made before the command runs.
--list-hunksproduces no patch. It reports every file's hunks — the same ones-pwould offer — each with anid. It needs no terminal, and works under--json, whereresultisplan.--hunk <path>:<id>, repeatable, sends exactly those hunks. It is aPickeranswered in advance: the selection still precedes the proof, so ADR-023's guarantee reads the same — the patched source renders to your file with only the chosen hunks.-p,--list-hunksand--hunkare mutually exclusive.--list-hunksalso excludes-o, since it writes no patch.
An unnamed file sends nothing¶
Once any --hunk is given, a file with no --hunk of its own contributes nothing.
This is the opposite of -p, which starts with everything selected, and the asymmetry is the same one ADR-023
draws.
-p starts from "everything" because a person is there to take things out.
With nobody there, "everything I did not mention" is the reading that ships a change the caller never saw.
Pathspecs and --exclude still apply first, and decide which files are considered at all.
The id is derived from content¶
An id is the first 12 hex digits of a SHA-256 over the file's path, the hunk's @@ header and its lines.
It is deterministic (invariant 2): it hashes text the cut already produced, and nothing else.
A position would have been simpler, and is wrong. A script that lists hunks and selects one later would silently get a different hunk if the file had been edited in between — hunk 2 is only "that" hunk while nothing before it has changed. A content-derived id fails the other way: an edited file has different ids, so the old one matches nothing and the command stops. The path is hashed too, so the same change in two files does not share an id.
The id is opaque. Callers copy it from the listing; the documentation does not describe how to build one, because a caller that could would stop checking.
A name that matches nothing is refused¶
tpl::backport::unknown_hunk covers a malformed --hunk and a well-formed one that no hunk answered to, and it
is raised even when every other name matched.
Skipping it would send a patch that silently lacks the change the caller asked for, and the likeliest cause —
the file changed since the listing — is exactly when the caller most needs to know.
The listing is produced before the proof¶
A listing does not transpose, un-substitute or round-trip anything.
A file that a real backport would refuse is therefore still listed, and naming one of its hunks produces the
refusal, attributed to that hunk by the existing hunk_refused.
The alternative — listing only hunks known to be carriable — would need the proof run per hunk, which is both
expensive and not the same question: whether a hunk can be carried depends on which others are chosen with it.
A binary file is reported under skipped rather than refused, so that one cannot hide every hunk that can be
named.
Consequences¶
- Two new flags,
--list-hunksand--hunk, and one new diagnostic code,tpl::backport::unknown_hunk.not_interactiveandhunk_refusedkeep their codes and meaning; only theirhelptext changes, to point at the new flags. - A new
--jsonresult value,plan, and aplanarray in that payload only. The payload of every otherbackportis unchanged. Pickinggains two variants,NamedandList, andopsexportsHunkSelection. ThePickertrait is unchanged.- No new capability is needed from Git, and nothing writes or spawns: invariants 1 and 5 are unaffected.
- Accepted cost: an id stops matching on any edit to its hunk or the file's path, including a harmless one. The remedy is listing again, which is cheap, and the failure is loud.
- Accepted cost: the hunks are still the rendered → project hunks of ADR-023, not the hunks of the emitted patch. A caller cannot name a hunk of the template source.