{"thread":{"id":"65159","subject":"[PATCH 0/4] line-log: route -L output through the standard diff pipeline","startedAt":"2026-03-07T01:02:21Z","lastAt":"2026-03-31T21:49:37Z","messageCount":18,"participants":["Michael Montalbo via GitGitGadget","Junio C Hamano","Michael Montalbo","Kristoffer Haugsbakk"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"538146","messageId":"pull.2065.git.1772845338.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":null,"subject":"[PATCH 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-07T01:02:14Z","receivedAt":"2026-03-07T01:02:21Z","isPatch":true,"body":"git log -L has bypassed the standard diff pipeline since its introduction,\nusing dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has\nacknowledged this from the start. This series removes dump_diff_hacky() and\nroutes -L output through builtin_diff() / fn_out_consume(), so that diff\nformatting options like --word-diff, --color-moved, -w, and pickaxe options\n(-S, -G) work with -L.\n\nThis replaces my earlier series \"line-log: fix -L with pickaxe options\" [1].\nPatch 1 is the crash fix from that series (unchanged). Patch 2/2 from that\nseries (rejecting -S/-G) is dropped because this series makes those options\nwork instead of rejecting them.\n\n[1]\nhttps://lore.kernel.org/git/pull.2061.git.1772651484.gitgitgadget@gmail.com/\n\nPatch 1 fixes a crash when combining -L with pickaxe options and a rename.\n\nPatch 2 is the core change: callback wrappers filter xdiff's output to\ntracked line ranges, and line ranges are carried on diff_filepair so each\nfile's ranges travel with its filepair through the pipeline. diffcore_std()\nruns at output time, so pickaxe, --orderfile, and --diff-filter also work.\n\nPatch 3 adds tests covering the newly-working options.\n\nPatch 4 updates documentation.\n\nUser-visible output change: -L output now includes index lines, new file\nmode headers, and funcname context in @@ headers that were previously\nmissing. Tools parsing -L output may need to handle these additional lines.\n\nKnown limitations not addressed in this series:\n\n * line_log_print() still calls show_log() and diff_flush() directly,\n   bypassing log_tree_diff_flush(). The early return in log_tree_commit()\n   (and its associated NEEDSWORK about no_free not being restored) is\n   pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a\n   larger change that would affect separator and header logic; it is left\n   for a follow-up.\n\n * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n   unimplemented for -L.\n\nMichael Montalbo (4): line-log: fix crash when combined with pickaxe options\nline-log: route -L output through the standard diff pipeline t4211: add\ntests for -L with standard diff options doc: note that -L supports patch\nformatting and pickaxe options\n\nMichael Montalbo (4):\n  line-log: fix crash when combined with pickaxe options\n  line-log: route -L output through the standard diff pipeline\n  t4211: add tests for -L with standard diff options\n  doc: note that -L supports patch formatting and pickaxe options\n\n Documentation/line-range-options.adoc         |   4 +\n diff.c                                        | 279 +++++++++++++-\n diffcore.h                                    |  16 +\n line-log.c                                    | 196 ++--------\n line-log.h                                    |  14 +-\n revision.c                                    |   2 +\n t/t4211-line-log.sh                           | 342 +++++++++++++++++-\n t/t4211/sha1/expect.beginning-of-file         |   4 +\n t/t4211/sha1/expect.end-of-file               |  11 +-\n t/t4211/sha1/expect.move-support-f            |   5 +\n t/t4211/sha1/expect.multiple                  |  10 +-\n t/t4211/sha1/expect.multiple-overlapping      |   7 +\n t/t4211/sha1/expect.multiple-superset         |   7 +\n t/t4211/sha1/expect.no-assertion-error        |  12 +-\n t/t4211/sha1/expect.parallel-change-f-to-main |   7 +\n t/t4211/sha1/expect.simple-f                  |   4 +\n t/t4211/sha1/expect.simple-f-to-main          |   5 +\n t/t4211/sha1/expect.simple-main               |  11 +-\n t/t4211/sha1/expect.simple-main-to-end        |  11 +-\n t/t4211/sha1/expect.two-ranges                |  10 +-\n t/t4211/sha1/expect.vanishes-early            |  10 +-\n t/t4211/sha256/expect.beginning-of-file       |   4 +\n t/t4211/sha256/expect.end-of-file             |  11 +-\n t/t4211/sha256/expect.move-support-f          |   5 +\n t/t4211/sha256/expect.multiple                |  10 +-\n t/t4211/sha256/expect.multiple-overlapping    |   7 +\n t/t4211/sha256/expect.multiple-superset       |   7 +\n t/t4211/sha256/expect.no-assertion-error      |  12 +-\n .../sha256/expect.parallel-change-f-to-main   |   7 +\n t/t4211/sha256/expect.simple-f                |   4 +\n t/t4211/sha256/expect.simple-f-to-main        |   5 +\n t/t4211/sha256/expect.simple-main             |  11 +-\n t/t4211/sha256/expect.simple-main-to-end      |  11 +-\n t/t4211/sha256/expect.two-ranges              |  10 +-\n t/t4211/sha256/expect.vanishes-early          |  10 +-\n 35 files changed, 864 insertions(+), 217 deletions(-)\n\n\nbase-commit: 7b2bccb0d58d4f24705bf985de1f4612e4cf06e5\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2065%2Fmmontalbo%2Fspike-xdiff-line-range-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2065/mmontalbo/spike-xdiff-line-range-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2065\n-- \ngitgitgadget\n"},{"id":"538147","messageId":"e7b8cc2c78b0e67420a46dae8fd444dfe925a6ec.1772845338.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"[PATCH 1/4] line-log: fix crash when combined with pickaxe options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-07T01:02:15Z","receivedAt":"2026-03-07T01:02:24Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nqueue_diffs() passes the caller's diff_options, which may carry\nuser-specified pickaxe state, to diff_tree_oid() and diffcore_std()\nwhen detecting renames for line-level history tracking.  When pickaxe\noptions are present on the command line (-G and -S to filter by text\npattern, --find-object to filter by object identity), diffcore_std()\nalso runs diffcore_pickaxe(), which may discard diff pairs that are\nrelevant for rename detection.  Losing those pairs breaks rename\nfollowing.\n\nBefore a2bb801f6a (line-log: avoid unnecessary full tree diffs,\n2019-08-21), this silently truncated history at rename boundaries.\nThat commit moved filter_diffs_for_paths() inside the rename-\ndetection block, so it only runs when diff_might_be_rename() returns\ntrue.  When pickaxe discards a rename pair, the rename goes\nundetected, and a deletion pair at a subsequent commit passes\nthrough uncleaned, reaching process_diff_filepair() with an invalid\nfilespec and triggering an assertion failure.\n\nFix this by building a private diff_options for the rename-detection\npath inside queue_diffs(), following the same pattern used by blame's\nfind_rename().  This isolates the rename machinery from unrelated\nuser-specified options.\n\nReported-by: Matthew Hughes <matthewhughes934@gmail.com>\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n line-log.c          | 22 ++++++++++++++++----\n t/t4211-line-log.sh | 49 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 67 insertions(+), 4 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex eeaf68454e..9d12ece181 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -858,15 +858,29 @@ static void queue_diffs(struct line_log_data *range,\n \tdiff_queue_clear(&diff_queued_diff);\n \tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", opt);\n \tif (opt->detect_rename && diff_might_be_rename()) {\n+\t\tstruct diff_options rename_opts;\n+\n+\t\t/*\n+\t\t * Build a private diff_options for rename detection so\n+\t\t * that any user-specified options on the original opts\n+\t\t * (e.g. pickaxe) cannot discard diff pairs needed for\n+\t\t * rename tracking.  Similar to blame's find_rename().\n+\t\t */\n+\t\trepo_diff_setup(opt->repo, &rename_opts);\n+\t\trename_opts.flags.recursive = 1;\n+\t\trename_opts.detect_rename = opt->detect_rename;\n+\t\trename_opts.rename_score = opt->rename_score;\n+\t\trename_opts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\t\tdiff_setup_done(&rename_opts);\n+\n \t\t/* must look at the full tree diff to detect renames */\n-\t\tclear_pathspec(&opt->pathspec);\n \t\tdiff_queue_clear(&diff_queued_diff);\n-\n-\t\tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", opt);\n+\t\tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", &rename_opts);\n \n \t\tfilter_diffs_for_paths(range, 1);\n-\t\tdiffcore_std(opt);\n+\t\tdiffcore_std(&rename_opts);\n \t\tfilter_diffs_for_paths(range, 0);\n+\t\tdiff_free(&rename_opts);\n \t}\n \tmove_diff_queue(queue, &diff_queued_diff);\n }\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 0a7c3ca42f..7acc38f72d 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -367,4 +367,53 @@ test_expect_success 'show line-log with graph' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup for -L with -G/-S/--find-object and a merge with rename' '\n+\tgit checkout --orphan pickaxe-rename &&\n+\tgit reset --hard &&\n+\n+\techo content >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b pickaxe-rename-side &&\n+\tgit mv file renamed-file &&\n+\tgit commit -m \"rename file\" &&\n+\n+\tgit checkout pickaxe-rename &&\n+\tgit commit --allow-empty -m \"diverge\" &&\n+\tgit merge --no-edit pickaxe-rename-side &&\n+\n+\tgit mv renamed-file file &&\n+\tgit commit -m \"rename back\"\n+'\n+\n+test_expect_success '-L -G does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -G \".\" >actual\n+'\n+\n+test_expect_success '-L -S does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -S content >actual\n+'\n+\n+test_expect_success '-L --find-object does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file \\\n+\t\t--find-object=$(git rev-parse HEAD:file) >actual\n+'\n+\n+test_expect_failure '-L -G should filter commits by pattern' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -G \"nomatch\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_failure '-L -S should filter commits by pattern' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -S \"nomatch\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_failure '-L --find-object should filter commits by object' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file \\\n+\t\t--find-object=$ZERO_OID >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"538149","messageId":"6921b07a0f8848b67eafa9dd29838456cb1b8d28.1772845338.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"[PATCH 2/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-07T01:02:16Z","receivedAt":"2026-03-07T01:02:26Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\n`git log -L` has always bypassed the standard diff pipeline.\n`dump_diff_hacky()` in line-log.c hand-rolls its own diff headers and\nhunk output, which means most diff formatting options are silently\nignored.  A NEEDSWORK comment has acknowledged this since the feature\nwas introduced:\n\n    /*\n     * NEEDSWORK: manually building a diff here is not the Right\n     * Thing(tm).  log -L should be built into the diff pipeline.\n     */\n\nRemove `dump_diff_hacky()` and its helpers and route -L output through\n`builtin_diff()` / `fn_out_consume()`, the same path used by `git diff`\nand `git log -p`.  The mechanism is a pair of callback wrappers that sit\nbetween `xdi_diff_outf()` and `fn_out_consume()`, filtering xdiff's\noutput to only the tracked line ranges.  To ensure xdiff emits all lines\nwithin each range as context, the context length is inflated to span the\nlargest range.\n\nWire up the `-L` implies `--patch` default in revision setup rather\nthan forcing it at output time, so `line_log_print()` is just\n`diffcore_std()` + `diff_flush()` with no format save/restore.\nRename detection is a no-op since pairs are already resolved during\nthe history walk in `queue_diffs()`, but running `diffcore_std()`\nmeans `-S`/`-G` (pickaxe), `--orderfile`, and `--diff-filter` now\nwork with `-L`, and `diff_resolve_rename_copy()` sets pair statuses\ncorrectly without manual assignment.\n\nSwitch `diff_filepair_dup()` from `xmalloc` to `xcalloc` so that new\nfields (including `line_ranges`) are zero-initialized by default.\n\nAs a result, diff formatting options that were previously silently\nignored (e.g. --word-diff, --no-prefix, -w, --color-moved) now work\nwith -L, and output gains `index` lines, `new file mode` headers, and\nfuncname context in `@@` headers.  This is a user-visible output change:\ntools that parse -L output may need to handle the additional header\nlines.\n\nThe context-length inflation means xdiff may process more output than\nneeded for very wide line ranges, but benchmarks on files up to 7800\nlines show no measurable regression.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                                        | 279 +++++++++++++++++-\n diffcore.h                                    |  16 +\n line-log.c                                    | 174 ++---------\n line-log.h                                    |  14 +-\n revision.c                                    |   2 +\n t/t4211-line-log.sh                           |  12 +-\n t/t4211/sha1/expect.beginning-of-file         |   4 +\n t/t4211/sha1/expect.end-of-file               |  11 +-\n t/t4211/sha1/expect.move-support-f            |   5 +\n t/t4211/sha1/expect.multiple                  |  10 +-\n t/t4211/sha1/expect.multiple-overlapping      |   7 +\n t/t4211/sha1/expect.multiple-superset         |   7 +\n t/t4211/sha1/expect.no-assertion-error        |  12 +-\n t/t4211/sha1/expect.parallel-change-f-to-main |   7 +\n t/t4211/sha1/expect.simple-f                  |   4 +\n t/t4211/sha1/expect.simple-f-to-main          |   5 +\n t/t4211/sha1/expect.simple-main               |  11 +-\n t/t4211/sha1/expect.simple-main-to-end        |  11 +-\n t/t4211/sha1/expect.two-ranges                |  10 +-\n t/t4211/sha1/expect.vanishes-early            |  10 +-\n t/t4211/sha256/expect.beginning-of-file       |   4 +\n t/t4211/sha256/expect.end-of-file             |  11 +-\n t/t4211/sha256/expect.move-support-f          |   5 +\n t/t4211/sha256/expect.multiple                |  10 +-\n t/t4211/sha256/expect.multiple-overlapping    |   7 +\n t/t4211/sha256/expect.multiple-superset       |   7 +\n t/t4211/sha256/expect.no-assertion-error      |  12 +-\n .../sha256/expect.parallel-change-f-to-main   |   7 +\n t/t4211/sha256/expect.simple-f                |   4 +\n t/t4211/sha256/expect.simple-f-to-main        |   5 +\n t/t4211/sha256/expect.simple-main             |  11 +-\n t/t4211/sha256/expect.simple-main-to-end      |  11 +-\n t/t4211/sha256/expect.two-ranges              |  10 +-\n t/t4211/sha256/expect.vanishes-early          |  10 +-\n 34 files changed, 512 insertions(+), 213 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 35b903a9a0..c8cbbbb1db 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -608,6 +608,52 @@ struct emit_callback {\n \tstruct strbuf *header;\n };\n \n+/*\n+ * State for the line-range callback wrappers that sit between\n+ * xdi_diff_outf() and fn_out_consume().  xdiff produces a normal,\n+ * unfiltered diff; the wrappers intercept each hunk header and line,\n+ * track post-image position, and forward only lines that fall within\n+ * the requested ranges.  Contiguous in-range lines are collected into\n+ * range hunks and flushed with a synthetic @@ header so that\n+ * fn_out_consume() sees well-formed unified-diff fragments.\n+ *\n+ * Removal lines ('-') cannot be classified by post-image position, so\n+ * they are buffered in pending_rm until the next '+' or ' ' line\n+ * reveals whether they precede an in-range line (flush into range hunk) or\n+ * an out-of-range line (discard).\n+ */\n+struct line_range_callback {\n+\txdiff_emit_line_fn orig_line_fn;\n+\tvoid *orig_cb_data;\n+\tconst struct range_set *ranges;\t/* 0-based [start, end) */\n+\tunsigned int cur_range;\t\t/* index into the range_set */\n+\n+\t/* Post/pre-image line counters (1-based, set from hunk headers) */\n+\tlong lno_post;\n+\tlong lno_pre;\n+\n+\t/*\n+\t * Function name from most recent xdiff hunk header;\n+\t * size matches struct func_line.buf in xdiff/xemit.c.\n+\t */\n+\tchar func[80];\n+\tlong funclen;\n+\n+\t/* Range hunk being accumulated for the current range */\n+\tstruct strbuf rhunk;\n+\tlong rhunk_old_begin, rhunk_old_count;\n+\tlong rhunk_new_begin, rhunk_new_count;\n+\tint rhunk_active;\n+\tint rhunk_has_changes;\t\t/* any '+' or '-' lines? */\n+\n+\t/* Removal lines not yet known to be in-range */\n+\tstruct strbuf pending_rm;\n+\tint pending_rm_count;\n+\tlong pending_rm_pre_begin;\t/* pre-image line of first pending */\n+\n+\tint ret;\t\t\t/* latched error from orig_line_fn */\n+};\n+\n static int count_lines(const char *data, int size)\n {\n \tint count, ch, completely_empty = 1, nl_just_seen = 0;\n@@ -2486,6 +2532,188 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED\n \treturn 1;\n }\n \n+static void discard_pending_rm(struct line_range_callback *s)\n+{\n+\tstrbuf_reset(&s->pending_rm);\n+\ts->pending_rm_count = 0;\n+}\n+\n+static void flush_rhunk(struct line_range_callback *s)\n+{\n+\tstruct strbuf hdr = STRBUF_INIT;\n+\tconst char *p, *end;\n+\n+\tif (!s->rhunk_active || s->ret)\n+\t\treturn;\n+\n+\t/* Drain any pending removal lines into the range hunk */\n+\tif (s->pending_rm_count) {\n+\t\tstrbuf_addbuf(&s->rhunk, &s->pending_rm);\n+\t\ts->rhunk_old_count += s->pending_rm_count;\n+\t\ts->rhunk_has_changes = 1;\n+\t\tdiscard_pending_rm(s);\n+\t}\n+\n+\t/*\n+\t * Suppress context-only hunks: they contain no actual changes\n+\t * and would just be noise.  This can happen when the inflated\n+\t * ctxlen causes xdiff to emit context covering a range that\n+\t * has no changes in this commit.\n+\t */\n+\tif (!s->rhunk_has_changes) {\n+\t\ts->rhunk_active = 0;\n+\t\tstrbuf_reset(&s->rhunk);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_addf(&hdr, \"@@ -%ld,%ld +%ld,%ld @@\",\n+\t\t    s->rhunk_old_begin, s->rhunk_old_count,\n+\t\t    s->rhunk_new_begin, s->rhunk_new_count);\n+\tif (s->funclen > 0) {\n+\t\tstrbuf_addch(&hdr, ' ');\n+\t\tstrbuf_add(&hdr, s->func, s->funclen);\n+\t}\n+\tstrbuf_addch(&hdr, '\\n');\n+\n+\ts->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);\n+\tstrbuf_release(&hdr);\n+\n+\t/*\n+\t * Replay buffered lines one at a time through fn_out_consume.\n+\t * The cast discards const because xdiff_emit_line_fn takes\n+\t * char *, though fn_out_consume does not modify the buffer.\n+\t */\n+\tp = s->rhunk.buf;\n+\tend = p + s->rhunk.len;\n+\twhile (!s->ret && p < end) {\n+\t\tconst char *eol = memchr(p, '\\n', end - p);\n+\t\tunsigned long line_len = eol ? (unsigned long)(eol - p + 1)\n+\t\t\t\t\t     : (unsigned long)(end - p);\n+\t\ts->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);\n+\t\tp += line_len;\n+\t}\n+\n+\ts->rhunk_active = 0;\n+\tstrbuf_reset(&s->rhunk);\n+}\n+\n+static void line_range_hunk_fn(void *data,\n+\t\t\t       long old_begin, long old_nr UNUSED,\n+\t\t\t       long new_begin, long new_nr UNUSED,\n+\t\t\t       const char *func, long funclen)\n+{\n+\tstruct line_range_callback *s = data;\n+\n+\t/*\n+\t * When count > 0, begin is 1-based.  When count == 0, begin is\n+\t * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of\n+\t * that type will arrive, so the value is unused.\n+\t *\n+\t * Any pending removal lines from the previous xdiff hunk are\n+\t * intentionally left in pending_rm: the line callback will\n+\t * flush or discard them when the next content line reveals\n+\t * whether the removals precede in-range content.\n+\t */\n+\ts->lno_post = new_begin;\n+\ts->lno_pre = old_begin;\n+\n+\tif (funclen > 0) {\n+\t\tif (funclen > (long)sizeof(s->func))\n+\t\t\tfunclen = sizeof(s->func);\n+\t\tmemcpy(s->func, func, funclen);\n+\t}\n+\ts->funclen = funclen;\n+}\n+\n+static int line_range_line_fn(void *priv, char *line, unsigned long len)\n+{\n+\tstruct line_range_callback *s = priv;\n+\tconst struct range *cur;\n+\tlong lno_0, cur_pre;\n+\n+\tif (s->ret)\n+\t\treturn s->ret;\n+\n+\tif (line[0] == '-') {\n+\t\tif (!s->pending_rm_count)\n+\t\t\ts->pending_rm_pre_begin = s->lno_pre;\n+\t\ts->lno_pre++;\n+\t\tstrbuf_add(&s->pending_rm, line, len);\n+\t\ts->pending_rm_count++;\n+\t\treturn s->ret;\n+\t}\n+\n+\tif (line[0] == '\\\\') {\n+\t\tif (s->pending_rm_count)\n+\t\t\tstrbuf_add(&s->pending_rm, line, len);\n+\t\telse if (s->rhunk_active)\n+\t\t\tstrbuf_add(&s->rhunk, line, len);\n+\t\t/* otherwise outside tracked range; drop silently */\n+\t\treturn s->ret;\n+\t}\n+\n+\tif (line[0] != '+' && line[0] != ' ')\n+\t\tBUG(\"unexpected diff line type '%c'\", line[0]);\n+\n+\tlno_0 = s->lno_post - 1;\n+\tcur_pre = s->lno_pre;\t/* save before advancing for context lines */\n+\ts->lno_post++;\n+\tif (line[0] == ' ')\n+\t\ts->lno_pre++;\n+\n+\t/* Advance past ranges we've passed */\n+\twhile (s->cur_range < s->ranges->nr &&\n+\t       lno_0 >= s->ranges->ranges[s->cur_range].end) {\n+\t\tif (s->rhunk_active)\n+\t\t\tflush_rhunk(s);\n+\t\tdiscard_pending_rm(s);\n+\t\ts->cur_range++;\n+\t}\n+\n+\t/* Past all ranges */\n+\tif (s->cur_range >= s->ranges->nr) {\n+\t\tdiscard_pending_rm(s);\n+\t\treturn s->ret;\n+\t}\n+\n+\tcur = &s->ranges->ranges[s->cur_range];\n+\n+\t/* Before current range */\n+\tif (lno_0 < cur->start) {\n+\t\tdiscard_pending_rm(s);\n+\t\treturn s->ret;\n+\t}\n+\n+\t/* In range so start a new range hunk if needed */\n+\tif (!s->rhunk_active) {\n+\t\ts->rhunk_active = 1;\n+\t\ts->rhunk_has_changes = 0;\n+\t\ts->rhunk_new_begin = lno_0 + 1;\n+\t\ts->rhunk_old_begin = s->pending_rm_count\n+\t\t\t? s->pending_rm_pre_begin : cur_pre;\n+\t\ts->rhunk_old_count = 0;\n+\t\ts->rhunk_new_count = 0;\n+\t\tstrbuf_reset(&s->rhunk);\n+\t}\n+\n+\t/* Flush pending removals into range hunk */\n+\tif (s->pending_rm_count) {\n+\t\tstrbuf_addbuf(&s->rhunk, &s->pending_rm);\n+\t\ts->rhunk_old_count += s->pending_rm_count;\n+\t\ts->rhunk_has_changes = 1;\n+\t\tdiscard_pending_rm(s);\n+\t}\n+\n+\tstrbuf_add(&s->rhunk, line, len);\n+\ts->rhunk_new_count++;\n+\tif (line[0] == '+')\n+\t\ts->rhunk_has_changes = 1;\n+\telse\n+\t\ts->rhunk_old_count++;\n+\n+\treturn s->ret;\n+}\n+\n static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n {\n \tconst char *old_name = a;\n@@ -3589,7 +3817,8 @@ static void builtin_diff(const char *name_a,\n \t\t\t const char *xfrm_msg,\n \t\t\t int must_show_header,\n \t\t\t struct diff_options *o,\n-\t\t\t int complete_rewrite)\n+\t\t\t int complete_rewrite,\n+\t\t\t const struct range_set *line_ranges)\n {\n \tmmfile_t mf1, mf2;\n \tconst char *lbl[2];\n@@ -3823,6 +4052,52 @@ static void builtin_diff(const char *name_a,\n \t\t\t */\n \t\t\txdi_diff_outf(&mf1, &mf2, NULL, quick_consume,\n \t\t\t\t      &ecbdata, &xpp, &xecfg);\n+\t\t} else if (line_ranges) {\n+\t\t\tstruct line_range_callback lr_state;\n+\t\t\tunsigned int i;\n+\t\t\tlong max_span = 0;\n+\n+\t\t\tmemset(&lr_state, 0, sizeof(lr_state));\n+\t\t\tlr_state.orig_line_fn = fn_out_consume;\n+\t\t\tlr_state.orig_cb_data = &ecbdata;\n+\t\t\tlr_state.ranges = line_ranges;\n+\t\t\tstrbuf_init(&lr_state.rhunk, 0);\n+\t\t\tstrbuf_init(&lr_state.pending_rm, 0);\n+\n+\t\t\t/*\n+\t\t\t * Inflate ctxlen so that all changes within\n+\t\t\t * any single range are merged into one xdiff\n+\t\t\t * hunk and the inter-change context is emitted.\n+\t\t\t * The callback clips back to range boundaries.\n+\t\t\t *\n+\t\t\t * The optimal ctxlen depends on where changes\n+\t\t\t * fall within the range, which is only known\n+\t\t\t * after xdiff runs; the max range span is the\n+\t\t\t * upper bound that guarantees correctness in a\n+\t\t\t * single pass.\n+\t\t\t */\n+\t\t\tfor (i = 0; i < line_ranges->nr; i++) {\n+\t\t\t\tlong span = line_ranges->ranges[i].end -\n+\t\t\t\t\t    line_ranges->ranges[i].start;\n+\t\t\t\tif (span > max_span)\n+\t\t\t\t\tmax_span = span;\n+\t\t\t}\n+\t\t\tif (max_span > xecfg.ctxlen)\n+\t\t\t\txecfg.ctxlen = max_span;\n+\n+\t\t\tif (xdi_diff_outf(&mf1, &mf2,\n+\t\t\t\t\t  line_range_hunk_fn,\n+\t\t\t\t\t  line_range_line_fn,\n+\t\t\t\t\t  &lr_state, &xpp, &xecfg))\n+\t\t\t\tdie(\"unable to generate diff for %s\",\n+\t\t\t\t    one->path);\n+\n+\t\t\tflush_rhunk(&lr_state);\n+\t\t\tif (lr_state.ret)\n+\t\t\t\tdie(\"unable to generate diff for %s\",\n+\t\t\t\t    one->path);\n+\t\t\tstrbuf_release(&lr_state.rhunk);\n+\t\t\tstrbuf_release(&lr_state.pending_rm);\n \t\t} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,\n \t\t\t\t\t &ecbdata, &xpp, &xecfg))\n \t\t\tdie(\"unable to generate diff for %s\", one->path);\n@@ -4660,7 +4935,7 @@ static void run_diff_cmd(const struct external_diff *pgm,\n \n \t\tbuiltin_diff(name, other ? other : name,\n \t\t\t     one, two, xfrm_msg, must_show_header,\n-\t\t\t     o, complete_rewrite);\n+\t\t\t     o, complete_rewrite, p->line_ranges);\n \t\tif (p->status == DIFF_STATUS_COPIED ||\n \t\t    p->status == DIFF_STATUS_RENAMED)\n \t\t\to->found_changes = 1;\ndiff --git a/diffcore.h b/diffcore.h\nindex 9c0a0e7aaf..d75038d1b3 100644\n--- a/diffcore.h\n+++ b/diffcore.h\n@@ -19,6 +19,17 @@ struct userdiff_driver;\n  * in anything else.\n  */\n \n+/* A range [start, end).  Lines are numbered starting at 0. */\n+struct range {\n+\tlong start, end;\n+};\n+\n+/* A set of ranges.  The ranges must always be disjoint and sorted. */\n+struct range_set {\n+\tunsigned int alloc, nr;\n+\tstruct range *ranges;\n+};\n+\n /* We internally use unsigned short as the score value,\n  * and rely on an int capable to hold 32-bits.  -B can take\n  * -Bmerge_score/break_score format and the two scores are\n@@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);\n struct diff_filepair {\n \tstruct diff_filespec *one;\n \tstruct diff_filespec *two;\n+\t/*\n+\t * Tracked line ranges for -L filtering; borrowed from\n+\t * line_log_data and must not be freed.\n+\t */\n+\tconst struct range_set *line_ranges;\n \tunsigned short int score;\n \tchar status; /* M C R A D U etc. (see Documentation/diff-format.adoc or DIFF_STATUS_* in diff.h) */\n \tunsigned broken_pair : 1;\ndiff --git a/line-log.c b/line-log.c\nindex 9d12ece181..858a899cd2 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -885,160 +885,6 @@ static void queue_diffs(struct line_log_data *range,\n \tmove_diff_queue(queue, &diff_queued_diff);\n }\n \n-static char *get_nth_line(long line, unsigned long *ends, void *data)\n-{\n-\tif (line == 0)\n-\t\treturn (char *)data;\n-\telse\n-\t\treturn (char *)data + ends[line] + 1;\n-}\n-\n-static void print_line(const char *prefix, char first,\n-\t\t       long line, unsigned long *ends, void *data,\n-\t\t       const char *color, const char *reset, FILE *file)\n-{\n-\tchar *begin = get_nth_line(line, ends, data);\n-\tchar *end = get_nth_line(line+1, ends, data);\n-\tint had_nl = 0;\n-\n-\tif (end > begin && end[-1] == '\\n') {\n-\t\tend--;\n-\t\thad_nl = 1;\n-\t}\n-\n-\tfputs(prefix, file);\n-\tfputs(color, file);\n-\tputc(first, file);\n-\tfwrite(begin, 1, end-begin, file);\n-\tfputs(reset, file);\n-\tputc('\\n', file);\n-\tif (!had_nl)\n-\t\tfputs(\"\\\\ No newline at end of file\\n\", file);\n-}\n-\n-static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n-{\n-\tunsigned int i, j = 0;\n-\tlong p_lines, t_lines;\n-\tunsigned long *p_ends = NULL, *t_ends = NULL;\n-\tstruct diff_filepair *pair = range->pair;\n-\tstruct diff_ranges *diff = &range->diff;\n-\n-\tstruct diff_options *opt = &rev->diffopt;\n-\tconst char *prefix = diff_line_prefix(opt);\n-\tconst char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);\n-\tconst char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);\n-\tconst char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);\n-\tconst char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);\n-\tconst char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);\n-\tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n-\n-\tif (!pair || !diff)\n-\t\tgoto out;\n-\n-\tif (pair->one->oid_valid)\n-\t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n-\tfill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);\n-\n-\tfprintf(opt->file, \"%s%sdiff --git a/%s b/%s%s\\n\", prefix, c_meta, pair->one->path, pair->two->path, c_reset);\n-\tfprintf(opt->file, \"%s%s--- %s%s%s\\n\", prefix, c_meta,\n-\t       pair->one->oid_valid ? \"a/\" : \"\",\n-\t       pair->one->oid_valid ? pair->one->path : \"/dev/null\",\n-\t       c_reset);\n-\tfprintf(opt->file, \"%s%s+++ b/%s%s\\n\", prefix, c_meta, pair->two->path, c_reset);\n-\tfor (i = 0; i < range->ranges.nr; i++) {\n-\t\tlong p_start, p_end;\n-\t\tlong t_start = range->ranges.ranges[i].start;\n-\t\tlong t_end = range->ranges.ranges[i].end;\n-\t\tlong t_cur = t_start;\n-\t\tunsigned int j_last;\n-\n-\t\t/*\n-\t\t * If a diff range touches multiple line ranges, then all\n-\t\t * those line ranges should be shown, so take a step back if\n-\t\t * the current line range is still in the previous diff range\n-\t\t * (even if only partially).\n-\t\t */\n-\t\tif (j > 0 && diff->target.ranges[j-1].end > t_start)\n-\t\t\tj--;\n-\n-\t\twhile (j < diff->target.nr && diff->target.ranges[j].end < t_start)\n-\t\t\tj++;\n-\t\tif (j == diff->target.nr || diff->target.ranges[j].start >= t_end)\n-\t\t\tcontinue;\n-\n-\t\t/* Scan ahead to determine the last diff that falls in this range */\n-\t\tj_last = j;\n-\t\twhile (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)\n-\t\t\tj_last++;\n-\t\tif (j_last > j)\n-\t\t\tj_last--;\n-\n-\t\t/*\n-\t\t * Compute parent hunk headers: we know that the diff\n-\t\t * has the correct line numbers (but not all hunks).\n-\t\t * So it suffices to shift the start/end according to\n-\t\t * the line numbers of the first/last hunk(s) that\n-\t\t * fall in this range.\n-\t\t */\n-\t\tif (t_start < diff->target.ranges[j].start)\n-\t\t\tp_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);\n-\t\telse\n-\t\t\tp_start = diff->parent.ranges[j].start;\n-\t\tif (t_end > diff->target.ranges[j_last].end)\n-\t\t\tp_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);\n-\t\telse\n-\t\t\tp_end = diff->parent.ranges[j_last].end;\n-\n-\t\tif (!p_start && !p_end) {\n-\t\t\tp_start = -1;\n-\t\t\tp_end = -1;\n-\t\t}\n-\n-\t\t/* Now output a diff hunk for this range */\n-\t\tfprintf(opt->file, \"%s%s@@ -%ld,%ld +%ld,%ld @@%s\\n\",\n-\t\t       prefix, c_frag,\n-\t\t       p_start+1, p_end-p_start, t_start+1, t_end-t_start,\n-\t\t       c_reset);\n-\t\twhile (j < diff->target.nr && diff->target.ranges[j].start < t_end) {\n-\t\t\tint k;\n-\t\t\tfor (; t_cur < diff->target.ranges[j].start; t_cur++)\n-\t\t\t\tprint_line(prefix, ' ', t_cur, t_ends, pair->two->data,\n-\t\t\t\t\t   c_context, c_reset, opt->file);\n-\t\t\tfor (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)\n-\t\t\t\tprint_line(prefix, '-', k, p_ends, pair->one->data,\n-\t\t\t\t\t   c_old, c_reset, opt->file);\n-\t\t\tfor (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)\n-\t\t\t\tprint_line(prefix, '+', t_cur, t_ends, pair->two->data,\n-\t\t\t\t\t   c_new, c_reset, opt->file);\n-\t\t\tj++;\n-\t\t}\n-\t\tfor (; t_cur < t_end; t_cur++)\n-\t\t\tprint_line(prefix, ' ', t_cur, t_ends, pair->two->data,\n-\t\t\t\t   c_context, c_reset, opt->file);\n-\t}\n-\n-out:\n-\tfree(p_ends);\n-\tfree(t_ends);\n-}\n-\n-/*\n- * NEEDSWORK: manually building a diff here is not the Right\n- * Thing(tm).  log -L should be built into the diff pipeline.\n- */\n-static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n-{\n-\tconst char *prefix = diff_line_prefix(&rev->diffopt);\n-\n-\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n-\n-\twhile (range) {\n-\t\tdump_diff_hacky_one(rev, range);\n-\t\trange = range->next;\n-\t}\n-}\n-\n /*\n  * Unlike most other functions, this destructively operates on\n  * 'range'.\n@@ -1102,7 +948,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)\n {\n-\tstruct diff_filepair *new_filepair = xmalloc(sizeof(struct diff_filepair));\n+\tstruct diff_filepair *new_filepair = xcalloc(1, sizeof(struct diff_filepair));\n \tnew_filepair->one = pair->one;\n \tnew_filepair->two = pair->two;\n \tnew_filepair->one->count++;\n@@ -1160,11 +1006,25 @@ static int process_all_files(struct line_log_data **range_out,\n \n int line_log_print(struct rev_info *rev, struct commit *commit)\n {\n-\n \tshow_log(rev);\n \tif (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {\n \t\tstruct line_log_data *range = lookup_line_range(rev, commit);\n-\t\tdump_diff_hacky(rev, range);\n+\t\tstruct line_log_data *r;\n+\t\tconst char *prefix = diff_line_prefix(&rev->diffopt);\n+\n+\t\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\n+\t\tfor (r = range; r; r = r->next) {\n+\t\t\tif (r->pair) {\n+\t\t\t\tstruct diff_filepair *p =\n+\t\t\t\t\tdiff_filepair_dup(r->pair);\n+\t\t\t\tp->line_ranges = &r->ranges;\n+\t\t\t\tdiff_q(&diff_queued_diff, p);\n+\t\t\t}\n+\t\t}\n+\n+\t\tdiffcore_std(&rev->diffopt);\n+\t\tdiff_flush(&rev->diffopt);\n \t}\n \treturn 1;\n }\ndiff --git a/line-log.h b/line-log.h\nindex e9dadbc1a5..04a6ea64d3 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -1,22 +1,12 @@\n #ifndef LINE_LOG_H\n #define LINE_LOG_H\n \n+#include \"diffcore.h\" /* struct range, struct range_set */\n+\n struct rev_info;\n struct commit;\n struct string_list;\n \n-/* A range [start,end].  Lines are numbered starting at 0, and the\n- * ranges include start but exclude end. */\n-struct range {\n-\tlong start, end;\n-};\n-\n-/* A set of ranges.  The ranges must always be disjoint and sorted. */\n-struct range_set {\n-\tunsigned int alloc, nr;\n-\tstruct range *ranges;\n-};\n-\n /* A diff, encoded as the set of pre- and post-image ranges where the\n  * files differ. A pair of ranges corresponds to a hunk. */\n struct diff_ranges {\ndiff --git a/revision.c b/revision.c\nindex 047ff7e458..9f68bedf13 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3111,6 +3111,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tif (want_ancestry(revs))\n \t\t\trevs->limited = 1;\n \t\trevs->topo_order = 1;\n+\t\tif (!revs->diffopt.output_format)\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_PATCH;\n \t}\n \n \tif (revs->topo_order && !generation_numbers_enabled(the_repository))\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 7acc38f72d..01f7ec8c32 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -129,7 +129,7 @@ test_expect_success '-L with --output' '\n \tgit checkout parallel-change &&\n \tgit log --output=log -L :main:b.c >output &&\n \ttest_must_be_empty output &&\n-\ttest_line_count = 70 log\n+\ttest_line_count = 75 log\n '\n \n test_expect_success 'range_set_union' '\n@@ -340,13 +340,19 @@ test_expect_success 'zero-width regex .* matches any function name' '\n '\n \n test_expect_success 'show line-log with graph' '\n+\tgit checkout parent-oids &&\n+\thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n+\thead_blob_new=$(git rev-parse --short HEAD:file.c) &&\n+\troot_blob=$(git rev-parse --short HEAD~4:file.c) &&\n+\tnull_blob=$(test_oid zero | cut -c1-7) &&\n \tqz_to_tab_space >expect <<-EOF &&\n \t* $head_oid Modify func2() in file.c\n \t|Z\n \t| diff --git a/file.c b/file.c\n+\t| index $head_blob_old..$head_blob_new 100644\n \t| --- a/file.c\n \t| +++ b/file.c\n-\t| @@ -6,4 +6,4 @@\n+\t| @@ -6,4 +6,4 @@ int func1()\n \t|  int func2()\n \t|  {\n \t| -    return F2;\n@@ -355,6 +361,8 @@ test_expect_success 'show line-log with graph' '\n \t* $root_oid Add func1() and func2() in file.c\n \tZZ\n \t  diff --git a/file.c b/file.c\n+\t  new file mode 100644\n+\t  index $null_blob..$root_blob\n \t  --- /dev/null\n \t  +++ b/file.c\n \t  @@ -0,0 +6,4 @@\ndiff --git a/t/t4211/sha1/expect.beginning-of-file b/t/t4211/sha1/expect.beginning-of-file\nindex 91b4054898..52c90afb3a 100644\n--- a/t/t4211/sha1/expect.beginning-of-file\n+++ b/t/t4211/sha1/expect.beginning-of-file\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100\n     change at very beginning\n \n diff --git a/a.c b/a.c\n+index bdb2bb1..5e709a1 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,4 @@\n@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,3 @@\n@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +1,3 @@\ndiff --git a/t/t4211/sha1/expect.end-of-file b/t/t4211/sha1/expect.end-of-file\nindex bd25bb2f59..c40036899a 100644\n--- a/t/t4211/sha1/expect.end-of-file\n+++ b/t/t4211/sha1/expect.end-of-file\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,5 @@\n+@@ -20,3 +20,5 @@ long f(long x)\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,3 @@\n+@@ -20,3 +20,3 @@ int main ()\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -19,3 +19,3 @@\n+@@ -19,3 +19,3 @@ int f(int x)\n -\tprintf(\"%d\\n\", f(15));\n +\tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +18,3 @@\ndiff --git a/t/t4211/sha1/expect.move-support-f b/t/t4211/sha1/expect.move-support-f\nindex c905e01bc2..ead6500d4d 100644\n--- a/t/t4211/sha1/expect.move-support-f\n+++ b/t/t4211/sha1/expect.move-support-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 5de3ea4..bf79c2f 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,9 +4,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple\nindex 1eee8a7801..a41851a51d 100644\n--- a/t/t4211/sha1/expect.multiple\n+++ b/t/t4211/sha1/expect.multiple\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.multiple-overlapping b/t/t4211/sha1/expect.multiple-overlapping\nindex d930b6eec4..0ec9990eab 100644\n--- a/t/t4211/sha1/expect.multiple-overlapping\n+++ b/t/t4211/sha1/expect.multiple-overlapping\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha1/expect.multiple-superset b/t/t4211/sha1/expect.multiple-superset\nindex d930b6eec4..0ec9990eab 100644\n--- a/t/t4211/sha1/expect.multiple-superset\n+++ b/t/t4211/sha1/expect.multiple-superset\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error\nindex 994c37db1e..54c568f273 100644\n--- a/t/t4211/sha1/expect.no-assertion-error\n+++ b/t/t4211/sha1/expect.no-assertion-error\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100\n     move within the file\n \n diff --git a/b.c b/b.c\n+index bf79c2f..27c829c 100644\n --- a/b.c\n +++ b/b.c\n @@ -25,0 +18,9 @@\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main\nindex 052def8074..65a8cc673a 100644\n--- a/t/t4211/sha1/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha1/expect.parallel-change-f-to-main\n@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 5de3ea4..bf79c2f 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,14 +4,14 @@\n@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200\n     change on another line of history while rename happens\n \n diff --git a/a.c b/a.c\n+index 5de3ea4..01b5b65 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,14 +4,14 @@\n@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha1/expect.simple-f b/t/t4211/sha1/expect.simple-f\nindex a1f5bc49c8..b24ae40e03 100644\n--- a/t/t4211/sha1/expect.simple-f\n+++ b/t/t4211/sha1/expect.simple-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.simple-f-to-main b/t/t4211/sha1/expect.simple-f-to-main\nindex a475768710..cd92100dfc 100644\n--- a/t/t4211/sha1/expect.simple-f-to-main\n+++ b/t/t4211/sha1/expect.simple-f-to-main\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha1/expect.simple-main b/t/t4211/sha1/expect.simple-main\nindex 39ce39bebe..ff31291d34 100644\n--- a/t/t4211/sha1/expect.simple-main\n+++ b/t/t4211/sha1/expect.simple-main\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.simple-main-to-end b/t/t4211/sha1/expect.simple-main-to-end\nindex 8480bd9cc4..4bef21e657 100644\n--- a/t/t4211/sha1/expect.simple-main-to-end\n+++ b/t/t4211/sha1/expect.simple-main-to-end\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges\nindex c5164f3be3..aed01522e3 100644\n--- a/t/t4211/sha1/expect.two-ranges\n+++ b/t/t4211/sha1/expect.two-ranges\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.vanishes-early b/t/t4211/sha1/expect.vanishes-early\nindex 1f7cd06941..a413ad3659 100644\n--- a/t/t4211/sha1/expect.vanishes-early\n+++ b/t/t4211/sha1/expect.vanishes-early\n@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +24,1 @@\n--}\n-\\ No newline at end of file\n+@@ -23,0 +24,1 @@ int main ()\n +/* incomplete lines are bad! */\n \n commit 100b61a6f2f720f812620a9d10afb3a960ccb73c\n@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +22,1 @@\n+@@ -22,1 +22,1 @@ int main ()\n -}\n +}\n \\ No newline at end of file\n@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +20,1 @@\ndiff --git a/t/t4211/sha256/expect.beginning-of-file b/t/t4211/sha256/expect.beginning-of-file\nindex 5adfdfc1a1..e8d62328cf 100644\n--- a/t/t4211/sha256/expect.beginning-of-file\n+++ b/t/t4211/sha256/expect.beginning-of-file\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100\n     change at very beginning\n \n diff --git a/a.c b/a.c\n+index 3a78aaf..d325124 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,4 @@\n@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,3 @@\n@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +1,3 @@\ndiff --git a/t/t4211/sha256/expect.end-of-file b/t/t4211/sha256/expect.end-of-file\nindex 03ab5c1784..3b2e2384da 100644\n--- a/t/t4211/sha256/expect.end-of-file\n+++ b/t/t4211/sha256/expect.end-of-file\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,5 @@\n+@@ -20,3 +20,5 @@ long f(long x)\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,3 @@\n+@@ -20,3 +20,3 @@ int main ()\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -19,3 +19,3 @@\n+@@ -19,3 +19,3 @@ int f(int x)\n -\tprintf(\"%d\\n\", f(15));\n +\tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +18,3 @@\ndiff --git a/t/t4211/sha256/expect.move-support-f b/t/t4211/sha256/expect.move-support-f\nindex 223b4ed2a0..f49abcea3e 100644\n--- a/t/t4211/sha256/expect.move-support-f\n+++ b/t/t4211/sha256/expect.move-support-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 62c1fc2..69cb69c 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,9 +4,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple\nindex dbd987b74a..0dee50ffb7 100644\n--- a/t/t4211/sha256/expect.multiple\n+++ b/t/t4211/sha256/expect.multiple\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.multiple-overlapping b/t/t4211/sha256/expect.multiple-overlapping\nindex 9015a45a25..b8c260e8ae 100644\n--- a/t/t4211/sha256/expect.multiple-overlapping\n+++ b/t/t4211/sha256/expect.multiple-overlapping\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha256/expect.multiple-superset b/t/t4211/sha256/expect.multiple-superset\nindex 9015a45a25..b8c260e8ae 100644\n--- a/t/t4211/sha256/expect.multiple-superset\n+++ b/t/t4211/sha256/expect.multiple-superset\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error\nindex 36ed12aa9c..c25f2ce19c 100644\n--- a/t/t4211/sha256/expect.no-assertion-error\n+++ b/t/t4211/sha256/expect.no-assertion-error\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100\n     move within the file\n \n diff --git a/b.c b/b.c\n+index 69cb69c..a0d566e 100644\n --- a/b.c\n +++ b/b.c\n @@ -25,0 +18,9 @@\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main\nindex e68f8928ea..3178989253 100644\n--- a/t/t4211/sha256/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha256/expect.parallel-change-f-to-main\n@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 62c1fc2..69cb69c 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,14 +4,14 @@\n@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200\n     change on another line of history while rename happens\n \n diff --git a/a.c b/a.c\n+index 62c1fc2..e1e8475 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,14 +4,14 @@\n@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha256/expect.simple-f b/t/t4211/sha256/expect.simple-f\nindex 65508d7c0b..983c711fe3 100644\n--- a/t/t4211/sha256/expect.simple-f\n+++ b/t/t4211/sha256/expect.simple-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.simple-f-to-main b/t/t4211/sha256/expect.simple-f-to-main\nindex 77b721c196..e67fa017a7 100644\n--- a/t/t4211/sha256/expect.simple-f-to-main\n+++ b/t/t4211/sha256/expect.simple-f-to-main\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha256/expect.simple-main b/t/t4211/sha256/expect.simple-main\nindex d20708c9f9..0792b27cad 100644\n--- a/t/t4211/sha256/expect.simple-main\n+++ b/t/t4211/sha256/expect.simple-main\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.simple-main-to-end b/t/t4211/sha256/expect.simple-main-to-end\nindex 617cdf3481..d3bd7c7bc6 100644\n--- a/t/t4211/sha256/expect.simple-main-to-end\n+++ b/t/t4211/sha256/expect.simple-main-to-end\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges\nindex 6a94d3b9cb..7735b19723 100644\n--- a/t/t4211/sha256/expect.two-ranges\n+++ b/t/t4211/sha256/expect.two-ranges\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.vanishes-early b/t/t4211/sha256/expect.vanishes-early\nindex 11ec9bdecf..bc33b963dc 100644\n--- a/t/t4211/sha256/expect.vanishes-early\n+++ b/t/t4211/sha256/expect.vanishes-early\n@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +24,1 @@\n--}\n-\\ No newline at end of file\n+@@ -23,0 +24,1 @@ int main ()\n +/* incomplete lines are bad! */\n \n commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10\n@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +22,1 @@\n+@@ -22,1 +22,1 @@ int main ()\n -}\n +}\n \\ No newline at end of file\n@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +20,1 @@\n-- \ngitgitgadget\n\n"},{"id":"538148","messageId":"09e00d3f82ae2a05bd7069cbde0d98400238d78e.1772845338.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"[PATCH 3/4] t4211: add tests for -L with standard diff options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-07T01:02:17Z","receivedAt":"2026-03-07T01:02:28Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L output flows through the standard diff pipeline, verify\nthat previously-ignored diff options work: formatting (--word-diff,\n--word-diff-regex, --no-prefix, --src/dst-prefix, --full-index,\n--abbrev), whitespace handling (-w, -b), output indicators\n(--output-indicator-new/old/context), direction reversal (-R),\n--color-moved, and pickaxe options (-S, -G).\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n t/t4211-line-log.sh | 281 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 281 insertions(+)\n\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 01f7ec8c32..b7f68be490 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -339,6 +339,92 @@ test_expect_success 'zero-width regex .* matches any function name' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup for diff pipeline tests' '\n+\tgit checkout parent-oids &&\n+\n+\thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n+\thead_blob_new=$(git rev-parse --short HEAD:file.c) &&\n+\troot_blob=$(git rev-parse --short HEAD~4:file.c) &&\n+\tnull_blob=$(test_oid zero | cut -c1-7) &&\n+\thead_blob_old_full=$(git rev-parse HEAD^:file.c) &&\n+\thead_blob_new_full=$(git rev-parse HEAD:file.c) &&\n+\troot_blob_full=$(git rev-parse HEAD~4:file.c) &&\n+\tnull_blob_full=$(test_oid zero)\n+'\n+\n+test_expect_success '-L diff output includes index and new file mode' '\n+\tgit log -L:func2:file.c --format= >actual &&\n+\n+\t# Output should contain index headers (not present in old code path)\n+\tgrep \"^index $head_blob_old\\.\\.$head_blob_new 100644\" actual &&\n+\n+\t# Root commit should show new file mode and null index\n+\tgrep \"^new file mode 100644\" actual &&\n+\tgrep \"^index $null_blob\\.\\.$root_blob$\" actual &&\n+\n+\t# Hunk headers should include funcname context\n+\tgrep \"^@@ .* @@ int func1()\" actual\n+'\n+\n+test_expect_success '-L with --word-diff' '\n+\tcat >expect <<-\\EOF &&\n+\n+\tdiff --git a/file.c b/file.c\n+\t--- a/file.c\n+\t+++ b/file.c\n+\t@@ -6,4 +6,4 @@ int func1()\n+\tint func2()\n+\t{\n+\t    return [-F2;-]{+F2 + 2;+}\n+\t}\n+\n+\tdiff --git a/file.c b/file.c\n+\tnew file mode 100644\n+\t--- /dev/null\n+\t+++ b/file.c\n+\t@@ -0,0 +6,4 @@\n+\t{+int func2()+}\n+\t{+{+}\n+\t{+    return F2;+}\n+\t{+}+}\n+\tEOF\n+\tgit log -L:func2:file.c --word-diff --format= >actual &&\n+\tgrep -v \"^index \" actual >actual.filtered &&\n+\tgrep -v \"^index \" expect >expect.filtered &&\n+\ttest_cmp expect.filtered actual.filtered\n+'\n+\n+test_expect_success '-L with --no-prefix' '\n+\tgit log -L:func2:file.c --no-prefix --format= >actual &&\n+\tgrep \"^diff --git file.c file.c\" actual &&\n+\tgrep \"^--- file.c\" actual &&\n+\t! grep \"^--- a/\" actual\n+'\n+\n+test_expect_success '-L with --full-index' '\n+\tgit log -L:func2:file.c --full-index --format= >actual &&\n+\tgrep \"^index $head_blob_old_full\\.\\.$head_blob_new_full 100644\" actual &&\n+\tgrep \"^index $null_blob_full\\.\\.$root_blob_full$\" actual\n+'\n+\n+test_expect_success 'setup -L with whitespace change' '\n+\tgit checkout -b ws-change parent-oids &&\n+\tsed \"s/    return F2 + 2;/\treturn F2 + 2;/\" file.c >tmp &&\n+\tmv tmp file.c &&\n+\tgit commit -a -m \"Whitespace change in func2()\"\n+'\n+\n+test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' '\n+\tgit log -L:func2:file.c --format= >without_w &&\n+\tgit log -L:func2:file.c --format= -w >with_w &&\n+\n+\t# Without -w: three commits produce diffs (whitespace, modify, root)\n+\ttest $(grep -c \"^diff --git\" without_w) = 3 &&\n+\n+\t# With -w: whitespace-only commit produces no hunk, so only two diffs\n+\ttest $(grep -c \"^diff --git\" with_w) = 2\n+'\n+\n test_expect_success 'show line-log with graph' '\n \tgit checkout parent-oids &&\n \thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n@@ -424,4 +510,199 @@ test_expect_failure '-L --find-object should filter commits by object' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success '-L with --word-diff-regex' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --word-diff \\\n+\t\t--word-diff-regex=\"[a-zA-Z0-9_]+\" --format= >actual &&\n+\t# Word-diff markers must be present\n+\tgrep \"{+\" actual &&\n+\tgrep \"+}\" actual &&\n+\t# No line-level +/- markers (word-diff replaces them);\n+\t# exclude --- header lines from the check\n+\t! grep \"^+[^+]\" actual &&\n+\t! grep \"^-[^-]\" actual\n+'\n+\n+test_expect_success '-L with --src-prefix and --dst-prefix' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \\\n+\t\t--format= >actual &&\n+\tgrep \"^diff --git old/file.c new/file.c\" actual &&\n+\tgrep \"^--- old/file.c\" actual &&\n+\tgrep \"^+++ new/file.c\" actual &&\n+\t! grep \"^--- a/\" actual\n+'\n+\n+test_expect_success '-L with --abbrev' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --abbrev=4 --format= -1 >actual &&\n+\t# 4-char abbreviated hashes on index line\n+\tgrep \"^index [0-9a-f]\\{4\\}\\.\\.[0-9a-f]\\{4\\}\" actual\n+'\n+\n+test_expect_success '-L with -b suppresses whitespace-only diff' '\n+\tgit checkout ws-change &&\n+\tgit log -L:func2:file.c --format= >without_b &&\n+\tgit log -L:func2:file.c --format= -b >with_b &&\n+\ttest $(grep -c \"^diff --git\" without_b) = 3 &&\n+\ttest $(grep -c \"^diff --git\" with_b) = 2\n+'\n+\n+test_expect_success '-L with --output-indicator-*' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --output-indicator-new=\">\" \\\n+\t\t--output-indicator-old=\"<\" --output-indicator-context=\"|\" \\\n+\t\t--format= -1 >actual &&\n+\tgrep \"^>\" actual &&\n+\tgrep \"^<\" actual &&\n+\tgrep \"^|\" actual &&\n+\t# No standard +/-/space content markers; exclude ---/+++ headers\n+\t! grep \"^+[^+]\" actual &&\n+\t! grep \"^-[^-]\" actual &&\n+\t! grep \"^ \" actual\n+'\n+\n+test_expect_success '-L with -R reverses diff' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -R --format= -1 >actual &&\n+\tgrep \"^diff --git b/file.c a/file.c\" actual &&\n+\tgrep \"^--- b/file.c\" actual &&\n+\tgrep \"^+++ a/file.c\" actual &&\n+\t# The modification added \"F2 + 2\", so reversed it is removed\n+\tgrep \"^-.*F2 + 2\" actual &&\n+\tgrep \"^+.*return F2;\" actual\n+'\n+\n+test_expect_success 'setup for color-moved test' '\n+\tgit checkout -b color-moved-test parent-oids &&\n+\tcat >big.c <<-\\EOF &&\n+\tint bigfunc()\n+\t{\n+\t    int a = 1;\n+\t    int b = 2;\n+\t    int c = 3;\n+\t    return a + b + c;\n+\t}\n+\tEOF\n+\tgit add big.c &&\n+\tgit commit -m \"add bigfunc\" &&\n+\tsed \"s/    /\t/\" big.c >tmp && mv tmp big.c &&\n+\tgit commit -a -m \"reindent bigfunc\"\n+'\n+\n+test_expect_success '-L with --color-moved' '\n+\tgit log -L:bigfunc:big.c --color-moved=zebra \\\n+\t\t--color-moved-ws=ignore-all-space \\\n+\t\t--color=always --format= -1 >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\t# Old moved lines: bold magenta; new moved lines: bold cyan\n+\tgrep \"BOLD;MAGENTA\" actual &&\n+\tgrep \"BOLD;CYAN\" actual\n+'\n+\n+test_expect_success 'setup for no-newline-at-eof tests' '\n+\tgit checkout --orphan no-newline &&\n+\tgit reset --hard &&\n+\tprintf \"int top()\\n{\\n    return 1;\\n}\\n\\nint bot()\\n{\\n    return 2;\\n}\" >noeol.c &&\n+\tgit add noeol.c &&\n+\ttest_tick &&\n+\tgit commit -m \"add noeol.c (no trailing newline)\" &&\n+\tsed \"s/return 2/return 22/\" noeol.c >tmp && mv tmp noeol.c &&\n+\tgit commit -a -m \"modify bot()\" &&\n+\tprintf \"int top()\\n{\\n    return 1;\\n}\\n\\nint bot()\\n{\\n    return 33;\\n}\\n\" >noeol.c &&\n+\tgit commit -a -m \"modify bot() and add trailing newline\"\n+'\n+\n+# When the tracked function is at the end of a file with no trailing\n+# newline, the \"\\ No newline at end of file\" marker should appear.\n+test_expect_success '-L no-newline-at-eof appears in tracked range' '\n+\tgit log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&\n+\tgrep \"No newline at end of file\" actual\n+'\n+\n+# When tracking a function that ends before the no-newline content,\n+# the marker should not appear in the output.\n+test_expect_success '-L no-newline-at-eof suppressed outside range' '\n+\tgit log -L:top:noeol.c --format= >actual &&\n+\t! grep \"No newline at end of file\" actual\n+'\n+\n+# When a commit removes a no-newline last line and replaces it with\n+# a newline-terminated line, the marker should still appear (on the\n+# old side of the diff).\n+test_expect_success '-L no-newline-at-eof marker with deleted line' '\n+\tgit log -L:bot:noeol.c --format= -1 >actual &&\n+\tgrep \"No newline at end of file\" actual\n+'\n+\n+test_expect_success 'setup for range boundary deletion test' '\n+\tgit checkout --orphan range-boundary &&\n+\tgit reset --hard &&\n+\tcat >boundary.c <<-\\EOF &&\n+\tvoid above()\n+\t{\n+\t    return;\n+\t}\n+\n+\tvoid tracked()\n+\t{\n+\t    int x = 1;\n+\t    int y = 2;\n+\t}\n+\n+\tvoid below()\n+\t{\n+\t    return;\n+\t}\n+\tEOF\n+\tgit add boundary.c &&\n+\ttest_tick &&\n+\tgit commit -m \"add boundary.c\" &&\n+\tcat >boundary.c <<-\\EOF &&\n+\tvoid above()\n+\t{\n+\t    return;\n+\t}\n+\n+\tvoid tracked()\n+\t{\n+\t    int x = 1;\n+\t    int y = 2;\n+\t}\n+\n+\tvoid below_renamed()\n+\t{\n+\t    return 0;\n+\t}\n+\tEOF\n+\tgit commit -a -m \"modify below() only\"\n+'\n+\n+# When only a function below the tracked range is modified, the\n+# tracked function should not produce a diff.\n+test_expect_success '-L suppresses deletions outside tracked range' '\n+\tgit log -L:tracked:boundary.c --format= >actual &&\n+\ttest $(grep -c \"^diff --git\" actual) = 1\n+'\n+\n+test_expect_success '-L with -S filters to string-count changes' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -S \"F2 + 2\" --format= >actual &&\n+\t# -S searches the whole file, not just the tracked range;\n+\t# combined with the -L range walk, this selects commits that\n+\t# both touch func2 and change the count of \"F2 + 2\" in the file.\n+\ttest $(grep -c \"^diff --git\" actual) = 1 &&\n+\tgrep \"F2 + 2\" actual\n+'\n+\n+test_expect_success '-L with -G filters to diff-text matches' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -G \"F2 [+] 2\" --format= >actual &&\n+\t# -G greps the whole-file diff text, not just the tracked range;\n+\t# combined with -L, this selects commits that both touch func2\n+\t# and have \"F2 + 2\" in their diff.\n+\ttest $(grep -c \"^diff --git\" actual) = 1 &&\n+\tgrep \"F2 + 2\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"538150","messageId":"0d3e84a088ec53368a748684268db89064632871.1772845338.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"[PATCH 4/4] doc: note that -L supports patch formatting and pickaxe options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-07T01:02:18Z","receivedAt":"2026-03-07T01:02:30Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L output flows through the standard diff pipeline,\ndocument that patch formatting options like --word-diff,\n--color-moved, --no-prefix, whitespace handling (-w, -b),\nand pickaxe options (-S, -G) are supported.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n Documentation/line-range-options.adoc | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc\nindex c44ba05320..aa236281b3 100644\n--- a/Documentation/line-range-options.adoc\n+++ b/Documentation/line-range-options.adoc\n@@ -11,5 +11,9 @@\n \tPatch output can be suppressed using `--no-patch`, but other diff formats\n \t(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,\n \t`--name-only`, `--name-status`, `--check`) are not currently implemented.\n++\n+\tPatch formatting options such as `--word-diff`, `--color-moved`,\n+\t`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n+\tas are pickaxe options (`-S`, `-G`).\n +\n include::line-range-format.adoc[]\n-- \ngitgitgadget\n"},{"id":"538154","messageId":"xmqqcy1gv351.fsf@gitster.g","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-07T01:28:58Z","receivedAt":"2026-03-07T01:29:01Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> git log -L has bypassed the standard diff pipeline since its introduction,\n> using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has\n> acknowledged this from the start. This series removes dump_diff_hacky() and\n> routes -L output through builtin_diff() / fn_out_consume(), so that diff\n> formatting options like --word-diff, --color-moved, -w, and pickaxe options\n> (-S, -G) work with -L.\n\nExciting.\n\n> User-visible output change: -L output now includes index lines, new file\n> mode headers, and funcname context in @@ headers that were previously\n> missing. Tools parsing -L output may need to handle these additional lines.\n>\n> Known limitations not addressed in this series:\n>\n>  * line_log_print() still calls show_log() and diff_flush() directly,\n>    bypassing log_tree_diff_flush(). The early return in log_tree_commit()\n>    (and its associated NEEDSWORK about no_free not being restored) is\n>    pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a\n>    larger change that would affect separator and header logic; it is left\n>    for a follow-up.\n\nOK.  Previously all the output routines were hand-rolled, but this\nreduces the extent of deviation---as long as we are moving in the\nright direction, it is a good idea to find a good place to stop and\nleave the rest for later.\n\n>  * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n>    unimplemented for -L.\n\nIt would not hurt if these are omitted.\n\n> Michael Montalbo (4): line-log: fix crash when combined with pickaxe options\n> line-log: route -L output through the standard diff pipeline t4211: add\n> tests for -L with standard diff options doc: note that -L supports patch\n> formatting and pickaxe options\n\nI am not sure what this bloc is, but it looks like a reflowed\nversion of the list of commits below?\n\n> Michael Montalbo (4):\n>   line-log: fix crash when combined with pickaxe options\n>   line-log: route -L output through the standard diff pipeline\n>   t4211: add tests for -L with standard diff options\n>   doc: note that -L supports patch formatting and pickaxe options\n\nLet me throw in a handful of names found in the output of \"git\nshortlog --no-merges -s -n line-log.[ch]\" on the Cc: line to solicit\nhelp.\n.\n\n"},{"id":"538157","messageId":"CAC2QwmLnLqMcWc8az6pVzz0oEdY282Nq-Lun5OrHaqHUiVE_9w@mail.gmail.com","threadId":"65159","inReplyTo":"xmqqcy1gv351.fsf@gitster.g","subject":"Re: [PATCH 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-03-07T01:37:45Z","receivedAt":"2026-03-07T01:37:57Z","isPatch":true,"body":"On Fri, Mar 6, 2026 at 5:29 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > git log -L has bypassed the standard diff pipeline since its introduction,\n> > using dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has\n> > acknowledged this from the start. This series removes dump_diff_hacky() and\n> > routes -L output through builtin_diff() / fn_out_consume(), so that diff\n> > formatting options like --word-diff, --color-moved, -w, and pickaxe options\n> > (-S, -G) work with -L.\n>\n> Exciting.\n\n:)\n\n> > User-visible output change: -L output now includes index lines, new file\n> > mode headers, and funcname context in @@ headers that were previously\n> > missing. Tools parsing -L output may need to handle these additional lines.\n> >\n> > Known limitations not addressed in this series:\n> >\n> >  * line_log_print() still calls show_log() and diff_flush() directly,\n> >    bypassing log_tree_diff_flush(). The early return in log_tree_commit()\n> >    (and its associated NEEDSWORK about no_free not being restored) is\n> >    pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a\n> >    larger change that would affect separator and header logic; it is left\n> >    for a follow-up.\n>\n> OK.  Previously all the output routines were hand-rolled, but this\n> reduces the extent of deviation---as long as we are moving in the\n> right direction, it is a good idea to find a good place to stop and\n> leave the rest for later.\n>\n> >  * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n> >    unimplemented for -L.\n>\n> It would not hurt if these are omitted.\n>\n\nMakes sense. I can omit in a follow-up.\n\n> > Michael Montalbo (4): line-log: fix crash when combined with pickaxe options\n> > line-log: route -L output through the standard diff pipeline t4211: add\n> > tests for -L with standard diff options doc: note that -L supports patch\n> > formatting and pickaxe options\n>\n> I am not sure what this bloc is, but it looks like a reflowed\n> version of the list of commits below?\n>\n\nYes, this was a mistake I made when crafting the cover letter.\n\n> > Michael Montalbo (4):\n> >   line-log: fix crash when combined with pickaxe options\n> >   line-log: route -L output through the standard diff pipeline\n> >   t4211: add tests for -L with standard diff options\n> >   doc: note that -L supports patch formatting and pickaxe options\n>\n> Let me throw in a handful of names found in the output of \"git\n> shortlog --no-merges -s -n line-log.[ch]\" on the Cc: line to solicit\n> help.\n> .\n>\n\nGreat! Thank you for your help on the review!\n"},{"id":"538158","messageId":"xmqq8qc4v1fw.fsf@gitster.g","threadId":"65159","inReplyTo":"CAC2QwmLnLqMcWc8az6pVzz0oEdY282Nq-Lun5OrHaqHUiVE_9w@mail.gmail.com","subject":"Re: [PATCH 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-07T02:05:39Z","receivedAt":"2026-03-07T02:05:41Z","isPatch":true,"body":"Michael Montalbo <mmontalbo@gmail.com> writes:\n\n>> >  * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n>> >    unimplemented for -L.\n>>\n>> It would not hurt if these are omitted.\n>>\n>\n> Makes sense. I can omit in a follow-up.\n\nYou are already omitting, no?  I took \"remain unimplemented\" to mean\nexactly that.  Mentioning that we are not adding support for them,\nlike you did in the above sentence that I commented on, is a good\nthing to do, I think.\n"},{"id":"538159","messageId":"CAC2QwmJoQKVANqjkHmuLVgvGKrkvHaZ0woL76-Z=Knm0gvLk4Q@mail.gmail.com","threadId":"65159","inReplyTo":"xmqq8qc4v1fw.fsf@gitster.g","subject":"Re: [PATCH 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-03-07T02:10:55Z","receivedAt":"2026-03-07T02:11:08Z","isPatch":true,"body":"On Fri, Mar 6, 2026 at 6:05 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Michael Montalbo <mmontalbo@gmail.com> writes:\n>\n> >> >  * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n> >> >    unimplemented for -L.\n> >>\n> >> It would not hurt if these are omitted.\n> >>\n> >\n> > Makes sense. I can omit in a follow-up.\n>\n> You are already omitting, no?  I took \"remain unimplemented\" to mean\n> exactly that.  Mentioning that we are not adding support for them,\n> like you did in the above sentence that I commented on, is a good\n> thing to do, I think.\n\nAh got it, I confused myself. I thought this meant updating docs/code\nto more explicitly disallow these options from interacting but this is\nalready the case.\n"},{"id":"538564","messageId":"e551ce17-8118-4b3b-ad8c-bdfe1a854c0b@app.fastmail.com","threadId":"65159","inReplyTo":"0d3e84a088ec53368a748684268db89064632871.1772845338.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 4/4] doc: note that -L supports patch formatting and pickaxe options","fromName":"Kristoffer Haugsbakk","fromEmail":"kristofferhaugsbakk@fastmail.com","sentAt":"2026-03-11T08:41:03Z","receivedAt":"2026-03-11T08:41:25Z","isPatch":true,"body":"On Sat, Mar 7, 2026, at 02:02, Michael Montalbo via GitGitGadget wrote:\n> From: Michael Montalbo <mmontalbo@gmail.com>\n>\n> Now that -L output flows through the standard diff pipeline,\n> document that patch formatting options like --word-diff,\n> --color-moved, --no-prefix, whitespace handling (-w, -b),\n> and pickaxe options (-S, -G) are supported.\n>\n> Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n> ---\n>  Documentation/line-range-options.adoc | 4 ++++\n>  1 file changed, 4 insertions(+)\n>\n> diff --git a/Documentation/line-range-options.adoc\n> b/Documentation/line-range-options.adoc\n> index c44ba05320..aa236281b3 100644\n> --- a/Documentation/line-range-options.adoc\n> +++ b/Documentation/line-range-options.adoc\n> @@ -11,5 +11,9 @@\n>  \tPatch output can be suppressed using `--no-patch`, but other diff\n> formats\n>  \t(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,\n>  \t`--name-only`, `--name-status`, `--check`) are not currently\n> implemented.\n> ++\n> +\tPatch formatting options such as `--word-diff`, `--color-moved`,\n> +\t`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n> +\tas are pickaxe options (`-S`, `-G`).\n\nYou shouldn’t use indentation here. This is a code block now.\n\nJust a line continuation and flush to the left.\n\n>  +\n>  include::line-range-format.adoc[]\n> --\n> gitgitgadget\n"},{"id":"538636","messageId":"CAC2QwmJTqYj5b531itc=8ZM3vXsg5oG3BLCHiEuoB7d0d4U=TQ@mail.gmail.com","threadId":"65159","inReplyTo":"e551ce17-8118-4b3b-ad8c-bdfe1a854c0b@app.fastmail.com","subject":"Re: [PATCH 4/4] doc: note that -L supports patch formatting and pickaxe options","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-03-11T17:35:01Z","receivedAt":"2026-03-11T17:35:14Z","isPatch":true,"body":"On Wed, Mar 11, 2026 at 1:41 AM Kristoffer Haugsbakk\n<kristofferhaugsbakk@fastmail.com> wrote:\n>\n> On Sat, Mar 7, 2026, at 02:02, Michael Montalbo via GitGitGadget wrote:\n> > From: Michael Montalbo <mmontalbo@gmail.com>\n> >\n> > Now that -L output flows through the standard diff pipeline,\n> > document that patch formatting options like --word-diff,\n> > --color-moved, --no-prefix, whitespace handling (-w, -b),\n> > and pickaxe options (-S, -G) are supported.\n> >\n> > Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n> > ---\n> >  Documentation/line-range-options.adoc | 4 ++++\n> >  1 file changed, 4 insertions(+)\n> >\n> > diff --git a/Documentation/line-range-options.adoc\n> > b/Documentation/line-range-options.adoc\n> > index c44ba05320..aa236281b3 100644\n> > --- a/Documentation/line-range-options.adoc\n> > +++ b/Documentation/line-range-options.adoc\n> > @@ -11,5 +11,9 @@\n> >       Patch output can be suppressed using `--no-patch`, but other diff\n> > formats\n> >       (namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,\n> >       `--name-only`, `--name-status`, `--check`) are not currently\n> > implemented.\n> > ++\n> > +     Patch formatting options such as `--word-diff`, `--color-moved`,\n> > +     `--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n> > +     as are pickaxe options (`-S`, `-G`).\n>\n> You shouldn’t use indentation here. This is a code block now.\n>\n> Just a line continuation and flush to the left.\n>\n\nGood catch, thank you. Will update in followup.\n\n> >  +\n> >  include::line-range-format.adoc[]\n> > --\n> > gitgitgadget\n"},{"id":"539193","messageId":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.git.1772845338.gitgitgadget@gmail.com","subject":"[PATCH v2 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T02:21:31Z","receivedAt":"2026-03-17T02:21:38Z","isPatch":true,"body":"git log -L has bypassed the standard diff pipeline since its introduction,\nusing dump_diff_hacky() to hand-roll diff output. A NEEDSWORK comment has\nacknowledged this from the start. This series removes dump_diff_hacky() and\nroutes -L output through builtin_diff() / fn_out_consume(), so that diff\nformatting options like --word-diff, --color-moved, -w, and pickaxe options\n(-S, -G) work with -L.\n\nThis replaces my earlier series \"line-log: fix -L with pickaxe options\" [1].\nPatch 1 is the crash fix from that series (unchanged). Patch 2/2 from that\nseries (rejecting -S/-G) is dropped because this series makes those options\nwork instead of rejecting them.\n\n[1]\nhttps://lore.kernel.org/git/pull.2061.git.1772651484.gitgitgadget@gmail.com/\n\nPatch 1 fixes a crash when combining -L with pickaxe options and a rename.\n\nPatch 2 is the core change: callback wrappers filter xdiff's output to\ntracked line ranges, and line ranges are carried on diff_filepair so each\nfile's ranges travel with its filepair through the pipeline. diffcore_std()\nruns at output time, so pickaxe, --orderfile, and --diff-filter also work.\n\nPatch 3 adds tests covering the newly-working options.\n\nPatch 4 updates documentation.\n\nUser-visible output change: -L output now includes index lines, new file\nmode headers, and funcname context in @@ headers that were previously\nmissing. Tools parsing -L output may need to handle these additional lines.\n\nKnown limitations not addressed in this series:\n\n * line_log_print() still calls show_log() and diff_flush() directly,\n   bypassing log_tree_diff_flush(). The early return in log_tree_commit()\n   (and its associated NEEDSWORK about no_free not being restored) is\n   pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a\n   larger change that would affect separator and header logic; it is left\n   for a follow-up.\n\n * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n   unimplemented for -L.\n\ncc: \"Kristoffer Haugsbakk\" kristofferhaugsbakk@fastmail.com\n\nChanges since v1:\n\n * Patch 4/4: fix documentation formatting: use line continuation\n   instead of indentation (Kristoffer Haugsbakk)\n\nMichael Montalbo (4):\n  line-log: fix crash when combined with pickaxe options\n  line-log: route -L output through the standard diff pipeline\n  t4211: add tests for -L with standard diff options\n  doc: note that -L supports patch formatting and pickaxe options\n\n Documentation/line-range-options.adoc         |   4 +\n diff.c                                        | 279 +++++++++++++-\n diffcore.h                                    |  16 +\n line-log.c                                    | 196 ++--------\n line-log.h                                    |  14 +-\n revision.c                                    |   2 +\n t/t4211-line-log.sh                           | 348 +++++++++++++++++-\n t/t4211/sha1/expect.beginning-of-file         |   4 +\n t/t4211/sha1/expect.end-of-file               |  11 +-\n t/t4211/sha1/expect.move-support-f            |   5 +\n t/t4211/sha1/expect.multiple                  |  10 +-\n t/t4211/sha1/expect.multiple-overlapping      |   7 +\n t/t4211/sha1/expect.multiple-superset         |   7 +\n t/t4211/sha1/expect.no-assertion-error        |  12 +-\n t/t4211/sha1/expect.parallel-change-f-to-main |   7 +\n t/t4211/sha1/expect.simple-f                  |   4 +\n t/t4211/sha1/expect.simple-f-to-main          |   5 +\n t/t4211/sha1/expect.simple-main               |  11 +-\n t/t4211/sha1/expect.simple-main-to-end        |  11 +-\n t/t4211/sha1/expect.two-ranges                |  10 +-\n t/t4211/sha1/expect.vanishes-early            |  10 +-\n t/t4211/sha256/expect.beginning-of-file       |   4 +\n t/t4211/sha256/expect.end-of-file             |  11 +-\n t/t4211/sha256/expect.move-support-f          |   5 +\n t/t4211/sha256/expect.multiple                |  10 +-\n t/t4211/sha256/expect.multiple-overlapping    |   7 +\n t/t4211/sha256/expect.multiple-superset       |   7 +\n t/t4211/sha256/expect.no-assertion-error      |  12 +-\n .../sha256/expect.parallel-change-f-to-main   |   7 +\n t/t4211/sha256/expect.simple-f                |   4 +\n t/t4211/sha256/expect.simple-f-to-main        |   5 +\n t/t4211/sha256/expect.simple-main             |  11 +-\n t/t4211/sha256/expect.simple-main-to-end      |  11 +-\n t/t4211/sha256/expect.two-ranges              |  10 +-\n t/t4211/sha256/expect.vanishes-early          |  10 +-\n 35 files changed, 870 insertions(+), 217 deletions(-)\n\n\nbase-commit: 7b2bccb0d58d4f24705bf985de1f4612e4cf06e5\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2065%2Fmmontalbo%2Fspike-xdiff-line-range-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2065/mmontalbo/spike-xdiff-line-range-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/2065\n\nRange-diff vs v1:\n\n 1:  e7b8cc2c78 ! 1:  ccfc1b03ff line-log: fix crash when combined with pickaxe options\n     @@ t/t4211-line-log.sh: test_expect_success 'show line-log with graph' '\n      +\t\t--find-object=$(git rev-parse HEAD:file) >actual\n      +'\n      +\n     ++# Commit-level filtering with pickaxe does not yet work for -L.\n     ++# show_log() prints the commit header before diffcore_std() runs\n     ++# pickaxe, so commits cannot be suppressed even when no diff pairs\n     ++# survive filtering.  Fixing this would require deferring show_log()\n     ++# until after diffcore_std(), which is a larger restructuring of the\n     ++# log-tree output pipeline.\n      +test_expect_failure '-L -G should filter commits by pattern' '\n      +\tgit log --format=\"%s\" --no-patch -L 1,1:file -G \"nomatch\" >actual &&\n      +\ttest_must_be_empty actual\n 2:  6921b07a0f = 2:  4e2bc55082 line-log: route -L output through the standard diff pipeline\n 3:  09e00d3f82 = 3:  cf7720ae98 t4211: add tests for -L with standard diff options\n 4:  0d3e84a088 ! 4:  93e771b7cd doc: note that -L supports patch formatting and pickaxe options\n     @@ Commit message\n      \n       ## Documentation/line-range-options.adoc ##\n      @@\n     - \tPatch output can be suppressed using `--no-patch`, but other diff formats\n       \t(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,\n       \t`--name-only`, `--name-status`, `--check`) are not currently implemented.\n     -++\n     -+\tPatch formatting options such as `--word-diff`, `--color-moved`,\n     -+\t`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n     -+\tas are pickaxe options (`-S`, `-G`).\n       +\n     ++Patch formatting options such as `--word-diff`, `--color-moved`,\n     ++`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n     ++as are pickaxe options (`-S`, `-G`).\n     +++\n       include::line-range-format.adoc[]\n\n-- \ngitgitgadget\n"},{"id":"539194","messageId":"ccfc1b03fff18771e0a63205ef44d343606b3c90.1773714095.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","subject":"[PATCH v2 1/4] line-log: fix crash when combined with pickaxe options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T02:21:32Z","receivedAt":"2026-03-17T02:21:39Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nqueue_diffs() passes the caller's diff_options, which may carry\nuser-specified pickaxe state, to diff_tree_oid() and diffcore_std()\nwhen detecting renames for line-level history tracking.  When pickaxe\noptions are present on the command line (-G and -S to filter by text\npattern, --find-object to filter by object identity), diffcore_std()\nalso runs diffcore_pickaxe(), which may discard diff pairs that are\nrelevant for rename detection.  Losing those pairs breaks rename\nfollowing.\n\nBefore a2bb801f6a (line-log: avoid unnecessary full tree diffs,\n2019-08-21), this silently truncated history at rename boundaries.\nThat commit moved filter_diffs_for_paths() inside the rename-\ndetection block, so it only runs when diff_might_be_rename() returns\ntrue.  When pickaxe discards a rename pair, the rename goes\nundetected, and a deletion pair at a subsequent commit passes\nthrough uncleaned, reaching process_diff_filepair() with an invalid\nfilespec and triggering an assertion failure.\n\nFix this by building a private diff_options for the rename-detection\npath inside queue_diffs(), following the same pattern used by blame's\nfind_rename().  This isolates the rename machinery from unrelated\nuser-specified options.\n\nReported-by: Matthew Hughes <matthewhughes934@gmail.com>\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n line-log.c          | 22 ++++++++++++++----\n t/t4211-line-log.sh | 55 +++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 73 insertions(+), 4 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex eeaf68454e..9d12ece181 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -858,15 +858,29 @@ static void queue_diffs(struct line_log_data *range,\n \tdiff_queue_clear(&diff_queued_diff);\n \tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", opt);\n \tif (opt->detect_rename && diff_might_be_rename()) {\n+\t\tstruct diff_options rename_opts;\n+\n+\t\t/*\n+\t\t * Build a private diff_options for rename detection so\n+\t\t * that any user-specified options on the original opts\n+\t\t * (e.g. pickaxe) cannot discard diff pairs needed for\n+\t\t * rename tracking.  Similar to blame's find_rename().\n+\t\t */\n+\t\trepo_diff_setup(opt->repo, &rename_opts);\n+\t\trename_opts.flags.recursive = 1;\n+\t\trename_opts.detect_rename = opt->detect_rename;\n+\t\trename_opts.rename_score = opt->rename_score;\n+\t\trename_opts.output_format = DIFF_FORMAT_NO_OUTPUT;\n+\t\tdiff_setup_done(&rename_opts);\n+\n \t\t/* must look at the full tree diff to detect renames */\n-\t\tclear_pathspec(&opt->pathspec);\n \t\tdiff_queue_clear(&diff_queued_diff);\n-\n-\t\tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", opt);\n+\t\tdiff_tree_oid(parent_tree_oid, tree_oid, \"\", &rename_opts);\n \n \t\tfilter_diffs_for_paths(range, 1);\n-\t\tdiffcore_std(opt);\n+\t\tdiffcore_std(&rename_opts);\n \t\tfilter_diffs_for_paths(range, 0);\n+\t\tdiff_free(&rename_opts);\n \t}\n \tmove_diff_queue(queue, &diff_queued_diff);\n }\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 0a7c3ca42f..659a943aa1 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -367,4 +367,59 @@ test_expect_success 'show line-log with graph' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup for -L with -G/-S/--find-object and a merge with rename' '\n+\tgit checkout --orphan pickaxe-rename &&\n+\tgit reset --hard &&\n+\n+\techo content >file &&\n+\tgit add file &&\n+\tgit commit -m \"add file\" &&\n+\n+\tgit checkout -b pickaxe-rename-side &&\n+\tgit mv file renamed-file &&\n+\tgit commit -m \"rename file\" &&\n+\n+\tgit checkout pickaxe-rename &&\n+\tgit commit --allow-empty -m \"diverge\" &&\n+\tgit merge --no-edit pickaxe-rename-side &&\n+\n+\tgit mv renamed-file file &&\n+\tgit commit -m \"rename back\"\n+'\n+\n+test_expect_success '-L -G does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -G \".\" >actual\n+'\n+\n+test_expect_success '-L -S does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -S content >actual\n+'\n+\n+test_expect_success '-L --find-object does not crash with merge and rename' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file \\\n+\t\t--find-object=$(git rev-parse HEAD:file) >actual\n+'\n+\n+# Commit-level filtering with pickaxe does not yet work for -L.\n+# show_log() prints the commit header before diffcore_std() runs\n+# pickaxe, so commits cannot be suppressed even when no diff pairs\n+# survive filtering.  Fixing this would require deferring show_log()\n+# until after diffcore_std(), which is a larger restructuring of the\n+# log-tree output pipeline.\n+test_expect_failure '-L -G should filter commits by pattern' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -G \"nomatch\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_failure '-L -S should filter commits by pattern' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file -S \"nomatch\" >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n+test_expect_failure '-L --find-object should filter commits by object' '\n+\tgit log --format=\"%s\" --no-patch -L 1,1:file \\\n+\t\t--find-object=$ZERO_OID >actual &&\n+\ttest_must_be_empty actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"539196","messageId":"4e2bc55082e79654ebf0d30fc00479a5eb29f750.1773714095.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","subject":"[PATCH v2 2/4] line-log: route -L output through the standard diff pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T02:21:33Z","receivedAt":"2026-03-17T02:21:42Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\n`git log -L` has always bypassed the standard diff pipeline.\n`dump_diff_hacky()` in line-log.c hand-rolls its own diff headers and\nhunk output, which means most diff formatting options are silently\nignored.  A NEEDSWORK comment has acknowledged this since the feature\nwas introduced:\n\n    /*\n     * NEEDSWORK: manually building a diff here is not the Right\n     * Thing(tm).  log -L should be built into the diff pipeline.\n     */\n\nRemove `dump_diff_hacky()` and its helpers and route -L output through\n`builtin_diff()` / `fn_out_consume()`, the same path used by `git diff`\nand `git log -p`.  The mechanism is a pair of callback wrappers that sit\nbetween `xdi_diff_outf()` and `fn_out_consume()`, filtering xdiff's\noutput to only the tracked line ranges.  To ensure xdiff emits all lines\nwithin each range as context, the context length is inflated to span the\nlargest range.\n\nWire up the `-L` implies `--patch` default in revision setup rather\nthan forcing it at output time, so `line_log_print()` is just\n`diffcore_std()` + `diff_flush()` with no format save/restore.\nRename detection is a no-op since pairs are already resolved during\nthe history walk in `queue_diffs()`, but running `diffcore_std()`\nmeans `-S`/`-G` (pickaxe), `--orderfile`, and `--diff-filter` now\nwork with `-L`, and `diff_resolve_rename_copy()` sets pair statuses\ncorrectly without manual assignment.\n\nSwitch `diff_filepair_dup()` from `xmalloc` to `xcalloc` so that new\nfields (including `line_ranges`) are zero-initialized by default.\n\nAs a result, diff formatting options that were previously silently\nignored (e.g. --word-diff, --no-prefix, -w, --color-moved) now work\nwith -L, and output gains `index` lines, `new file mode` headers, and\nfuncname context in `@@` headers.  This is a user-visible output change:\ntools that parse -L output may need to handle the additional header\nlines.\n\nThe context-length inflation means xdiff may process more output than\nneeded for very wide line ranges, but benchmarks on files up to 7800\nlines show no measurable regression.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n diff.c                                        | 279 +++++++++++++++++-\n diffcore.h                                    |  16 +\n line-log.c                                    | 174 ++---------\n line-log.h                                    |  14 +-\n revision.c                                    |   2 +\n t/t4211-line-log.sh                           |  12 +-\n t/t4211/sha1/expect.beginning-of-file         |   4 +\n t/t4211/sha1/expect.end-of-file               |  11 +-\n t/t4211/sha1/expect.move-support-f            |   5 +\n t/t4211/sha1/expect.multiple                  |  10 +-\n t/t4211/sha1/expect.multiple-overlapping      |   7 +\n t/t4211/sha1/expect.multiple-superset         |   7 +\n t/t4211/sha1/expect.no-assertion-error        |  12 +-\n t/t4211/sha1/expect.parallel-change-f-to-main |   7 +\n t/t4211/sha1/expect.simple-f                  |   4 +\n t/t4211/sha1/expect.simple-f-to-main          |   5 +\n t/t4211/sha1/expect.simple-main               |  11 +-\n t/t4211/sha1/expect.simple-main-to-end        |  11 +-\n t/t4211/sha1/expect.two-ranges                |  10 +-\n t/t4211/sha1/expect.vanishes-early            |  10 +-\n t/t4211/sha256/expect.beginning-of-file       |   4 +\n t/t4211/sha256/expect.end-of-file             |  11 +-\n t/t4211/sha256/expect.move-support-f          |   5 +\n t/t4211/sha256/expect.multiple                |  10 +-\n t/t4211/sha256/expect.multiple-overlapping    |   7 +\n t/t4211/sha256/expect.multiple-superset       |   7 +\n t/t4211/sha256/expect.no-assertion-error      |  12 +-\n .../sha256/expect.parallel-change-f-to-main   |   7 +\n t/t4211/sha256/expect.simple-f                |   4 +\n t/t4211/sha256/expect.simple-f-to-main        |   5 +\n t/t4211/sha256/expect.simple-main             |  11 +-\n t/t4211/sha256/expect.simple-main-to-end      |  11 +-\n t/t4211/sha256/expect.two-ranges              |  10 +-\n t/t4211/sha256/expect.vanishes-early          |  10 +-\n 34 files changed, 512 insertions(+), 213 deletions(-)\n\ndiff --git a/diff.c b/diff.c\nindex 35b903a9a0..c8cbbbb1db 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -608,6 +608,52 @@ struct emit_callback {\n \tstruct strbuf *header;\n };\n \n+/*\n+ * State for the line-range callback wrappers that sit between\n+ * xdi_diff_outf() and fn_out_consume().  xdiff produces a normal,\n+ * unfiltered diff; the wrappers intercept each hunk header and line,\n+ * track post-image position, and forward only lines that fall within\n+ * the requested ranges.  Contiguous in-range lines are collected into\n+ * range hunks and flushed with a synthetic @@ header so that\n+ * fn_out_consume() sees well-formed unified-diff fragments.\n+ *\n+ * Removal lines ('-') cannot be classified by post-image position, so\n+ * they are buffered in pending_rm until the next '+' or ' ' line\n+ * reveals whether they precede an in-range line (flush into range hunk) or\n+ * an out-of-range line (discard).\n+ */\n+struct line_range_callback {\n+\txdiff_emit_line_fn orig_line_fn;\n+\tvoid *orig_cb_data;\n+\tconst struct range_set *ranges;\t/* 0-based [start, end) */\n+\tunsigned int cur_range;\t\t/* index into the range_set */\n+\n+\t/* Post/pre-image line counters (1-based, set from hunk headers) */\n+\tlong lno_post;\n+\tlong lno_pre;\n+\n+\t/*\n+\t * Function name from most recent xdiff hunk header;\n+\t * size matches struct func_line.buf in xdiff/xemit.c.\n+\t */\n+\tchar func[80];\n+\tlong funclen;\n+\n+\t/* Range hunk being accumulated for the current range */\n+\tstruct strbuf rhunk;\n+\tlong rhunk_old_begin, rhunk_old_count;\n+\tlong rhunk_new_begin, rhunk_new_count;\n+\tint rhunk_active;\n+\tint rhunk_has_changes;\t\t/* any '+' or '-' lines? */\n+\n+\t/* Removal lines not yet known to be in-range */\n+\tstruct strbuf pending_rm;\n+\tint pending_rm_count;\n+\tlong pending_rm_pre_begin;\t/* pre-image line of first pending */\n+\n+\tint ret;\t\t\t/* latched error from orig_line_fn */\n+};\n+\n static int count_lines(const char *data, int size)\n {\n \tint count, ch, completely_empty = 1, nl_just_seen = 0;\n@@ -2486,6 +2532,188 @@ static int quick_consume(void *priv, char *line UNUSED, unsigned long len UNUSED\n \treturn 1;\n }\n \n+static void discard_pending_rm(struct line_range_callback *s)\n+{\n+\tstrbuf_reset(&s->pending_rm);\n+\ts->pending_rm_count = 0;\n+}\n+\n+static void flush_rhunk(struct line_range_callback *s)\n+{\n+\tstruct strbuf hdr = STRBUF_INIT;\n+\tconst char *p, *end;\n+\n+\tif (!s->rhunk_active || s->ret)\n+\t\treturn;\n+\n+\t/* Drain any pending removal lines into the range hunk */\n+\tif (s->pending_rm_count) {\n+\t\tstrbuf_addbuf(&s->rhunk, &s->pending_rm);\n+\t\ts->rhunk_old_count += s->pending_rm_count;\n+\t\ts->rhunk_has_changes = 1;\n+\t\tdiscard_pending_rm(s);\n+\t}\n+\n+\t/*\n+\t * Suppress context-only hunks: they contain no actual changes\n+\t * and would just be noise.  This can happen when the inflated\n+\t * ctxlen causes xdiff to emit context covering a range that\n+\t * has no changes in this commit.\n+\t */\n+\tif (!s->rhunk_has_changes) {\n+\t\ts->rhunk_active = 0;\n+\t\tstrbuf_reset(&s->rhunk);\n+\t\treturn;\n+\t}\n+\n+\tstrbuf_addf(&hdr, \"@@ -%ld,%ld +%ld,%ld @@\",\n+\t\t    s->rhunk_old_begin, s->rhunk_old_count,\n+\t\t    s->rhunk_new_begin, s->rhunk_new_count);\n+\tif (s->funclen > 0) {\n+\t\tstrbuf_addch(&hdr, ' ');\n+\t\tstrbuf_add(&hdr, s->func, s->funclen);\n+\t}\n+\tstrbuf_addch(&hdr, '\\n');\n+\n+\ts->ret = s->orig_line_fn(s->orig_cb_data, hdr.buf, hdr.len);\n+\tstrbuf_release(&hdr);\n+\n+\t/*\n+\t * Replay buffered lines one at a time through fn_out_consume.\n+\t * The cast discards const because xdiff_emit_line_fn takes\n+\t * char *, though fn_out_consume does not modify the buffer.\n+\t */\n+\tp = s->rhunk.buf;\n+\tend = p + s->rhunk.len;\n+\twhile (!s->ret && p < end) {\n+\t\tconst char *eol = memchr(p, '\\n', end - p);\n+\t\tunsigned long line_len = eol ? (unsigned long)(eol - p + 1)\n+\t\t\t\t\t     : (unsigned long)(end - p);\n+\t\ts->ret = s->orig_line_fn(s->orig_cb_data, (char *)p, line_len);\n+\t\tp += line_len;\n+\t}\n+\n+\ts->rhunk_active = 0;\n+\tstrbuf_reset(&s->rhunk);\n+}\n+\n+static void line_range_hunk_fn(void *data,\n+\t\t\t       long old_begin, long old_nr UNUSED,\n+\t\t\t       long new_begin, long new_nr UNUSED,\n+\t\t\t       const char *func, long funclen)\n+{\n+\tstruct line_range_callback *s = data;\n+\n+\t/*\n+\t * When count > 0, begin is 1-based.  When count == 0, begin is\n+\t * adjusted down by 1 by xdl_emit_hunk_hdr(), but no lines of\n+\t * that type will arrive, so the value is unused.\n+\t *\n+\t * Any pending removal lines from the previous xdiff hunk are\n+\t * intentionally left in pending_rm: the line callback will\n+\t * flush or discard them when the next content line reveals\n+\t * whether the removals precede in-range content.\n+\t */\n+\ts->lno_post = new_begin;\n+\ts->lno_pre = old_begin;\n+\n+\tif (funclen > 0) {\n+\t\tif (funclen > (long)sizeof(s->func))\n+\t\t\tfunclen = sizeof(s->func);\n+\t\tmemcpy(s->func, func, funclen);\n+\t}\n+\ts->funclen = funclen;\n+}\n+\n+static int line_range_line_fn(void *priv, char *line, unsigned long len)\n+{\n+\tstruct line_range_callback *s = priv;\n+\tconst struct range *cur;\n+\tlong lno_0, cur_pre;\n+\n+\tif (s->ret)\n+\t\treturn s->ret;\n+\n+\tif (line[0] == '-') {\n+\t\tif (!s->pending_rm_count)\n+\t\t\ts->pending_rm_pre_begin = s->lno_pre;\n+\t\ts->lno_pre++;\n+\t\tstrbuf_add(&s->pending_rm, line, len);\n+\t\ts->pending_rm_count++;\n+\t\treturn s->ret;\n+\t}\n+\n+\tif (line[0] == '\\\\') {\n+\t\tif (s->pending_rm_count)\n+\t\t\tstrbuf_add(&s->pending_rm, line, len);\n+\t\telse if (s->rhunk_active)\n+\t\t\tstrbuf_add(&s->rhunk, line, len);\n+\t\t/* otherwise outside tracked range; drop silently */\n+\t\treturn s->ret;\n+\t}\n+\n+\tif (line[0] != '+' && line[0] != ' ')\n+\t\tBUG(\"unexpected diff line type '%c'\", line[0]);\n+\n+\tlno_0 = s->lno_post - 1;\n+\tcur_pre = s->lno_pre;\t/* save before advancing for context lines */\n+\ts->lno_post++;\n+\tif (line[0] == ' ')\n+\t\ts->lno_pre++;\n+\n+\t/* Advance past ranges we've passed */\n+\twhile (s->cur_range < s->ranges->nr &&\n+\t       lno_0 >= s->ranges->ranges[s->cur_range].end) {\n+\t\tif (s->rhunk_active)\n+\t\t\tflush_rhunk(s);\n+\t\tdiscard_pending_rm(s);\n+\t\ts->cur_range++;\n+\t}\n+\n+\t/* Past all ranges */\n+\tif (s->cur_range >= s->ranges->nr) {\n+\t\tdiscard_pending_rm(s);\n+\t\treturn s->ret;\n+\t}\n+\n+\tcur = &s->ranges->ranges[s->cur_range];\n+\n+\t/* Before current range */\n+\tif (lno_0 < cur->start) {\n+\t\tdiscard_pending_rm(s);\n+\t\treturn s->ret;\n+\t}\n+\n+\t/* In range so start a new range hunk if needed */\n+\tif (!s->rhunk_active) {\n+\t\ts->rhunk_active = 1;\n+\t\ts->rhunk_has_changes = 0;\n+\t\ts->rhunk_new_begin = lno_0 + 1;\n+\t\ts->rhunk_old_begin = s->pending_rm_count\n+\t\t\t? s->pending_rm_pre_begin : cur_pre;\n+\t\ts->rhunk_old_count = 0;\n+\t\ts->rhunk_new_count = 0;\n+\t\tstrbuf_reset(&s->rhunk);\n+\t}\n+\n+\t/* Flush pending removals into range hunk */\n+\tif (s->pending_rm_count) {\n+\t\tstrbuf_addbuf(&s->rhunk, &s->pending_rm);\n+\t\ts->rhunk_old_count += s->pending_rm_count;\n+\t\ts->rhunk_has_changes = 1;\n+\t\tdiscard_pending_rm(s);\n+\t}\n+\n+\tstrbuf_add(&s->rhunk, line, len);\n+\ts->rhunk_new_count++;\n+\tif (line[0] == '+')\n+\t\ts->rhunk_has_changes = 1;\n+\telse\n+\t\ts->rhunk_old_count++;\n+\n+\treturn s->ret;\n+}\n+\n static void pprint_rename(struct strbuf *name, const char *a, const char *b)\n {\n \tconst char *old_name = a;\n@@ -3589,7 +3817,8 @@ static void builtin_diff(const char *name_a,\n \t\t\t const char *xfrm_msg,\n \t\t\t int must_show_header,\n \t\t\t struct diff_options *o,\n-\t\t\t int complete_rewrite)\n+\t\t\t int complete_rewrite,\n+\t\t\t const struct range_set *line_ranges)\n {\n \tmmfile_t mf1, mf2;\n \tconst char *lbl[2];\n@@ -3823,6 +4052,52 @@ static void builtin_diff(const char *name_a,\n \t\t\t */\n \t\t\txdi_diff_outf(&mf1, &mf2, NULL, quick_consume,\n \t\t\t\t      &ecbdata, &xpp, &xecfg);\n+\t\t} else if (line_ranges) {\n+\t\t\tstruct line_range_callback lr_state;\n+\t\t\tunsigned int i;\n+\t\t\tlong max_span = 0;\n+\n+\t\t\tmemset(&lr_state, 0, sizeof(lr_state));\n+\t\t\tlr_state.orig_line_fn = fn_out_consume;\n+\t\t\tlr_state.orig_cb_data = &ecbdata;\n+\t\t\tlr_state.ranges = line_ranges;\n+\t\t\tstrbuf_init(&lr_state.rhunk, 0);\n+\t\t\tstrbuf_init(&lr_state.pending_rm, 0);\n+\n+\t\t\t/*\n+\t\t\t * Inflate ctxlen so that all changes within\n+\t\t\t * any single range are merged into one xdiff\n+\t\t\t * hunk and the inter-change context is emitted.\n+\t\t\t * The callback clips back to range boundaries.\n+\t\t\t *\n+\t\t\t * The optimal ctxlen depends on where changes\n+\t\t\t * fall within the range, which is only known\n+\t\t\t * after xdiff runs; the max range span is the\n+\t\t\t * upper bound that guarantees correctness in a\n+\t\t\t * single pass.\n+\t\t\t */\n+\t\t\tfor (i = 0; i < line_ranges->nr; i++) {\n+\t\t\t\tlong span = line_ranges->ranges[i].end -\n+\t\t\t\t\t    line_ranges->ranges[i].start;\n+\t\t\t\tif (span > max_span)\n+\t\t\t\t\tmax_span = span;\n+\t\t\t}\n+\t\t\tif (max_span > xecfg.ctxlen)\n+\t\t\t\txecfg.ctxlen = max_span;\n+\n+\t\t\tif (xdi_diff_outf(&mf1, &mf2,\n+\t\t\t\t\t  line_range_hunk_fn,\n+\t\t\t\t\t  line_range_line_fn,\n+\t\t\t\t\t  &lr_state, &xpp, &xecfg))\n+\t\t\t\tdie(\"unable to generate diff for %s\",\n+\t\t\t\t    one->path);\n+\n+\t\t\tflush_rhunk(&lr_state);\n+\t\t\tif (lr_state.ret)\n+\t\t\t\tdie(\"unable to generate diff for %s\",\n+\t\t\t\t    one->path);\n+\t\t\tstrbuf_release(&lr_state.rhunk);\n+\t\t\tstrbuf_release(&lr_state.pending_rm);\n \t\t} else if (xdi_diff_outf(&mf1, &mf2, NULL, fn_out_consume,\n \t\t\t\t\t &ecbdata, &xpp, &xecfg))\n \t\t\tdie(\"unable to generate diff for %s\", one->path);\n@@ -4660,7 +4935,7 @@ static void run_diff_cmd(const struct external_diff *pgm,\n \n \t\tbuiltin_diff(name, other ? other : name,\n \t\t\t     one, two, xfrm_msg, must_show_header,\n-\t\t\t     o, complete_rewrite);\n+\t\t\t     o, complete_rewrite, p->line_ranges);\n \t\tif (p->status == DIFF_STATUS_COPIED ||\n \t\t    p->status == DIFF_STATUS_RENAMED)\n \t\t\to->found_changes = 1;\ndiff --git a/diffcore.h b/diffcore.h\nindex 9c0a0e7aaf..d75038d1b3 100644\n--- a/diffcore.h\n+++ b/diffcore.h\n@@ -19,6 +19,17 @@ struct userdiff_driver;\n  * in anything else.\n  */\n \n+/* A range [start, end).  Lines are numbered starting at 0. */\n+struct range {\n+\tlong start, end;\n+};\n+\n+/* A set of ranges.  The ranges must always be disjoint and sorted. */\n+struct range_set {\n+\tunsigned int alloc, nr;\n+\tstruct range *ranges;\n+};\n+\n /* We internally use unsigned short as the score value,\n  * and rely on an int capable to hold 32-bits.  -B can take\n  * -Bmerge_score/break_score format and the two scores are\n@@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);\n struct diff_filepair {\n \tstruct diff_filespec *one;\n \tstruct diff_filespec *two;\n+\t/*\n+\t * Tracked line ranges for -L filtering; borrowed from\n+\t * line_log_data and must not be freed.\n+\t */\n+\tconst struct range_set *line_ranges;\n \tunsigned short int score;\n \tchar status; /* M C R A D U etc. (see Documentation/diff-format.adoc or DIFF_STATUS_* in diff.h) */\n \tunsigned broken_pair : 1;\ndiff --git a/line-log.c b/line-log.c\nindex 9d12ece181..858a899cd2 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -885,160 +885,6 @@ static void queue_diffs(struct line_log_data *range,\n \tmove_diff_queue(queue, &diff_queued_diff);\n }\n \n-static char *get_nth_line(long line, unsigned long *ends, void *data)\n-{\n-\tif (line == 0)\n-\t\treturn (char *)data;\n-\telse\n-\t\treturn (char *)data + ends[line] + 1;\n-}\n-\n-static void print_line(const char *prefix, char first,\n-\t\t       long line, unsigned long *ends, void *data,\n-\t\t       const char *color, const char *reset, FILE *file)\n-{\n-\tchar *begin = get_nth_line(line, ends, data);\n-\tchar *end = get_nth_line(line+1, ends, data);\n-\tint had_nl = 0;\n-\n-\tif (end > begin && end[-1] == '\\n') {\n-\t\tend--;\n-\t\thad_nl = 1;\n-\t}\n-\n-\tfputs(prefix, file);\n-\tfputs(color, file);\n-\tputc(first, file);\n-\tfwrite(begin, 1, end-begin, file);\n-\tfputs(reset, file);\n-\tputc('\\n', file);\n-\tif (!had_nl)\n-\t\tfputs(\"\\\\ No newline at end of file\\n\", file);\n-}\n-\n-static void dump_diff_hacky_one(struct rev_info *rev, struct line_log_data *range)\n-{\n-\tunsigned int i, j = 0;\n-\tlong p_lines, t_lines;\n-\tunsigned long *p_ends = NULL, *t_ends = NULL;\n-\tstruct diff_filepair *pair = range->pair;\n-\tstruct diff_ranges *diff = &range->diff;\n-\n-\tstruct diff_options *opt = &rev->diffopt;\n-\tconst char *prefix = diff_line_prefix(opt);\n-\tconst char *c_reset = diff_get_color(opt->use_color, DIFF_RESET);\n-\tconst char *c_frag = diff_get_color(opt->use_color, DIFF_FRAGINFO);\n-\tconst char *c_meta = diff_get_color(opt->use_color, DIFF_METAINFO);\n-\tconst char *c_old = diff_get_color(opt->use_color, DIFF_FILE_OLD);\n-\tconst char *c_new = diff_get_color(opt->use_color, DIFF_FILE_NEW);\n-\tconst char *c_context = diff_get_color(opt->use_color, DIFF_CONTEXT);\n-\n-\tif (!pair || !diff)\n-\t\tgoto out;\n-\n-\tif (pair->one->oid_valid)\n-\t\tfill_line_ends(rev->diffopt.repo, pair->one, &p_lines, &p_ends);\n-\tfill_line_ends(rev->diffopt.repo, pair->two, &t_lines, &t_ends);\n-\n-\tfprintf(opt->file, \"%s%sdiff --git a/%s b/%s%s\\n\", prefix, c_meta, pair->one->path, pair->two->path, c_reset);\n-\tfprintf(opt->file, \"%s%s--- %s%s%s\\n\", prefix, c_meta,\n-\t       pair->one->oid_valid ? \"a/\" : \"\",\n-\t       pair->one->oid_valid ? pair->one->path : \"/dev/null\",\n-\t       c_reset);\n-\tfprintf(opt->file, \"%s%s+++ b/%s%s\\n\", prefix, c_meta, pair->two->path, c_reset);\n-\tfor (i = 0; i < range->ranges.nr; i++) {\n-\t\tlong p_start, p_end;\n-\t\tlong t_start = range->ranges.ranges[i].start;\n-\t\tlong t_end = range->ranges.ranges[i].end;\n-\t\tlong t_cur = t_start;\n-\t\tunsigned int j_last;\n-\n-\t\t/*\n-\t\t * If a diff range touches multiple line ranges, then all\n-\t\t * those line ranges should be shown, so take a step back if\n-\t\t * the current line range is still in the previous diff range\n-\t\t * (even if only partially).\n-\t\t */\n-\t\tif (j > 0 && diff->target.ranges[j-1].end > t_start)\n-\t\t\tj--;\n-\n-\t\twhile (j < diff->target.nr && diff->target.ranges[j].end < t_start)\n-\t\t\tj++;\n-\t\tif (j == diff->target.nr || diff->target.ranges[j].start >= t_end)\n-\t\t\tcontinue;\n-\n-\t\t/* Scan ahead to determine the last diff that falls in this range */\n-\t\tj_last = j;\n-\t\twhile (j_last < diff->target.nr && diff->target.ranges[j_last].start < t_end)\n-\t\t\tj_last++;\n-\t\tif (j_last > j)\n-\t\t\tj_last--;\n-\n-\t\t/*\n-\t\t * Compute parent hunk headers: we know that the diff\n-\t\t * has the correct line numbers (but not all hunks).\n-\t\t * So it suffices to shift the start/end according to\n-\t\t * the line numbers of the first/last hunk(s) that\n-\t\t * fall in this range.\n-\t\t */\n-\t\tif (t_start < diff->target.ranges[j].start)\n-\t\t\tp_start = diff->parent.ranges[j].start - (diff->target.ranges[j].start-t_start);\n-\t\telse\n-\t\t\tp_start = diff->parent.ranges[j].start;\n-\t\tif (t_end > diff->target.ranges[j_last].end)\n-\t\t\tp_end = diff->parent.ranges[j_last].end + (t_end-diff->target.ranges[j_last].end);\n-\t\telse\n-\t\t\tp_end = diff->parent.ranges[j_last].end;\n-\n-\t\tif (!p_start && !p_end) {\n-\t\t\tp_start = -1;\n-\t\t\tp_end = -1;\n-\t\t}\n-\n-\t\t/* Now output a diff hunk for this range */\n-\t\tfprintf(opt->file, \"%s%s@@ -%ld,%ld +%ld,%ld @@%s\\n\",\n-\t\t       prefix, c_frag,\n-\t\t       p_start+1, p_end-p_start, t_start+1, t_end-t_start,\n-\t\t       c_reset);\n-\t\twhile (j < diff->target.nr && diff->target.ranges[j].start < t_end) {\n-\t\t\tint k;\n-\t\t\tfor (; t_cur < diff->target.ranges[j].start; t_cur++)\n-\t\t\t\tprint_line(prefix, ' ', t_cur, t_ends, pair->two->data,\n-\t\t\t\t\t   c_context, c_reset, opt->file);\n-\t\t\tfor (k = diff->parent.ranges[j].start; k < diff->parent.ranges[j].end; k++)\n-\t\t\t\tprint_line(prefix, '-', k, p_ends, pair->one->data,\n-\t\t\t\t\t   c_old, c_reset, opt->file);\n-\t\t\tfor (; t_cur < diff->target.ranges[j].end && t_cur < t_end; t_cur++)\n-\t\t\t\tprint_line(prefix, '+', t_cur, t_ends, pair->two->data,\n-\t\t\t\t\t   c_new, c_reset, opt->file);\n-\t\t\tj++;\n-\t\t}\n-\t\tfor (; t_cur < t_end; t_cur++)\n-\t\t\tprint_line(prefix, ' ', t_cur, t_ends, pair->two->data,\n-\t\t\t\t   c_context, c_reset, opt->file);\n-\t}\n-\n-out:\n-\tfree(p_ends);\n-\tfree(t_ends);\n-}\n-\n-/*\n- * NEEDSWORK: manually building a diff here is not the Right\n- * Thing(tm).  log -L should be built into the diff pipeline.\n- */\n-static void dump_diff_hacky(struct rev_info *rev, struct line_log_data *range)\n-{\n-\tconst char *prefix = diff_line_prefix(&rev->diffopt);\n-\n-\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n-\n-\twhile (range) {\n-\t\tdump_diff_hacky_one(rev, range);\n-\t\trange = range->next;\n-\t}\n-}\n-\n /*\n  * Unlike most other functions, this destructively operates on\n  * 'range'.\n@@ -1102,7 +948,7 @@ static int process_diff_filepair(struct rev_info *rev,\n \n static struct diff_filepair *diff_filepair_dup(struct diff_filepair *pair)\n {\n-\tstruct diff_filepair *new_filepair = xmalloc(sizeof(struct diff_filepair));\n+\tstruct diff_filepair *new_filepair = xcalloc(1, sizeof(struct diff_filepair));\n \tnew_filepair->one = pair->one;\n \tnew_filepair->two = pair->two;\n \tnew_filepair->one->count++;\n@@ -1160,11 +1006,25 @@ static int process_all_files(struct line_log_data **range_out,\n \n int line_log_print(struct rev_info *rev, struct commit *commit)\n {\n-\n \tshow_log(rev);\n \tif (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {\n \t\tstruct line_log_data *range = lookup_line_range(rev, commit);\n-\t\tdump_diff_hacky(rev, range);\n+\t\tstruct line_log_data *r;\n+\t\tconst char *prefix = diff_line_prefix(&rev->diffopt);\n+\n+\t\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n+\n+\t\tfor (r = range; r; r = r->next) {\n+\t\t\tif (r->pair) {\n+\t\t\t\tstruct diff_filepair *p =\n+\t\t\t\t\tdiff_filepair_dup(r->pair);\n+\t\t\t\tp->line_ranges = &r->ranges;\n+\t\t\t\tdiff_q(&diff_queued_diff, p);\n+\t\t\t}\n+\t\t}\n+\n+\t\tdiffcore_std(&rev->diffopt);\n+\t\tdiff_flush(&rev->diffopt);\n \t}\n \treturn 1;\n }\ndiff --git a/line-log.h b/line-log.h\nindex e9dadbc1a5..04a6ea64d3 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -1,22 +1,12 @@\n #ifndef LINE_LOG_H\n #define LINE_LOG_H\n \n+#include \"diffcore.h\" /* struct range, struct range_set */\n+\n struct rev_info;\n struct commit;\n struct string_list;\n \n-/* A range [start,end].  Lines are numbered starting at 0, and the\n- * ranges include start but exclude end. */\n-struct range {\n-\tlong start, end;\n-};\n-\n-/* A set of ranges.  The ranges must always be disjoint and sorted. */\n-struct range_set {\n-\tunsigned int alloc, nr;\n-\tstruct range *ranges;\n-};\n-\n /* A diff, encoded as the set of pre- and post-image ranges where the\n  * files differ. A pair of ranges corresponds to a hunk. */\n struct diff_ranges {\ndiff --git a/revision.c b/revision.c\nindex 047ff7e458..9f68bedf13 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3111,6 +3111,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tif (want_ancestry(revs))\n \t\t\trevs->limited = 1;\n \t\trevs->topo_order = 1;\n+\t\tif (!revs->diffopt.output_format)\n+\t\t\trevs->diffopt.output_format = DIFF_FORMAT_PATCH;\n \t}\n \n \tif (revs->topo_order && !generation_numbers_enabled(the_repository))\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 659a943aa1..6a307e911b 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -129,7 +129,7 @@ test_expect_success '-L with --output' '\n \tgit checkout parallel-change &&\n \tgit log --output=log -L :main:b.c >output &&\n \ttest_must_be_empty output &&\n-\ttest_line_count = 70 log\n+\ttest_line_count = 75 log\n '\n \n test_expect_success 'range_set_union' '\n@@ -340,13 +340,19 @@ test_expect_success 'zero-width regex .* matches any function name' '\n '\n \n test_expect_success 'show line-log with graph' '\n+\tgit checkout parent-oids &&\n+\thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n+\thead_blob_new=$(git rev-parse --short HEAD:file.c) &&\n+\troot_blob=$(git rev-parse --short HEAD~4:file.c) &&\n+\tnull_blob=$(test_oid zero | cut -c1-7) &&\n \tqz_to_tab_space >expect <<-EOF &&\n \t* $head_oid Modify func2() in file.c\n \t|Z\n \t| diff --git a/file.c b/file.c\n+\t| index $head_blob_old..$head_blob_new 100644\n \t| --- a/file.c\n \t| +++ b/file.c\n-\t| @@ -6,4 +6,4 @@\n+\t| @@ -6,4 +6,4 @@ int func1()\n \t|  int func2()\n \t|  {\n \t| -    return F2;\n@@ -355,6 +361,8 @@ test_expect_success 'show line-log with graph' '\n \t* $root_oid Add func1() and func2() in file.c\n \tZZ\n \t  diff --git a/file.c b/file.c\n+\t  new file mode 100644\n+\t  index $null_blob..$root_blob\n \t  --- /dev/null\n \t  +++ b/file.c\n \t  @@ -0,0 +6,4 @@\ndiff --git a/t/t4211/sha1/expect.beginning-of-file b/t/t4211/sha1/expect.beginning-of-file\nindex 91b4054898..52c90afb3a 100644\n--- a/t/t4211/sha1/expect.beginning-of-file\n+++ b/t/t4211/sha1/expect.beginning-of-file\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100\n     change at very beginning\n \n diff --git a/a.c b/a.c\n+index bdb2bb1..5e709a1 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,4 @@\n@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,3 @@\n@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +1,3 @@\ndiff --git a/t/t4211/sha1/expect.end-of-file b/t/t4211/sha1/expect.end-of-file\nindex bd25bb2f59..c40036899a 100644\n--- a/t/t4211/sha1/expect.end-of-file\n+++ b/t/t4211/sha1/expect.end-of-file\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,5 @@\n+@@ -20,3 +20,5 @@ long f(long x)\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,3 @@\n+@@ -20,3 +20,3 @@ int main ()\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -19,3 +19,3 @@\n+@@ -19,3 +19,3 @@ int f(int x)\n -\tprintf(\"%d\\n\", f(15));\n +\tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +18,3 @@\ndiff --git a/t/t4211/sha1/expect.move-support-f b/t/t4211/sha1/expect.move-support-f\nindex c905e01bc2..ead6500d4d 100644\n--- a/t/t4211/sha1/expect.move-support-f\n+++ b/t/t4211/sha1/expect.move-support-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 5de3ea4..bf79c2f 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,9 +4,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.multiple b/t/t4211/sha1/expect.multiple\nindex 1eee8a7801..a41851a51d 100644\n--- a/t/t4211/sha1/expect.multiple\n+++ b/t/t4211/sha1/expect.multiple\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.multiple-overlapping b/t/t4211/sha1/expect.multiple-overlapping\nindex d930b6eec4..0ec9990eab 100644\n--- a/t/t4211/sha1/expect.multiple-overlapping\n+++ b/t/t4211/sha1/expect.multiple-overlapping\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha1/expect.multiple-superset b/t/t4211/sha1/expect.multiple-superset\nindex d930b6eec4..0ec9990eab 100644\n--- a/t/t4211/sha1/expect.multiple-superset\n+++ b/t/t4211/sha1/expect.multiple-superset\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha1/expect.no-assertion-error b/t/t4211/sha1/expect.no-assertion-error\nindex 994c37db1e..54c568f273 100644\n--- a/t/t4211/sha1/expect.no-assertion-error\n+++ b/t/t4211/sha1/expect.no-assertion-error\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100\n     move within the file\n \n diff --git a/b.c b/b.c\n+index bf79c2f..27c829c 100644\n --- a/b.c\n +++ b/b.c\n @@ -25,0 +18,9 @@\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main\nindex 052def8074..65a8cc673a 100644\n--- a/t/t4211/sha1/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha1/expect.parallel-change-f-to-main\n@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 5de3ea4..bf79c2f 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,14 +4,14 @@\n@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200\n     change on another line of history while rename happens\n \n diff --git a/a.c b/a.c\n+index 5de3ea4..01b5b65 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,14 +4,14 @@\n@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha1/expect.simple-f b/t/t4211/sha1/expect.simple-f\nindex a1f5bc49c8..b24ae40e03 100644\n--- a/t/t4211/sha1/expect.simple-f\n+++ b/t/t4211/sha1/expect.simple-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.simple-f-to-main b/t/t4211/sha1/expect.simple-f-to-main\nindex a475768710..cd92100dfc 100644\n--- a/t/t4211/sha1/expect.simple-f-to-main\n+++ b/t/t4211/sha1/expect.simple-f-to-main\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index e51de13..bdb2bb1 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha1/expect.simple-main b/t/t4211/sha1/expect.simple-main\nindex 39ce39bebe..ff31291d34 100644\n--- a/t/t4211/sha1/expect.simple-main\n+++ b/t/t4211/sha1/expect.simple-main\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.simple-main-to-end b/t/t4211/sha1/expect.simple-main-to-end\nindex 8480bd9cc4..4bef21e657 100644\n--- a/t/t4211/sha1/expect.simple-main-to-end\n+++ b/t/t4211/sha1/expect.simple-main-to-end\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha1/expect.two-ranges b/t/t4211/sha1/expect.two-ranges\nindex c5164f3be3..aed01522e3 100644\n--- a/t/t4211/sha1/expect.two-ranges\n+++ b/t/t4211/sha1/expect.two-ranges\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 3233403..e51de13 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 444e415..3233403 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha1/expect.vanishes-early b/t/t4211/sha1/expect.vanishes-early\nindex 1f7cd06941..a413ad3659 100644\n--- a/t/t4211/sha1/expect.vanishes-early\n+++ b/t/t4211/sha1/expect.vanishes-early\n@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index 0b9cae5..5de3ea4 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +24,1 @@\n--}\n-\\ No newline at end of file\n+@@ -23,0 +24,1 @@ int main ()\n +/* incomplete lines are bad! */\n \n commit 100b61a6f2f720f812620a9d10afb3a960ccb73c\n@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index 5e709a1..0b9cae5 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +22,1 @@\n+@@ -22,1 +22,1 @@ int main ()\n -}\n +}\n \\ No newline at end of file\n@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..444e415\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +20,1 @@\ndiff --git a/t/t4211/sha256/expect.beginning-of-file b/t/t4211/sha256/expect.beginning-of-file\nindex 5adfdfc1a1..e8d62328cf 100644\n--- a/t/t4211/sha256/expect.beginning-of-file\n+++ b/t/t4211/sha256/expect.beginning-of-file\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:47:40 2013 +0100\n     change at very beginning\n \n diff --git a/a.c b/a.c\n+index 3a78aaf..d325124 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,4 @@\n@@ -20,6 +21,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -1,3 +1,3 @@\n@@ -35,6 +37,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +1,3 @@\ndiff --git a/t/t4211/sha256/expect.end-of-file b/t/t4211/sha256/expect.end-of-file\nindex 03ab5c1784..3b2e2384da 100644\n--- a/t/t4211/sha256/expect.end-of-file\n+++ b/t/t4211/sha256/expect.end-of-file\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,5 @@\n+@@ -20,3 +20,5 @@ long f(long x)\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -20,3 +20,3 @@\n+@@ -20,3 +20,3 @@ int main ()\n  \tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n -}\n@@ -39,9 +41,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -19,3 +19,3 @@\n+@@ -19,3 +19,3 @@ int f(int x)\n -\tprintf(\"%d\\n\", f(15));\n +\tprintf(\"%ld\\n\", f(15));\n  \treturn 0;\n@@ -54,6 +57,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +18,3 @@\ndiff --git a/t/t4211/sha256/expect.move-support-f b/t/t4211/sha256/expect.move-support-f\nindex 223b4ed2a0..f49abcea3e 100644\n--- a/t/t4211/sha256/expect.move-support-f\n+++ b/t/t4211/sha256/expect.move-support-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 62c1fc2..69cb69c 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,9 +4,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -47,6 +49,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -67,6 +70,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.multiple b/t/t4211/sha256/expect.multiple\nindex dbd987b74a..0dee50ffb7 100644\n--- a/t/t4211/sha256/expect.multiple\n+++ b/t/t4211/sha256/expect.multiple\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,6 +45,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -71,6 +74,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -91,6 +95,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.multiple-overlapping b/t/t4211/sha256/expect.multiple-overlapping\nindex 9015a45a25..b8c260e8ae 100644\n--- a/t/t4211/sha256/expect.multiple-overlapping\n+++ b/t/t4211/sha256/expect.multiple-overlapping\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha256/expect.multiple-superset b/t/t4211/sha256/expect.multiple-superset\nindex 9015a45a25..b8c260e8ae 100644\n--- a/t/t4211/sha256/expect.multiple-superset\n+++ b/t/t4211/sha256/expect.multiple-superset\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,21 @@\n@@ -39,6 +40,7 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,19 +4,19 @@\n@@ -71,6 +73,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -102,6 +105,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,19 +3,19 @@\n@@ -134,6 +138,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,18 +3,19 @@\n@@ -164,6 +169,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,18 @@\ndiff --git a/t/t4211/sha256/expect.no-assertion-error b/t/t4211/sha256/expect.no-assertion-error\nindex 36ed12aa9c..c25f2ce19c 100644\n--- a/t/t4211/sha256/expect.no-assertion-error\n+++ b/t/t4211/sha256/expect.no-assertion-error\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:50:24 2013 +0100\n     move within the file\n \n diff --git a/b.c b/b.c\n+index 69cb69c..a0d566e 100644\n --- a/b.c\n +++ b/b.c\n @@ -25,0 +18,9 @@\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -45,9 +47,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -63,9 +66,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -80,6 +84,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main\nindex e68f8928ea..3178989253 100644\n--- a/t/t4211/sha256/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha256/expect.parallel-change-f-to-main\n@@ -13,6 +13,7 @@ Date:   Thu Feb 28 10:49:50 2013 +0100\n     another simple change\n \n diff --git a/b.c b/b.c\n+index 62c1fc2..69cb69c 100644\n --- a/b.c\n +++ b/b.c\n @@ -4,14 +4,14 @@\n@@ -39,6 +40,7 @@ Date:   Fri Apr 12 16:15:57 2013 +0200\n     change on another line of history while rename happens\n \n diff --git a/a.c b/a.c\n+index 62c1fc2..e1e8475 100644\n --- a/a.c\n +++ b/a.c\n @@ -4,14 +4,14 @@\n@@ -65,6 +67,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -91,6 +94,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -117,6 +121,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -142,6 +147,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha256/expect.simple-f b/t/t4211/sha256/expect.simple-f\nindex 65508d7c0b..983c711fe3 100644\n--- a/t/t4211/sha256/expect.simple-f\n+++ b/t/t4211/sha256/expect.simple-f\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -26,6 +27,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -46,6 +48,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.simple-f-to-main b/t/t4211/sha256/expect.simple-f-to-main\nindex 77b721c196..e67fa017a7 100644\n--- a/t/t4211/sha256/expect.simple-f-to-main\n+++ b/t/t4211/sha256/expect.simple-f-to-main\n@@ -5,6 +5,7 @@ Date:   Thu Feb 28 10:45:41 2013 +0100\n     touch comment\n \n diff --git a/a.c b/a.c\n+index 75c0119..3a78aaf 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -31,6 +32,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,14 +3,14 @@\n@@ -57,6 +59,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,13 +3,14 @@\n@@ -82,6 +85,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,13 @@\ndiff --git a/t/t4211/sha256/expect.simple-main b/t/t4211/sha256/expect.simple-main\nindex d20708c9f9..0792b27cad 100644\n--- a/t/t4211/sha256/expect.simple-main\n+++ b/t/t4211/sha256/expect.simple-main\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,9 +43,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -58,6 +61,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.simple-main-to-end b/t/t4211/sha256/expect.simple-main-to-end\nindex 617cdf3481..d3bd7c7bc6 100644\n--- a/t/t4211/sha256/expect.simple-main-to-end\n+++ b/t/t4211/sha256/expect.simple-main-to-end\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,7 @@\n+@@ -18,5 +18,7 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -25,9 +26,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -43,9 +45,10 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n-@@ -17,5 +17,5 @@\n+@@ -17,5 +17,5 @@ int f(int x)\n  int main ()\n  {\n -\tprintf(\"%d\\n\", f(15));\n@@ -60,6 +63,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +16,5 @@\ndiff --git a/t/t4211/sha256/expect.two-ranges b/t/t4211/sha256/expect.two-ranges\nindex 6a94d3b9cb..7735b19723 100644\n--- a/t/t4211/sha256/expect.two-ranges\n+++ b/t/t4211/sha256/expect.two-ranges\n@@ -5,9 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -23,9 +24,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -18,5 +18,5 @@\n+@@ -18,5 +18,5 @@ long f(long x)\n  int main ()\n  {\n  \tprintf(\"%ld\\n\", f(15));\n@@ -41,6 +43,7 @@ Date:   Thu Feb 28 10:45:16 2013 +0100\n     touch both functions\n \n diff --git a/a.c b/a.c\n+index 7a296b9..75c0119 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,9 +3,9 @@\n@@ -69,6 +72,7 @@ Date:   Thu Feb 28 10:44:55 2013 +0100\n     change f()\n \n diff --git a/a.c b/a.c\n+index 9f550c3..7a296b9 100644\n --- a/a.c\n +++ b/a.c\n @@ -3,8 +3,9 @@\n@@ -89,6 +93,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +3,8 @@\ndiff --git a/t/t4211/sha256/expect.vanishes-early b/t/t4211/sha256/expect.vanishes-early\nindex 11ec9bdecf..bc33b963dc 100644\n--- a/t/t4211/sha256/expect.vanishes-early\n+++ b/t/t4211/sha256/expect.vanishes-early\n@@ -5,11 +5,10 @@ Date:   Thu Feb 28 10:48:43 2013 +0100\n     change back to complete line\n \n diff --git a/a.c b/a.c\n+index e4fa1d8..62c1fc2 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +24,1 @@\n--}\n-\\ No newline at end of file\n+@@ -23,0 +24,1 @@ int main ()\n +/* incomplete lines are bad! */\n \n commit 29f32ac3141c48b22803e5c4127b719917b67d0f8ca8c5248bebfa2a19f7da10\n@@ -19,9 +18,10 @@ Date:   Thu Feb 28 10:48:10 2013 +0100\n     change to an incomplete line at end\n \n diff --git a/a.c b/a.c\n+index d325124..e4fa1d8 100644\n --- a/a.c\n +++ b/a.c\n-@@ -22,1 +22,1 @@\n+@@ -22,1 +22,1 @@ int main ()\n -}\n +}\n \\ No newline at end of file\n@@ -33,6 +33,8 @@ Date:   Thu Feb 28 10:44:48 2013 +0100\n     initial\n \n diff --git a/a.c b/a.c\n+new file mode 100644\n+index 0000000..9f550c3\n --- /dev/null\n +++ b/a.c\n @@ -0,0 +20,1 @@\n-- \ngitgitgadget\n\n"},{"id":"539195","messageId":"cf7720ae9847c3060929ebd7ff30fd7762ac4dfe.1773714095.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","subject":"[PATCH v2 3/4] t4211: add tests for -L with standard diff options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T02:21:34Z","receivedAt":"2026-03-17T02:21:43Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L output flows through the standard diff pipeline, verify\nthat previously-ignored diff options work: formatting (--word-diff,\n--word-diff-regex, --no-prefix, --src/dst-prefix, --full-index,\n--abbrev), whitespace handling (-w, -b), output indicators\n(--output-indicator-new/old/context), direction reversal (-R),\n--color-moved, and pickaxe options (-S, -G).\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n t/t4211-line-log.sh | 281 ++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 281 insertions(+)\n\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 6a307e911b..aaf197d2ed 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -339,6 +339,92 @@ test_expect_success 'zero-width regex .* matches any function name' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'setup for diff pipeline tests' '\n+\tgit checkout parent-oids &&\n+\n+\thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n+\thead_blob_new=$(git rev-parse --short HEAD:file.c) &&\n+\troot_blob=$(git rev-parse --short HEAD~4:file.c) &&\n+\tnull_blob=$(test_oid zero | cut -c1-7) &&\n+\thead_blob_old_full=$(git rev-parse HEAD^:file.c) &&\n+\thead_blob_new_full=$(git rev-parse HEAD:file.c) &&\n+\troot_blob_full=$(git rev-parse HEAD~4:file.c) &&\n+\tnull_blob_full=$(test_oid zero)\n+'\n+\n+test_expect_success '-L diff output includes index and new file mode' '\n+\tgit log -L:func2:file.c --format= >actual &&\n+\n+\t# Output should contain index headers (not present in old code path)\n+\tgrep \"^index $head_blob_old\\.\\.$head_blob_new 100644\" actual &&\n+\n+\t# Root commit should show new file mode and null index\n+\tgrep \"^new file mode 100644\" actual &&\n+\tgrep \"^index $null_blob\\.\\.$root_blob$\" actual &&\n+\n+\t# Hunk headers should include funcname context\n+\tgrep \"^@@ .* @@ int func1()\" actual\n+'\n+\n+test_expect_success '-L with --word-diff' '\n+\tcat >expect <<-\\EOF &&\n+\n+\tdiff --git a/file.c b/file.c\n+\t--- a/file.c\n+\t+++ b/file.c\n+\t@@ -6,4 +6,4 @@ int func1()\n+\tint func2()\n+\t{\n+\t    return [-F2;-]{+F2 + 2;+}\n+\t}\n+\n+\tdiff --git a/file.c b/file.c\n+\tnew file mode 100644\n+\t--- /dev/null\n+\t+++ b/file.c\n+\t@@ -0,0 +6,4 @@\n+\t{+int func2()+}\n+\t{+{+}\n+\t{+    return F2;+}\n+\t{+}+}\n+\tEOF\n+\tgit log -L:func2:file.c --word-diff --format= >actual &&\n+\tgrep -v \"^index \" actual >actual.filtered &&\n+\tgrep -v \"^index \" expect >expect.filtered &&\n+\ttest_cmp expect.filtered actual.filtered\n+'\n+\n+test_expect_success '-L with --no-prefix' '\n+\tgit log -L:func2:file.c --no-prefix --format= >actual &&\n+\tgrep \"^diff --git file.c file.c\" actual &&\n+\tgrep \"^--- file.c\" actual &&\n+\t! grep \"^--- a/\" actual\n+'\n+\n+test_expect_success '-L with --full-index' '\n+\tgit log -L:func2:file.c --full-index --format= >actual &&\n+\tgrep \"^index $head_blob_old_full\\.\\.$head_blob_new_full 100644\" actual &&\n+\tgrep \"^index $null_blob_full\\.\\.$root_blob_full$\" actual\n+'\n+\n+test_expect_success 'setup -L with whitespace change' '\n+\tgit checkout -b ws-change parent-oids &&\n+\tsed \"s/    return F2 + 2;/\treturn F2 + 2;/\" file.c >tmp &&\n+\tmv tmp file.c &&\n+\tgit commit -a -m \"Whitespace change in func2()\"\n+'\n+\n+test_expect_success '-L with --ignore-all-space suppresses whitespace-only diff' '\n+\tgit log -L:func2:file.c --format= >without_w &&\n+\tgit log -L:func2:file.c --format= -w >with_w &&\n+\n+\t# Without -w: three commits produce diffs (whitespace, modify, root)\n+\ttest $(grep -c \"^diff --git\" without_w) = 3 &&\n+\n+\t# With -w: whitespace-only commit produces no hunk, so only two diffs\n+\ttest $(grep -c \"^diff --git\" with_w) = 2\n+'\n+\n test_expect_success 'show line-log with graph' '\n \tgit checkout parent-oids &&\n \thead_blob_old=$(git rev-parse --short HEAD^:file.c) &&\n@@ -430,4 +516,199 @@ test_expect_failure '-L --find-object should filter commits by object' '\n \ttest_must_be_empty actual\n '\n \n+test_expect_success '-L with --word-diff-regex' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --word-diff \\\n+\t\t--word-diff-regex=\"[a-zA-Z0-9_]+\" --format= >actual &&\n+\t# Word-diff markers must be present\n+\tgrep \"{+\" actual &&\n+\tgrep \"+}\" actual &&\n+\t# No line-level +/- markers (word-diff replaces them);\n+\t# exclude --- header lines from the check\n+\t! grep \"^+[^+]\" actual &&\n+\t! grep \"^-[^-]\" actual\n+'\n+\n+test_expect_success '-L with --src-prefix and --dst-prefix' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --src-prefix=old/ --dst-prefix=new/ \\\n+\t\t--format= >actual &&\n+\tgrep \"^diff --git old/file.c new/file.c\" actual &&\n+\tgrep \"^--- old/file.c\" actual &&\n+\tgrep \"^+++ new/file.c\" actual &&\n+\t! grep \"^--- a/\" actual\n+'\n+\n+test_expect_success '-L with --abbrev' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --abbrev=4 --format= -1 >actual &&\n+\t# 4-char abbreviated hashes on index line\n+\tgrep \"^index [0-9a-f]\\{4\\}\\.\\.[0-9a-f]\\{4\\}\" actual\n+'\n+\n+test_expect_success '-L with -b suppresses whitespace-only diff' '\n+\tgit checkout ws-change &&\n+\tgit log -L:func2:file.c --format= >without_b &&\n+\tgit log -L:func2:file.c --format= -b >with_b &&\n+\ttest $(grep -c \"^diff --git\" without_b) = 3 &&\n+\ttest $(grep -c \"^diff --git\" with_b) = 2\n+'\n+\n+test_expect_success '-L with --output-indicator-*' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --output-indicator-new=\">\" \\\n+\t\t--output-indicator-old=\"<\" --output-indicator-context=\"|\" \\\n+\t\t--format= -1 >actual &&\n+\tgrep \"^>\" actual &&\n+\tgrep \"^<\" actual &&\n+\tgrep \"^|\" actual &&\n+\t# No standard +/-/space content markers; exclude ---/+++ headers\n+\t! grep \"^+[^+]\" actual &&\n+\t! grep \"^-[^-]\" actual &&\n+\t! grep \"^ \" actual\n+'\n+\n+test_expect_success '-L with -R reverses diff' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -R --format= -1 >actual &&\n+\tgrep \"^diff --git b/file.c a/file.c\" actual &&\n+\tgrep \"^--- b/file.c\" actual &&\n+\tgrep \"^+++ a/file.c\" actual &&\n+\t# The modification added \"F2 + 2\", so reversed it is removed\n+\tgrep \"^-.*F2 + 2\" actual &&\n+\tgrep \"^+.*return F2;\" actual\n+'\n+\n+test_expect_success 'setup for color-moved test' '\n+\tgit checkout -b color-moved-test parent-oids &&\n+\tcat >big.c <<-\\EOF &&\n+\tint bigfunc()\n+\t{\n+\t    int a = 1;\n+\t    int b = 2;\n+\t    int c = 3;\n+\t    return a + b + c;\n+\t}\n+\tEOF\n+\tgit add big.c &&\n+\tgit commit -m \"add bigfunc\" &&\n+\tsed \"s/    /\t/\" big.c >tmp && mv tmp big.c &&\n+\tgit commit -a -m \"reindent bigfunc\"\n+'\n+\n+test_expect_success '-L with --color-moved' '\n+\tgit log -L:bigfunc:big.c --color-moved=zebra \\\n+\t\t--color-moved-ws=ignore-all-space \\\n+\t\t--color=always --format= -1 >actual.raw &&\n+\ttest_decode_color <actual.raw >actual &&\n+\t# Old moved lines: bold magenta; new moved lines: bold cyan\n+\tgrep \"BOLD;MAGENTA\" actual &&\n+\tgrep \"BOLD;CYAN\" actual\n+'\n+\n+test_expect_success 'setup for no-newline-at-eof tests' '\n+\tgit checkout --orphan no-newline &&\n+\tgit reset --hard &&\n+\tprintf \"int top()\\n{\\n    return 1;\\n}\\n\\nint bot()\\n{\\n    return 2;\\n}\" >noeol.c &&\n+\tgit add noeol.c &&\n+\ttest_tick &&\n+\tgit commit -m \"add noeol.c (no trailing newline)\" &&\n+\tsed \"s/return 2/return 22/\" noeol.c >tmp && mv tmp noeol.c &&\n+\tgit commit -a -m \"modify bot()\" &&\n+\tprintf \"int top()\\n{\\n    return 1;\\n}\\n\\nint bot()\\n{\\n    return 33;\\n}\\n\" >noeol.c &&\n+\tgit commit -a -m \"modify bot() and add trailing newline\"\n+'\n+\n+# When the tracked function is at the end of a file with no trailing\n+# newline, the \"\\ No newline at end of file\" marker should appear.\n+test_expect_success '-L no-newline-at-eof appears in tracked range' '\n+\tgit log -L:bot:noeol.c --format= -1 HEAD~1 >actual &&\n+\tgrep \"No newline at end of file\" actual\n+'\n+\n+# When tracking a function that ends before the no-newline content,\n+# the marker should not appear in the output.\n+test_expect_success '-L no-newline-at-eof suppressed outside range' '\n+\tgit log -L:top:noeol.c --format= >actual &&\n+\t! grep \"No newline at end of file\" actual\n+'\n+\n+# When a commit removes a no-newline last line and replaces it with\n+# a newline-terminated line, the marker should still appear (on the\n+# old side of the diff).\n+test_expect_success '-L no-newline-at-eof marker with deleted line' '\n+\tgit log -L:bot:noeol.c --format= -1 >actual &&\n+\tgrep \"No newline at end of file\" actual\n+'\n+\n+test_expect_success 'setup for range boundary deletion test' '\n+\tgit checkout --orphan range-boundary &&\n+\tgit reset --hard &&\n+\tcat >boundary.c <<-\\EOF &&\n+\tvoid above()\n+\t{\n+\t    return;\n+\t}\n+\n+\tvoid tracked()\n+\t{\n+\t    int x = 1;\n+\t    int y = 2;\n+\t}\n+\n+\tvoid below()\n+\t{\n+\t    return;\n+\t}\n+\tEOF\n+\tgit add boundary.c &&\n+\ttest_tick &&\n+\tgit commit -m \"add boundary.c\" &&\n+\tcat >boundary.c <<-\\EOF &&\n+\tvoid above()\n+\t{\n+\t    return;\n+\t}\n+\n+\tvoid tracked()\n+\t{\n+\t    int x = 1;\n+\t    int y = 2;\n+\t}\n+\n+\tvoid below_renamed()\n+\t{\n+\t    return 0;\n+\t}\n+\tEOF\n+\tgit commit -a -m \"modify below() only\"\n+'\n+\n+# When only a function below the tracked range is modified, the\n+# tracked function should not produce a diff.\n+test_expect_success '-L suppresses deletions outside tracked range' '\n+\tgit log -L:tracked:boundary.c --format= >actual &&\n+\ttest $(grep -c \"^diff --git\" actual) = 1\n+'\n+\n+test_expect_success '-L with -S filters to string-count changes' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -S \"F2 + 2\" --format= >actual &&\n+\t# -S searches the whole file, not just the tracked range;\n+\t# combined with the -L range walk, this selects commits that\n+\t# both touch func2 and change the count of \"F2 + 2\" in the file.\n+\ttest $(grep -c \"^diff --git\" actual) = 1 &&\n+\tgrep \"F2 + 2\" actual\n+'\n+\n+test_expect_success '-L with -G filters to diff-text matches' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -G \"F2 [+] 2\" --format= >actual &&\n+\t# -G greps the whole-file diff text, not just the tracked range;\n+\t# combined with -L, this selects commits that both touch func2\n+\t# and have \"F2 + 2\" in their diff.\n+\ttest $(grep -c \"^diff --git\" actual) = 1 &&\n+\tgrep \"F2 + 2\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n\n"},{"id":"539197","messageId":"93e771b7cd7775cc67e3305adb4216773acc8e90.1773714095.git.gitgitgadget@gmail.com","threadId":"65159","inReplyTo":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","subject":"[PATCH v2 4/4] doc: note that -L supports patch formatting and pickaxe options","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-03-17T02:21:35Z","receivedAt":"2026-03-17T02:21:45Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L output flows through the standard diff pipeline,\ndocument that patch formatting options like --word-diff,\n--color-moved, --no-prefix, whitespace handling (-w, -b),\nand pickaxe options (-S, -G) are supported.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n Documentation/line-range-options.adoc | 4 ++++\n 1 file changed, 4 insertions(+)\n\ndiff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc\nindex c44ba05320..ecb2c79fb9 100644\n--- a/Documentation/line-range-options.adoc\n+++ b/Documentation/line-range-options.adoc\n@@ -12,4 +12,8 @@\n \t(namely `--raw`, `--numstat`, `--shortstat`, `--dirstat`, `--summary`,\n \t`--name-only`, `--name-status`, `--check`) are not currently implemented.\n +\n+Patch formatting options such as `--word-diff`, `--color-moved`,\n+`--no-prefix`, and whitespace options (`-w`, `-b`) are supported,\n+as are pickaxe options (`-S`, `-G`).\n++\n include::line-range-format.adoc[]\n-- \ngitgitgadget\n"},{"id":"539259","messageId":"xmqqecliji1a.fsf@gitster.g","threadId":"65159","inReplyTo":"4e2bc55082e79654ebf0d30fc00479a5eb29f750.1773714095.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 2/4] line-log: route -L output through the standard diff pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-17T20:52:01Z","receivedAt":"2026-03-17T20:52:03Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Michael Montalbo <mmontalbo@gmail.com>\n>\n> `git log -L` has always bypassed the standard diff pipeline.\n> `dump_diff_hacky()` in line-log.c hand-rolls its own diff headers and\n> hunk output, which means most diff formatting options are silently\n> ignored.  A NEEDSWORK comment has acknowledged this since the feature\n> was introduced:\n>\n>     /*\n>      * NEEDSWORK: manually building a diff here is not the Right\n>      * Thing(tm).  log -L should be built into the diff pipeline.\n>      */\n>\n> Remove `dump_diff_hacky()` and its helpers and route -L output through\n> `builtin_diff()` / `fn_out_consume()`, the same path used by `git diff`\n> and `git log -p`.  The mechanism is a pair of callback wrappers that sit\n> between `xdi_diff_outf()` and `fn_out_consume()`, filtering xdiff's\n> output to only the tracked line ranges.  To ensure xdiff emits all lines\n> within each range as context, the context length is inflated to span the\n> largest range.\n>\n> Wire up the `-L` implies `--patch` default in revision setup rather\n> than forcing it at output time, so `line_log_print()` is just\n> `diffcore_std()` + `diff_flush()` with no format save/restore.\n> Rename detection is a no-op since pairs are already resolved during\n> the history walk in `queue_diffs()`, but running `diffcore_std()`\n> means `-S`/`-G` (pickaxe), `--orderfile`, and `--diff-filter` now\n> work with `-L`, and `diff_resolve_rename_copy()` sets pair statuses\n> correctly without manual assignment.\n>\n> Switch `diff_filepair_dup()` from `xmalloc` to `xcalloc` so that new\n> fields (including `line_ranges`) are zero-initialized by default.\n>\n> As a result, diff formatting options that were previously silently\n> ignored (e.g. --word-diff, --no-prefix, -w, --color-moved) now work\n> with -L, and output gains `index` lines, `new file mode` headers, and\n> funcname context in `@@` headers.  This is a user-visible output change:\n> tools that parse -L output may need to handle the additional header\n> lines.\n>\n> The context-length inflation means xdiff may process more output than\n> needed for very wide line ranges, but benchmarks on files up to 7800\n> lines show no measurable regression.\n>\n> Signed-off-by: Michael Montalbo <mmontalbo@gmail.com>\n> ---\n>  diff.c                                        | 279 +++++++++++++++++-\n>  diffcore.h                                    |  16 +\n>  line-log.c                                    | 174 ++---------\n>  line-log.h                                    |  14 +-\n>  revision.c                                    |   2 +\n>  t/t4211-line-log.sh                           |  12 +-\n>  t/t4211/sha1/expect.beginning-of-file         |   4 +\n>  t/t4211/sha1/expect.end-of-file               |  11 +-\n>  t/t4211/sha1/expect.move-support-f            |   5 +\n>  t/t4211/sha1/expect.multiple                  |  10 +-\n>  t/t4211/sha1/expect.multiple-overlapping      |   7 +\n>  t/t4211/sha1/expect.multiple-superset         |   7 +\n>  t/t4211/sha1/expect.no-assertion-error        |  12 +-\n>  t/t4211/sha1/expect.parallel-change-f-to-main |   7 +\n>  t/t4211/sha1/expect.simple-f                  |   4 +\n>  t/t4211/sha1/expect.simple-f-to-main          |   5 +\n>  t/t4211/sha1/expect.simple-main               |  11 +-\n>  t/t4211/sha1/expect.simple-main-to-end        |  11 +-\n>  t/t4211/sha1/expect.two-ranges                |  10 +-\n>  t/t4211/sha1/expect.vanishes-early            |  10 +-\n>  t/t4211/sha256/expect.beginning-of-file       |   4 +\n>  t/t4211/sha256/expect.end-of-file             |  11 +-\n>  t/t4211/sha256/expect.move-support-f          |   5 +\n>  t/t4211/sha256/expect.multiple                |  10 +-\n>  t/t4211/sha256/expect.multiple-overlapping    |   7 +\n>  t/t4211/sha256/expect.multiple-superset       |   7 +\n>  t/t4211/sha256/expect.no-assertion-error      |  12 +-\n>  .../sha256/expect.parallel-change-f-to-main   |   7 +\n>  t/t4211/sha256/expect.simple-f                |   4 +\n>  t/t4211/sha256/expect.simple-f-to-main        |   5 +\n>  t/t4211/sha256/expect.simple-main             |  11 +-\n>  t/t4211/sha256/expect.simple-main-to-end      |  11 +-\n>  t/t4211/sha256/expect.two-ranges              |  10 +-\n>  t/t4211/sha256/expect.vanishes-early          |  10 +-\n>  34 files changed, 512 insertions(+), 213 deletions(-)\n\nHuge diff to the test material mostly comes from the addition of the\ndiff headers like the index line, etc., which makes this patch scary\nbut is very welcome addition.\n\n> @@ -106,6 +117,11 @@ int diff_filespec_is_binary(struct repository *, struct diff_filespec *);\n>  struct diff_filepair {\n>  \tstruct diff_filespec *one;\n>  \tstruct diff_filespec *two;\n> +\t/*\n> +\t * Tracked line ranges for -L filtering; borrowed from\n> +\t * line_log_data and must not be freed.\n> +\t */\n> +\tconst struct range_set *line_ranges;\n\nOK.\n\n> diff --git a/line-log.c b/line-log.c\n> index 9d12ece181..858a899cd2 100644\n> --- a/line-log.c\n> +++ b/line-log.c\n> @@ -885,160 +885,6 @@ static void queue_diffs(struct line_log_data *range,\n>  \tmove_diff_queue(queue, &diff_queued_diff);\n>  }\n>  int line_log_print(struct rev_info *rev, struct commit *commit)\n>  {\n> -\n>  \tshow_log(rev);\n>  \tif (!(rev->diffopt.output_format & DIFF_FORMAT_NO_OUTPUT)) {\n>  \t\tstruct line_log_data *range = lookup_line_range(rev, commit);\n> -\t\tdump_diff_hacky(rev, range);\n> +\t\tstruct line_log_data *r;\n> +\t\tconst char *prefix = diff_line_prefix(&rev->diffopt);\n> +\n> +\t\tfprintf(rev->diffopt.file, \"%s\\n\", prefix);\n> +\n> +\t\tfor (r = range; r; r = r->next) {\n> +\t\t\tif (r->pair) {\n> +\t\t\t\tstruct diff_filepair *p =\n> +\t\t\t\t\tdiff_filepair_dup(r->pair);\n> +\t\t\t\tp->line_ranges = &r->ranges;\n> +\t\t\t\tdiff_q(&diff_queued_diff, p);\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tdiffcore_std(&rev->diffopt);\n> +\t\tdiff_flush(&rev->diffopt);\n\nVery welcome change.\n"},{"id":"540566","messageId":"xmqqbjg3y8i9.fsf@gitster.g","threadId":"65159","inReplyTo":"pull.2065.v2.git.1773714095.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/4] line-log: route -L output through the standard diff pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-03-31T21:49:34Z","receivedAt":"2026-03-31T21:49:37Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Known limitations not addressed in this series:\n>\n>  * line_log_print() still calls show_log() and diff_flush() directly,\n>    bypassing log_tree_diff_flush(). The early return in log_tree_commit()\n>    (and its associated NEEDSWORK about no_free not being restored) is\n>    pre-existing. Restructuring -L to flow through log_tree_diff_flush() is a\n>    larger change that would affect separator and header logic; it is left\n>    for a follow-up.\n>\n>  * Non-patch diff formats (--raw, --numstat, --stat, etc.) remain\n>    unimplemented for -L.\n>\n> cc: \"Kristoffer Haugsbakk\" kristofferhaugsbakk@fastmail.com\n>\n> Changes since v1:\n>\n>  * Patch 4/4: fix documentation formatting: use line continuation\n>    instead of indentation (Kristoffer Haugsbakk)\n\nThe central part of the series (i.e., patch #2) looked quite\nsensible.  I haven't read the tests very carefully, though.\n\nI was hoping that we will see another set of eyes or two to help\nreview this series, but nothing has happend in the past few weeks,\nso let's mark the topic for 'next'.\n\nThanks.\n"}]}