Skip to contentSkip to conversation

line-log: route -L output through the standard diff pipeline

PATCH Superseded Michael Montalbo via GitGitGadget 4 participants 18 messages Mar 7 – Mar 31 Documentation

lore
Share & tools
lore
#0 lore raw

cover line-log: route -L output through the standard diff pipeline

git log -L has bypassed the standard diff pipeline since its introduction, using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has acknowledged this from the start. This series removes dump_diff_hacky() and routes -L output through builtin_diff() / fn_out_consume(), so that diff formatting options like --word-diff, --color-moved, -w, and pickaxe options (-S, -G) work with -L.

This replaces my earlier series "line-log: fix -L with pickaxe options" [1]. Patch 1 is the crash fix from that series (unchanged). Patch 2/2 from that series (rejecting -S/-G) is dropped because this series makes those options work instead of rejecting them.

[1]
https://lore.kernel.org/git/[email protected]/

Patch 1 fixes a crash when combining -L with pickaxe options and a rename.

Patch 2 is the core change: callback wrappers filter xdiff's output to tracked line ranges, and line ranges are carried on diff_filepair so each file's ranges travel with its filepair through the pipeline. diffcore_std() runs at output time, so pickaxe, --orderfile, and --diff-filter also work.

Patch 3 adds tests covering the newly-working options.

Patch 4 updates documentation.

User-visible output change: -L output now includes index lines, new file mode headers, and funcname context in @@ headers that were previously missing. Tools parsing -L output may need to handle these additional lines.

Known limitations not addressed in this series:

* line_log_print() still calls show_log() and diff_flush() directly, bypassing log_tree_diff_flush(). The early return in log_tree_commit() (and its associated NEEDSWORK about no_free not being restored) is pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a larger change that would affect separator and header logic; it is left for a follow-up.

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

Michael Montalbo (4): line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline t4211: add tests for -L with standard diff options doc: note that -L supports patch formatting and pickaxe options

Michael Montalbo (4):
line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline
t4211: add tests for -L with standard diff options
doc: note that -L supports patch formatting and pickaxe options

 Documentation/line-range-options.adoc         |   4 +
 diff.c                                        | 279 +++++++++++++-
 diffcore.h                                    |  16 +
 line-log.c                                    | 196 ++--------
 line-log.h                                    |  14 +-
 revision.c                                    |   2 +
 t/t4211-line-log.sh                           | 342 +++++++++++++++++-
 t/t4211/sha1/expect.beginning-of-file         |   4 +
 t/t4211/sha1/expect.end-of-file               |  11 +-
 t/t4211/sha1/expect.move-support-f            |   5 +
 t/t4211/sha1/expect.multiple                  |  10 +-
 t/t4211/sha1/expect.multiple-overlapping      |   7 +
 t/t4211/sha1/expect.multiple-superset         |   7 +
 t/t4211/sha1/expect.no-assertion-error        |  12 +-
 t/t4211/sha1/expect.parallel-change-f-to-main |   7 +
 t/t4211/sha1/expect.simple-f                  |   4 +
 t/t4211/sha1/expect.simple-f-to-main          |   5 +
 t/t4211/sha1/expect.simple-main               |  11 +-
 t/t4211/sha1/expect.simple-main-to-end        |  11 +-
 t/t4211/sha1/expect.two-ranges                |  10 +-
 t/t4211/sha1/expect.vanishes-early            |  10 +-
 t/t4211/sha256/expect.beginning-of-file       |   4 +
 t/t4211/sha256/expect.end-of-file             |  11 +-
 t/t4211/sha256/expect.move-support-f          |   5 +
 t/t4211/sha256/expect.multiple                |  10 +-
 t/t4211/sha256/expect.multiple-overlapping    |   7 +
 t/t4211/sha256/expect.multiple-superset       |   7 +
 t/t4211/sha256/expect.no-assertion-error      |  12 +-
 .../sha256/expect.parallel-change-f-to-main   |   7 +
 t/t4211/sha256/expect.simple-f                |   4 +
 t/t4211/sha256/expect.simple-f-to-main        |   5 +
 t/t4211/sha256/expect.simple-main             |  11 +-
 t/t4211/sha256/expect.simple-main-to-end      |  11 +-
 t/t4211/sha256/expect.two-ranges              |  10 +-
 t/t4211/sha256/expect.vanishes-early          |  10 +-
 35 files changed, 864 insertions(+), 217 deletions(-)

base-commit: 7b2bccb0d58d4f24705bf985de1f4612e4cf06e5
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2065%2Fmmontalbo%2Fspike-xdiff-line-range-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2065/mmontalbo/spike-xdiff-line-range-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/2065

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/4 line-log: fix crash when combined with pickaxe options

From: Michael Montalbo <[email protected]>

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.

---
 line-log.c          | 22 ++++++++++++++++----
 t/t4211-line-log.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 4 deletions(-)
line-log.c +18 -4
index eeaf68454e..9d12ece181 100644
@@ -858,15 +858,29 @@ static void queue_diffs(struct line_log_data *range,
 	diff_queue_clear(&diff_queued_diff);
 	diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
 	if (opt->detect_rename && diff_might_be_rename()) {
+		struct diff_options rename_opts;
+
+		/*
+		 * Build a private diff_options for rename detection so
+		 * that any user-specified options on the original opts
+		 * (e.g. pickaxe) cannot discard diff pairs needed for
+		 * rename tracking.  Similar to blame's find_rename().
+		 */
+		repo_diff_setup(opt->repo, &rename_opts);
+		rename_opts.flags.recursive = 1;
+		rename_opts.detect_rename = opt->detect_rename;
+		rename_opts.rename_score = opt->rename_score;
+		rename_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+		diff_setup_done(&rename_opts);
+
 		/* must look at the full tree diff to detect renames */
-		clear_pathspec(&opt->pathspec);
 		diff_queue_clear(&diff_queued_diff);
-
-		diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+		diff_tree_oid(parent_tree_oid, tree_oid, "", &rename_opts);
 
 		filter_diffs_for_paths(range, 1);
-		diffcore_std(opt);
+		diffcore_std(&rename_opts);
 		filter_diffs_for_paths(range, 0);
+		diff_free(&rename_opts);
 	}
 	move_diff_queue(queue, &diff_queued_diff);
 }
t/t4211-line-log.sh +49 -0

53 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/4 line-log: route -L output through the standard diff pipeline

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

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.

---
 diff.c                                        | 279 +++++++++++++++++-
 diffcore.h                                    |  16 +
 line-log.c                                    | 174 ++---------
 line-log.h                                    |  14 +-
 revision.c                                    |   2 +
 t/t4211-line-log.sh                           |  12 +-
 t/t4211/sha1/expect.beginning-of-file         |   4 +
 t/t4211/sha1/expect.end-of-file               |  11 +-
 t/t4211/sha1/expect.move-support-f            |   5 +
 t/t4211/sha1/expect.multiple                  |  10 +-
 t/t4211/sha1/expect.multiple-overlapping      |   7 +
 t/t4211/sha1/expect.multiple-superset         |   7 +
 t/t4211/sha1/expect.no-assertion-error        |  12 +-
 t/t4211/sha1/expect.parallel-change-f-to-main |   7 +
 t/t4211/sha1/expect.simple-f                  |   4 +
 t/t4211/sha1/expect.simple-f-to-main          |   5 +
 t/t4211/sha1/expect.simple-main               |  11 +-
 t/t4211/sha1/expect.simple-main-to-end        |  11 +-
 t/t4211/sha1/expect.two-ranges                |  10 +-
 t/t4211/sha1/expect.vanishes-early            |  10 +-
 t/t4211/sha256/expect.beginning-of-file       |   4 +
 t/t4211/sha256/expect.end-of-file             |  11 +-
 t/t4211/sha256/expect.move-support-f          |   5 +
 t/t4211/sha256/expect.multiple                |  10 +-
 t/t4211/sha256/expect.multiple-overlapping    |   7 +
 t/t4211/sha256/expect.multiple-superset       |   7 +
 t/t4211/sha256/expect.no-assertion-error      |  12 +-
 .../sha256/expect.parallel-change-f-to-main   |   7 +
 t/t4211/sha256/expect.simple-f                |   4 +
 t/t4211/sha256/expect.simple-f-to-main        |   5 +
 t/t4211/sha256/expect.simple-main             |  11 +-
 t/t4211/sha256/expect.simple-main-to-end      |  11 +-
 t/t4211/sha256/expect.two-ranges              |  10 +-
 t/t4211/sha256/expect.vanishes-early          |  10 +-
 34 files changed, 512 insertions(+), 213 deletions(-)
diff.c +277 -2
index 35b903a9a0..c8cbbbb1db 100644
@@ -608,6 +608,52 @@ struct emit_callback {
 	struct strbuf *header;
 };
 
+/*
+ * State for the line-range callback wrappers that sit between
+ * xdi_diff_outf() and fn_out_consume().  xdiff produces a normal,
+ * unfiltered diff; the wrappers intercept each hunk header and line,
+ * track post-image position, and forward only lines that fall within
+ * the requested ranges.  Contiguous in-range lines are collected into
+ * range hunks and flushed with a synthetic @@ header so that
+ * fn_out_consume() sees well-formed unified-diff fragments.
+ *
+ * Removal lines ('-') cannot be classified by post-image position, so
+ * they are buffered in pending_rm until the next '+' or ' ' line
+ * reveals whether they precede an in-range line (flush into range hunk) or
+ * an out-of-range line (discard).
+ */
+struct line_range_callback {
+	xdiff_emit_line_fn orig_line_fn;
+	void *orig_cb_data;
+	const struct range_set *ranges;	/* 0-based [start, end) */
+	unsigned int cur_range;		/* index into the range_set */
+
+	/* Post/pre-image line counters (1-based, set from hunk headers) */
+	long lno_post;
+	long lno_pre;
+
+	/*
+	 * Function name from most recent xdiff hunk header;
+	 * size matches struct func_line.buf in xdiff/xemit.c.
+	 */
+	char func[80];
+	long funclen;
+
+	/* Range hunk being accumulated for the current range */
+	struct strbuf rhunk;
+	long rhunk_old_begin, rhunk_old_count;
+	long rhunk_new_begin, rhunk_new_count;
+	int rhunk_active;
+	int rhunk_has_changes;		/* any '+' or '-' lines? */
+
+	/* Removal lines not yet known to be in-range */
+	struct strbuf pending_rm;
+	int pending_rm_count;
+	long pending_rm_pre_begin;	/* pre-image line of first pending */
+
+	int ret;			/* latched error from orig_line_fn */
+};
+
 static int count_lines(const char *data, int size)
 {
 	int count, ch, completely_empty = 1, nl_just_seen = 0;
@@ -2486,6 +2532,188 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED
 	return 1;
 }
 
