<feed xmlns='http://www.w3.org/2005/Atom'>
<title>git/line-log.c, branch jch</title>
<subtitle>Mirror of https://git.kernel.org/pub/scm/git/git.git/
</subtitle>
<id>https://git.shady.money/git/atom?h=jch</id>
<link rel='self' href='https://git.shady.money/git/atom?h=jch'/>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/'/>
<updated>2026-03-17T04:05:42Z</updated>
<entry>
<title>line-log: route -L output through the standard diff pipeline</title>
<updated>2026-03-17T04:05:42Z</updated>
<author>
<name>Michael Montalbo</name>
<email>mmontalbo@gmail.com</email>
</author>
<published>2026-03-17T02:21:33Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=86e986f166d207e1f4b80062c2befb4f94c191c4'/>
<id>urn:sha1:86e986f166d207e1f4b80062c2befb4f94c191c4</id>
<content type='text'>
`git log -L` has always bypassed the standard diff pipeline.
`dump_diff_hacky()` in line-log.c hand-rolls its own diff headers and
hunk output, which means most diff formatting options are silently
ignored.  A NEEDSWORK comment has acknowledged this since the feature
was introduced:

    /*
     * NEEDSWORK: manually building a diff here is not the Right
     * Thing(tm).  log -L should be built into the diff pipeline.
     */

Remove `dump_diff_hacky()` and its helpers and route -L output through
`builtin_diff()` / `fn_out_consume()`, the same path used by `git diff`
and `git log -p`.  The mechanism is a pair of callback wrappers that sit
between `xdi_diff_outf()` and `fn_out_consume()`, filtering xdiff's
output to only the tracked line ranges.  To ensure xdiff emits all lines
within each range as context, the context length is inflated to span the
largest range.

Wire up the `-L` implies `--patch` default in revision setup rather
than forcing it at output time, so `line_log_print()` is just
`diffcore_std()` + `diff_flush()` with no format save/restore.
Rename detection is a no-op since pairs are already resolved during
the history walk in `queue_diffs()`, but running `diffcore_std()`
means `-S`/`-G` (pickaxe), `--orderfile`, and `--diff-filter` now
work with `-L`, and `diff_resolve_rename_copy()` sets pair statuses
correctly without manual assignment.

Switch `diff_filepair_dup()` from `xmalloc` to `xcalloc` so that new
fields (including `line_ranges`) are zero-initialized by default.

As a result, diff formatting options that were previously silently
ignored (e.g. --word-diff, --no-prefix, -w, --color-moved) now work
with -L, and output gains `index` lines, `new file mode` headers, and
funcname context in `@@` headers.  This is a user-visible output change:
tools that parse -L output may need to handle the additional header
lines.

The context-length inflation means xdiff may process more output than
needed for very wide line ranges, but benchmarks on files up to 7800
lines show no measurable regression.

Signed-off-by: Michael Montalbo &lt;mmontalbo@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: fix crash when combined with pickaxe options</title>
<updated>2026-03-17T04:05:42Z</updated>
<author>
<name>Michael Montalbo</name>
<email>mmontalbo@gmail.com</email>
</author>
<published>2026-03-17T02:21:32Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=81cf6ccc29002467f44798ada7d74993a44c94b0'/>
<id>urn:sha1:81cf6ccc29002467f44798ada7d74993a44c94b0</id>
<content type='text'>
queue_diffs() passes the caller's diff_options, which may carry
user-specified pickaxe state, to diff_tree_oid() and diffcore_std()
when detecting renames for line-level history tracking.  When pickaxe
options are present on the command line (-G and -S to filter by text
pattern, --find-object to filter by object identity), diffcore_std()
also runs diffcore_pickaxe(), which may discard diff pairs that are
relevant for rename detection.  Losing those pairs breaks rename
following.

Before a2bb801f6a (line-log: avoid unnecessary full tree diffs,
2019-08-21), this silently truncated history at rename boundaries.
That commit moved filter_diffs_for_paths() inside the rename-
detection block, so it only runs when diff_might_be_rename() returns
true.  When pickaxe discards a rename pair, the rename goes
undetected, and a deletion pair at a subsequent commit passes
through uncleaned, reaching process_diff_filepair() with an invalid
filespec and triggering an assertion failure.

