Skip to content

Releases: VitalyOstanin/hunkpick

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 19:31

Added

  • FileDiff::tail and FileDiff::ends_on_a_trailer_line say which trailer lines follow an
    entry's last hunk, and what having one means for the line the entry ends on. split and
    select each spelled that rule out for themselves, and the two spellings were free to drift.
  • parser::reads_as_a_diff_line answers whether a line is one a diff writes: a marker line
    carrying what its marker promises — for @@, a hunk header that parses — or an extended
    header (index, similarity index, rename from, the mode lines) carrying what stands after
    it. A rename and a mode-only change carry no ---, +++ or @@, so past the diff --git
    line those headers are all such an entry has. The four a path follows (rename from,
    rename to, copy from, copy to) are read on their own head, because a path may be in any
    script; what follows the others is an object name, a mode or a percentage, written in ASCII
    wherever the patch was, so a bare index says as little as a bare --- . A caller weighing a
    line out of a stream with no byte-order mark asks about the diff format, and the answer lives
    next to the code that reads that format rather than as a length beside the check.
  • parser::markers reports every marker a diff line may open with, ordinary and combined.
    #[doc(hidden)], like cli and gitenv: the binary is a separate crate, and its tests of
    the UTF-16 check weigh a corpus against each marker the parser knows, which they otherwise
    write out a second time.
  • parser::headers_ascii_follows reports the extended headers whose line is ASCII past them,
    the ones weighed the way a marker is. #[doc(hidden)], for the reason parser::markers is:
    the binary's test that two bare headers say nothing named three of the seven, and was free to
    keep naming three as the list grew.
  • parser::headers_a_path_follows reports the four extended headers read on their own head.
    #[doc(hidden)] for the same reason: the binary's test of what the count of two costs weighs
    those four, and named them a second time to do it.
  • parser::the_header_a_path_follows_in reports which of the four headers a path follows opens
    a line, the longest of them where one opens another. #[doc(hidden)] for the reason
    parser::markers is: the binary's UTF-16 check weighs such a header against the line it read
    it out of, and a lookup written out there would be free to take the first that opens the line —
    whose length then decides how much of the line counts as said past it.
  • parser::carries_something_past answers whether a line says anything past a prefix beyond
    whitespace — the one reading the parser gives both lists a line is weighed against.
    #[doc(hidden)] for the reason parser::markers is: the binary's UTF-16 check weighs a header
    a path follows against the line it read that header out of, and writing "something past it" out
    there would be a second reading of the same tail.

Changed

  • SplitError::OutOfBounds names what it addressed with the new split::Addressed enum instead
    of a &'static str holding "file" or "hunk". A library caller can now match on the two
    exhaustively; the message is unchanged.
  • gitenv::insulated_git builds the insulated git invocation that both test layers were each
    building for themselves; it also pins the message locale, which neither copy did.
  • model::TrailerLine names the position-and-line pair FileDiff::trailer holds, which three
    signatures spelled out. The field's type is unchanged.
  • gitenv::MESSAGE_LOCALE_VARS pairs each variable with the new gitenv::LocaleAction saying
    what happens to it, instead of naming the one dropped variable a second time in a constant of
    its own. A rename in the list and the constant left behind no longer disagree in silence.