+static void discard_pending_rm(struct line_range_callback *s)
+{
+	strbuf_reset(&s->pending_rm);
+	s->pending_rm_count = 0;
+}
+
+static void flush_rhunk(struct line_range_callback *s)
+{
+	struct strbuf hdr = STRBUF_INIT;
+	const char *p, *end;
+
+	if (!s->rhunk_active || s->ret)
+		return;
+
+	/* Drain any pending removal lines into the range hunk */
+	if (s->pending_rm_count) {
+		strbuf_addbuf(&s->rhunk, &s->pending_rm);
+		s->rhunk_old_count += s->pending_rm_count;
+		s->rhunk_has_changes = 1;
+		discard_pending_rm(s);
+	}
+
+	/*
+	 * Suppress context-only hunks: they contain no actual changes
+	 * and would just be noise.  This can happen when the inflated
+	 * ctxlen causes xdiff to emit context covering a range that
+	 * has no changes in this commit.
+	 */
+	if (!s->rhunk_has_changes) {
+		s->rhunk_active = 0;
+		strbuf_reset(&s->rhunk);
+		return;
+	}
+
+	strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@",
+		    s->rhunk_old_begin, s->rhunk_old_count,
+		    s->rhunk_new_begin, s->rhunk_new_count);
+	if (s->funclen > 0) {
+		strbuf_addch(&hdr, ' ');
+		strbuf_add(&hdr, s->func, s->funclen);
+	}
+	strbuf_addch(&hdr, '\n');
+
+	s->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);
+	strbuf_release(&hdr);
+
+	/*
+	 * Replay buffered lines one at a time through fn_out_consume.
+	 * The cast discards const because xdiff_emit_line_fn takes
+	 * char *, though fn_out_consume does not modify the buffer.
+	 */
+	p = s->rhunk.buf;
+	end = p + s->rhunk.len;
+	while (!s->ret && p < end) {
+		const char *eol = memchr(p, '\n', end - p);
+		unsigned long line_len = eol ? (unsigned long)(eol - p + 1)
+					     : (unsigned long)(end - p);
+		s->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);
+		p += line_len;
+	}
+
+	s->rhunk_active = 0;
+	strbuf_reset(&s->rhunk);
+}
+
+static void line_range_hunk_fn(void *data,
+			       long old_begin, long old_nr UNUSED,
+			       long new_begin, long new_nr UNUSED,
+			       const char *func, long funclen)
+{
+	struct line_range_callback *s = data;
+
+	/*
+	 * When count > 0, begin is 1-based.  When count == 0, begin is
+	 * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
+	 * that type will arrive, so the value is unused.
+	 *
+	 * Any pending removal lines from the previous xdiff hunk are
+	 * intentionally left in pending_rm: the line callback will
+	 * flush or discard them when the next content line reveals
+	 * whether the removals precede in-range content.
+	 */
+	s->lno_post = new_begin;
+	s->lno_pre = old_begin;
+
+	if (funclen > 0) {
+		if (funclen > (long)sizeof(s->func))
+			funclen = sizeof(s->func);
+		memcpy(s->func, func, funclen);
+	}
+	s->funclen = funclen;
+}
+
+static int line_range_line_fn(void *priv, char *line, unsigned long len)
+{
+	struct line_range_callback *s = priv;
+	const struct range *cur;
+	long lno_0, cur_pre;
+
+	if (s->ret)
+		return s->ret;
+
+	if (line[0] == '-') {
+		if (!s->pending_rm_count)
+			s->pending_rm_pre_begin = s->lno_pre;
+		s->lno_pre++;
+		strbuf_add(&s->pending_rm, line, len);
+		s->pending_rm_count++;
+		return s->ret;
+	}
+
+	if (line[0] == '\\') {
+		if (s->pending_rm_count)
+			strbuf_add(&s->pending_rm, line, len);
+		else if (s->rhunk_active)
+			strbuf_add(&s->rhunk, line, len);
+		/* otherwise outside tracked range; drop silently */
+		return s->ret;
+	}
+
+	if (line[0] != '+' && line[0] != ' ')
+		BUG("unexpected diff line type '%c'", line[0]);
+
+	lno_0 = s->lno_post - 1;
+	cur_pre = s->lno_pre;	/* save before advancing for context lines */
+	s->lno_post++;
+	if (line[0] == ' ')
+		s->lno_pre++;
+
+	/* Advance past ranges we've passed */
+	while (s->cur_range < s->ranges->nr &&
+	       lno_0 >= s->ranges->ranges[s->cur_range].end) {
+		if (s->rhunk_active)
+			flush_rhunk(s);
+		discard_pending_rm(s);
+		s->cur_range++;
+	}
+
+	/* Past all ranges */
+	if (s->cur_range >= s->ranges->nr) {
+		discard_pending_rm(s);
+		return s->ret;
+	}
+
+	cur = &s->ranges->ranges[s->cur_range];
+
+	/* Before current range */
+	if (lno_0 < cur->start) {
+		discard_pending_rm(s);
+		return s->ret;
+	}
+
+	/* In range so start a new range hunk if needed */
+	if (!s->rhunk_active) {
+		s->rhunk_active = 1;
+		s->rhunk_has_changes = 0;
+		s->rhunk_new_begin = lno_0 + 1;
+		s->rhunk_old_begin = s->pending_rm_count
+			? s->pending_rm_pre_begin : cur_pre;
+		s->rhunk_old_count = 0;
+		s->rhunk_new_count = 0;
+		strbuf_reset(&s->rhunk);
+	}
+
+	/* Flush pending removals into range hunk */
+	if (s->pending_rm_count) {
+		strbuf_addbuf(&s->rhunk, &s->pending_rm);
+		s->rhunk_old_count += s->pending_rm_count;
+		s->rhunk_has_changes = 1;
+		discard_pending_rm(s);
+	}
+
+	strbuf_add(&s->rhunk, line, len);
+	s->rhunk_new_count++;
+	if (line[0] == '+')
+		s->rhunk_has_changes = 1;
+	else
+		s->rhunk_old_count++;
+
+	return s->ret;
+}
+
 static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 {
 	const char *old_name = a;
@@ -3589,7 +3817,8 @@ static void builtin_diff(const char *name_a,
 			 const char *xfrm_msg,
 			 int must_show_header,
 			 struct diff_options *o,
-			 int complete_rewrite)
+			 int complete_rewrite,
+			 const struct range_set *line_ranges)
 {
 	mmfile_t mf1, mf2;
 	const char *lbl[2];
@@ -3823,6 +4052,52 @@ static void builtin_diff(const char *name_a,
 			 */
 			xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
 				      &ecbdata, &xpp, &xecfg);
+		} else if (line_ranges) {
+			struct line_range_callback lr_state;
+			unsigned int i;
+			long max_span = 0;
+
+			memset(&lr_state, 0, sizeof(lr_state));
+			lr_state.orig_line_fn = fn_out_consume;
+			lr_state.orig_cb_data = &ecbdata;
+			lr_state.ranges = line_ranges;
+			strbuf_init(&lr_state.rhunk, 0);
+			strbuf_init(&lr_state.pending_rm, 0);
+
+			/*
+			 * Inflate ctxlen so that all changes within
+			 * any single range are merged into one xdiff
+			 * hunk and the inter-change context is emitted.
+			 * The callback clips back to range boundaries.
+			 *
+			 * The optimal ctxlen depends on where changes
+			 * fall within the range, which is only known
+			 * after xdiff runs; the max range span is the
+			 * upper bound that guarantees correctness in a
+			 * single pass.
+			 */
+			for (i = 0; i < line_ranges->nr; i++) {
+				long span = line_ranges->ranges[i].end -
+					    line_ranges->ranges[i].start;
+				if (span > max_span)
+					max_span = span;
+			}
+			if (max_span > xecfg.ctxlen)
+				xecfg.ctxlen = max_span;
+
+			if (xdi_diff_outf(&mf1, &mf2,
+					  line_range_hunk_fn,
+					  line_range_line_fn,
+					  &lr_state, &xpp, &xecfg))
+				die("unable to generate diff for %s",
+				    one->path);
+
+			flush_rhunk(&lr_state);
+			if (lr_state.ret)
+				die("unable to generate diff for %s",
+				    one->path);
+			strbuf_release(&lr_state.rhunk);
+			strbuf_release(&lr_state.pending_rm);
 		} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
 					 &ecbdata, &xpp, &xecfg))
 			die("unable to generate diff for %s", one->path);
@@ -4660,7 +4935,7 @@ static void run_diff_cmd(const struct external_diff *pgm,
 
 		builtin_diff(name, other ? other : name,
 			     one, two, xfrm_msg, must_show_header,
-			     o, complete_rewrite);
+			     o, complete_rewrite, p->line_ranges);
 		if (p->status == DIFF_STATUS_COPIED ||
 		    p->status == DIFF_STATUS_RENAMED)
 			o->found_changes = 1;
diffcore.h +16 -0
index 9c0a0e7aaf..d75038d1b3 100644
@@ -19,6 +19,17 @@ struct userdiff_driver;
  * in anything else.
  */
 
+/* A range [start, end).  Lines are numbered starting at 0. */
+struct range {
+	long start, end;
+};
+
+/* A set of ranges.  The ranges must always be disjoint and sorted. */
+struct range_set {
+	unsigned int alloc, nr;
+	struct range *ranges;
+};
+
 /* We internally use unsigned short as the score value,
  * and rely on an int capable to hold 32-bits.  -B can take
  * -Bmerge_score/break_score format and the two scores are
@@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
 struct diff_filepair {
 	struct diff_filespec *one;
 	struct diff_filespec *two;
+	/*
+	 * Tracked line ranges for -L filtering; borrowed from
+	 * line_log_data and must not be freed.
+	 */
+	const struct range_set *line_ranges;
 	unsigned short int score;
 	char status; /* M C R A D U etc. (see Documentation/diff-format.adoc or DIFF_STATUS_* in diff.h) */
 	unsigned broken_pair : 1;
line-log.c +17 -157

195 lines not on this page · raw message

line-log.h +2 -12
index e9dadbc1a5..04a6ea64d3 100644
@@ -1,22 +1,12 @@
 #ifndef LINE_LOG_H
 #define LINE_LOG_H
 
+#include "diffcore.h" /* struct range, struct range_set */
+
 struct rev_info;
 struct commit;
 struct string_list;
 
-/* A range [start,end].  Lines are numbered starting at 0, and the
- * ranges include start but exclude end. */
-struct range {
-	long start, end;
-};
-
-/* A set of ranges.  The ranges must always be disjoint and sorted. */
-struct range_set {
-	unsigned int alloc, nr;
-	struct range *ranges;
-};
-
 /* A diff, encoded as the set of pre- and post-image ranges where the
  * files differ. A pair of ranges corresponds to a hunk. */
 struct diff_ranges {
revision.c +2 -0
index 047ff7e458..9f68bedf13 100644
@@ -3111,6 +3111,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 		if (want_ancestry(revs))
 			revs->limited = 1;
 		revs->topo_order = 1;
+		if (!revs->diffopt.output_format)
+			revs->diffopt.output_format = DIFF_FORMAT_PATCH;
 	}
 
 	if (revs->topo_order && !generation_numbers_enabled(the_repository))
t/t4211-line-log.sh +10 -2
index 7acc38f72d..01f7ec8c32 100755
@@ -129,7 +129,7 @@ test_expect_success '-L with --output' '
 	git checkout parallel-change &&
 	git log --output=log -L :main:b.c >output &&
 	test_must_be_empty output &&
-	test_line_count = 70 log
+	test_line_count = 75 log
 '
 
 test_expect_success 'range_set_union' '
@@ -340,13 +340,19 @@ test_expect_success 'zero-width regex .* matches any function name' '
 '
 
 test_expect_success 'show line-log with graph' '
+	git checkout parent-oids &&
+	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
+	head_blob_new=$(git rev-parse --short HEAD:file.c) &&
+	root_blob=$(git rev-parse --short HEAD~4:file.c) &&
+	null_blob=$(test_oid zero | cut -c1-7) &&
 	qz_to_tab_space >expect <<-EOF &&
 	* $head_oid Modify func2() in file.c
 	|Z
 	| diff --git a/file.c b/file.c
+	| index $head_blob_old..$head_blob_new 100644
 	| --- a/file.c
 	| +++ b/file.c
