Skip to content

Commit 33caec9

Browse files
committed
line-log: consult diff process for range tracking
git log -L tracks line ranges by diffing each commit against its parent in collect_diff(). This pass used the builtin diff while the displayed diff (builtin_diff()) consults a configured diff.<driver>.process, so the two could disagree: a reformat-only commit selected by builtin tracking was then rendered with an empty diff because the tool reported the files equivalent. Consult the process in collect_diff() too, mirroring the blame integration. When the tool reports the files equivalent, collect no ranges; the tracked range then maps across unchanged and the commit drops out of the log, matching what is displayed. Like the summary formats, the tracking pass diffs raw content, so the tool is consulted on the raw blobs here. Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>
1 parent cdb58cf commit 33caec9

3 files changed

Lines changed: 65 additions & 12 deletions

File tree

Documentation/gitattributes.adoc

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1015,12 +1015,16 @@ Features that ask "which lines changed" use the tool's hunks in place
10151015
of the builtin algorithm:
10161016

10171017
- `git diff` patch output, together with everything layered on it:
1018-
word diff, function context (`-W`), `--color-moved`, the `@@` hunk
1019-
headers, and the `-L` line-range display. These operate on the
1020-
lines the patch step already emitted, so they reflect the tool's
1021-
hunks without any further negotiation.
1018+
word diff, function context (`-W`), `--color-moved`, and the `@@`
1019+
hunk headers. These operate on the lines the patch step already
1020+
emitted, so they reflect the tool's hunks without any further
1021+
negotiation.
10221022
- `git blame`: a commit whose change the tool reports as equivalent is
10231023
skipped, and its lines are attributed to an earlier commit.
1024+
- `git log -L`: both the line-range display and the underlying range
1025+
tracking consult the tool, so a commit it reports as equivalent is
1026+
dropped from the log (its tracked range maps across unchanged)
1027+
rather than selected and then shown with an empty diff.
10241028
- `--stat`, `--numstat`, and `--shortstat`: the inserted and deleted
10251029
counts come from the tool's hunks, so a file the tool calls
10261030
equivalent contributes no stat line, matching the empty patch that
@@ -1057,11 +1061,9 @@ design:
10571061
- `--raw`, `--name-only`, and `--name-status` compare object ids at
10581062
the tree level and never run a line-level diff at all.
10591063

1060-
Two cases ask "which lines changed" but still use the builtin
1061-
algorithm, and may consult the process in a later change: `git log
1062-
-L`'s commit selection and parent range propagation (as distinct from
1063-
its display, which is covered above), and combined diffs (`--cc` and
1064-
merge diffs), whose protocol would have to be extended from a single
1064+
Combined diffs (`--cc` and merge diffs) ask "which lines changed" but
1065+
still use the builtin algorithm, and may consult the process in a
1066+
later change; their protocol would have to be extended from a single
10651067
old/new pair to one comparison per merge parent.
10661068

10671069
`--no-ext-diff` and `--diff-algorithm` bypass the process entirely,

line-log.c

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "tag.h"
88
#include "tree.h"
99
#include "diff.h"
10+
#include "diff-process.h"
1011
#include "commit.h"
1112
#include "decorate.h"
1213
#include "repository.h"
@@ -330,12 +331,15 @@ static int collect_diff_cb(long start_a, long count_a,
330331
return 0;
331332
}
332333

333-
static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *out)
334+
static int collect_diff(struct diff_options *diffopt, const char *path,
335+
mmfile_t *parent, mmfile_t *target,
336+
struct diff_ranges *out)
334337
{
335338
struct collect_diff_cbdata cbdata = {NULL};
336339
xpparam_t xpp;
337340
xdemitconf_t xecfg;
338341
xdemitcb_t ecb;
342+
int ret = 0;
339343

340344
memset(&xpp, 0, sizeof(xpp));
341345
memset(&xecfg, 0, sizeof(xecfg));
@@ -345,7 +349,20 @@ static int collect_diff(mmfile_t *parent, mmfile_t *target, struct diff_ranges *
345349
xecfg.hunk_func = collect_diff_cb;
346350
memset(&ecb, 0, sizeof(ecb));
347351
ecb.priv = &cbdata;
348-
return xdi_diff(parent, target, &xpp, &xecfg, &ecb);
352+
353+
/*
354+
* Consult the diff process so range tracking agrees with the
355+
* diff that will be shown. When the tool reports the files as
356+
* equivalent we collect no ranges, so the tracked range maps
357+
* across unchanged and the commit drops out of the log, rather
358+
* than being selected here but rendered with an empty diff by
359+
* the process-aware builtin_diff().
360+
*/
361+
if (diff_process_fill_hunks(diffopt, path, parent, target, &xpp)
362+
!= DIFF_PROCESS_EQUIVALENT)
363+
ret = xdi_diff(parent, target, &xpp, &xecfg, &ecb);
364+
free(xpp.external_hunks);
365+
return ret;
349366
}
350367

351368
/*
@@ -927,7 +944,8 @@ static int process_diff_filepair(struct rev_info *rev,
927944
}
928945

929946
diff_ranges_init(&diff);
930-
if (collect_diff(&file_parent, &file_target, &diff))
947+
if (collect_diff(&rev->diffopt, pair->two->path,
948+
&file_parent, &file_target, &diff))
931949
die("unable to generate diff for %s", pair->one->path);
932950

933951
/* NEEDSWORK should apply some heuristics to prevent mismatches */

t/t4080-diff-process.sh

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,4 +796,37 @@ test_expect_success 'blame -w bypasses diff process' '
796796
test_path_is_missing backend.log
797797
'
798798

799+
#
800+
# Line-log (git log -L) range tracking.
801+
#
802+
803+
test_expect_success 'diff process drops equivalent commit from log -L' '
804+
test_when_finished "rm -f backend.log" &&
805+
cat >linelog.c <<-\EOF &&
806+
int tracked(void) { return 1; }
807+
EOF
808+
git add linelog.c &&
809+
git commit -m "add linelog.c" &&
810+
811+
cat >linelog.c <<-\EOF &&
812+
int tracked(void) { return 2; }
813+
EOF
814+
git commit -am "change tracked line" &&
815+
816+
# Builtin line tracking selects the change commit.
817+
git log --no-ext-diff -L1,1:linelog.c --format="%s" >builtin &&
818+
test_grep "change tracked line" builtin &&
819+
820+
# With the tool reporting the change as equivalent, tracking
821+
# drops the commit (the range maps across unchanged) instead of
822+
# selecting it and rendering an empty diff.
823+
git -c diff.cdiff.process="$BACKEND --mode=no-hunks --log=backend.log" \
824+
log -L1,1:linelog.c --format="%s" >actual &&
825+
test_grep ! "change tracked line" actual &&
826+
# The creating commit still appears, so the change commit was
827+
# selectively dropped rather than the whole log going empty.
828+
test_grep "add linelog.c" actual &&
829+
test_grep "command=hunks pathname=linelog.c" backend.log
830+
'
831+
799832
test_done

0 commit comments

Comments
 (0)