Fixed

  • Binary data holding a NUL-padded diff marker was answered with an iconv -f UTF-16LE for a
    stream that is not UTF-16. Three code units spelling @@ , or four spelling --- , are a
    diff marker, and every rule tried against them read a single line: a minimum length, then the
    length of the marker that matched, then what the marker promises — under which @@ -1 was
    refused as no hunk header while --- x was accepted as a path, though both are the same
    accident of the bytes a PNG holds. What a stream is asked for now is a second line: two lines
    of the opening window have to read as lines a diff writes. A patch has them — an entry carries
    a header and a hunk, a rename carries the lines naming both paths — and binary data does not.
    Nothing is asked about the stream around those lines, so the header order of a mail, a patch
    opening with a blank line and a path in another script stay free; each of them was once a
    reason to answer a UTF-16 patch as binary input.
  • Binary data holding a NUL-padded rename from twice was answered with an iconv all the
    same. The four headers a path follows are read on their own head, because the path may be in
    any script and the parser is handed the line already read up to the first unit that is not
    ASCII — which leaves a bare rename from and a real rename from файл.md the same twelve
    bytes. The check now weighs those four against the line it read them out of, where the path is
    still there: the header counts where something stands past it, whatever script that something
    is written in. The patch the reading was written for keeps both its lines, and a window that
    holds nothing but the header twice keeps neither.
  • A git format-patch mail written in UTF-16 without a byte-order mark was rejected as binary
    input. The encoding was looked for in the opening 128 characters, which a mail spends on its
    headers, so the diff marker the check waits for arrived too late. The window is now wide
    enough for those headers, and text that is not ASCII no longer ends the reading before the
    marker: a commit subject or message in another script is read past, and a path in another
    script leaves the marker before it standing. Such a mail — and a rename, a mode change or a
    binary file whose only marker line carries that path — is answered with the encoding to
    convert from. Nothing about the stream around the marker line is asked for any more, so the
    order git wrote the mail headers in and a patch that opens with a blank line no longer decide
    the answer either.
  • select appended a newline to a diff that ends in an entry with no hunks — a pure rename or a
    mode change — and arrived without its final newline. Such an entry is written from its headers
    alone and is always emitted whole, but it was judged by its picks, of which it has none, so the
    result was taken to end somewhere other than where the input ended.
  • split dropped the trailing-newline flag of a diff that ends in a git format-patch
    signature. The signature is emitted after the last hunk, so the output still ended on the line
    the input ended on, but the cut was judged by the last line of the last hunk alone: hunkpick split 1 --at 3 on such a diff appended a newline the input never had. select has taken the
    trailer into account since it learnt the rule; split now does the same.
  • A panic in one of the threads reading the child's output was reported as a panic in the thread
    feeding its input: FeedError::WriterPanicked covered all three threads, and the message a
    caller saw named the feeding side whichever thread had actually died. Reading now has its own
    variant, FeedError::ReaderPanicked, and GitCheckError::ReaderPanicked behind it; both still
    exit 70 as an internal error.

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 21 Aug 08:02