-	| @@ -6,4 +6,4 @@
+	| @@ -6,4 +6,4 @@ int func1()
 	|  int func2()
 	|  {
 	| -    return F2;
@@ -355,6 +361,8 @@ test_expect_success 'show line-log with graph' '
 	* $root_oid Add func1() and func2() in file.c
 	ZZ
 	  diff --git a/file.c b/file.c
+	  new file mode 100644
+	  index $null_blob..$root_blob
 	  --- /dev/null
 	  +++ b/file.c
 	  @@ -0,0 +6,4 @@
t/t4211/sha1/expect.beginning-of-file +4 -0
index 91b4054898..52c90afb3a 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100
     change at very beginning
 
 diff --git a/a.c b/a.c
+index bdb2bb1..5e709a1 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,4 @@
@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,3 @@
@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +1,3 @@
t/t4211/sha1/expect.end-of-file +8 -3
index bd25bb2f59..c40036899a 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,5 @@
+@@ -20,3 +20,5 @@ long f(long x)
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,3 @@
+@@ -20,3 +20,3 @@ int main ()
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -19,3 +19,3 @@
+@@ -19,3 +19,3 @@ int f(int x)
 -	printf("%d\n", f(15));
 +	printf("%ld\n", f(15));
  	return 0;
@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +18,3 @@
t/t4211/sha1/expect.move-support-f +5 -0
index c905e01bc2..ead6500d4d 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 5de3ea4..bf79c2f 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,9 +4,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.multiple +8 -2
index 1eee8a7801..a41851a51d 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.multiple-overlapping +7 -0
index d930b6eec4..0ec9990eab 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha1/expect.multiple-superset +7 -0
index d930b6eec4..0ec9990eab 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha1/expect.no-assertion-error +9 -3
index 994c37db1e..54c568f273 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100
     move within the file
 
 diff --git a/b.c b/b.c
+index bf79c2f..27c829c 100644
 --- a/b.c
 +++ b/b.c
 @@ -25,0 +18,9 @@
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.parallel-change-f-to-main +7 -0
index 052def8074..65a8cc673a 100644
@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 5de3ea4..bf79c2f 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,14 +4,14 @@
@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200
     change on another line of history while rename happens
 
 diff --git a/a.c b/a.c
+index 5de3ea4..01b5b65 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,14 +4,14 @@
@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha1/expect.simple-f +4 -0
index a1f5bc49c8..b24ae40e03 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.simple-f-to-main +5 -0
index a475768710..cd92100dfc 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha1/expect.simple-main +8 -3
index 39ce39bebe..ff31291d34 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.simple-main-to-end +8 -3
index 8480bd9cc4..4bef21e657 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.two-ranges +8 -2
index c5164f3be3..aed01522e3 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.vanishes-early +6 -4
index 1f7cd06941..a413ad3659 100644
@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +24,1 @@
--}
-\ No newline at end of file
+@@ -23,0 +24,1 @@ int main ()
 +/* incomplete lines are bad! */
 
 commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +22,1 @@
+@@ -22,1 +22,1 @@ int main ()
 -}
 +}
 \ No newline at end of file
@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +20,1 @@
t/t4211/sha256/expect.beginning-of-file +4 -0
index 5adfdfc1a1..e8d62328cf 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100
     change at very beginning
 
 diff --git a/a.c b/a.c
+index 3a78aaf..d325124 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,4 @@
@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,3 @@
@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +1,3 @@
t/t4211/sha256/expect.end-of-file +8 -3
index 03ab5c1784..3b2e2384da 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,5 @@
+@@ -20,3 +20,5 @@ long f(long x)
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,3 @@
+@@ -20,3 +20,3 @@ int main ()
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -19,3 +19,3 @@
+@@ -19,3 +19,3 @@ int f(int x)
 -	printf("%d\n", f(15));
 +	printf("%ld\n", f(15));
  	return 0;
@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +18,3 @@
t/t4211/sha256/expect.move-support-f +5 -0
index 223b4ed2a0..f49abcea3e 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 62c1fc2..69cb69c 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,9 +4,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.multiple +8 -2
index dbd987b74a..0dee50ffb7 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.multiple-overlapping +7 -0
index 9015a45a25..b8c260e8ae 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha256/expect.multiple-superset +7 -0
index 9015a45a25..b8c260e8ae 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha256/expect.no-assertion-error +9 -3
index 36ed12aa9c..c25f2ce19c 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100
     move within the file
 
 diff --git a/b.c b/b.c
+index 69cb69c..a0d566e 100644
 --- a/b.c
 +++ b/b.c
 @@ -25,0 +18,9 @@
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.parallel-change-f-to-main +7 -0
index e68f8928ea..3178989253 100644
@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 62c1fc2..69cb69c 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,14 +4,14 @@
@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200
     change on another line of history while rename happens
 
 diff --git a/a.c b/a.c
+index 62c1fc2..e1e8475 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,14 +4,14 @@
@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha256/expect.simple-f +4 -0
index 65508d7c0b..983c711fe3 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.simple-f-to-main +5 -0
index 77b721c196..e67fa017a7 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha256/expect.simple-main +8 -3
index d20708c9f9..0792b27cad 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.simple-main-to-end +8 -3
index 617cdf3481..d3bd7c7bc6 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.two-ranges +8 -2
index 6a94d3b9cb..7735b19723 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.vanishes-early +6 -4
index 11ec9bdecf..bc33b963dc 100644
@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +24,1 @@
--}
-\ No newline at end of file
+@@ -23,0 +24,1 @@ int main ()
 +/* incomplete lines are bad! */
 
 commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +22,1 @@
+@@ -22,1 +22,1 @@ int main ()
 -}
 +}
 \ No newline at end of file
@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +20,1 @@
Signature · 1 line
gitgitgadget
#3 lore raw

PATCH 3/4 t4211: add tests for -L with standard diff options

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, verify that previously-ignored diff options work: formatting (--word-diff, --word-diff-regex, --no-prefix, --src/dst-prefix, --full-index, --abbrev), whitespace handling (-w, -b), output indicators (--output-indicator-new/old/context), direction reversal (-R), --color-moved, and pickaxe options (-S, -G).

---
 t/t4211-line-log.sh | 281 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 281 insertions(+)
t/t4211-line-log.sh +281 -0
index 01f7ec8c32..b7f68be490 100755
@@ -339,6 +339,92 @@ test_expect_success 'zero-width regex .* matches any function name' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup for diff pipeline tests' '
+	git checkout parent-oids &&
+
+	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
+	head_blob_new=$(git rev-parse --short HEAD:file.c) &&
+	root_blob=$(git rev-parse --short HEAD~4:file.c) &&
+	null_blob=$(test_oid zero | cut -c1-7) &&
+	head_blob_old_full=$(git rev-parse HEAD^:file.c) &&
+	head_blob_new_full=$(git rev-parse HEAD:file.c) &&
+	root_blob_full=$(git rev-parse HEAD~4:file.c) &&
+	null_blob_full=$(test_oid zero)
+'
+
+test_expect_success '-L diff output includes index and new file mode' '
+	git log -L:func2:file.c --format= >actual &&
+
+	# Output should contain index headers (not present in old code path)
+	grep "^index $head_blob_old\.\.$head_blob_new 100644" actual &&
+
+	# Root commit should show new file mode and null index
+	grep "^new file mode 100644" actual &&
+	grep "^index $null_blob\.\.$root_blob$" actual &&
+
+	# Hunk headers should include funcname context
+	grep "^@@ .* @@ int func1()" actual
+'
+
+test_expect_success '-L with --word-diff' '
+	cat >expect <<-\EOF &&
+
+	diff --git a/file.c b/file.c
+	--- a/file.c
+	+++ b/file.c
+	@@ -6,4 +6,4 @@ int func1()
+	int func2()
+	{
+	    return [-F2;-]{+F2 + 2;+}
+	}
+
+	diff --git a/file.c b/file.c
+	new file mode 100644
+	--- /dev/null
+	+++ b/file.c
+	@@ -0,0 +6,4 @@
+	{+int func2()+}
+	{+{+}
+	{+    return F2;+}
+	{+}+}
+	EOF
+	git log -L:func2:file.c --word-diff --format= >actual &&
+	grep -v "^index " actual >actual.filtered &&
+	grep -v "^index " expect >expect.filtered &&
+	test_cmp expect.filtered actual.filtered
+'
+
+test_expect_success '-L with --no-prefix' '
+	git log -L:func2:file.c --no-prefix --format= >actual &&
+	grep "^diff --git file.c file.c" actual &&
+	grep "^--- file.c" actual &&
+	! grep "^--- a/" actual
+'
+
+test_expect_success '-L with --full-index' '
+	git log -L:func2:file.c --full-index --format= >actual &&
+	grep "^index $head_blob_old_full\.\.$head_blob_new_full 100644" actual &&
+	grep "^index $null_blob_full\.\.$root_blob_full$" actual
+'
+
+test_expect_success 'setup -L with whitespace change' '
+	git checkout -b ws-change parent-oids &&
+	sed "s/    return F2 + 2;/	return F2 + 2;/" file.c >tmp &&
+	mv tmp file.c &&
+	git commit -a -m "Whitespace change in func2()"
+'
+
+test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' '
+	git log -L:func2:file.c --format= >without_w &&
+	git log -L:func2:file.c --format= -w >with_w &&
+
+	# Without -w: three commits produce diffs (whitespace, modify, root)
+	test $(grep -c "^diff --git" without_w) = 3 &&
+
+	# With -w: whitespace-only commit produces no hunk, so only two diffs
+	test $(grep -c "^diff --git" with_w) = 2
+'
+
 test_expect_success 'show line-log with graph' '
 	git checkout parent-oids &&
 	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
@@ -424,4 +510,199 @@ test_expect_failure '-L --find-object should filter commits by object' '
 	test_must_be_empty actual
 '
 
+test_expect_success '-L with --word-diff-regex' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --word-diff \
+		--word-diff-regex="[a-zA-Z0-9_]+" --format= >actual &&
+	# Word-diff markers must be present
+	grep "{+" actual &&
+	grep "+}" actual &&
+	# No line-level +/- markers (word-diff replaces them);
+	# exclude --- header lines from the check
+	! grep "^+[^+]" actual &&
+	! grep "^-[^-]" actual
+'
+
+test_expect_success '-L with --src-prefix and --dst-prefix' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \
+		--format= >actual &&
+	grep "^diff --git old/file.c new/file.c" actual &&
+	grep "^--- old/file.c" actual &&
+	grep "^+++ new/file.c" actual &&
+	! grep "^--- a/" actual
+'
+
+test_expect_success '-L with --abbrev' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --abbrev=4 --format= -1 >actual &&
+	# 4-char abbreviated hashes on index line
+	grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual
+'
+
+test_expect_success '-L with -b suppresses whitespace-only diff' '
+	git checkout ws-change &&
+	git log -L:func2:file.c --format= >without_b &&
+	git log -L:func2:file.c --format= -b >with_b &&
+	test $(grep -c "^diff --git" without_b) = 3 &&
+	test $(grep -c "^diff --git" with_b) = 2
+'
+
+test_expect_success '-L with --output-indicator-*' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --output-indicator-new=">" \
+		--output-indicator-old="<" --output-indicator-context="|" \
+		--format= -1 >actual &&
+	grep "^>" actual &&
+	grep "^<" actual &&
+	grep "^|" actual &&
+	# No standard +/-/space content markers; exclude ---/+++ headers
+	! grep "^+[^+]" actual &&
+	! grep "^-[^-]" actual &&
+	! grep "^ " actual
+'
+
+test_expect_success '-L with -R reverses diff' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -R --format= -1 >actual &&
+	grep "^diff --git b/file.c a/file.c" actual &&
+	grep "^--- b/file.c" actual &&
+	grep "^+++ a/file.c" actual &&
+	# The modification added "F2 + 2", so reversed it is removed
+	grep "^-.*F2 + 2" actual &&
+	grep "^+.*return F2;" actual
+'
+
+test_expect_success 'setup for color-moved test' '
+	git checkout -b color-moved-test parent-oids &&
+	cat >big.c <<-\EOF &&
+	int bigfunc()
+	{
+	    int a = 1;
+	    int b = 2;
+	    int c = 3;
+	    return a + b + c;
+	}
+	EOF
+	git add big.c &&
+	git commit -m "add bigfunc" &&
+	sed "s/    /	/" big.c >tmp && mv tmp big.c &&
+	git commit -a -m "reindent bigfunc"
+'
+
+test_expect_success '-L with --color-moved' '
+	git log -L:bigfunc:big.c --color-moved=zebra \
+		--color-moved-ws=ignore-all-space \
+		--color=always --format= -1 >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	# Old moved lines: bold magenta; new moved lines: bold cyan
+	grep "BOLD;MAGENTA" actual &&
+	grep "BOLD;CYAN" actual
+'
+
+test_expect_success 'setup for no-newline-at-eof tests' '
+	git checkout --orphan no-newline &&
+	git reset --hard &&
+	printf "int top()\n{\n    return 1;\n}\n\nint bot()\n{\n    return 2;\n}" >noeol.c &&
+	git add noeol.c &&
+	test_tick &&
+	git commit -m "add noeol.c (no trailing newline)" &&
+	sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c &&
+	git commit -a -m "modify bot()" &&
+	printf "int top()\n{\n    return 1;\n}\n\nint bot()\n{\n    return 33;\n}\n" >noeol.c &&
+	git commit -a -m "modify bot() and add trailing newline"
+'
+
+# When the tracked function is at the end of a file with no trailing
+# newline, the "\ No newline at end of file" marker should appear.
+test_expect_success '-L no-newline-at-eof appears in tracked range' '
+	git log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&
+	grep "No newline at end of file" actual
+'
+
+# When tracking a function that ends before the no-newline content,
+# the marker should not appear in the output.
+test_expect_success '-L no-newline-at-eof suppressed outside range' '
+	git log -L:top:noeol.c --format= >actual &&
+	! grep "No newline at end of file" actual
+'
+
+# When a commit removes a no-newline last line and replaces it with
+# a newline-terminated line, the marker should still appear (on the
+# old side of the diff).
+test_expect_success '-L no-newline-at-eof marker with deleted line' '
+	git log -L:bot:noeol.c --format= -1 >actual &&
+	grep "No newline at end of file" actual
+'
+
+test_expect_success 'setup for range boundary deletion test' '
+	git checkout --orphan range-boundary &&
+	git reset --hard &&
+	cat >boundary.c <<-\EOF &&
+	void above()
+	{
+	    return;
+	}
+
+	void tracked()
+	{
+	    int x = 1;
+	    int y = 2;
+	}
+
+	void below()
+	{
+	    return;
+	}
+	EOF
+	git add boundary.c &&
+	test_tick &&
+	git commit -m "add boundary.c" &&
+	cat >boundary.c <<-\EOF &&
+	void above()
+	{
+	    return;
+	}
+
+	void tracked()
+	{
+	    int x = 1;
+	    int y = 2;
+	}
+
+	void below_renamed()
+	{
+	    return 0;
+	}
+	EOF
+	git commit -a -m "modify below() only"
+'
+
+# When only a function below the tracked range is modified, the
+# tracked function should not produce a diff.
+test_expect_success '-L suppresses deletions outside tracked range' '
+	git log -L:tracked:boundary.c --format= >actual &&
+	test $(grep -c "^diff --git" actual) = 1
+'
+
+test_expect_success '-L with -S filters to string-count changes' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -S "F2 + 2" --format= >actual &&
+	# -S searches the whole file, not just the tracked range;
+	# combined with the -L range walk, this selects commits that
+	# both touch func2 and change the count of "F2 + 2" in the file.
+	test $(grep -c "^diff --git" actual) = 1 &&
+	grep "F2 + 2" actual
+'
+
+test_expect_success '-L with -G filters to diff-text matches' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
+	# -G greps the whole-file diff text, not just the tracked range;
+	# combined with -L, this selects commits that both touch func2
+	# and have "F2 + 2" in their diff.
+	test $(grep -c "^diff --git" actual) = 1 &&
+	grep "F2 + 2" actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#4 lore raw

