Ray's Knowledge Base

libgit2 applies a hunk at its new-side start line, so a hand-built one-hunk patch needs that line on the + side

PitfallVerified 28 Sep 2026Holds anywhere
Pitfall. The symptom, what causes it, and the fix that was run and seen to work.

Symptom#

Code builds a patch that holds only one hunk of a diff (for example to unstage or discard one hunk), and applies it with libgit2 (Repository::apply with Diff::from_buffer). It works for the first hunk of a file but fails for a later one:

hunk at line 26 did not apply

It fails when an earlier hunk of the same file, which is not in the patch, adds or removes lines.

Cause#

libgit2's apply_hunk (src/libgit2/apply.c) does not search for the hunk like git apply does. It takes the position from the + side of the header: line_num = hunk.new_start - skipped_new + skipped_old - 1, then find_hunk_linenum checks only that exact line. A reversed hunk built by swapping the header (@@ -new_start,new_lines +old_start,old_lines @@) puts the other side's line number there, which is wrong as soon as earlier, not-included hunks shift lines.

Fix#

For a patch that contains only this hunk, write the preimage's own start line on both sides, since no earlier hunk in the patch shifts it. For an empty side, git's convention names the line before, so add 1:

format!(
    "@@ -{},{} +{},{} @@\n",
    hunk.new_start(),
    hunk.new_lines(),
    hunk.new_start() + u32::from(hunk.new_lines() == 0),
    hunk.old_lines(),
)

When you apply several hunks one call at a time, go from the last hunk to the first, so a change never moves the start line of a hunk that is still to come.

Evidence#

rgit, 2026-09-27: a file with changes at three places, each adding a line; staged hunks at index lines 1 and 27. rgit unstage f.txt --hunk 27 failed with hunk at line 26 did not apply before the fix and worked after it. Also checked: unstaging a pure-delete and a pure-add hunk after a shifting hunk. Fix in reverse_hunk_patch, rgit commit 72db1bf; libgit2-sys 0.18.8 (libgit2 1.9.7), apply.c lines 245-256.