Fixed

  • A hunk header whose ranges were separated by something other than one ASCII space — a tab, two
    spaces, or a non-ASCII whitespace character such as U+00A0 — parsed, and the header was then
    rebuilt on the way out with a plain space. A diff git calls a corrupt patch came back out as
    one git accepts, at exit 0. Such a header is now refused as malformed (exit 2), the same as any
    other header hunkpick cannot reproduce byte for byte.

  • The sign of a hunk range was not checked at all: @@ +1,3 +1,3 @@, @@ -1,3 1,3 @@,
    @@ -+1,3 +1,3 @@ and @@ -1,+3 +1,3 @@ all parsed and were rendered back as
    @@ -1,3 +1,3 @@ at exit 0, while git reads every one of them as a corrupt patch. The leading
    - and + are now required, and a range component has to be plain ASCII digits.

  • Selecting by a content id shared by several sub-hunks scaled as the square of the number of
    matches, in both time and memory. Reading every id out of list --json and passing them back
    — the documented batch flow — names such an id once per sub-hunk, and each naming both
    appended a pick per match and re-ran the collision check over every match. A diff of 8 000
    identical changes (150 KB) took 10 s and 3 GB of RSS; it is now a fraction of a second. The
    same accumulator defect was reachable through a repeated *.

  • The cap on how many indices a selector may materialise (1048576) applied to one selector
    while the number of selectors is bounded only by the length of the command line: 200 copies of
    1-1048576 — 2.6 KB of arguments against a four-sub-hunk diff — reached 1.6 GB of RSS before
    any index was compared against the diff. The cap is now one allowance for the whole
    invocation, and its message says so.

  • split now applies the same trailing-newline rule as select. A diff that arrived without
    its final newline keeps that property only while the result still ends on the line the input
    ended on, and a cut that drops the piece holding the tail does not: hunkpick split 1 --at 3
    on such a diff ended its output mid-line at exit 0, and git apply called the result a
    corrupt patch. select was taught this in 0.8.1; split was not.

  • The defect a scheduled fuzzing run found — a hunk header declaring six old lines over a body
    of three, whose selection then failed the tool's own output check — was closed by making the
    fuzz target skip such input, and nothing recorded it afterwards. The precondition is now
    stated on select, the crate example screens its input the way the CLI does, and
    select_carries_an_inconsistent_header_into_its_result holds the shape, which until now
    existed only in one machine's fuzz/artifacts/.

  • The selectors fuzz target recounts a hunk header from its body instead of skipping the
    input. Skipping cost roughly a third of the parsed stream — the mutations libFuzzer favours
    break the header-to-body correspondence more often than not — so most of what it generated no
    longer reached the selection code the target exists for.

  • Fuzz seeds are checked by tests/fuzz_seeds.rs, which found three that had stopped reaching
    the code they were written for: simple-content-id named a made-up id, two-files-path-index
    named a path absent from its own diff, and two-files-star used a bare * on a multi-file
    diff, which is a usage error. Each was silently skipped by the target rather than reported. A
    seed for the @id collision path was added, and the byte-identical parse/ and roundtrip/
    seed directories were merged into one diff/.

  • The property tests reach two shapes they could not generate before: an edit whose changed
    lines repeat (so several sub-hunks share a content id, and a selection by that id is checked
    against id_count from list --json), and a git format-patch signature after the last
    hunk (so the trailing-line shift in split_file_hunk is covered by something other than one
    example).

  • Fuzzing is run from scripts/fuzz-all.sh and scripts/fuzz-repro.sh instead of a command
    copied out of the guides. The documented command was missing mkdir -p fuzz/corpus/<target>,
    and the corpus directory is gitignored, so on a fresh clone libFuzzer refused to start; the
    scheduled CI run had already failed on exactly that and gained the step, while the guides did
    not. fuzz/seeds/README.md also omitted the nightly override and the explicit target triple
    that CONTRIBUTING.md calls mandatory, and the local command had no -timeout=10, so one hung
    input could swallow a short run's whole budget. The scripts carry all four, and a test checks
    that every script the documentation names exists and is executable.

  • Documentation no longer contradicts the tool. Two README examples still recommended
    --verify-result-diff-git on a git diff | hunkpick ... | git apply --cached pipeline after
    the README gained the paragraph explaining that such a command reports a correct result as a
    failure (exit 70); both now show a patch file against a tree at the pre-patch state, and a test
    reads the shell blocks of README.md and CONTRIBUTING.md so the shape cannot come back.
    hunkpick --help claimed "every subset applies" for @L while select --help and the README
    already listed the two exceptions. The README described the selector index ceiling as per
    selector, which it no longer is, and called two of the four lossy JSON text fields lossy.

  • Only git's own verdict now decides exit 70 for --verify-result-diff-git. git apply --check
    answers 1, and only 1, when a patch does not apply; every other status is git failing before it
    looked at the diff. A repository git refuses to read (fatal: bad config line …, exit 128) was
    reported as git apply --check rejected the result diff, and a git killed by a signal — the OOM
    killer, a timeout — produced the same sentence with nothing after the colon. Both now name the
    status and exit 74, so exit 70 keeps meaning what ADR 0013 reserves it for: a result hunkpick
    itself produced.

  • -C DIR naming something that is not a directory is reported against that argument. A mistyped
    path reaches Command::spawn as the same NotFound a missing git binary does, so both printed
    failed to run git: No such file or directory (os error 2) at exit 74 and sent the caller after
    their git installation instead of after their own typo. The path is now checked before git is
    started and named in the message at exit 2; if git still fails to start, the message says which
    directory it was to run in.

  • The one non-ASCII character in hunkpick's own output is gone. The encoding diagnostic added in
    this cycle carried an em dash, and it is addressed by construction to a Windows user, whose
    console on cp866 or cp1251 shows it as mojibake in the middle of the sentence. The guard added
    alongside it covered the help texts only, so the character came back in an error message the
    same week; it now covers every string literal of src/ outside the test modules.

  • One copy of the plumbing that runs git with a diff on its stdin. gitenv::feed_and_wait
    replaces three near-identical blocks — the result-diff check and two test helpers, two of them
    added last cycle — that had already drifted apart in how they treat a failed write. It also
    closes the failure path they shared: the child is now kept in hand and killed if waiting on it
    fails, because std::thread::scope parks until every thread it started has finished, and the
    writing thread can be blocked in a write nobody is going to read. The failure
    was a hang rather than a diagnostic, and the fallback test command documented in CONTRIBUTING
    has no per-test timeout. The stub file names that keep a developer's git configuration out of
    the test repositories moved to gitenv::insulate_config, next to the list of variables that
    was consolidated there last cycle, and tests/git_config_isolation.rs now poisons
    GIT_CONFIG_SYSTEM as well as GIT_CONFIG_GLOBAL, so dropping either one turns the suite red.

  • The crates.io token no longer reaches cargo publish --dry-run in the release workflow. A dry
    run never asks the registry for credentials, but it does run a verification build, which
    compiles the package along with the build scripts and proc macros of its dependencies — all of
    them inheriting the step's environment. A rehearsal (workflow_dispatch with an empty tag)
    builds whatever branch the dialog names, so the step handed a credential good for every crate
    of the account, and outliving any access to this repository, to code from an arbitrary branch.
    The publishing step keeps its own copy of the token.

  • The release pipeline checks the public API too. cargo semver-checks check-release was a CI
    gate only, and CI runs on branch pushes: a tag on a commit CI never saw, or one whose version
    was bumped after the last run, reached the irreversible cargo publish with the API unchecked.
    The release workflow now runs the same gate for the same reason its test, lint and MSRV jobs
    are duplicated there.

  • The documented local loop covers what CI gates. The fuzz directory is a workspace of its own,
    so the --all in cargo fmt/cargo clippy never reached it, while CI lints it as a separate
    step: a fuzz target edited locally passed every documented command and failed the pull request.
    The two commands are in CONTRIBUTING.md now, along with cargo-semver-checks as a tool the
    project expects; RELEASING.md runs the semver and MSRV checks before the release commit, and
    the README says its list is the everyday loop and points at CONTRIBUTING.md for the full set
    rather than claiming to be it.

  • RELEASING.md describes the pipeline that exists. Its table of jobs still began with test and
    attributed the tag, manifest, lock and changelog checks to publish, while they have their own
    first job (verify-metadata) that every other job waits on — the same document said so three
    sectio...

