aboutsummaryrefslogtreecommitdiff
path: root/line-log.c
diff options
context:
space:
mode:
authorMichael Montalbo <mmontalbo@gmail.com>2026-03-17 02:21:33 +0000
committerJunio C Hamano <gitster@pobox.com>2026-03-16 21:05:42 -0700
commit86e986f166d207e1f4b80062c2befb4f94c191c4 (patch)
treeae6a0dabd16a5c499373ce8d8c1fd5120296d984 /line-log.c
parent81cf6ccc29002467f44798ada7d74993a44c94b0 (diff)
downloadgit-86e986f166d207e1f4b80062c2befb4f94c191c4.tar.xz
line-log: route -L output through the standard diff pipeline
`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 <mmontalbo@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'line-log.c')
-rw-r--r--line-log.c174
1 files changed, 17 insertions, 157 deletions
diff --git a/line-log.c b/line-log.c
index 9d12ece181..858a899cd2 100644
--- a/line-log.c
+++ b/line-log.c
@@ -885,160 +885,6 @@ static void queue_diffs(struct line_log_data *range,
move_diff_queue(queue, &diff_queued_diff);
}
-static char *get_nth_line(long line, unsigned long *ends, void *data)
-{
- if (line == 0)
- return (char *)data;
- else
- return (char *)data + ends[line] + 1;
-}
-
-static void print_line(const char *prefix, char first,
- long line, unsigned long *ends, void *data,
- const char *color, const char *reset, FILE *file)
-{
- char *begin = get_nth_line(line, ends, data);
- char *end = get_nth_line(line+1, ends, data);
- int had_nl = 0;
-
- if (end > begin && end[-1] == '\n') {
- end--;
- had_nl = 1;
- }
-
- fputs(prefix, file);
- fputs(color, file);
- putc(first, file);
- fwrite(begin, 1, end-begin, file);
- fputs(reset, file);
- putc('\n', file);
- if (!had_nl)
- fputs("\\ No newline at end of file\n", file);
-}
-
-static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)
-{
- unsigned int i, j = 0;
- long p_lines, t_lines;
- unsigned long *p_ends = NULL, *t_ends = NULL;
- struct diff_filepair *pair = range->pair;
- struct diff_ranges *diff = &range->diff;
-
- struct diff_options *opt = &rev->diffopt;
- const char *prefix = diff_line_prefix(opt);
- const char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);
- const char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);
- const char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);
- const char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);
- const char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);
- const char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);
-
- if (!pair || !diff)
- goto out;
-
- if (pair->one->oid_valid)
- fill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);
- fill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);
-
- fprintf(opt->file, "%s%sdiff --git a/%s b/%s%s\n", prefix, c_meta, pair->one->path, pair->two->path, c_reset);
- fprintf(opt->file, "%s%s--- %s%s%s\n", prefix, c_meta,
- pair->one->oid_valid ? "a/" : "",
- pair->one->oid_valid ? pair->one->path : "/dev/null",
- c_reset);
- fprintf(opt->file, "%s%s+++ b/%s%s\n", prefix, c_meta, pair->two->path, c_reset);
- for (i = 0; i < range->ranges.nr; i++) {
- long p_start, p_end;
- long t_start = range->ranges.ranges[i].start;
- long t_end = range->ranges.ranges[i].end;
- long t_cur = t_start;
- unsigned int j_last;
-
- /*
- * If a diff range touches multiple line ranges, then all
- * those line ranges should be shown, so take a step back if
- * the current line range is still in the previous diff range
- * (even if only partially).
- */
- if (j > 0 && diff->target.ranges[j-1].end > t_start)
- j--;
-
- while (j < diff->target.nr && diff->target.ranges[j].end < t_start)
- j++;
- if (j == diff->target.nr || diff->target.ranges[j].start >= t_end)
- continue;
-
- /* Scan ahead to determine the last diff that falls in this range */
- j_last = j;
- while (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)
- j_last++;
- if (j_last > j)
- j_last--;
-
- /*
- * Compute parent hunk headers: we know that the diff
- * has the correct line numbers (but not all hunks).
- * So it suffices to shift the start/end according to
- * the line numbers of the first/last hunk(s) that
- * fall in this range.
- */
- if (t_start < diff->target.ranges[j].start)
- p_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);
- else
- p_start = diff->parent.ranges[j].start;
- if (t_end > diff->target.ranges[j_last].end)
- p_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);
- else
- p_end = diff->parent.ranges[j_last].end;
-
- if (!p_start && !p_end) {
- p_start = -1;
- p_end = -1;
- }
-
- /* Now output a diff hunk for this range */
- fprintf(opt->file, "%s%s@@ -%ld,%ld +%ld,%ld @@%s\n",
- prefix, c_frag,
- p_start+1, p_end-p_start, t_start+1, t_end-t_start,
- c_reset);
- while (j < diff->target.nr && diff->target.ranges[j].start < t_end) {
- int k;
- for (; t_cur < diff->target.ranges[j].start; t_cur++)
- print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
- c_context, c_reset, opt->file);
- for (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)
- print_line(prefix, '-', k, p_ends, pair->one->data,
- c_old, c_reset, opt->file);
- for (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)
- print_line(prefix, '+', t_cur, t_ends, pair->two->data,
- c_new, c_reset, opt->file);
- j++;
- }
- for (; t_cur < t_end; t_cur++)
- print_line(prefix, ' ', t_cur, t_ends, pair->two->data,
- c_context, c_reset, opt->file);
- }
-
-out:
- free(p_ends);
- free(t_ends);
-}
-
-/*
- * NEEDSWORK: manually building a diff here is not the Right
- * Thing(tm). log -L should be built into the diff pipeline.
- */
-static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)
-{
- const char *prefix = diff_line_prefix(&rev->diffopt);
-
- fprintf(rev->diffopt.file, "%s\n", prefix);
-
- while (range) {
- dump_diff_hacky_one(rev, range);
- range = range->next;
- }
-}
-
/*
* Unlike most other functions, this destructively operates on
* 'range'.
@@ -1102,7 +948,7 @@ static int process_diff_filepair(struct rev_info *rev,
static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)
{
- struct diff_filepair *new_filepair = xmalloc(sizeof(struct diff_filepair));
+ struct diff_filepair *new_filepair = xcalloc(1, sizeof(struct diff_filepair));
new_filepair->one = pair->one;
new_filepair->two = pair->two;
new_filepair->one->count++;
@@ -1160,11 +1006,25 @@ static int process_all_files(struct line_log_data **range_out,
int line_log_print(struct rev_info *rev, struct commit *commit)
{
-
show_log(rev);
if (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {
struct line_log_data *range = lookup_line_range(rev, commit);
- dump_diff_hacky(rev, range);
+ struct line_log_data *r;
+ const char *prefix = diff_line_prefix(&rev->diffopt);
+
+ fprintf(rev->diffopt.file, "%s\n", prefix);
+
+ for (r = range; r; r = r->next) {
+ if (r->pair) {
+ struct diff_filepair *p =
+ diff_filepair_dup(r->pair);
+ p->line_ranges = &r->ranges;
+ diff_q(&diff_queued_diff, p);
+ }
+ }
+
+ diffcore_std(&rev->diffopt);
+ diff_flush(&rev->diffopt);
}
return 1;
}