Fix this by building a private diff_options for the rename-detection
path inside queue_diffs(), following the same pattern used by blame's
find_rename().  This isolates the rename machinery from unrelated
user-specified options.

Reported-by: Matthew Hughes &lt;matthewhughes934@gmail.com&gt;
Signed-off-by: Michael Montalbo &lt;mmontalbo@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>commit: rename `free_commit_list()` to conform to coding guidelines</title>
<updated>2026-01-15T13:32:31Z</updated>
<author>
<name>Patrick Steinhardt</name>
<email>ps@pks.im</email>
</author>
<published>2026-01-15T09:35:34Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=9f18d089c51fba2776fe1fece877a359c47417f7'/>
<id>urn:sha1:9f18d089c51fba2776fe1fece877a359c47417f7</id>
<content type='text'>
Our coding guidelines say that:

  Functions that operate on `struct S` are named `S_&lt;verb&gt;()` and should
  generally receive a pointer to `struct S` as first parameter.

While most of the functions related to `struct commit_list` already
follow that naming schema, `free_commit_list()` doesn't.

Rename the function to address this and adjust all of its callers. Add a
compatibility wrapper for the old function name to ease the transition
and avoid any semantic conflicts with in-flight patch series. This
wrapper will be removed once Git 2.53 has been released.

Signed-off-by: Patrick Steinhardt &lt;ps@pks.im&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>Merge branch 'sg/line-log-boundary-fixes'</title>
<updated>2025-09-18T17:07:01Z</updated>
<author>
<name>Junio C Hamano</name>
<email>gitster@pobox.com</email>
</author>
<published>2025-09-18T17:07:01Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=cf47560e945f3d9a0a42e89b9d6ef59fb085bbb5'/>
<id>urn:sha1:cf47560e945f3d9a0a42e89b9d6ef59fb085bbb5</id>
<content type='text'>
A corner case bug in "git log -L..." has been corrected.

* sg/line-log-boundary-fixes:
  line-log: show all line ranges touched by the same diff range
  line-log: fix assertion error
</content>
</entry>
<entry>
<title>line-log: simplify condition checking for merge commits</title>
<updated>2025-08-25T15:30:27Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-24T19:06:44Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=0a15bb634cf005a0266ee1108ac31aa75649a61c'/>
<id>urn:sha1:0a15bb634cf005a0266ee1108ac31aa75649a61c</id>
<content type='text'>
In process_ranges_arbitrary_commit() the condition deciding whether
the given commit is not a merge, i.e. that it doesn't have more than
one parent, is head-scratchingly backwards, flip it.

Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: initialize diff queue in process_ranges_ordinary_commit()</title>
<updated>2025-08-25T15:30:26Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-24T19:06:43Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=62e4ef85fbc5574fd80caababbf41bd33f53a46d'/>
<id>urn:sha1:62e4ef85fbc5574fd80caababbf41bd33f53a46d</id>
<content type='text'>
process_ranges_ordinary_commit() uses a local diff queue variable,
which it leaves uninitialized before passing its address to
queue_diffs().  This is not an issue, because at the end of that
function the contents of an other diff queue is moved into it by
simply overwriting whatever is in there, i.e. without reading any
uninitialized memory.

Still, seeing the uninitialized diff queue being passed around scared
me more than once, so out of caution let's make sure that it's
initialized.

Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: get rid of the parents array in process_ranges_merge_commit()</title>
<updated>2025-08-25T15:30:26Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-24T19:06:42Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=9df27c258edf89ea8ea0472a0a9c260e026f197f'/>
<id>urn:sha1:9df27c258edf89ea8ea0472a0a9c260e026f197f</id>
<content type='text'>
We can easily iterate through the parents of a merge commit without
turning the list of parents into a dynamically allocated array of
parents, so let's do so.  This way we can avoid a memory allocation
for each processed merge commit, though its effect on runtime seems to
be unmeasurable.

Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: avoid unnecessary tree diffs when processing merge commits</title>
<updated>2025-08-25T15:30:26Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-24T19:06:41Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=a3540ed20efad4e1aebb71edac2fc74604f2122e'/>
<id>urn:sha1:a3540ed20efad4e1aebb71edac2fc74604f2122e</id>
<content type='text'>
In process_ranges_merge_commit(), the line-level log first creates an
array of diff queues by iterating over all parents of a merge commit
and computing a tree diff for each.  Then in a second loop it iterates
over those diff queues, and if it finds that none of the interesting
paths were modified in one of them, then it will return early.  This
means that when none of the interesting paths were modified between a
merge and its first parent, then the tree diff between the merge and
its second (Nth...) parent was computed in vain.