Read more

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 17 Aug 23:09

Fixed

  • The two-round @L example in select --help and in the README could not be run: it reused
    the first round's line numbers (1@L91-120) after a re-diff that renumbers them, so the
    second command failed with exit 2. Both now show the corrected round and say why the numbers
    change; an integration test drives the selectors straight out of the help text.

Changed

  • Reading a diff from a terminal — no pipe, no -i — now says so in one line on stderr before
    it blocks. A forgotten pipe used to be indistinguishable from a hang. Nothing is printed when
    stdin is not a terminal, so pipelines are unaffected.
  • Selecting by content id resolves through an index built once per invocation instead of a scan
    per id: on a 20 000 sub-hunk diff, selecting every id went from 0.22 s to 0.06 s.
  • Release archives are now verified by running the packaged binary and comparing its
    --version with the archive name, so a packaging fault is caught before upload. CI gained a
    cargo semver-checks gate over the public API, the scheduled fuzz run bounds a single input
    with -timeout, and the versions of the tools CI installs are pinned.

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 17 Aug 20:55

Fixed

  • The overlap check compared raw header numbers, ignoring git's convention that a side with
    no lines reports the line before its empty range. A hunk ending in a pure deletion —
    a file whose tail is removed — therefore looked like an overlap, and select '*' refused
    a diff git itself writes: exit code 70 on roughly one in ten ordinary diffs (measured over
    200 generated cases). Present since 0.7.0, when the new-side check was introduced.
  • A full binary patch (git diff --binary) lost its structure: the payload lines
    (literal <n> and the base85 body) were filed as leading headers and emitted above the
    GIT binary patch marker, so git apply rejected the result as garbage while hunkpick
    exited 0. This is the form needed to stage a binary change, and the binary marker of a
    CRLF diff went unrecognised for the same reason.
  • A sub-hunk with no old-side lines (an appended block) carried a header one line past
    where git puts it (@@ -4,0 +5 @@ instead of @@ -3,0 +4 @@), and the new-side anchor
    inherited the shift.
  • An @L slice of a whole-file deletion produced a self-contradictory patch: the header
    declares the file removed while the body keeps the unselected lines as context, which git
    rejects (deleted file f still has contents). Such a selection is now a usage error.
  • A combined diff (diff --cc, @@@ headers — what git writes for a merge) was read as a
    two-sided one: the hunk body was truncated at its first line and a --- removed in both
    line invented a file entry. It is now rejected as unsupported (exit 2) instead of losing
    data silently. See docs/ADR/0012-two-sided-diffs-only.md.
  • The listing gave a CRLF diff's @@ header a separating space and a raw CR — inside a JSON
    string field — where emit already knew the CR is the line ending, not section text.
  • A context line for an empty source line is a lone space, and transports that strip
    trailing whitespace deliver it as a zero-length line. Parsing treated that line as the
    end of the hunk, dropped the rest of the body into the file's headers and emitted a
    diff git apply rejects as garbage — with exit code 0. Such a line is now read as
    context and emitted with its marker restored.
  • Lines that follow a hunk body — a blank separator between hunks, the -- signature
    git format-patch appends, a stray Binary files ... marker — were emitted with the
    leading headers, which moved them above the first @@. They now keep their position,
    and the binary marker is no longer dropped silently.
  • split replaces one hunk with several but left those trailing lines at their old
    positions, so a signature recorded after the last hunk was emitted between the pieces.
    git apply rejected the result (patch fragment without header) while hunkpick exited 0.
    Each line now follows the hunk it followed before the split.
  • Emitting those lines rescanned the whole list for every hunk, which is quadratic in their
    number: a 7 MB diff carrying a separator after each of its 128 000 hunks took 15 s to
    re-emit, against 0,2 s to list. One pass now walks the list alongside the hunks (0,18 s on
    the same input).
  • A binary file, a pure rename and a mode-only change have no ---/+++ lines, so they
    had no path and could not be addressed in a multi-file diff. Their paths are now read
    from the diff --git line, and * takes a hunkless entry whole.
  • Paths quoted and C-escaped by git ("a/\303\251.txt", the core.quotePath default for
    non-ASCII names) are decoded, so a selector spelled with the real file name matches.
    The emitted diff keeps the original bytes.
  • A CRLF diff left the CR in the file path (breaking path: selectors) and added a stray
    space to the hunk header, so the round-trip was not byte-identical.
  • A deleted file was listed as /dev/null; it is now shown under its old name.
  • Line numbers near u32::MAX from the input header overflowed while checking hunk
    overlap: a debug build panicked with exit 101, a release build wrapped and decided the
    check on a meaningless value. The bounds are computed in u64, and sub-hunk starts
    saturate rather than wrap.
  • A defect of the input diff (a header that disagrees with its body, e.g. a truncated
    diff) was reported as a verification failure of hunkpick's own result: exit code 70 and
    a Debug dump of internal fields. It is now a usage error (exit 2) in prose, with the
    sub-hunk numbered from one as list numbers it.
  • A reader that closes the pipe first (hunkpick list | head) ended the run with exit 74
    and a Broken pipe diagnostic; it is now a normal end of work (exit 0). Both this and the
    exit-code change above are recorded in docs/ADR/0013-exit-code-contract.md.
  • In the path:set selector form a broken set was re-read together with the path, so
    f:2-1 was reported as not a number: f:2 instead of reversed range.
  • split now recomputes new-side anchors like select does, so both commands treat a
    diff carved out of a larger one the same way.
  • In a plain (non-git) diff a header-only entry absorbed the next file's marker lines.
  • The human listing escapes text a terminal would act on (escape sequences, control
    bytes, bidirectional overrides) instead of passing it through.
  • A file whose name is not valid UTF-8 (legal on Unix) could not be addressed: the
    argument was refused before hunkpick saw it. Selector paths are now taken as raw bytes,
    so such a file is reachable by name; only the set after the : must be ASCII.
  • A selection that did not include the input's last line inherited its "no final newline"
    flag, so the result ended mid-line and git apply called it a corrupt patch. The flag now
    travels only when the result does end on that line.
  • A hunk header carrying a token hunkpick cannot represent — @@ -1,3,9 +1,3 @@,
    @@ -1,3 +1,3 junk @@ — was parsed as if the extra part were absent and emitted without it,
    at exit 0. Both are now parse errors (exit 2).
  • A diff saved in UTF-16 (what git diff > patch.diff writes in Windows PowerShell 5.1) was
    reported as "binary input: NUL byte found", which points at the wrong thing. A UTF-16/UTF-32
    byte-order mark is now named, with the re-encoding command to fix it.
  • A git that could not be started for --verify-result-diff-git was reported as a failed
    verification of the result diff (exit 70). The check never ran, so it is an environment
    failure: exit 74.
  • Output written after the last newline could be lost silently: stdout is line-buffered and the
    runtime's implicit flush at exit discards its error. It is flushed explicitly, and a failure
    is exit 74.
  • list --json did not end its output with a newline.
  • A combined diff whose entries carry no ---/+++ pair (a file resolved the same way in both
    parents) was reported as "no diff markers found" rather than as the combined diff it is.