PATCH 4/4 doc: note that -L supports patch formatting and pickaxe options

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, document that patch formatting options like --word-diff, --color-moved, --no-prefix, whitespace handling (-w, -b), and pickaxe options (-S, -G) are supported.

---
 Documentation/line-range-options.adoc | 4 ++++
 1 file changed, 4 insertions(+)
Documentation/line-range-options.adoc +4 -0
index c44ba05320..aa236281b3 100644
@@ -11,5 +11,9 @@
 	Patch output can be suppressed using `--no-patch`, but other diff formats
 	(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,
 	`--name-only`, `--name-status`, `--check`) are not currently implemented.
++
+	Patch formatting options such as `--word-diff`, `--color-moved`,
+	`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
+	as are pickaxe options (`-S`, `-G`).
 +
 include::line-range-format.adoc[]
Signature · 1 line
gitgitgadget
#5 lore raw

Re: [PATCH 4/4] doc: note that -L supports patch formatting and pickaxe options

Quoted 24 lines · Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, document that patch formatting options like --word-diff, --color-moved, --no-prefix, whitespace handling (-w, -b), and pickaxe options (-S, -G) are supported.

---
 Documentation/line-range-options.adoc | 4 ++++
 1 file changed, 4 insertions(+)
Documentation/line-range-options.adoc +0 -0
index c44ba05320..aa236281b3 100644
@@ -11,5 +11,9 @@
 	Patch output can be suppressed using `--no-patch`, but other diff

formats

Documentation/line-range-options.adoc +0 -0
 	(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,
 	`--name-only`, `--name-status`, `--check`) are not currently

implemented.
++

Documentation/line-range-options.adoc line 17

Quoted 3 lines
Documentation/line-range-options.adoc +3 -0
+	Patch formatting options such as `--word-diff`, `--color-moved`,
+	`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
+	as are pickaxe options (`-S`, `-G`).

You shouldn’t use indentation here. This is a code block now.

Just a line continuation and flush to the left.

Quoted 4 lines

+
include::line-range-format.adoc[]
--
gitgitgadget

#6 lore raw

Re: [PATCH 4/4] doc: note that -L supports patch formatting and pickaxe options

Quoted 32 lines · Kristoffer Haugsbakk
Quoted 24 lines · Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, document that patch formatting options like --word-diff, --color-moved, --no-prefix, whitespace handling (-w, -b), and pickaxe options (-S, -G) are supported.

---
 Documentation/line-range-options.adoc | 4 ++++
 1 file changed, 4 insertions(+)
Documentation/line-range-options.adoc +0 -0
index c44ba05320..aa236281b3 100644
@@ -11,5 +11,9 @@
      Patch output can be suppressed using `--no-patch`, but other diff

formats

      (namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,
      `--name-only`, `--name-status`, `--check`) are not currently

implemented.
++

Documentation/line-range-options.adoc line 17