Unify these two loops, so when it iterates over all parents of a merge
commit, then it first computes the tree diff between the merge and
that particular parent and then processes the resulting diff queue
right away.  This way we can spare some tree diff computing, thereby
speeding up line-level log in repositories with mergy history:

  # git.git, 25.8% of commits are merges:
  Benchmark 1: ./git_v2.51.0 -C ~/src/git log -L:'lookup_commit(':commit.c v2.51.0
    Time (mean ± σ):      1.001 s ±  0.009 s    [User: 0.906 s, System: 0.095 s]
    Range (min … max):    0.991 s …  1.023 s    10 runs

  Benchmark 2: ./git -C ~/src/git log -L:'lookup_commit(':commit.c v2.51.0
    Time (mean ± σ):     445.5 ms ±   3.4 ms    [User: 358.8 ms, System: 84.3 ms]
    Range (min … max):   440.1 ms … 450.3 ms    10 runs

  Summary
    './git -C ~/src/git log -L:'lookup_commit(':commit.c v2.51.0' ran
      2.25 ± 0.03 times faster than './git_v2.51.0 -C ~/src/git log -L:'lookup_commit(':commit.c v2.51.0'

  # linux.git, 7.5% of commits are merges:
  Benchmark 1: ./git_v2.51.0 -C ~/src/linux.git log -L:build_restore_work_registers:arch/mips/mm/tlbex.c v6.16
    Time (mean ± σ):      3.246 s ±  0.007 s    [User: 2.835 s, System: 0.409 s]
    Range (min … max):    3.232 s …  3.255 s    10 runs

  Benchmark 2: ./git -C ~/src/linux.git log -L:build_restore_work_registers:arch/mips/mm/tlbex.c v6.16
    Time (mean ± σ):      2.467 s ±  0.014 s    [User: 2.113 s, System: 0.353 s]
    Range (min … max):    2.455 s …  2.505 s    10 runs

  Summary
    './git -C ~/src/linux.git log -L:build_restore_work_registers:arch/mips/mm/tlbex.c v6.16' ran
      1.32 ± 0.01 times faster than './git_v2.51.0 -C ~/src/linux.git log -L:build_restore_work_registers:arch/mips/mm/tlbex.c v6.16'

And since now each iteration computes a tree diff and processes its
result, there is no reason to store the diff queues for each merge
parent anymore, so replace that diff queue array with a loop-local
diff queue variable.  With this change the static free_diffqueues()
helper function in 'line-log.c' has no more callers left, remove it.

Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: show all line ranges touched by the same diff range</title>
<updated>2025-08-20T20:48:21Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-18T11:13:10Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=e3106998ff84c9e1b548b72e0b034ca837d6c06b'/>
<id>urn:sha1:e3106998ff84c9e1b548b72e0b034ca837d6c06b</id>
<content type='text'>
When line-level log is invoked with more than one disjoint line range
in the same file, and one of the commits happens to change that file
such that one diff range modifies more than one line range, then
changes to all modified line ranges should be shown, but only the
changes in the first modified line range are:

  $ git log --oneline -p
  80ca903 (HEAD -&gt; master) Initial
  diff --git a/file b/file
  new file mode 100644
  index 0000000..00935f1
  --- /dev/null
  +++ b/file
  @@ -0,0 +1,10 @@
  +Line 1
  +Line 2
  +Line 3
  +Line 4
  +Line 5
  +Line 6
  +Line 7
  +Line 8
  +Line 9
  +Line 10
  $ git log --oneline -L1,2:file -L4,5:file -L7,8:file
  80ca903 (HEAD -&gt; master) Initial

  diff --git a/file b/file
  --- /dev/null
  +++ b/file
  @@ -0,0 +1,2 @@
  +Line 1
  +Line 2

The line-log-specific diff printer is already clever enough to handle
the case when one line range covers multiple diff ranges, but the
possibility of one diff range touching multiple disjoint line ranges
was apparently overlooked.

Add the necessary condition to dump_diff_hacky_one() to handle this case
as well, and show all modified line ranges:

  $ git log --oneline -L1,2:file -L4,5:file -L7,8:file
  0f9a5b4 (HEAD -&gt; master) Initial

  diff --git a/file b/file
  --- /dev/null
  +++ b/file
  @@ -0,0 +1,2 @@
  +Line 1
  +Line 2
  @@ -0,0 +4,2 @@
  +Line 4
  +Line 5
  @@ -0,0 +7,2 @@
  +Line 7
  +Line 8

This bug was already present in the initial line-log implementation
added in 2da1d1f6f (Implement line-history search (git log -L),
2013-03-28).  Interestingly, that commit already contained a canned
test case covering a similar scenario:

  "-L '/long f/',/^}/:a.c -L /main/,/^}/:a.c simple"

This test case looks for two line ranges in the same file, and both
trace back disjointly to the test repository's inital commit,
therefore changes to both line ranges should have been shown for the
initial commit, but only changes for the first line range are shown.
So this test case should have failed from the very beginning, but it
never did, because, unfortunately, the canned expected result is
incorrect, as it doesn't include changes for the second line range.

A similar test with a similarly incorrect canned expected result was
added later in 209618860c (log -L: fix overlapping input ranges,
2013-04-05).

Correct these two canned expected results to contain the changes for
the second line range for the initial commit as well.

Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
<entry>
<title>line-log: fix assertion error</title>
<updated>2025-08-20T20:48:21Z</updated>
<author>
<name>SZEDER Gábor</name>
<email>szeder.dev@gmail.com</email>
</author>
<published>2025-08-18T11:13:09Z</published>
<link rel='alternate' type='text/html' href='https://git.shady.money/git/commit/?id=ab60c693a2f473c9952ad8ce31e94df5f76431b9'/>
<id>urn:sha1:ab60c693a2f473c9952ad8ce31e94df5f76431b9</id>
<content type='text'>
When line-level log is invoked with more than one disjoint line range
in the same file, and one of the commits happens to change that file
such that:

  - the last line of a line range R(n) immediately preceeds the first line
    modified or added by a hunk H, and
  - subtracting the number of lines added by hunk H from the start and
    end of the subsequent line range R(n+1) would result in a range
    overlapping with line range R(n),

then git aborts with an assertion error, because those overlapping
line ranges violate the invariants:

  $ git log --oneline -p
  73e4e2f (HEAD -&gt; master) Add lines 6 7 8 9 10
  diff --git a/file b/file
  index 572d5d9..00935f1 100644
  --- a/file
  +++ b/file
  @@ -3,3 +3,8 @@ Line 2
   Line 3
   Line 4
   Line 5
  +Line 6
  +Line 7
  +Line 8
  +Line 9
  +Line 10
  66e3561 Add lines 1 2 3 4 5
  diff --git a/file b/file
  new file mode 100644
  index 0000000..572d5d9
  --- /dev/null
  +++ b/file
  @@ -0,0 +1,5 @@
  +Line 1
  +Line 2
  +Line 3
  +Line 4
  +Line 5
  $ git log --oneline -L3,5:file -L7,8:file
  git: line-log.c:73: range_set_append: Assertion `rs-&gt;nr == 0 || rs-&gt;ranges[rs-&gt;nr-1].end &lt;= a' failed.
  Aborted (core dumped)

The line-log machinery encodes line and diff ranges internally as
[start, end) pairs, i.e. include 'start' but exclude 'end', and line
numbering starts at 0 (as opposed to the -LX,Y option, where it starts
at 1, IOW the parameter -L3,5 is represented internally as { start =
2, end = 5 }).

The reason for this assertion error and some related issues is that
there are a couple of places where 'end' is mistakenly considered to
be part of the range:

  - When a commit modifies an interesting path, the line-log machinery
    first checks which diff range (i.e. hunk) modify any line ranges.
    This is done in diff_ranges_filter_touched(), where the outer loop
    iterates over the diff ranges, and in each iteration the inner
    loop advances the line ranges supposedly until the current line
    range ends at or after the current diff range starts, and then the
    current diff and line ranges are checked for overlap.

    For HEAD in the above example the first line range [2, 5) ends
    just before the diff range [5, 10) starts, so the inner loop
    should advance, and then the second line range [6, 8) and the diff
    range should be checked for overlap.

    Unfortunately, the condition of the inner loop mistakenly
    considers 'end' as part of the line range, and, seeing the diff
    range starting at 5 and the line range ending at 5, it doesn't
    skip the first range.  Consequently, the diff range and the first
    line range are checked for overlap, and after that the outer loop
    runs out of diff ranges, and then the processing goes on in the
    false belief that this commit didn't touch any of the interesting
    line ranges.

    The line-log machinery later shifts the line ranges to account for
    any added/removed lines in the diff ranges preceeding each line
    range.  This leaves the first line range intact, but attempts to
    shift the second line range [6, 8) by 5 lines towards the
    beginning of the file, resulting in [1, 3), triggering the
    assertion error, because the two overlapping line ranges violate
    the invariants.

    Fix that loop condition in diff_ranges_filter_touched() to not
    treat 'end' as part of the line range.

  - With the above fix the assertion error is gone... but, alas, we
    now get stuck in an endless loop!

    This happens in range_set_difference(), where a couple of nested
    loops iterate over the line and diff ranges, and a condition is
    supposed to break the middle loop when the current line range ends
    before the current diff range, so processing could continue with
    the next line range.

    For HEAD in the above example the first line range [2, 5) ends
    just before the diff range [5, 10) starts, so this condition
    should trigger and break the middle loop.

    Unfortunately, just like in the case of the assertion error, this
    conditions mistakenly considers 'end' as part of the line range,
    and, seeing the line range ending at 5 and the diff range starting
    at 5, it doesn't break the loop, which will then go on and on.

    Fix this condition in range_set_difference() to not treat 'end' as
    part of the line range.

  - With the above fix the endless loop is gone... but, alas, the
    output is now wrong, as it shows both line ranges for HEAD, even
    though the first line range is not modified by that commit:

      $ git log --oneline -L3,5:file -L7,8:file
      73e4e2f (HEAD -&gt; master) Add lines 6 7 8 9 10

      diff --git a/file b/file
      --- a/file
      +++ b/file
      @@ -3,3 +3,3 @@
       Line 3
       Line 4
       Line 5
      @@ -6,0 +7,2 @@
      +Line 7
      +Line 8
      66e3561 Add lines 1 2 3 4 5

      diff --git a/file b/file
      --- /dev/null
      +++ b/file
      @@ -0,0 +3,3 @@
      +Line 3
      +Line 4
      +Line 5

    In dump_diff_hacky_one() a couple of nested loops are responsible
    for finding and printing the modified line ranges: the big outer
    loop iterates over all line ranges, and the first inner loop skips
    over the diff ranges that end before the start of the current line
    range.  This is followed by a condition checking whether the
    current diff range starts after the end of the current line range,
    which, when fulfilled, continues and advances the outer loop to
    the next line range.

    For HEAD in the above example the first line range [2, 5) ends
    just before the diff range [5, 10), so this condition should
    trigger, and the outer loop should advance to the second line
    range.

    Unfortunately, just like in the previous cases, this condition
    mistakenly considers 'end' as part of the line range, and, seeing
    the first line range ending at 5 and the diff range starting at 5,
    it doesn't continue to advance the outher loop, but goes on to
    show the (unmodified) first line range.

    Fix this condition to not treat 'end' as part of the line range,
    just like in the previous cases.

After all this the command in the above example finally finishes and
produces the right output:

  $ git log --oneline -L3,5:file -L7,8:file
  73e4e2f (HEAD -&gt; master) Add lines 6 7 8 9 10

  diff --git a/file b/file
  --- a/file
  +++ b/file
  @@ -6,0 +7,2 @@
  +Line 7
  +Line 8
  66e3561 Add lines 1 2 3 4 5

  diff --git a/file b/file
  --- /dev/null
  +++ b/file
  @@ -0,0 +3,3 @@
  +Line 3
  +Line 4
  +Line 5

Add a canned test similar to the above example, with the line ranges
adjusted to the test repository's history.

Reported-by: Evgeni Chasnovski &lt;evgeni.chasnovski@gmail.com&gt;
Signed-off-by: SZEDER Gábor &lt;szeder.dev@gmail.com&gt;
Signed-off-by: Junio C Hamano &lt;gitster@pobox.com&gt;
</content>
</entry>
</feed>