Changed

  • Auto-splitting a hunk is linear in the number of change runs; it re-counted the whole
    prefix per sub-hunk before. On a 1.9 MB one-hunk diff with 64 000 runs list went from
    3.9 s to 0.06 s.
  • split no longer clones the whole parsed diff to rewrite one hunk.
  • The git apply --check child process no longer inherits GIT_DIR, GIT_WORK_TREE and
    related variables, so -C DIR alone selects the repository.
  • --color is described in list --help; the exit-code table in the README distinguishes
    SIGINT (130) from SIGTERM (143) and documents the closed-pipe case.
  • Release archives carry THIRD-PARTY-NOTICES.md with the license texts of the crates
    linked into the binary, and the release pipeline builds and verifies every archive
    before the irreversible cargo publish rather than after it. The release procedure is
    written down in RELEASING.md.
  • README states the measured peak memory (6x–19x the input, depending on average line
    length) instead of "a few hundred MiB", so the input limit is not read as a RAM ceiling.
  • The crate is built on the Rust 2024 edition, formatted with the matching style edition.
    The minimum supported Rust version is unchanged (1.85, the release that stabilised the
    edition), so nothing is required of consumers; the dependency resolver now honours that
    minimum when picking versions. See docs/ADR/0011-rust-2024-edition.md.
  • path: selectors resolve through an index built once per invocation instead of scanning the
    file list per selector: 16 000 selectors over 16 000 files went from 1.5 s to 0.04 s.
  • --verify-result-diff-git documents what it actually checks — the working tree, which in the
    usual staging pipeline already holds the edits, so the flag reports a correct result as not
    applying. Same for the JSON listing, whose text fields are the diff's own content: not
    display-sanitised the way the human listing is, and lossy for non-UTF-8 bytes.
  • The selector index limit (2^20 per selector) is named in the error message and in the README.
  • The release pipeline checks the tag against the manifest, both lockfiles and the CHANGELOG in
    a job of its own, before anything is built, and a release can be rehearsed by running the
    workflow with no tag named, which reads the version from Cargo.toml and publishes nothing.
  • Behavioural decisions of this release are recorded as ADRs: docs/ADR/0012-two-sided-diffs-only.md
    and docs/ADR/0013-exit-code-contract.md.