Quoted 3 lines
Documentation/line-range-options.adoc +3 -0
+     Patch formatting options such as `--word-diff`, `--color-moved`,
+     `--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
+     as are pickaxe options (`-S`, `-G`).

You shouldn’t use indentation here. This is a code block now.

Quoted 2 lines

Just a line continuation and flush to the left.

Good catch, thank you. Will update in followup.

Quoted 4 lines
Quoted 4 lines

+
include::line-range-format.adoc[]
--
gitgitgadget

#7 lore raw
In reply to Michael Montalbo via GitGitGadget
Quoted 6 lines · Michael Montalbo via GitGitGadget

git log -L has bypassed the standard diff pipeline since its introduction, using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has acknowledged this from the start. This series removes dump_diff_hacky() and routes -L output through builtin_diff() / fn_out_consume(), so that diff formatting options like --word-diff, --color-moved, -w, and pickaxe options (-S, -G) work with -L.

Exciting.

Quoted 4 lines

User-visible output change: -L output now includes index lines, new file mode headers, and funcname context in @@ headers that were previously missing. Tools parsing -L output may need to handle these additional lines.

Quoted 8 lines

Known limitations not addressed in this series:

* line_log_print() still calls show_log() and diff_flush() directly, bypassing log_tree_diff_flush(). The early return in log_tree_commit() (and its associated NEEDSWORK about no_free not being restored) is pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a larger change that would affect separator and header logic; it is left for a follow-up.

OK. Previously all the output routines were hand-rolled, but this reduces the extent of deviation---as long as we are moving in the right direction, it is a good idea to find a good place to stop and leave the rest for later.

Quoted 2 lines

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

It would not hurt if these are omitted.

Quoted 4 lines

Michael Montalbo (4): line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline t4211: add tests for -L with standard diff options doc: note that -L supports patch formatting and pickaxe options

I am not sure what this bloc is, but it looks like a reflowed version of the list of commits below?

Quoted 5 lines

Michael Montalbo (4):
line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline
t4211: add tests for -L with standard diff options
doc: note that -L supports patch formatting and pickaxe options

Let me throw in a handful of names found in the output of "git shortlog --no-merges -s -n line-log.[ch]" on the Cc: line to solicit help.
.

#8 lore raw
Quoted 11 lines · Junio C Hamano
Quoted 6 lines · Michael Montalbo via GitGitGadget

git log -L has bypassed the standard diff pipeline since its introduction, using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has acknowledged this from the start. This series removes dump_diff_hacky() and routes -L output through builtin_diff() / fn_out_consume(), so that diff formatting options like --word-diff, --color-moved, -w, and pickaxe options (-S, -G) work with -L.

Exciting.

:)

Quoted 18 lines
Quoted 4 lines

User-visible output change: -L output now includes index lines, new file mode headers, and funcname context in @@ headers that were previously missing. Tools parsing -L output may need to handle these additional lines.

Quoted 8 lines

Known limitations not addressed in this series:

* line_log_print() still calls show_log() and diff_flush() directly, bypassing log_tree_diff_flush(). The early return in log_tree_commit() (and its associated NEEDSWORK about no_free not being restored) is pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a larger change that would affect separator and header logic; it is left for a follow-up.

OK. Previously all the output routines were hand-rolled, but this reduces the extent of deviation---as long as we are moving in the right direction, it is a good idea to find a good place to stop and leave the rest for later.

Quoted 5 lines
Quoted 2 lines

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

It would not hurt if these are omitted.

Makes sense. I can omit in a follow-up.

Quoted 8 lines
Quoted 4 lines

Michael Montalbo (4): line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline t4211: add tests for -L with standard diff options doc: note that -L supports patch formatting and pickaxe options

I am not sure what this bloc is, but it looks like a reflowed version of the list of commits below?

Yes, this was a mistake I made when crafting the cover letter.

Quoted 11 lines
Quoted 5 lines

Michael Montalbo (4):
line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline
t4211: add tests for -L with standard diff options
doc: note that -L supports patch formatting and pickaxe options

Let me throw in a handful of names found in the output of "git shortlog --no-merges -s -n line-log.[ch]" on the Cc: line to solicit help.
.

Great! Thank you for your help on the review!

#9 lore raw
Quoted 7 lines · Michael Montalbo
Quoted 5 lines
Quoted 2 lines

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

It would not hurt if these are omitted.

Makes sense. I can omit in a follow-up.

You are already omitting, no? I took "remain unimplemented" to mean exactly that. Mentioning that we are not adding support for them, like you did in the above sentence that I commented on, is a good thing to do, I think.

#10 lore raw
Quoted 9 lines · Junio C Hamano
Quoted 6 lines · Michael Montalbo
Quoted 5 lines
Quoted 2 lines

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

It would not hurt if these are omitted.

Quoted 6 lines
Quoted 1 line

Makes sense. I can omit in a follow-up.

You are already omitting, no? I took "remain unimplemented" to mean exactly that. Mentioning that we are not adding support for them, like you did in the above sentence that I commented on, is a good thing to do, I think.

Ah got it, I confused myself. I thought this meant updating docs/code to more explicitly disallow these options from interacting but this is already the case.

#11 lore raw

v2 cover line-log: route -L output through the standard diff pipeline

In reply to Michael Montalbo via GitGitGadget

git log -L has bypassed the standard diff pipeline since its introduction, using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has acknowledged this from the start. This series removes dump_diff_hacky() and routes -L output through builtin_diff() / fn_out_consume(), so that diff formatting options like --word-diff, --color-moved, -w, and pickaxe options (-S, -G) work with -L.

This replaces my earlier series "line-log: fix -L with pickaxe options" [1]. Patch 1 is the crash fix from that series (unchanged). Patch 2/2 from that series (rejecting -S/-G) is dropped because this series makes those options work instead of rejecting them.

[1]
https://lore.kernel.org/git/[email protected]/

Patch 1 fixes a crash when combining -L with pickaxe options and a rename.

Patch 2 is the core change: callback wrappers filter xdiff's output to tracked line ranges, and line ranges are carried on diff_filepair so each file's ranges travel with its filepair through the pipeline. diffcore_std() runs at output time, so pickaxe, --orderfile, and --diff-filter also work.

Patch 3 adds tests covering the newly-working options.

Patch 4 updates documentation.

User-visible output change: -L output now includes index lines, new file mode headers, and funcname context in @@ headers that were previously missing. Tools parsing -L output may need to handle these additional lines.

Known limitations not addressed in this series:

* line_log_print() still calls show_log() and diff_flush() directly, bypassing log_tree_diff_flush(). The early return in log_tree_commit() (and its associated NEEDSWORK about no_free not being restored) is pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a larger change that would affect separator and header logic; it is left for a follow-up.

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

cc "Kristoffer Haugsbakk" [email protected]

Changes since v1:

* Patch 4/4: fix documentation formatting: use line continuation instead of indentation (Kristoffer Haugsbakk)

Michael Montalbo (4):
line-log: fix crash when combined with pickaxe options
line-log: route -L output through the standard diff pipeline
t4211: add tests for -L with standard diff options
doc: note that -L supports patch formatting and pickaxe options

 Documentation/line-range-options.adoc         |   4 +
 diff.c                                        | 279 +++++++++++++-
 diffcore.h                                    |  16 +
 line-log.c                                    | 196 ++--------
 line-log.h                                    |  14 +-
 revision.c                                    |   2 +
 t/t4211-line-log.sh                           | 348 +++++++++++++++++-
 t/t4211/sha1/expect.beginning-of-file         |   4 +
 t/t4211/sha1/expect.end-of-file               |  11 +-
 t/t4211/sha1/expect.move-support-f            |   5 +
 t/t4211/sha1/expect.multiple                  |  10 +-
 t/t4211/sha1/expect.multiple-overlapping      |   7 +
 t/t4211/sha1/expect.multiple-superset         |   7 +
 t/t4211/sha1/expect.no-assertion-error        |  12 +-
 t/t4211/sha1/expect.parallel-change-f-to-main |   7 +
 t/t4211/sha1/expect.simple-f                  |   4 +
 t/t4211/sha1/expect.simple-f-to-main          |   5 +
 t/t4211/sha1/expect.simple-main               |  11 +-
 t/t4211/sha1/expect.simple-main-to-end        |  11 +-
 t/t4211/sha1/expect.two-ranges                |  10 +-
 t/t4211/sha1/expect.vanishes-early            |  10 +-
 t/t4211/sha256/expect.beginning-of-file       |   4 +
 t/t4211/sha256/expect.end-of-file             |  11 +-
 t/t4211/sha256/expect.move-support-f          |   5 +
 t/t4211/sha256/expect.multiple                |  10 +-
 t/t4211/sha256/expect.multiple-overlapping    |   7 +
 t/t4211/sha256/expect.multiple-superset       |   7 +
 t/t4211/sha256/expect.no-assertion-error      |  12 +-
 .../sha256/expect.parallel-change-f-to-main   |   7 +
 t/t4211/sha256/expect.simple-f                |   4 +
 t/t4211/sha256/expect.simple-f-to-main        |   5 +
 t/t4211/sha256/expect.simple-main             |  11 +-
 t/t4211/sha256/expect.simple-main-to-end      |  11 +-
 t/t4211/sha256/expect.two-ranges              |  10 +-
 t/t4211/sha256/expect.vanishes-early          |  10 +-
 35 files changed, 870 insertions(+), 217 deletions(-)

base-commit: 7b2bccb0d58d4f24705bf985de1f4612e4cf06e5
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2065%2Fmmontalbo%2Fspike-xdiff-line-range-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2065/mmontalbo/spike-xdiff-line-range-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2065

Range-diff vs v1:

1: e7b8cc2c78 ! 1: ccfc1b03ff line-log: fix crash when combined with pickaxe options
@@ t/t4211-line-log.sh: test_expect_success 'show line-log with graph' '
+ --find-object=$(git rev-parse HEAD:file) >actual
+'
+
++# Commit-level filtering with pickaxe does not yet work for -L. ++# show_log() prints the commit header before diffcore_std() runs ++# pickaxe, so commits cannot be suppressed even when no diff pairs ++# survive filtering. Fixing this would require deferring show_log() ++# until after diffcore_std(), which is a larger restructuring of the ++# log-tree output pipeline.
+test_expect_failure '-L -G should filter commits by pattern' '
+ git log --format="%s" --no-patch -L 1,1:file -G "nomatch" >actual &&
+ test_must_be_empty actual

 2:  6921b07a0f = 2:  4e2bc55082 line-log: route -L output through the standard diff pipeline
 3:  09e00d3f82 = 3:  cf7720ae98 t4211: add tests for -L with standard diff options
 4:  0d3e84a088 ! 4:  93e771b7cd doc: note that -L supports patch formatting and pickaxe options
     @@ Commit message
      
       ## Documentation/line-range-options.adoc ##
      @@
     - 	Patch output can be suppressed using `--no-patch`, but other diff formats
       	(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,
       	`--name-only`, `--name-status`, `--check`) are not currently implemented.
     -++
     -+	Patch formatting options such as `--word-diff`, `--color-moved`,
     -+	`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
     -+	as are pickaxe options (`-S`, `-G`).
       +
     ++Patch formatting options such as `--word-diff`, `--color-moved`,
     ++`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
     ++as are pickaxe options (`-S`, `-G`).
     +++
       include::line-range-format.adoc[]
Signature · 1 line
gitgitgadget
#12 lore raw

PATCH v2 1/4 line-log: fix crash when combined with pickaxe options

From: Michael Montalbo <[email protected]>

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.

---
 line-log.c          | 22 ++++++++++++++----
 t/t4211-line-log.sh | 55 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 73 insertions(+), 4 deletions(-)
line-log.c +18 -4
index eeaf68454e..9d12ece181 100644
@@ -858,15 +858,29 @@ static void queue_diffs(struct line_log_data *range,
 	diff_queue_clear(&diff_queued_diff);
 	diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
 	if (opt->detect_rename && diff_might_be_rename()) {
+		struct diff_options rename_opts;
+
+		/*
+		 * Build a private diff_options for rename detection so
+		 * that any user-specified options on the original opts
+		 * (e.g. pickaxe) cannot discard diff pairs needed for
+		 * rename tracking.  Similar to blame's find_rename().
+		 */
+		repo_diff_setup(opt->repo, &rename_opts);
+		rename_opts.flags.recursive = 1;
+		rename_opts.detect_rename = opt->detect_rename;
+		rename_opts.rename_score = opt->rename_score;
+		rename_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
+		diff_setup_done(&rename_opts);
+
 		/* must look at the full tree diff to detect renames */
-		clear_pathspec(&opt->pathspec);
 		diff_queue_clear(&diff_queued_diff);
-
-		diff_tree_oid(parent_tree_oid, tree_oid, "", opt);
+		diff_tree_oid(parent_tree_oid, tree_oid, "", &rename_opts);
 
 		filter_diffs_for_paths(range, 1);
-		diffcore_std(opt);
+		diffcore_std(&rename_opts);
 		filter_diffs_for_paths(range, 0);
+		diff_free(&rename_opts);
 	}
 	move_diff_queue(queue, &diff_queued_diff);
 }
t/t4211-line-log.sh +55 -0

59 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#13 lore raw

PATCH v2 2/4 line-log: route -L output through the standard diff pipeline

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

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.

---
 diff.c                                        | 279 +++++++++++++++++-
 diffcore.h                                    |  16 +
 line-log.c                                    | 174 ++---------
 line-log.h                                    |  14 +-
 revision.c                                    |   2 +
 t/t4211-line-log.sh                           |  12 +-
 t/t4211/sha1/expect.beginning-of-file         |   4 +
 t/t4211/sha1/expect.end-of-file               |  11 +-
 t/t4211/sha1/expect.move-support-f            |   5 +
 t/t4211/sha1/expect.multiple                  |  10 +-
 t/t4211/sha1/expect.multiple-overlapping      |   7 +
 t/t4211/sha1/expect.multiple-superset         |   7 +
 t/t4211/sha1/expect.no-assertion-error        |  12 +-
 t/t4211/sha1/expect.parallel-change-f-to-main |   7 +
 t/t4211/sha1/expect.simple-f                  |   4 +
 t/t4211/sha1/expect.simple-f-to-main          |   5 +
 t/t4211/sha1/expect.simple-main               |  11 +-
 t/t4211/sha1/expect.simple-main-to-end        |  11 +-
 t/t4211/sha1/expect.two-ranges                |  10 +-
 t/t4211/sha1/expect.vanishes-early            |  10 +-
 t/t4211/sha256/expect.beginning-of-file       |   4 +
 t/t4211/sha256/expect.end-of-file             |  11 +-
 t/t4211/sha256/expect.move-support-f          |   5 +
 t/t4211/sha256/expect.multiple                |  10 +-
 t/t4211/sha256/expect.multiple-overlapping    |   7 +
 t/t4211/sha256/expect.multiple-superset       |   7 +
 t/t4211/sha256/expect.no-assertion-error      |  12 +-
 .../sha256/expect.parallel-change-f-to-main   |   7 +
 t/t4211/sha256/expect.simple-f                |   4 +
 t/t4211/sha256/expect.simple-f-to-main        |   5 +
 t/t4211/sha256/expect.simple-main             |  11 +-
 t/t4211/sha256/expect.simple-main-to-end      |  11 +-
 t/t4211/sha256/expect.two-ranges              |  10 +-
 t/t4211/sha256/expect.vanishes-early          |  10 +-
 34 files changed, 512 insertions(+), 213 deletions(-)
diff.c +277 -2
index 35b903a9a0..c8cbbbb1db 100644
@@ -608,6 +608,52 @@ struct emit_callback {
 	struct strbuf *header;
 };
 
+/*
+ * State for the line-range callback wrappers that sit between
+ * xdi_diff_outf() and fn_out_consume().  xdiff produces a normal,
+ * unfiltered diff; the wrappers intercept each hunk header and line,
+ * track post-image position, and forward only lines that fall within
+ * the requested ranges.  Contiguous in-range lines are collected into
+ * range hunks and flushed with a synthetic @@ header so that
+ * fn_out_consume() sees well-formed unified-diff fragments.
+ *
+ * Removal lines ('-') cannot be classified by post-image position, so
+ * they are buffered in pending_rm until the next '+' or ' ' line
+ * reveals whether they precede an in-range line (flush into range hunk) or
+ * an out-of-range line (discard).
+ */
+struct line_range_callback {
+	xdiff_emit_line_fn orig_line_fn;
+	void *orig_cb_data;
+	const struct range_set *ranges;	/* 0-based [start, end) */
+	unsigned int cur_range;		/* index into the range_set */
+
+	/* Post/pre-image line counters (1-based, set from hunk headers) */
+	long lno_post;
+	long lno_pre;
+
+	/*
+	 * Function name from most recent xdiff hunk header;
+	 * size matches struct func_line.buf in xdiff/xemit.c.
+	 */
+	char func[80];
+	long funclen;
+
+	/* Range hunk being accumulated for the current range */
+	struct strbuf rhunk;
+	long rhunk_old_begin, rhunk_old_count;
+	long rhunk_new_begin, rhunk_new_count;
+	int rhunk_active;
+	int rhunk_has_changes;		/* any '+' or '-' lines? */
+
+	/* Removal lines not yet known to be in-range */
+	struct strbuf pending_rm;
+	int pending_rm_count;
+	long pending_rm_pre_begin;	/* pre-image line of first pending */
+
+	int ret;			/* latched error from orig_line_fn */
+};
+
 static int count_lines(const char *data, int size)
 {
 	int count, ch, completely_empty = 1, nl_just_seen = 0;
@@ -2486,6 +2532,188 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED
 	return 1;
 }
 
+static void discard_pending_rm(struct line_range_callback *s)
+{
+	strbuf_reset(&s->pending_rm);
+	s->pending_rm_count = 0;
+}
+
+static void flush_rhunk(struct line_range_callback *s)
+{
+	struct strbuf hdr = STRBUF_INIT;
+	const char *p, *end;
+
+	if (!s->rhunk_active || s->ret)
+		return;
+
+	/* Drain any pending removal lines into the range hunk */
+	if (s->pending_rm_count) {
+		strbuf_addbuf(&s->rhunk, &s->pending_rm);
+		s->rhunk_old_count += s->pending_rm_count;
+		s->rhunk_has_changes = 1;
+		discard_pending_rm(s);
+	}
+
+	/*
+	 * Suppress context-only hunks: they contain no actual changes
+	 * and would just be noise.  This can happen when the inflated
+	 * ctxlen causes xdiff to emit context covering a range that
+	 * has no changes in this commit.
+	 */
+	if (!s->rhunk_has_changes) {
+		s->rhunk_active = 0;
+		strbuf_reset(&s->rhunk);
+		return;
+	}
+
+	strbuf_addf(&hdr, "@@ -%ld,%ld +%ld,%ld @@",
+		    s->rhunk_old_begin, s->rhunk_old_count,
+		    s->rhunk_new_begin, s->rhunk_new_count);
+	if (s->funclen > 0) {
+		strbuf_addch(&hdr, ' ');
+		strbuf_add(&hdr, s->func, s->funclen);
+	}
+	strbuf_addch(&hdr, '\n');
+
+	s->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);
+	strbuf_release(&hdr);
+
+	/*
+	 * Replay buffered lines one at a time through fn_out_consume.
+	 * The cast discards const because xdiff_emit_line_fn takes
+	 * char *, though fn_out_consume does not modify the buffer.
+	 */
+	p = s->rhunk.buf;
+	end = p + s->rhunk.len;
+	while (!s->ret && p < end) {
+		const char *eol = memchr(p, '\n', end - p);
+		unsigned long line_len = eol ? (unsigned long)(eol - p + 1)
+					     : (unsigned long)(end - p);
+		s->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);
+		p += line_len;
+	}
+
+	s->rhunk_active = 0;
+	strbuf_reset(&s->rhunk);
+}
+
+static void line_range_hunk_fn(void *data,
+			       long old_begin, long old_nr UNUSED,
+			       long new_begin, long new_nr UNUSED,
+			       const char *func, long funclen)
+{
+	struct line_range_callback *s = data;
+
+	/*
+	 * When count > 0, begin is 1-based.  When count == 0, begin is
+	 * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of
+	 * that type will arrive, so the value is unused.
+	 *
+	 * Any pending removal lines from the previous xdiff hunk are
+	 * intentionally left in pending_rm: the line callback will
+	 * flush or discard them when the next content line reveals
+	 * whether the removals precede in-range content.
+	 */
+	s->lno_post = new_begin;
+	s->lno_pre = old_begin;
+
+	if (funclen > 0) {
+		if (funclen > (long)sizeof(s->func))
+			funclen = sizeof(s->func);
+		memcpy(s->func, func, funclen);
+	}
+	s->funclen = funclen;
+}
+
+static int line_range_line_fn(void *priv, char *line, unsigned long len)
+{
+	struct line_range_callback *s = priv;
+	const struct range *cur;
+	long lno_0, cur_pre;
+
+	if (s->ret)
+		return s->ret;
+
+	if (line[0] == '-') {
+		if (!s->pending_rm_count)
+			s->pending_rm_pre_begin = s->lno_pre;
+		s->lno_pre++;
+		strbuf_add(&s->pending_rm, line, len);
+		s->pending_rm_count++;
+		return s->ret;
+	}
+
+	if (line[0] == '\\') {
+		if (s->pending_rm_count)
+			strbuf_add(&s->pending_rm, line, len);
+		else if (s->rhunk_active)
+			strbuf_add(&s->rhunk, line, len);
+		/* otherwise outside tracked range; drop silently */
+		return s->ret;
+	}
+
+	if (line[0] != '+' && line[0] != ' ')
+		BUG("unexpected diff line type '%c'", line[0]);
+
+	lno_0 = s->lno_post - 1;
+	cur_pre = s->lno_pre;	/* save before advancing for context lines */
+	s->lno_post++;
+	if (line[0] == ' ')
+		s->lno_pre++;
+
+	/* Advance past ranges we've passed */
+	while (s->cur_range < s->ranges->nr &&
+	       lno_0 >= s->ranges->ranges[s->cur_range].end) {
+		if (s->rhunk_active)
+			flush_rhunk(s);
+		discard_pending_rm(s);
+		s->cur_range++;
+	}
+
+	/* Past all ranges */
+	if (s->cur_range >= s->ranges->nr) {
+		discard_pending_rm(s);
+		return s->ret;
+	}
+
+	cur = &s->ranges->ranges[s->cur_range];
+
+	/* Before current range */
+	if (lno_0 < cur->start) {
+		discard_pending_rm(s);
+		return s->ret;
+	}
+
+	/* In range so start a new range hunk if needed */
+	if (!s->rhunk_active) {
+		s->rhunk_active = 1;
+		s->rhunk_has_changes = 0;
+		s->rhunk_new_begin = lno_0 + 1;
+		s->rhunk_old_begin = s->pending_rm_count
+			? s->pending_rm_pre_begin : cur_pre;
+		s->rhunk_old_count = 0;
+		s->rhunk_new_count = 0;
+		strbuf_reset(&s->rhunk);
+	}
+
+	/* Flush pending removals into range hunk */
+	if (s->pending_rm_count) {
+		strbuf_addbuf(&s->rhunk, &s->pending_rm);
+		s->rhunk_old_count += s->pending_rm_count;
+		s->rhunk_has_changes = 1;
+		discard_pending_rm(s);
+	}
+
+	strbuf_add(&s->rhunk, line, len);
+	s->rhunk_new_count++;
+	if (line[0] == '+')
+		s->rhunk_has_changes = 1;
+	else
+		s->rhunk_old_count++;
+
+	return s->ret;
+}
+
 static void pprint_rename(struct strbuf *name, const char *a, const char *b)
 {
 	const char *old_name = a;
@@ -3589,7 +3817,8 @@ static void builtin_diff(const char *name_a,
 			 const char *xfrm_msg,
 			 int must_show_header,
 			 struct diff_options *o,
-			 int complete_rewrite)
+			 int complete_rewrite,
+			 const struct range_set *line_ranges)
 {
 	mmfile_t mf1, mf2;
 	const char *lbl[2];
@@ -3823,6 +4052,52 @@ static void builtin_diff(const char *name_a,
 			 */
 			xdi_diff_outf(&mf1, &mf2, NULL, quick_consume,
 				      &ecbdata, &xpp, &xecfg);
+		} else if (line_ranges) {
+			struct line_range_callback lr_state;
+			unsigned int i;
+			long max_span = 0;
+
+			memset(&lr_state, 0, sizeof(lr_state));
+			lr_state.orig_line_fn = fn_out_consume;
+			lr_state.orig_cb_data = &ecbdata;
+			lr_state.ranges = line_ranges;
+			strbuf_init(&lr_state.rhunk, 0);
+			strbuf_init(&lr_state.pending_rm, 0);
+
+			/*
+			 * Inflate ctxlen so that all changes within
+			 * any single range are merged into one xdiff
+			 * hunk and the inter-change context is emitted.
+			 * The callback clips back to range boundaries.
+			 *
+			 * The optimal ctxlen depends on where changes
+			 * fall within the range, which is only known
+			 * after xdiff runs; the max range span is the
+			 * upper bound that guarantees correctness in a
+			 * single pass.
+			 */
+			for (i = 0; i < line_ranges->nr; i++) {
+				long span = line_ranges->ranges[i].end -
+					    line_ranges->ranges[i].start;
+				if (span > max_span)
+					max_span = span;
+			}
+			if (max_span > xecfg.ctxlen)
+				xecfg.ctxlen = max_span;
+
+			if (xdi_diff_outf(&mf1, &mf2,
+					  line_range_hunk_fn,
+					  line_range_line_fn,
+					  &lr_state, &xpp, &xecfg))
+				die("unable to generate diff for %s",
+				    one->path);
+
+			flush_rhunk(&lr_state);
+			if (lr_state.ret)
+				die("unable to generate diff for %s",
+				    one->path);
+			strbuf_release(&lr_state.rhunk);
+			strbuf_release(&lr_state.pending_rm);
 		} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,
 					 &ecbdata, &xpp, &xecfg))
 			die("unable to generate diff for %s", one->path);
@@ -4660,7 +4935,7 @@ static void run_diff_cmd(const struct external_diff *pgm,
 
 		builtin_diff(name, other ? other : name,
 			     one, two, xfrm_msg, must_show_header,
-			     o, complete_rewrite);
+			     o, complete_rewrite, p->line_ranges);
 		if (p->status == DIFF_STATUS_COPIED ||
 		    p->status == DIFF_STATUS_RENAMED)
 			o->found_changes = 1;
diffcore.h +16 -0
index 9c0a0e7aaf..d75038d1b3 100644
@@ -19,6 +19,17 @@ struct userdiff_driver;
  * in anything else.
  */
 
+/* A range [start, end).  Lines are numbered starting at 0. */
+struct range {
+	long start, end;
+};
+
+/* A set of ranges.  The ranges must always be disjoint and sorted. */
+struct range_set {
+	unsigned int alloc, nr;
+	struct range *ranges;
+};
+
 /* We internally use unsigned short as the score value,
  * and rely on an int capable to hold 32-bits.  -B can take
  * -Bmerge_score/break_score format and the two scores are
@@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
 struct diff_filepair {
 	struct diff_filespec *one;
 	struct diff_filespec *two;
+	/*
+	 * Tracked line ranges for -L filtering; borrowed from
+	 * line_log_data and must not be freed.
+	 */
+	const struct range_set *line_ranges;
 	unsigned short int score;
 	char status; /* M C R A D U etc. (see Documentation/diff-format.adoc or DIFF_STATUS_* in diff.h) */
 	unsigned broken_pair : 1;
line-log.c +17 -157

195 lines not on this page · raw message

line-log.h +2 -12
index e9dadbc1a5..04a6ea64d3 100644
@@ -1,22 +1,12 @@
 #ifndef LINE_LOG_H
 #define LINE_LOG_H
 
+#include "diffcore.h" /* struct range, struct range_set */
+
 struct rev_info;
 struct commit;
 struct string_list;
 
-/* A range [start,end].  Lines are numbered starting at 0, and the
- * ranges include start but exclude end. */
-struct range {
-	long start, end;
-};
-
-/* A set of ranges.  The ranges must always be disjoint and sorted. */
-struct range_set {
-	unsigned int alloc, nr;
-	struct range *ranges;
-};
-
 /* A diff, encoded as the set of pre- and post-image ranges where the
  * files differ. A pair of ranges corresponds to a hunk. */
 struct diff_ranges {
revision.c +2 -0
index 047ff7e458..9f68bedf13 100644
@@ -3111,6 +3111,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
 		if (want_ancestry(revs))
 			revs->limited = 1;
 		revs->topo_order = 1;
+		if (!revs->diffopt.output_format)
+			revs->diffopt.output_format = DIFF_FORMAT_PATCH;
 	}
 
 	if (revs->topo_order && !generation_numbers_enabled(the_repository))
t/t4211-line-log.sh +10 -2
index 659a943aa1..6a307e911b 100755
@@ -129,7 +129,7 @@ test_expect_success '-L with --output' '
 	git checkout parallel-change &&
 	git log --output=log -L :main:b.c >output &&
 	test_must_be_empty output &&
-	test_line_count = 70 log
+	test_line_count = 75 log
 '
 
 test_expect_success 'range_set_union' '
@@ -340,13 +340,19 @@ test_expect_success 'zero-width regex .* matches any function name' '
 '
 
 test_expect_success 'show line-log with graph' '
+	git checkout parent-oids &&
+	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
+	head_blob_new=$(git rev-parse --short HEAD:file.c) &&
+	root_blob=$(git rev-parse --short HEAD~4:file.c) &&
+	null_blob=$(test_oid zero | cut -c1-7) &&
 	qz_to_tab_space >expect <<-EOF &&
 	* $head_oid Modify func2() in file.c
 	|Z
 	| diff --git a/file.c b/file.c
+	| index $head_blob_old..$head_blob_new 100644
 	| --- a/file.c
 	| +++ b/file.c
-	| @@ -6,4 +6,4 @@
+	| @@ -6,4 +6,4 @@ int func1()
 	|  int func2()
 	|  {
 	| -    return F2;
@@ -355,6 +361,8 @@ test_expect_success 'show line-log with graph' '
 	* $root_oid Add func1() and func2() in file.c
 	ZZ
 	  diff --git a/file.c b/file.c
+	  new file mode 100644
+	  index $null_blob..$root_blob
 	  --- /dev/null
 	  +++ b/file.c
 	  @@ -0,0 +6,4 @@
t/t4211/sha1/expect.beginning-of-file +4 -0
index 91b4054898..52c90afb3a 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100
     change at very beginning
 
 diff --git a/a.c b/a.c
+index bdb2bb1..5e709a1 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,4 @@
@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,3 @@
@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +1,3 @@
t/t4211/sha1/expect.end-of-file +8 -3
index bd25bb2f59..c40036899a 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,5 @@
+@@ -20,3 +20,5 @@ long f(long x)
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,3 @@
+@@ -20,3 +20,3 @@ int main ()
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -19,3 +19,3 @@
+@@ -19,3 +19,3 @@ int f(int x)
 -	printf("%d\n", f(15));
 +	printf("%ld\n", f(15));
  	return 0;
@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +18,3 @@
t/t4211/sha1/expect.move-support-f +5 -0
index c905e01bc2..ead6500d4d 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 5de3ea4..bf79c2f 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,9 +4,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.multiple +8 -2
index 1eee8a7801..a41851a51d 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.multiple-overlapping +7 -0
index d930b6eec4..0ec9990eab 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha1/expect.multiple-superset +7 -0
index d930b6eec4..0ec9990eab 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha1/expect.no-assertion-error +9 -3
index 994c37db1e..54c568f273 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100
     move within the file
 
 diff --git a/b.c b/b.c
+index bf79c2f..27c829c 100644
 --- a/b.c
 +++ b/b.c
 @@ -25,0 +18,9 @@
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.parallel-change-f-to-main +7 -0
index 052def8074..65a8cc673a 100644
@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 5de3ea4..bf79c2f 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,14 +4,14 @@
@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200
     change on another line of history while rename happens
 
 diff --git a/a.c b/a.c
+index 5de3ea4..01b5b65 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,14 +4,14 @@
@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha1/expect.simple-f +4 -0
index a1f5bc49c8..b24ae40e03 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.simple-f-to-main +5 -0
index a475768710..cd92100dfc 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index e51de13..bdb2bb1 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha1/expect.simple-main +8 -3
index 39ce39bebe..ff31291d34 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.simple-main-to-end +8 -3
index 8480bd9cc4..4bef21e657 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha1/expect.two-ranges +8 -2
index c5164f3be3..aed01522e3 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 3233403..e51de13 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 444e415..3233403 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha1/expect.vanishes-early +6 -4
index 1f7cd06941..a413ad3659 100644
@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index 0b9cae5..5de3ea4 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +24,1 @@
--}
-\ No newline at end of file
+@@ -23,0 +24,1 @@ int main ()
 +/* incomplete lines are bad! */
 
 commit 100b61a6f2f720f812620a9d10afb3a960ccb73c
@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index 5e709a1..0b9cae5 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +22,1 @@
+@@ -22,1 +22,1 @@ int main ()
 -}
 +}
 \ No newline at end of file
@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..444e415
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +20,1 @@
t/t4211/sha256/expect.beginning-of-file +4 -0
index 5adfdfc1a1..e8d62328cf 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100
     change at very beginning
 
 diff --git a/a.c b/a.c
+index 3a78aaf..d325124 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,4 @@
@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -1,3 +1,3 @@
@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +1,3 @@
t/t4211/sha256/expect.end-of-file +8 -3
index 03ab5c1784..3b2e2384da 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,5 @@
+@@ -20,3 +20,5 @@ long f(long x)
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -20,3 +20,3 @@
+@@ -20,3 +20,3 @@ int main ()
  	printf("%ld\n", f(15));
  	return 0;
 -}
@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -19,3 +19,3 @@
+@@ -19,3 +19,3 @@ int f(int x)
 -	printf("%d\n", f(15));
 +	printf("%ld\n", f(15));
  	return 0;
@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +18,3 @@
t/t4211/sha256/expect.move-support-f +5 -0
index 223b4ed2a0..f49abcea3e 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 62c1fc2..69cb69c 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,9 +4,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.multiple +8 -2
index dbd987b74a..0dee50ffb7 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.multiple-overlapping +7 -0
index 9015a45a25..b8c260e8ae 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha256/expect.multiple-superset +7 -0
index 9015a45a25..b8c260e8ae 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,21 @@
@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,19 +4,19 @@
@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,19 +3,19 @@
@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,18 +3,19 @@
@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,18 @@
t/t4211/sha256/expect.no-assertion-error +9 -3
index 36ed12aa9c..c25f2ce19c 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100
     move within the file
 
 diff --git a/b.c b/b.c
+index 69cb69c..a0d566e 100644
 --- a/b.c
 +++ b/b.c
 @@ -25,0 +18,9 @@
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.parallel-change-f-to-main +7 -0
index e68f8928ea..3178989253 100644
@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100
     another simple change
 
 diff --git a/b.c b/b.c
+index 62c1fc2..69cb69c 100644
 --- a/b.c
 +++ b/b.c
 @@ -4,14 +4,14 @@
@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200
     change on another line of history while rename happens
 
 diff --git a/a.c b/a.c
+index 62c1fc2..e1e8475 100644
 --- a/a.c
 +++ b/a.c
 @@ -4,14 +4,14 @@
@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha256/expect.simple-f +4 -0
index 65508d7c0b..983c711fe3 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.simple-f-to-main +5 -0
index 77b721c196..e67fa017a7 100644
@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100
     touch comment
 
 diff --git a/a.c b/a.c
+index 75c0119..3a78aaf 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,14 +3,14 @@
@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,13 +3,14 @@
@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,13 @@
t/t4211/sha256/expect.simple-main +8 -3
index d20708c9f9..0792b27cad 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.simple-main-to-end +8 -3
index 617cdf3481..d3bd7c7bc6 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,7 @@
+@@ -18,5 +18,7 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
-@@ -17,5 +17,5 @@
+@@ -17,5 +17,5 @@ int f(int x)
  int main ()
  {
 -	printf("%d\n", f(15));
@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +16,5 @@
t/t4211/sha256/expect.two-ranges +8 -2
index 6a94d3b9cb..7735b19723 100644
@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -18,5 +18,5 @@
+@@ -18,5 +18,5 @@ long f(long x)
  int main ()
  {
  	printf("%ld\n", f(15));
@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100
     touch both functions
 
 diff --git a/a.c b/a.c
+index 7a296b9..75c0119 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,9 +3,9 @@
@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100
     change f()
 
 diff --git a/a.c b/a.c
+index 9f550c3..7a296b9 100644
 --- a/a.c
 +++ b/a.c
 @@ -3,8 +3,9 @@
@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +3,8 @@
t/t4211/sha256/expect.vanishes-early +6 -4
index 11ec9bdecf..bc33b963dc 100644
@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100
     change back to complete line
 
 diff --git a/a.c b/a.c
+index e4fa1d8..62c1fc2 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +24,1 @@
--}
-\ No newline at end of file
+@@ -23,0 +24,1 @@ int main ()
 +/* incomplete lines are bad! */
 
 commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10
@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100
     change to an incomplete line at end
 
 diff --git a/a.c b/a.c
+index d325124..e4fa1d8 100644
 --- a/a.c
 +++ b/a.c
-@@ -22,1 +22,1 @@
+@@ -22,1 +22,1 @@ int main ()
 -}
 +}
 \ No newline at end of file
@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100
     initial
 
 diff --git a/a.c b/a.c
+new file mode 100644
+index 0000000..9f550c3
 --- /dev/null
 +++ b/a.c
 @@ -0,0 +20,1 @@
Signature · 1 line
gitgitgadget
#14 lore raw
Quoted 73 lines · Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

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.

---
 diff.c                                        | 279 +++++++++++++++++-
 diffcore.h                                    |  16 +
 line-log.c                                    | 174 ++---------
 line-log.h                                    |  14 +-
 revision.c                                    |   2 +
 t/t4211-line-log.sh                           |  12 +-
 t/t4211/sha1/expect.beginning-of-file         |   4 +
 t/t4211/sha1/expect.end-of-file               |  11 +-
 t/t4211/sha1/expect.move-support-f            |   5 +
 t/t4211/sha1/expect.multiple                  |  10 +-
 t/t4211/sha1/expect.multiple-overlapping      |   7 +
 t/t4211/sha1/expect.multiple-superset         |   7 +
 t/t4211/sha1/expect.no-assertion-error        |  12 +-
 t/t4211/sha1/expect.parallel-change-f-to-main |   7 +
 t/t4211/sha1/expect.simple-f                  |   4 +
 t/t4211/sha1/expect.simple-f-to-main          |   5 +
 t/t4211/sha1/expect.simple-main               |  11 +-
 t/t4211/sha1/expect.simple-main-to-end        |  11 +-
 t/t4211/sha1/expect.two-ranges                |  10 +-
 t/t4211/sha1/expect.vanishes-early            |  10 +-
 t/t4211/sha256/expect.beginning-of-file       |   4 +
 t/t4211/sha256/expect.end-of-file             |  11 +-
 t/t4211/sha256/expect.move-support-f          |   5 +
 t/t4211/sha256/expect.multiple                |  10 +-
 t/t4211/sha256/expect.multiple-overlapping    |   7 +
 t/t4211/sha256/expect.multiple-superset       |   7 +
 t/t4211/sha256/expect.no-assertion-error      |  12 +-
Quoted 8 lines
 .../sha256/expect.parallel-change-f-to-main   |   7 +
 t/t4211/sha256/expect.simple-f                |   4 +
 t/t4211/sha256/expect.simple-f-to-main        |   5 +
 t/t4211/sha256/expect.simple-main             |  11 +-
 t/t4211/sha256/expect.simple-main-to-end      |  11 +-
 t/t4211/sha256/expect.two-ranges              |  10 +-
 t/t4211/sha256/expect.vanishes-early          |  10 +-
 34 files changed, 512 insertions(+), 213 deletions(-)

Huge diff to the test material mostly comes from the addition of the diff headers like the index line, etc., which makes this patch scary but is very welcome addition.

diffcore.h int diff_filespec_is_binary(struct repository *, struct diff_filespec *); · line 124

Quoted 9 lines
diffcore.h +5 -0
@@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);
 struct diff_filepair {
 	struct diff_filespec *one;
 	struct diff_filespec *two;
+	/*
+	 * Tracked line ranges for -L filtering; borrowed from
+	 * line_log_data and must not be freed.
+	 */
+	const struct range_set *line_ranges;

OK.

Quoted 20 lines
line-log.c +6 -2
index 9d12ece181..858a899cd2 100644
@@ -885,160 +885,6 @@ static void queue_diffs(struct line_log_data *range,
 	move_diff_queue(queue, &diff_queued_diff);
 }
 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) {

line-log.c static void queue_diffs(struct line_log_data *range, · line 1027

Quoted 10 lines
line-log.c +10 -0
+			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);

Very welcome change.

#15 lore raw

PATCH v2 3/4 t4211: add tests for -L with standard diff options

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, verify that previously-ignored diff options work: formatting (--word-diff, --word-diff-regex, --no-prefix, --src/dst-prefix, --full-index, --abbrev), whitespace handling (-w, -b), output indicators (--output-indicator-new/old/context), direction reversal (-R), --color-moved, and pickaxe options (-S, -G).

---
 t/t4211-line-log.sh | 281 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 281 insertions(+)
t/t4211-line-log.sh +281 -0
index 6a307e911b..aaf197d2ed 100755
@@ -339,6 +339,92 @@ test_expect_success 'zero-width regex .* matches any function name' '
 	test_cmp expect actual
 '
 
+test_expect_success 'setup for diff pipeline tests' '
+	git checkout parent-oids &&
+
+	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
+	head_blob_new=$(git rev-parse --short HEAD:file.c) &&
+	root_blob=$(git rev-parse --short HEAD~4:file.c) &&
+	null_blob=$(test_oid zero | cut -c1-7) &&
+	head_blob_old_full=$(git rev-parse HEAD^:file.c) &&
+	head_blob_new_full=$(git rev-parse HEAD:file.c) &&
+	root_blob_full=$(git rev-parse HEAD~4:file.c) &&
+	null_blob_full=$(test_oid zero)
+'
+
+test_expect_success '-L diff output includes index and new file mode' '
+	git log -L:func2:file.c --format= >actual &&
+
+	# Output should contain index headers (not present in old code path)
+	grep "^index $head_blob_old\.\.$head_blob_new 100644" actual &&
+
+	# Root commit should show new file mode and null index
+	grep "^new file mode 100644" actual &&
+	grep "^index $null_blob\.\.$root_blob$" actual &&
+
+	# Hunk headers should include funcname context
+	grep "^@@ .* @@ int func1()" actual
+'
+
+test_expect_success '-L with --word-diff' '
+	cat >expect <<-\EOF &&
+
+	diff --git a/file.c b/file.c
+	--- a/file.c
+	+++ b/file.c
+	@@ -6,4 +6,4 @@ int func1()
+	int func2()
+	{
+	    return [-F2;-]{+F2 + 2;+}
+	}
+
+	diff --git a/file.c b/file.c
+	new file mode 100644
+	--- /dev/null
+	+++ b/file.c
+	@@ -0,0 +6,4 @@
+	{+int func2()+}
+	{+{+}
+	{+    return F2;+}
+	{+}+}
+	EOF
+	git log -L:func2:file.c --word-diff --format= >actual &&
+	grep -v "^index " actual >actual.filtered &&
+	grep -v "^index " expect >expect.filtered &&
+	test_cmp expect.filtered actual.filtered
+'
+
+test_expect_success '-L with --no-prefix' '
+	git log -L:func2:file.c --no-prefix --format= >actual &&
+	grep "^diff --git file.c file.c" actual &&
+	grep "^--- file.c" actual &&
+	! grep "^--- a/" actual
+'
+
+test_expect_success '-L with --full-index' '
+	git log -L:func2:file.c --full-index --format= >actual &&
+	grep "^index $head_blob_old_full\.\.$head_blob_new_full 100644" actual &&
+	grep "^index $null_blob_full\.\.$root_blob_full$" actual
+'
+
+test_expect_success 'setup -L with whitespace change' '
+	git checkout -b ws-change parent-oids &&
+	sed "s/    return F2 + 2;/	return F2 + 2;/" file.c >tmp &&
+	mv tmp file.c &&
+	git commit -a -m "Whitespace change in func2()"
+'
+
+test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' '
+	git log -L:func2:file.c --format= >without_w &&
+	git log -L:func2:file.c --format= -w >with_w &&
+
+	# Without -w: three commits produce diffs (whitespace, modify, root)
+	test $(grep -c "^diff --git" without_w) = 3 &&
+
+	# With -w: whitespace-only commit produces no hunk, so only two diffs
+	test $(grep -c "^diff --git" with_w) = 2
+'
+
 test_expect_success 'show line-log with graph' '
 	git checkout parent-oids &&
 	head_blob_old=$(git rev-parse --short HEAD^:file.c) &&
@@ -430,4 +516,199 @@ test_expect_failure '-L --find-object should filter commits by object' '
 	test_must_be_empty actual
 '
 
+test_expect_success '-L with --word-diff-regex' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --word-diff \
+		--word-diff-regex="[a-zA-Z0-9_]+" --format= >actual &&
+	# Word-diff markers must be present
+	grep "{+" actual &&
+	grep "+}" actual &&
+	# No line-level +/- markers (word-diff replaces them);
+	# exclude --- header lines from the check
+	! grep "^+[^+]" actual &&
+	! grep "^-[^-]" actual
+'
+
+test_expect_success '-L with --src-prefix and --dst-prefix' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \
+		--format= >actual &&
+	grep "^diff --git old/file.c new/file.c" actual &&
+	grep "^--- old/file.c" actual &&
+	grep "^+++ new/file.c" actual &&
+	! grep "^--- a/" actual
+'
+
+test_expect_success '-L with --abbrev' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --abbrev=4 --format= -1 >actual &&
+	# 4-char abbreviated hashes on index line
+	grep "^index [0-9a-f]\{4\}\.\.[0-9a-f]\{4\}" actual
+'
+
+test_expect_success '-L with -b suppresses whitespace-only diff' '
+	git checkout ws-change &&
+	git log -L:func2:file.c --format= >without_b &&
+	git log -L:func2:file.c --format= -b >with_b &&
+	test $(grep -c "^diff --git" without_b) = 3 &&
+	test $(grep -c "^diff --git" with_b) = 2
+'
+
+test_expect_success '-L with --output-indicator-*' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c --output-indicator-new=">" \
+		--output-indicator-old="<" --output-indicator-context="|" \
+		--format= -1 >actual &&
+	grep "^>" actual &&
+	grep "^<" actual &&
+	grep "^|" actual &&
+	# No standard +/-/space content markers; exclude ---/+++ headers
+	! grep "^+[^+]" actual &&
+	! grep "^-[^-]" actual &&
+	! grep "^ " actual
+'
+
+test_expect_success '-L with -R reverses diff' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -R --format= -1 >actual &&
+	grep "^diff --git b/file.c a/file.c" actual &&
+	grep "^--- b/file.c" actual &&
+	grep "^+++ a/file.c" actual &&
+	# The modification added "F2 + 2", so reversed it is removed
+	grep "^-.*F2 + 2" actual &&
+	grep "^+.*return F2;" actual
+'
+
+test_expect_success 'setup for color-moved test' '
+	git checkout -b color-moved-test parent-oids &&
+	cat >big.c <<-\EOF &&
+	int bigfunc()
+	{
+	    int a = 1;
+	    int b = 2;
+	    int c = 3;
+	    return a + b + c;
+	}
+	EOF
+	git add big.c &&
+	git commit -m "add bigfunc" &&
+	sed "s/    /	/" big.c >tmp && mv tmp big.c &&
+	git commit -a -m "reindent bigfunc"
+'
+
+test_expect_success '-L with --color-moved' '
+	git log -L:bigfunc:big.c --color-moved=zebra \
+		--color-moved-ws=ignore-all-space \
+		--color=always --format= -1 >actual.raw &&
+	test_decode_color <actual.raw >actual &&
+	# Old moved lines: bold magenta; new moved lines: bold cyan
+	grep "BOLD;MAGENTA" actual &&
+	grep "BOLD;CYAN" actual
+'
+
+test_expect_success 'setup for no-newline-at-eof tests' '
+	git checkout --orphan no-newline &&
+	git reset --hard &&
+	printf "int top()\n{\n    return 1;\n}\n\nint bot()\n{\n    return 2;\n}" >noeol.c &&
+	git add noeol.c &&
+	test_tick &&
+	git commit -m "add noeol.c (no trailing newline)" &&
+	sed "s/return 2/return 22/" noeol.c >tmp && mv tmp noeol.c &&
+	git commit -a -m "modify bot()" &&
+	printf "int top()\n{\n    return 1;\n}\n\nint bot()\n{\n    return 33;\n}\n" >noeol.c &&
+	git commit -a -m "modify bot() and add trailing newline"
+'
+
+# When the tracked function is at the end of a file with no trailing
+# newline, the "\ No newline at end of file" marker should appear.
+test_expect_success '-L no-newline-at-eof appears in tracked range' '
+	git log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&
+	grep "No newline at end of file" actual
+'
+
+# When tracking a function that ends before the no-newline content,
+# the marker should not appear in the output.
+test_expect_success '-L no-newline-at-eof suppressed outside range' '
+	git log -L:top:noeol.c --format= >actual &&
+	! grep "No newline at end of file" actual
+'
+
+# When a commit removes a no-newline last line and replaces it with
+# a newline-terminated line, the marker should still appear (on the
+# old side of the diff).
+test_expect_success '-L no-newline-at-eof marker with deleted line' '
+	git log -L:bot:noeol.c --format= -1 >actual &&
+	grep "No newline at end of file" actual
+'
+
+test_expect_success 'setup for range boundary deletion test' '
+	git checkout --orphan range-boundary &&
+	git reset --hard &&
+	cat >boundary.c <<-\EOF &&
+	void above()
+	{
+	    return;
+	}
+
+	void tracked()
+	{
+	    int x = 1;
+	    int y = 2;
+	}
+
+	void below()
+	{
+	    return;
+	}
+	EOF
+	git add boundary.c &&
+	test_tick &&
+	git commit -m "add boundary.c" &&
+	cat >boundary.c <<-\EOF &&
+	void above()
+	{
+	    return;
+	}
+
+	void tracked()
+	{
+	    int x = 1;
+	    int y = 2;
+	}
+
+	void below_renamed()
+	{
+	    return 0;
+	}
+	EOF
+	git commit -a -m "modify below() only"
+'
+
+# When only a function below the tracked range is modified, the
+# tracked function should not produce a diff.
+test_expect_success '-L suppresses deletions outside tracked range' '
+	git log -L:tracked:boundary.c --format= >actual &&
+	test $(grep -c "^diff --git" actual) = 1
+'
+
+test_expect_success '-L with -S filters to string-count changes' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -S "F2 + 2" --format= >actual &&
+	# -S searches the whole file, not just the tracked range;
+	# combined with the -L range walk, this selects commits that
+	# both touch func2 and change the count of "F2 + 2" in the file.
+	test $(grep -c "^diff --git" actual) = 1 &&
+	grep "F2 + 2" actual
+'
+
+test_expect_success '-L with -G filters to diff-text matches' '
+	git checkout parent-oids &&
+	git log -L:func2:file.c -G "F2 [+] 2" --format= >actual &&
+	# -G greps the whole-file diff text, not just the tracked range;
+	# combined with -L, this selects commits that both touch func2
+	# and have "F2 + 2" in their diff.
+	test $(grep -c "^diff --git" actual) = 1 &&
+	grep "F2 + 2" actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#16 lore raw

PATCH v2 4/4 doc: note that -L supports patch formatting and pickaxe options

In reply to Michael Montalbo via GitGitGadget

From: Michael Montalbo <[email protected]>

Now that -L output flows through the standard diff pipeline, document that patch formatting options like --word-diff, --color-moved, --no-prefix, whitespace handling (-w, -b), and pickaxe options (-S, -G) are supported.

---
 Documentation/line-range-options.adoc | 4 ++++
 1 file changed, 4 insertions(+)
Documentation/line-range-options.adoc +4 -0
index c44ba05320..ecb2c79fb9 100644
@@ -12,4 +12,8 @@
 	(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,
 	`--name-only`, `--name-status`, `--check`) are not currently implemented.
 +
+Patch formatting options such as `--word-diff`, `--color-moved`,
+`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,
+as are pickaxe options (`-S`, `-G`).
++
 include::line-range-format.adoc[]
Signature · 1 line
gitgitgadget
#17 lore raw
In reply to Michael Montalbo via GitGitGadget
Quoted 12 lines · Michael Montalbo via GitGitGadget

Known limitations not addressed in this series:

* line_log_print() still calls show_log() and diff_flush() directly, bypassing log_tree_diff_flush(). The early return in log_tree_commit() (and its associated NEEDSWORK about no_free not being restored) is pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a larger change that would affect separator and header logic; it is left for a follow-up.

* Non-patch diff formats (--raw, --numstat, --stat, etc.) remain unimplemented for -L.

Quoted 6 lines
cc "Kristoffer Haugsbakk" [email protected]

Changes since v1:

* Patch 4/4: fix documentation formatting: use line continuation instead of indentation (Kristoffer Haugsbakk)

The central part of the series (i.e., patch #2) looked quite sensible. I haven't read the tests very carefully, though.

I was hoping that we will see another set of eyes or two to help review this series, but nothing has happend in the past few weeks, so let's mark the topic for 'next'.

Thanks.