Added

  • Generated tests, because the defects above were found by generating inputs rather than by
    reading code: tests/differential.rs compares hunkpick with real git over generated diffs
    (a selection applies, staging one sub-hunk at a time converges on the target, the output is
    valid input for the next invocation), tests/property.rs uses proptest for the forms git
    will not produce on demand (CRLF, a missing final newline, a mail preamble), and fuzz/
    holds libFuzzer targets for parsing, the parse . emit fixed point and selector handling,
    with committed seeds (`fuzz/seeds...
Read more

v0.7.0

Choose a tag to compare

@github-actions github-actions released this 29 Jul 11:37

Fixed

  • select now recomputes the new-side (+) start of every emitted hunk instead of
    carrying over the value from the input diff. Leaving a sub-hunk out changes how many
    lines the result adds or removes above each later hunk, so the inherited anchors
    described a file the selection does not produce. git apply starts its search at the
    new-side position: with a drifted anchor a selection could be rejected
    (patch does not apply) or, where the surrounding context occurs more than once,
    applied cleanly to the wrong occurrence. Selecting several sub-hunks in one invocation
    no longer needs the diff → stage → re-diff loop as a workaround.

Changed

  • The default internal consistency check also verifies the new-side starts against the
    accumulated added - deleted of the preceding hunks, reported as a StaleNewStart
    error. A diff whose anchors are stale (for example, a result diff produced by an
    earlier version) is now rejected rather than passed on to git apply.

v0.6.0

Choose a tag to compare

@github-actions github-actions released this 20 Jul 22:26

Added

  • CLICOLOR_FORCE (any non-empty value) forces coloured output in the default
    --color auto mode even when stdout is not a terminal (e.g. a pipe).
    NO_COLOR still takes precedence when both are set, and an explicit
    --color always|never overrides both.

Changed

  • Result-diff validation now rejects a change-free (all-context) hunk with a
    dedicated NoChangeHunk error. Such a hunk balances the header counts but
    git apply rejects it; it is unreachable from a real git diff and only
    possible from a synthetic patch.
  • The parser no longer appends body lines past the counts declared in a hunk
    header: once a side's declared count is exhausted, a further line of that kind
    ends the hunk instead of being absorbed into it. Well-formed git diffs are
    unaffected.

v0.5.1

Choose a tag to compare

@github-actions github-actions released this 20 Jul 21:07

Fixed

  • parse no longer mistakes a deletion line whose content begins with --
    for a new-file header. Such a deletion renders as --- <text> in the hunk
    body; new-file detection previously triggered on any --- line while inside
    a hunk, dropping the real change and emitting a phantom file. The parser now
    tracks the old/new line counts declared by the hunk header and only treats
    --- as the next file once the current hunk body is fully consumed.
  • read_limited no longer overflows limit + 1 at --max-input-bytes
    18446744073709551615 (u64::MAX), which wrapped to 0 in release builds
    and silently treated any input as empty. It now uses a saturating add.
  • split --at on a hunk's first context line no longer emits a leading
    context-only piece with zero net change (a hunk git apply rejects); such a
    piece is dropped.

v0.5.0

Choose a tag to compare

@github-actions github-actions released this 11 Jul 04:26

Removed

  • Breaking: the [path:]INDEX@lo-hi added-line range selector (added in
    0.4.0) is removed. Its contiguous-slice implementation produced an
    unapplicable hunk for a leading or interior slice of an addition block placed
    mid-file: it kept the leading context but dropped the trailing context, so
    git apply could not anchor the piece (patch does not apply). The per-line
    [path:]INDEX@L<set> selector supersedes it: @L addresses any subset of a
    sub-hunk's changed lines, keeps both leading and trailing context (every
    subset applies), and can express any added-line range plus what @lo-hi
    could not (deletions, interior slices, replacements). A selector that still
    uses the @lo-hi form now fails with exit 2 and a message pointing at the
    @L replacement.

Changed

  • The human list marker for an all-additions sub-hunk is renamed from
    [+range] to [+add] (the @lo-hi range form it advertised is gone). The
    addition_only field in list --json is unchanged.

v0.4.0

Choose a tag to compare

@github-actions github-actions released this 05 Jul 16:46

Added

  • select accepts a per-line changed-line selector [path:]INDEX@L<set> that
    keeps an arbitrary subset of a sub-hunk's changed (+/-) lines, where
    <set> is an index list over the changed lines (L1,3, L1-2,4). Unlike
    INDEX@RANGE (added-side only, cut between two + lines, deletions atomic),
    it has no boundary restriction: a deletion surrounded by additions
    (+x -y +z) can be isolated, and the deletions and additions of a
    replacement can be separated across commits via the diff → stage → re-diff
    loop. Unselected deletions are kept as context (anchoring the hunk, so the
    old-side footprint is invariant and no --unidiff-zero is needed);
    unselected additions are dropped. A sub-hunk addressed by @L must be
    addressed once per invocation (exit 2 otherwise).
  • list --json reports a changed_lines array per sub-hunk —
    [{i, kind, text}] with 1-based indices shared across deletions and
    additions — the machine-readable source for building @L selectors without
    parsing the diff body.

Tests

  • Added unit and end-to-end coverage for the line-set selector: separating
    deletions from additions, isolating a deletion among additions, full-selection
    round-trip, the no-newline-at-EOF re-add edge, the once-per-invocation rule,
    and the changed_lines JSON numbering.

v0.3.1

Choose a tag to compare

@github-actions github-actions released this 25 Jun 13:06

Fixed

  • select now orders multiple INDEX@RANGE selectors that address the same
    sub-hunk by their first added line, so two disjoint ranges given in any order
    (e.g. 1@3-4 1@1-2) emit in ascending new-file order and apply cleanly
    instead of being rejected as overlapping.
  • Overlapping added-line ranges of one sub-hunk (e.g. 1@1-3 1@2-4) are now
    rejected as a selector error (exit 2) before emission, so they can no longer
    slip past --no-verify-result-diff-internal as a corrupt diff.
  • A malformed selector now reports the specific reason it was rejected — a
    reversed range, a zero bound, or a non-numeric bound — instead of a bare
    bad selector: <text>.

Tests

  • Added unit and end-to-end coverage for range ordering, overlap rejection
    (including under --no-verify-result-diff-internal), and the specific
    bad-selector reasons.