{"thread":{"id":"65560","subject":"[PATCH 0/3] line-log: integrate -L with the standard log output pipeline","startedAt":"2026-04-28T04:05:29Z","lastAt":"2026-06-01T21:53:15Z","messageCount":20,"participants":["Michael Montalbo via GitGitGadget","Junio C Hamano","D. Ben Knoble","Michael Montalbo","Ben Knoble"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"542411","messageId":"pull.2094.git.1777349126.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":null,"subject":"[PATCH 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T04:05:23Z","receivedAt":"2026-04-28T04:05:29Z","isPatch":true,"body":"Since its introduction, git log -L has short-circuited from\nlog_tree_commit() into its own output function, bypassing log_tree_diff()\nand log_tree_diff_flush(). This skips no_free save/restore,\nalways_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n--find-object) and --diff-filter cannot suppress commits whose pairs are all\nfiltered out, because show_log() runs before diffcore_std().\n\nThis series restructures the flow so that -L goes through the same\nlog_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\nmerge diffs, then uses that to enable several non-patch diff formats.\n\nPatch 1: revision: move -L setup before output_format-to-diff derivation\n\nPreparatory reorder in setup_revisions(). The -L block sets a default\nDIFF_FORMAT_PATCH when no format is requested; move it before the derivation\nof revs->diff from output_format so the default is visible to that check. No\nbehavior change on its own.\n\nPatch 2: line-log: integrate -L output with the standard log-tree pipeline\n\nRename line_log_print() to line_log_queue_pairs(), stripping it down to only\nqueue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\ndiffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\nsuppression, and aligns the commit/diff separator with the rest of log\noutput. Also rejects --full-diff, which is meaningless when filepairs are\npre-computed.\n\nPatch 3: line-log: allow non-patch diff formats with -L\n\nExpand the allowlist to accept --raw, --name-only, --name-status, and\n--summary. These only read filepair metadata already set by the line-log\nmachinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\nremain blocked because they call compute_diffstat() on full blob content and\nwould show whole-file statistics rather than range-scoped ones.\n\nMichael Montalbo (3):\n  revision: move -L setup before output_format-to-diff derivation\n  line-log: integrate -L output with the standard log-tree pipeline\n  line-log: allow non-patch diff formats with -L\n\n Documentation/line-range-options.adoc         | 10 +-\n line-log.c                                    | 30 ++----\n line-log.h                                    |  2 +-\n log-tree.c                                    |  9 +-\n revision.c                                    | 25 +++--\n t/t4211-line-log.sh                           | 99 ++++++++++++++++---\n t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n .../sha256/expect.parallel-change-f-to-main   |  1 -\n 8 files changed, 120 insertions(+), 57 deletions(-)\n\n\nbase-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2094\n-- \ngitgitgadget\n"},{"id":"542412","messageId":"9633eb62c620c7935f4e0aa87cae8808a6128c26.1777349126.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"[PATCH 1/3] revision: move -L setup before output_format-to-diff derivation","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T04:05:24Z","receivedAt":"2026-04-28T04:05:30Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe line_level_traverse block sets a default DIFF_FORMAT_PATCH when\nno output format has been explicitly requested.  This default must\nbe visible to the \"Did the user ask for any diff output?\" check\nthat derives revs->diff from revs->diffopt.output_format.\n\nCurrently the -L block runs after that derivation, so revs->diff\nstays 0 when no explicit format is given.  This does not matter yet\nbecause log_tree_commit() short-circuits into line_log_print()\nbefore consulting revs->diff, but the next commit will route -L\nthrough the normal log_tree_diff() path, which checks revs->diff.\n\nMove the block above the derivation so the default DIFF_FORMAT_PATCH\nis in place when revs->diff is computed.  No behavior change on its\nown.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n revision.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 599b3a66c3..4a8e24bc38 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3112,6 +3112,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tobject_context_release(&oc);\n \t}\n \n+\tif (revs->line_level_traverse) {\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 \t/* Did the user ask for any diff output? Run the diff! */\n \tif (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)\n \t\trevs->diff = 1;\n@@ -3125,14 +3133,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->diffopt.objfind)\n \t\trevs->simplify_history = 0;\n \n-\tif (revs->line_level_traverse) {\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))\n \t\trevs->limited = 1;\n \n-- \ngitgitgadget\n\n"},{"id":"542413","messageId":"2d9e0ca0153b118c6dbe2b4d186e90a882e31597.1777349126.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"[PATCH 2/3] line-log: integrate -L output with the standard log-tree pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T04:05:25Z","receivedAt":"2026-04-28T04:05:32Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\n`git log -L` has bypassed log_tree_diff() and log_tree_diff_flush()\nsince the feature was introduced, short-circuiting from\nlog_tree_commit() directly into line_log_print().  This skips the\nno_free save/restore (noted in a NEEDSWORK comment added by\nf8781bfda3), the always_show_header fallback, show_diff_of_diff(),\nand diff_free() cleanup.\n\nRestructure so that -L flows through log_tree_diff() ->\nlog_tree_diff_flush(), the same path used by the normal\nsingle-parent and merge diff codepaths:\n\n - Rename line_log_print() to line_log_queue_pairs() and strip it\n   down to just queuing pre-computed filepairs.  The show_log(),\n   separator, diffcore_std(), and diff_flush() calls are removed\n   since log_tree_diff_flush() handles all of those.\n\n - In log_tree_diff(), call line_log_queue_pairs() then\n   log_tree_diff_flush(), mirroring the diff_tree_oid() + flush\n   pattern used by the single-parent and merge codepaths.\n\n - Remove the early return in log_tree_commit() that bypassed\n   no_free save/restore, always_show_header, and diff_free().\n\nBecause show_log() is now deferred until after diffcore_std() inside\nlog_tree_diff_flush(), pickaxe (-S, -G, --find-object) and\n--diff-filter now properly suppress commits when all pairs are\nfiltered out.\n\nThe blank-line separator between commit header and diff changes\nslightly: the old code printed one unconditionally, while\nlog_tree_diff_flush() only emits one for verbose headers.  This\nmatches the rest of log output.\n\nAlso reject --full-diff, which is meaningless with -L: the filepairs\nare pre-computed during the history walk and scoped to tracked paths,\nso there is no tree diff to widen.\n\nUpdate tests accordingly.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n line-log.c                                    | 30 ++++-------\n line-log.h                                    |  2 +-\n log-tree.c                                    |  9 ++--\n revision.c                                    |  2 +\n t/t4211-line-log.sh                           | 52 ++++++++++++++-----\n t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n .../sha256/expect.parallel-change-f-to-main   |  1 -\n 7 files changed, 56 insertions(+), 41 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 858a899cd2..7ee55b05cc 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -13,7 +13,6 @@\n #include \"revision.h\"\n #include \"xdiff-interface.h\"\n #include \"strbuf.h\"\n-#include \"log-tree.h\"\n #include \"line-log.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n@@ -1004,29 +1003,18 @@ static int process_all_files(struct line_log_data **range_out,\n \treturn changed;\n }\n \n-int line_log_print(struct rev_info *rev, struct commit *commit)\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit)\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\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+\tstruct line_log_data *range = lookup_line_range(rev, commit);\n+\tstruct line_log_data *r;\n \n-\t\tdiffcore_std(&rev->diffopt);\n-\t\tdiff_flush(&rev->diffopt);\n+\tfor (r = range; r; r = r->next) {\n+\t\tif (r->pair) {\n+\t\t\tstruct diff_filepair *p = diff_filepair_dup(r->pair);\n+\t\t\tp->line_ranges = &r->ranges;\n+\t\t\tdiff_q(&diff_queued_diff, p);\n+\t\t}\n \t}\n-\treturn 1;\n }\n \n static int bloom_filter_check(struct rev_info *rev,\ndiff --git a/line-log.h b/line-log.h\nindex 04a6ea64d3..99e1755ce3 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -46,7 +46,7 @@ int line_log_filter(struct rev_info *rev);\n int line_log_process_ranges_arbitrary_commit(struct rev_info *rev,\n \t\t\t\t\t\t    struct commit *commit);\n \n-int line_log_print(struct rev_info *rev, struct commit *commit);\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit);\n \n void line_log_free(struct rev_info *rev);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 7e048701d0..1ead481891 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1105,6 +1105,11 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \tif (!all_need_diff && !opt->merges_need_diff)\n \t\treturn 0;\n \n+\tif (opt->line_level_traverse) {\n+\t\tline_log_queue_pairs(opt, commit);\n+\t\treturn log_tree_diff_flush(opt);\n+\t}\n+\n \tparse_commit_or_die(commit);\n \toid = get_commit_tree_oid(commit);\n \n@@ -1179,10 +1184,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)\n \topt->loginfo = &log;\n \topt->diffopt.no_free = 1;\n \n-\t/* NEEDSWORK: no restoring of no_free?  Why? */\n-\tif (opt->line_level_traverse)\n-\t\treturn line_log_print(opt, commit);\n-\n \tif (opt->track_linear && !opt->linear && !opt->reverse_output_stage)\n \t\tfprintf(opt->diffopt.file, \"\\n%s\\n\", opt->break_bar);\n \tshown = log_tree_diff(opt, commit, &log);\ndiff --git a/revision.c b/revision.c\nindex 4a8e24bc38..a1c795de96 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3181,6 +3181,8 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->line_level_traverse &&\n \t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n \t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n+\tif (revs->line_level_traverse && revs->full_diff)\n+\t\tdie(_(\"-L is not compatible with --full-diff\"));\n \n \tif (revs->expand_tabs_in_log < 0)\n \t\trevs->expand_tabs_in_log = revs->expand_tabs_in_log_default;\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex aaf197d2ed..1d566ea9bd 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -368,7 +368,6 @@ test_expect_success '-L diff output includes index and new file mode' '\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@@ -377,7 +376,6 @@ test_expect_success '-L with --word-diff' '\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@@ -433,7 +431,6 @@ test_expect_success 'show line-log with graph' '\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@@ -445,7 +442,6 @@ test_expect_success 'show line-log with graph' '\n \t| +    return F2 + 2;\n \t|  }\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@@ -494,23 +490,17 @@ test_expect_success '-L --find-object does not crash with merge and rename' '\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+test_expect_success '-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+test_expect_success '-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+test_expect_success '-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@@ -711,4 +701,40 @@ test_expect_success '-L with -G filters to diff-text matches' '\n \tgrep \"F2 + 2\" actual\n '\n \n+test_expect_success '-L with --diff-filter=M excludes root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual &&\n+\t# Root commit is an Add (A), not a Modify (M), so it should\n+\t# be excluded; only the modification commit remains.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with --diff-filter=A shows only root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual &&\n+\techo \"Add func1() and func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with -S suppresses non-matching commits' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -S \"F2 + 2\" --format=%s --no-patch >actual &&\n+\t# Only the commit that changes the count of \"F2 + 2\" should appear.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--full-diff is not supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --full-diff 2>err &&\n+\ttest_grep \"not compatible with --full-diff\" err\n+'\n+\n+test_expect_success '-L --oneline has no extra blank line before diff' '\n+\tgit checkout parent-oids &&\n+\tgit log --oneline -L:func2:file.c -1 >actual &&\n+\t# Oneline header on line 1, diff starts immediately on line 2\n+\tsed -n 2p actual | grep \"^diff --git\"\n+'\n+\n test_done\ndiff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main\nindex 65a8cc673a..6d7a201036 100644\n--- a/t/t4211/sha1/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha1/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 6ce3c4ff690136099bb17e1a8766b75764726ea7\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\ndiff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main\nindex 3178989253..c93e03bef4 100644\n--- a/t/t4211/sha256/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha256/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\n-- \ngitgitgadget\n\n"},{"id":"542414","messageId":"06c24b416f55f43d5d05340130180176ee8029f0.1777349126.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"[PATCH 3/3] line-log: allow non-patch diff formats with -L","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T04:05:26Z","receivedAt":"2026-04-28T04:05:33Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L flows through log_tree_diff_flush() and diff_flush(),\nmetadata-only diff formats work because they only read filepair\nfields (status, mode, path, oid) already set on the pre-computed\npairs.\n\nExpand the allowlist in setup_revisions() to also accept --raw,\n--name-only, --name-status, and --summary.  Diff stat formats\n(--stat, --numstat, --shortstat, --dirstat) remain blocked because\nthey call compute_diffstat() on full blob content and would show\nwhole-file statistics rather than range-scoped ones.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n Documentation/line-range-options.adoc | 10 +++---\n revision.c                            |  7 ++--\n t/t4211-line-log.sh                   | 47 +++++++++++++++++++++++++--\n 3 files changed, 56 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc\nindex ecb2c79fb9..72f639b5e7 100644\n--- a/Documentation/line-range-options.adoc\n+++ b/Documentation/line-range-options.adoc\n@@ -8,12 +8,14 @@\n \tgive zero or one positive revision arguments, and\n \t_<start>_ and _<end>_ (or _<funcname>_) must exist in the starting revision.\n \tYou can specify this option more than once. Implies `--patch`.\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+\tPatch output can be suppressed using `--no-patch`.\n+\tNon-patch diff formats `--raw`, `--name-only`, `--name-status`,\n+\tand `--summary` are supported.  Diff stat formats\n+\t(`--stat`, `--numstat`, `--shortstat`, `--dirstat`) are not\n+\tcurrently 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+as are pickaxe options (`-S`, `-G`) and `--diff-filter`.\n +\n include::line-range-format.adoc[]\ndiff --git a/revision.c b/revision.c\nindex a1c795de96..b41ec4016b 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3179,8 +3179,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n \n \tif (revs->line_level_traverse &&\n-\t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n-\t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n+\t    (revs->diffopt.output_format &\n+\t     ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n+\t       DIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n+\t       DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY)))\n+\t\tdie(_(\"-L does not yet support the requested diff format\"));\n \tif (revs->line_level_traverse && revs->full_diff)\n \t\tdie(_(\"-L is not compatible with --full-diff\"));\n \ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex 1d566ea9bd..63cf8e5d9f 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -155,8 +155,45 @@ test_expect_success '-p shows the default patch output' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--raw is forbidden' '\n-\ttest_must_fail git log -L1,24:b.c --raw\n+test_expect_success '--raw shows mode, oid, status and path' '\n+\tgit log -L1,24:b.c --raw --format= >actual &&\n+\tgrep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n+\t! grep \"^diff --git\" actual &&\n+\t! grep \"^@@\" actual\n+'\n+\n+test_expect_success '--name-only shows path' '\n+\tgit log -L1,24:b.c --name-only --format= >actual &&\n+\tgrep \"^b.c$\" actual &&\n+\t! grep \"^diff --git\" actual &&\n+\t! grep \"^@@\" actual\n+'\n+\n+test_expect_success '--name-status shows status and path' '\n+\tgit log -L1,24:b.c --name-status --format= >actual &&\n+\tgrep \"^M\tb.c$\" actual &&\n+\t! grep \"^diff --git\" actual &&\n+\t! grep \"^@@\" actual\n+'\n+\n+test_expect_success '--stat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --stat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--numstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --numstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--shortstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --shortstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--dirstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --dirstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n '\n \n test_expect_success 'setup for checking fancy rename following' '\n@@ -737,4 +774,10 @@ test_expect_success '-L --oneline has no extra blank line before diff' '\n \tsed -n 2p actual | grep \"^diff --git\"\n '\n \n+test_expect_success '--summary shows new file on root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --summary --format= >actual &&\n+\tgrep \"create mode 100644 file.c\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"543130","messageId":"xmqqfr3xp98b.fsf@gitster.g","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-12T04:01:08Z","receivedAt":"2026-05-12T04:01:11Z","isPatch":true,"body":"\"Michael Montalbo via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> Since its introduction, git log -L has short-circuited from\n> log_tree_commit() into its own output function, bypassing log_tree_diff()\n> and log_tree_diff_flush(). This skips no_free save/restore,\n> always_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n> --find-object) and --diff-filter cannot suppress commits whose pairs are all\n> filtered out, because show_log() runs before diffcore_std().\n>\n> This series restructures the flow so that -L goes through the same\n> log_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\n> merge diffs, then uses that to enable several non-patch diff formats.\n\nThis unfortunately saw no reviews and\n\n  https://lore.kernel.org/git/pull.2094.git.1777349126.gitgitgadget@gmail.com/\n\ndoes not show the previous rouns so I am assuming nobody is\ninterested in the topic?  \n\nOr are people more busily writing their own patches than reviewing\nothers' patches?  Unfortunately that is not sustainable.\n\n\n\n\n> Patch 1: revision: move -L setup before output_format-to-diff derivation\n>\n> Preparatory reorder in setup_revisions(). The -L block sets a default\n> DIFF_FORMAT_PATCH when no format is requested; move it before the derivation\n> of revs->diff from output_format so the default is visible to that check. No\n> behavior change on its own.\n>\n> Patch 2: line-log: integrate -L output with the standard log-tree pipeline\n>\n> Rename line_log_print() to line_log_queue_pairs(), stripping it down to only\n> queue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\n> diffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\n> suppression, and aligns the commit/diff separator with the rest of log\n> output. Also rejects --full-diff, which is meaningless when filepairs are\n> pre-computed.\n>\n> Patch 3: line-log: allow non-patch diff formats with -L\n>\n> Expand the allowlist to accept --raw, --name-only, --name-status, and\n> --summary. These only read filepair metadata already set by the line-log\n> machinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\n> remain blocked because they call compute_diffstat() on full blob content and\n> would show whole-file statistics rather than range-scoped ones.\n>\n> Michael Montalbo (3):\n>   revision: move -L setup before output_format-to-diff derivation\n>   line-log: integrate -L output with the standard log-tree pipeline\n>   line-log: allow non-patch diff formats with -L\n>\n>  Documentation/line-range-options.adoc         | 10 +-\n>  line-log.c                                    | 30 ++----\n>  line-log.h                                    |  2 +-\n>  log-tree.c                                    |  9 +-\n>  revision.c                                    | 25 +++--\n>  t/t4211-line-log.sh                           | 99 ++++++++++++++++---\n>  t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n>  .../sha256/expect.parallel-change-f-to-main   |  1 -\n>  8 files changed, 120 insertions(+), 57 deletions(-)\n>\n>\n> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2094\n"},{"id":"543951","messageId":"CALnO6CApG=26Fdx2LZELf6rfTC9SgUu9W4R=K6-ozN0C15LJ0A@mail.gmail.com","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-05-22T18:48:00Z","receivedAt":"2026-05-22T18:48:13Z","isPatch":true,"body":"Hi Michael,\n\nOn Tue, Apr 28, 2026 at 12:06 AM Michael Montalbo via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Since its introduction, git log -L has short-circuited from\n> log_tree_commit() into its own output function, bypassing log_tree_diff()\n> and log_tree_diff_flush(). This skips no_free save/restore,\n> always_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n> --find-object) and --diff-filter cannot suppress commits whose pairs are all\n> filtered out, because show_log() runs before diffcore_std().\n>\n> This series restructures the flow so that -L goes through the same\n> log_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\n> merge diffs, then uses that to enable several non-patch diff formats.\n\nCleanup by itself to shrink the number of concepts in the code is\nalready a good thing IMO, so getting additional features out of it is\neven nicer.\n\n> Patch 1: revision: move -L setup before output_format-to-diff derivation\n>\n> Preparatory reorder in setup_revisions(). The -L block sets a default\n> DIFF_FORMAT_PATCH when no format is requested; move it before the derivation\n> of revs->diff from output_format so the default is visible to that check. No\n> behavior change on its own.\n\nStraightforward, nice.\n\n>\n> Patch 2: line-log: integrate -L output with the standard log-tree pipeline\n>\n> Rename line_log_print() to line_log_queue_pairs(), stripping it down to only\n> queue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\n> diffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\n> suppression, and aligns the commit/diff separator with the rest of log\n> output. Also rejects --full-diff, which is meaningless when filepairs are\n> pre-computed.\n\nAt first I questioned the removal of the DIFF_FORMAT_NO_OUTPUT\nconditional in line_log_queue_pairs, but now that it only queues pairs\nit shouldn't be checking output formats. Good.\n\nI also noted that log_tree_diff() returns the result of\nlog_tree_diff_flush() in the -L case, which is a bit different from\nthe other patterns. I think the difference is that the other cases\nhave some conditional logic around the log_tree_diff_flush cases (?)\nbut I'm not sure. Perhaps that branch should also be looking at\nopt->loginfo ?\n\nFinally, I wonder if in describing the removal of the early return:\n\n> - Remove the early return in log_tree_commit() that bypassed\n>   no_free save/restore, always_show_header, and diff_free().\n\nwe might want to be more explicit that this is _because_ line-level\ndiff is now handled in the regular pipeline?\n\n[I suppose we could, in theory, split the rejection of --full-diff to\na separate prep commit, idk.j]\n\n> Patch 3: line-log: allow non-patch diff formats with -L\n>\n> Expand the allowlist to accept --raw, --name-only, --name-status, and\n> --summary. These only read filepair metadata already set by the line-log\n> machinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\n> remain blocked because they call compute_diffstat() on full blob content and\n> would show whole-file statistics rather than range-scoped ones.\n\nShort and sweet.\n\nThe stat formats are kind of like --full-diff, and I think they should\nprobably all be rejected or all allowed: since the stats are based on\nthe full-diff, it makes sense to enable them if we can also make -L +\n--full-diff semantically sensible.\n\nOtherwise, we'd need to find a way to make the stat formats scoped for -L.\n\n> Michael Montalbo (3):\n>   revision: move -L setup before output_format-to-diff derivation\n>   line-log: integrate -L output with the standard log-tree pipeline\n>   line-log: allow non-patch diff formats with -L\n>\n>  Documentation/line-range-options.adoc         | 10 +-\n>  line-log.c                                    | 30 ++----\n>  line-log.h                                    |  2 +-\n>  log-tree.c                                    |  9 +-\n>  revision.c                                    | 25 +++--\n>  t/t4211-line-log.sh                           | 99 ++++++++++++++++---\n>  t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n>  .../sha256/expect.parallel-change-f-to-main   |  1 -\n>  8 files changed, 120 insertions(+), 57 deletions(-)\n>\n>\n> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v1\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v1\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2094\n> --\n> gitgitgadget\n\nA few other comments:\n\n- Tests should use test_grep; some do, but some don't.\n- There is one occurrence of \"sed | grep\" that I wonder if we want to\nrewrite to avoid issues with exit status one side of the pipe?\n\nThanks for working on this!\n\n[Apologies for the unusual review format; this was easier for me at\nthe moment than digging up the individual patches, and I don't think\n_most_ of the review would benefit from spreading out across multiple\nmails.]\n\n-- \nD. Ben Knoble\n"},{"id":"543989","messageId":"CAC2QwmK_aFoeb1dybvkXxO+ZsQ_NoS8wUtYPvKD_=gxgcOEkCw@mail.gmail.com","threadId":"65560","inReplyTo":"CALnO6CBh7nDCwT=u1xSN2c6_x88t_gNfAaT_B4PzYKr=5i_bNA@mail.gmail.com","subject":"Re: [PATCH 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-05-24T02:00:55Z","receivedAt":"2026-05-24T02:01:07Z","isPatch":true,"body":"On Fri, May 22, 2026 at 11:46 AM D. Ben Knoble <ben.knoble@gmail.com> wrote:\n>\n> Hi Michael,\n>\n\nHi Ben,\n\nThanks for the thorough review!\n\n> On Tue, Apr 28, 2026 at 12:06 AM Michael Montalbo via GitGitGadget <gitgitgadget@gmail.com> wrote:\n>>\n>> Since its introduction, git log -L has short-circuited from\n>> log_tree_commit() into its own output function, bypassing log_tree_diff()\n>> and log_tree_diff_flush(). This skips no_free save/restore,\n>> always_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n>> --find-object) and --diff-filter cannot suppress commits whose pairs are all\n>> filtered out, because show_log() runs before diffcore_std().\n>>\n>> This series restructures the flow so that -L goes through the same\n>> log_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\n>> merge diffs, then uses that to enable several non-patch diff formats.\n>\n>\n> Cleanup by itself to shrink the number of concepts in the code is already a good thing IMO, so getting additional features out of it is even nicer.\n>\n>> Patch 1: revision: move -L setup before output_format-to-diff derivation\n>>\n>> Preparatory reorder in setup_revisions(). The -L block sets a default\n>> DIFF_FORMAT_PATCH when no format is requested; move it before the derivation\n>> of revs->diff from output_format so the default is visible to that check. No\n>> behavior change on its own.\n>\n>\n> Straightforward, nice.\n>\n>>\n>> Patch 2: line-log: integrate -L output with the standard log-tree pipeline\n>>\n>> Rename line_log_print() to line_log_queue_pairs(), stripping it down to only\n>> queue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\n>> diffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\n>> suppression, and aligns the commit/diff separator with the rest of log\n>> output. Also rejects --full-diff, which is meaningless when filepairs are\n>> pre-computed.\n>\n>\n> At first I questioned the removal of the DIFF_FORMAT_NO_OUTPUT conditional in line_log_queue_pairs, but now that it only queues pairs it shouldn't be checking output formats. Good.\n>\n> I also noted that log_tree_diff() returns the result of log_tree_diff_flush() in the -L case, which is a bit different from the other patterns. I think the difference is that the other cases have some conditional logic around the log_tree_diff_flush cases (?) but I'm not sure. Perhaps that branch should also be looking at opt->loginfo ?\n>\n\nGood catch. In practice I think they agree, since `log_tree_diff_flush()`\nreturns 1 exactly when it calls `show_log()` which consumes loginfo,\nbut matching the existing convention is cleaner. Will update.\n\n> Finally, I wonder if in describing the removal of the early return:\n>\n> > - Remove the early return in log_tree_commit() that bypassed\n> >   no_free save/restore, always_show_header, and diff_free().\n>\n> we might want to be more explicit that this is _because_ line-level diff is now handled in the regular pipeline?\n>\n\nAgreed, will reword to: \"Remove the early return in log_tree_commit()\nthat is no longer needed now that -L output flows through\nlog_tree_diff() and log_tree_diff_flush(); this restores no_free\nsave/restore, always_show_header, and diff_free() cleanup.\"\n\n> [I suppose we could, in theory, split the rejection of --full-diff to a separate prep commit, idk.j]\n>\n\nIt felt natural to me to put alongside the integration since\n--full-diff is not yet implementable with pre-computed filepairs.\nHappy to split it out if you feel strongly though.\n\n>> Patch 3: line-log: allow non-patch diff formats with -L\n>>\n>> Expand the allowlist to accept --raw, --name-only, --name-status, and\n>> --summary. These only read filepair metadata already set by the line-log\n>> machinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\n>> remain blocked because they call compute_diffstat() on full blob content and\n>> would show whole-file statistics rather than range-scoped ones.\n>\n>\n> Short and sweet.\n>\n> The stat formats are kind of like --full-diff, and I think they should probably all be rejected or all allowed: since the stats are based on the full-diff, it makes sense to enable them if we can also make -L + --full-diff semantically sensible.\n>\n> Otherwise, we'd need to find a way to make the stat formats scoped for -L.\n>\n\nI am working on a follow up series that takes the second path\nyou suggest: it adds a line-range filter in `diffcore_std()` that\nclips insertions and deletions to the tracked ranges before\n`compute_diffstat()` runs, so `--stat`, `--numstat`, etc. report\nrange-scoped numbers. That series builds on top of these three patches,\nwhich is why stats remain blocked here.\n\nFor `--full-diff`, thinking about it more, the semantics would actually\nbe well-defined: \"filter commits by line range, but show the full\ndiff for those commits.\" Right now, there might be a higher\nimplementation barrier, though. The line-log machinery fuses\ncommit filtering with diff generation, so there is no separate\n\"full diff\" to fall back to for display. I will soften the rejection to\n\"not yet supported\" rather than \"incompatible,\" since it could\nbe wired up if someone separates the two concerns.\n\n>>\n>>\n>> Michael Montalbo (3):\n>>   revision: move -L setup before output_format-to-diff derivation\n>>   line-log: integrate -L output with the standard log-tree pipeline\n>>   line-log: allow non-patch diff formats with -L\n>>\n>>  Documentation/line-range-options.adoc         | 10 +-\n>>  line-log.c                                    | 30 ++----\n>>  line-log.h                                    |  2 +-\n>>  log-tree.c                                    |  9 +-\n>>  revision.c                                    | 25 +++--\n>>  t/t4211-line-log.sh                           | 99 ++++++++++++++++---\n>>  t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n>>  .../sha256/expect.parallel-change-f-to-main   |  1 -\n>>  8 files changed, 120 insertions(+), 57 deletions(-)\n>>\n>>\n>> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n>> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v1\n>> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v1\n>> Pull-Request: https://github.com/gitgitgadget/git/pull/2094\n>> --\n>> gitgitgadget\n>\n>\n> A few other comments:\n>\n> - Tests should use test_grep; some do, but some don't.\n> - There is one occurrence of \"sed | grep\" that I wonder if we want to rewrite to avoid issues with exit status one side of the pipe?\n>\n\nWill fix both these issues.\n\n> Thanks for working on this!\n>\n> [Apologies for the unusual review format; this was easier for me at the moment than digging up the individual patches, and I don't think _most_ of the review would benefit from spreading out across multiple mails.]\n>\n\nThank you too! This review format worked fine for me :)\n\n> --\n> D. Ben Knoble\n"},{"id":"544074","messageId":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.git.1777349126.gitgitgadget@gmail.com","subject":"[PATCH v2 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-25T19:40:55Z","receivedAt":"2026-05-25T19:41:01Z","isPatch":true,"body":"Since its introduction, git log -L has short-circuited from\nlog_tree_commit() into its own output function, bypassing log_tree_diff()\nand log_tree_diff_flush(). This skips no_free save/restore,\nalways_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n--find-object) and --diff-filter cannot suppress commits whose pairs are all\nfiltered out, because show_log() runs before diffcore_std().\n\nThis series restructures the flow so that -L goes through the same\nlog_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\nmerge diffs, then uses that to enable several non-patch diff formats.\n\nPatch 1: revision: move -L setup before output_format-to-diff derivation\n\nPreparatory reorder in setup_revisions(). The -L block sets a default\nDIFF_FORMAT_PATCH when no format is requested; move it before the derivation\nof revs->diff from output_format so the default is visible to that check. No\nbehavior change on its own.\n\nPatch 2: line-log: integrate -L output with the standard log-tree pipeline\n\nRename line_log_print() to line_log_queue_pairs(), stripping it down to only\nqueue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\ndiffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\nsuppression, and aligns the commit/diff separator with the rest of log\noutput. Rejects --full-diff, which is not yet supported when filepairs are\npre-computed.\n\nPatch 3: line-log: allow non-patch diff formats with -L\n\nExpand the allowlist to accept --raw, --name-only, --name-status, and\n--summary. These only read filepair metadata already set by the line-log\nmachinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\nremain blocked because they call compute_diffstat() on full blob content and\nwould show whole-file statistics rather than range-scoped ones.\n\nChanges since v1:\n\n * Patch 2: use !opt->loginfo return convention in log_tree_diff() to match\n   the existing single-parent and merge codepaths, instead of returning\n   log_tree_diff_flush() directly.\n * Patch 2: reword the early-return removal to explicitly tie it to the\n   pipeline change.\n * Patch 2: soften --full-diff rejection to \"not yet supported\".\n * Patches 2-3: use test_grep consistently in new tests.\n * Patch 2: replace sed | grep pipe with sed > file && test_grep for proper\n   exit status handling.\n\nMichael Montalbo (3):\n  revision: move -L setup before output_format-to-diff derivation\n  line-log: integrate -L output with the standard log-tree pipeline\n  line-log: allow non-patch diff formats with -L\n\n Documentation/line-range-options.adoc         |  10 +-\n line-log.c                                    |  30 ++----\n line-log.h                                    |   2 +-\n log-tree.c                                    |  10 +-\n revision.c                                    |  24 +++--\n t/t4211-line-log.sh                           | 100 +++++++++++++++---\n t/t4211/sha1/expect.parallel-change-f-to-main |   1 -\n .../sha256/expect.parallel-change-f-to-main   |   1 -\n 8 files changed, 121 insertions(+), 57 deletions(-)\n\n\nbase-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/2094\n\nRange-diff vs v1:\n\n 1:  9633eb62c6 = 1:  9633eb62c6 revision: move -L setup before output_format-to-diff derivation\n 2:  2d9e0ca015 ! 2:  7acfc5376e line-log: integrate -L output with the standard log-tree pipeline\n     @@ Commit message\n             log_tree_diff_flush(), mirroring the diff_tree_oid() + flush\n             pattern used by the single-parent and merge codepaths.\n      \n     -     - Remove the early return in log_tree_commit() that bypassed\n     -       no_free save/restore, always_show_header, and diff_free().\n     +     - Remove the early return in log_tree_commit() that is no longer\n     +       needed now that -L output flows through log_tree_diff() and\n     +       log_tree_diff_flush(); this restores no_free save/restore,\n     +       always_show_header, and diff_free() cleanup.\n      \n          Because show_log() is now deferred until after diffcore_std() inside\n          log_tree_diff_flush(), pickaxe (-S, -G, --find-object) and\n     @@ Commit message\n          log_tree_diff_flush() only emits one for verbose headers.  This\n          matches the rest of log output.\n      \n     -    Also reject --full-diff, which is meaningless with -L: the filepairs\n     -    are pre-computed during the history walk and scoped to tracked paths,\n     -    so there is no tree diff to widen.\n     +    Also reject --full-diff, which is not yet supported with -L: the\n     +    filepairs are pre-computed during the history walk and scoped to\n     +    tracked line ranges, so there is currently no full-tree diff to\n     +    fall back to for display.\n      \n          Update tests accordingly.\n      \n     @@ log-tree.c: static int log_tree_diff(struct rev_info *opt, struct commit *commit\n       \n      +\tif (opt->line_level_traverse) {\n      +\t\tline_log_queue_pairs(opt, commit);\n     -+\t\treturn log_tree_diff_flush(opt);\n     ++\t\tlog_tree_diff_flush(opt);\n     ++\t\treturn !opt->loginfo;\n      +\t}\n      +\n       \tparse_commit_or_die(commit);\n     @@ log-tree.c: int log_tree_commit(struct rev_info *opt, struct commit *commit)\n      \n       ## revision.c ##\n      @@ revision.c: int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n     + \t\tdie(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n     + \n       \tif (revs->line_level_traverse &&\n     - \t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n     - \t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n     -+\tif (revs->line_level_traverse && revs->full_diff)\n     -+\t\tdie(_(\"-L is not compatible with --full-diff\"));\n     +-\t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n     +-\t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n     ++\t    (revs->full_diff ||\n     ++\t     (revs->diffopt.output_format &\n     ++\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n     ++\t\tdie(_(\"-L does not yet support the requested diff format\"));\n       \n       \tif (revs->expand_tabs_in_log < 0)\n       \t\trevs->expand_tabs_in_log = revs->expand_tabs_in_log_default;\n     @@ t/t4211-line-log.sh: test_expect_success '-L with -G filters to diff-text matche\n      +\ttest_cmp expect actual\n      +'\n      +\n     -+test_expect_success '--full-diff is not supported with -L' '\n     ++test_expect_success '--full-diff is not yet supported with -L' '\n      +\ttest_must_fail git log -L1,24:b.c --full-diff 2>err &&\n     -+\ttest_grep \"not compatible with --full-diff\" err\n     ++\ttest_grep \"does not yet support\" err\n      +'\n      +\n      +test_expect_success '-L --oneline has no extra blank line before diff' '\n      +\tgit checkout parent-oids &&\n      +\tgit log --oneline -L:func2:file.c -1 >actual &&\n      +\t# Oneline header on line 1, diff starts immediately on line 2\n     -+\tsed -n 2p actual | grep \"^diff --git\"\n     ++\tsed -n 2p actual >line2 &&\n     ++\ttest_grep \"^diff --git\" line2\n      +'\n      +\n       test_done\n 3:  06c24b416f ! 3:  10a3d8dde2 line-log: allow non-patch diff formats with -L\n     @@ Documentation/line-range-options.adoc\n      \n       ## revision.c ##\n      @@ revision.c: int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n     - \t\tdie(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n     - \n       \tif (revs->line_level_traverse &&\n     --\t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n     --\t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n     -+\t    (revs->diffopt.output_format &\n     -+\t     ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n     -+\t       DIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n     -+\t       DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY)))\n     -+\t\tdie(_(\"-L does not yet support the requested diff format\"));\n     - \tif (revs->line_level_traverse && revs->full_diff)\n     - \t\tdie(_(\"-L is not compatible with --full-diff\"));\n     + \t    (revs->full_diff ||\n     + \t     (revs->diffopt.output_format &\n     +-\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n     ++\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n     ++\t\tDIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n     ++\t\tDIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY))))\n     + \t\tdie(_(\"-L does not yet support the requested diff format\"));\n       \n     + \tif (revs->expand_tabs_in_log < 0)\n      \n       ## t/t4211-line-log.sh ##\n      @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n     @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n      -\ttest_must_fail git log -L1,24:b.c --raw\n      +test_expect_success '--raw shows mode, oid, status and path' '\n      +\tgit log -L1,24:b.c --raw --format= >actual &&\n     -+\tgrep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n     -+\t! grep \"^diff --git\" actual &&\n     -+\t! grep \"^@@\" actual\n     ++\ttest_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n     ++\t! test_grep \"^diff --git\" actual &&\n     ++\t! test_grep \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--name-only shows path' '\n      +\tgit log -L1,24:b.c --name-only --format= >actual &&\n     -+\tgrep \"^b.c$\" actual &&\n     -+\t! grep \"^diff --git\" actual &&\n     -+\t! grep \"^@@\" actual\n     ++\ttest_grep \"^b.c$\" actual &&\n     ++\t! test_grep \"^diff --git\" actual &&\n     ++\t! test_grep \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--name-status shows status and path' '\n      +\tgit log -L1,24:b.c --name-status --format= >actual &&\n     -+\tgrep \"^M\tb.c$\" actual &&\n     -+\t! grep \"^diff --git\" actual &&\n     -+\t! grep \"^@@\" actual\n     ++\ttest_grep \"^M\tb.c$\" actual &&\n     ++\t! test_grep \"^diff --git\" actual &&\n     ++\t! test_grep \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--stat is not yet supported with -L' '\n     @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n       \n       test_expect_success 'setup for checking fancy rename following' '\n      @@ t/t4211-line-log.sh: test_expect_success '-L --oneline has no extra blank line before diff' '\n     - \tsed -n 2p actual | grep \"^diff --git\"\n     + \ttest_grep \"^diff --git\" line2\n       '\n       \n      +test_expect_success '--summary shows new file on root commit' '\n      +\tgit checkout parent-oids &&\n      +\tgit log -L:func2:file.c --summary --format= >actual &&\n     -+\tgrep \"create mode 100644 file.c\" actual\n     ++\ttest_grep \"create mode 100644 file.c\" actual\n      +'\n      +\n       test_done\n\n-- \ngitgitgadget\n"},{"id":"544075","messageId":"9633eb62c620c7935f4e0aa87cae8808a6128c26.1779738059.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","subject":"[PATCH v2 1/3] revision: move -L setup before output_format-to-diff derivation","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-25T19:40:56Z","receivedAt":"2026-05-25T19:41:03Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe line_level_traverse block sets a default DIFF_FORMAT_PATCH when\nno output format has been explicitly requested.  This default must\nbe visible to the \"Did the user ask for any diff output?\" check\nthat derives revs->diff from revs->diffopt.output_format.\n\nCurrently the -L block runs after that derivation, so revs->diff\nstays 0 when no explicit format is given.  This does not matter yet\nbecause log_tree_commit() short-circuits into line_log_print()\nbefore consulting revs->diff, but the next commit will route -L\nthrough the normal log_tree_diff() path, which checks revs->diff.\n\nMove the block above the derivation so the default DIFF_FORMAT_PATCH\nis in place when revs->diff is computed.  No behavior change on its\nown.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n revision.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 599b3a66c3..4a8e24bc38 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3112,6 +3112,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tobject_context_release(&oc);\n \t}\n \n+\tif (revs->line_level_traverse) {\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 \t/* Did the user ask for any diff output? Run the diff! */\n \tif (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)\n \t\trevs->diff = 1;\n@@ -3125,14 +3133,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->diffopt.objfind)\n \t\trevs->simplify_history = 0;\n \n-\tif (revs->line_level_traverse) {\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))\n \t\trevs->limited = 1;\n \n-- \ngitgitgadget\n\n"},{"id":"544076","messageId":"7acfc5376e14fb3d69e06ab8a5ba68973bfe808f.1779738059.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","subject":"[PATCH v2 2/3] line-log: integrate -L output with the standard log-tree pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-25T19:40:57Z","receivedAt":"2026-05-25T19:41:05Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\n`git log -L` has bypassed log_tree_diff() and log_tree_diff_flush()\nsince the feature was introduced, short-circuiting from\nlog_tree_commit() directly into line_log_print().  This skips the\nno_free save/restore (noted in a NEEDSWORK comment added by\nf8781bfda3), the always_show_header fallback, show_diff_of_diff(),\nand diff_free() cleanup.\n\nRestructure so that -L flows through log_tree_diff() ->\nlog_tree_diff_flush(), the same path used by the normal\nsingle-parent and merge diff codepaths:\n\n - Rename line_log_print() to line_log_queue_pairs() and strip it\n   down to just queuing pre-computed filepairs.  The show_log(),\n   separator, diffcore_std(), and diff_flush() calls are removed\n   since log_tree_diff_flush() handles all of those.\n\n - In log_tree_diff(), call line_log_queue_pairs() then\n   log_tree_diff_flush(), mirroring the diff_tree_oid() + flush\n   pattern used by the single-parent and merge codepaths.\n\n - Remove the early return in log_tree_commit() that is no longer\n   needed now that -L output flows through log_tree_diff() and\n   log_tree_diff_flush(); this restores no_free save/restore,\n   always_show_header, and diff_free() cleanup.\n\nBecause show_log() is now deferred until after diffcore_std() inside\nlog_tree_diff_flush(), pickaxe (-S, -G, --find-object) and\n--diff-filter now properly suppress commits when all pairs are\nfiltered out.\n\nThe blank-line separator between commit header and diff changes\nslightly: the old code printed one unconditionally, while\nlog_tree_diff_flush() only emits one for verbose headers.  This\nmatches the rest of log output.\n\nAlso reject --full-diff, which is not yet supported with -L: the\nfilepairs are pre-computed during the history walk and scoped to\ntracked line ranges, so there is currently no full-tree diff to\nfall back to for display.\n\nUpdate tests accordingly.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n line-log.c                                    | 30 ++++-------\n line-log.h                                    |  2 +-\n log-tree.c                                    | 10 ++--\n revision.c                                    |  6 ++-\n t/t4211-line-log.sh                           | 53 ++++++++++++++-----\n t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n .../sha256/expect.parallel-change-f-to-main   |  1 -\n 7 files changed, 60 insertions(+), 43 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 858a899cd2..7ee55b05cc 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -13,7 +13,6 @@\n #include \"revision.h\"\n #include \"xdiff-interface.h\"\n #include \"strbuf.h\"\n-#include \"log-tree.h\"\n #include \"line-log.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n@@ -1004,29 +1003,18 @@ static int process_all_files(struct line_log_data **range_out,\n \treturn changed;\n }\n \n-int line_log_print(struct rev_info *rev, struct commit *commit)\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit)\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\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+\tstruct line_log_data *range = lookup_line_range(rev, commit);\n+\tstruct line_log_data *r;\n \n-\t\tdiffcore_std(&rev->diffopt);\n-\t\tdiff_flush(&rev->diffopt);\n+\tfor (r = range; r; r = r->next) {\n+\t\tif (r->pair) {\n+\t\t\tstruct diff_filepair *p = diff_filepair_dup(r->pair);\n+\t\t\tp->line_ranges = &r->ranges;\n+\t\t\tdiff_q(&diff_queued_diff, p);\n+\t\t}\n \t}\n-\treturn 1;\n }\n \n static int bloom_filter_check(struct rev_info *rev,\ndiff --git a/line-log.h b/line-log.h\nindex 04a6ea64d3..99e1755ce3 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -46,7 +46,7 @@ int line_log_filter(struct rev_info *rev);\n int line_log_process_ranges_arbitrary_commit(struct rev_info *rev,\n \t\t\t\t\t\t    struct commit *commit);\n \n-int line_log_print(struct rev_info *rev, struct commit *commit);\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit);\n \n void line_log_free(struct rev_info *rev);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 7e048701d0..88b3019293 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1105,6 +1105,12 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \tif (!all_need_diff && !opt->merges_need_diff)\n \t\treturn 0;\n \n+\tif (opt->line_level_traverse) {\n+\t\tline_log_queue_pairs(opt, commit);\n+\t\tlog_tree_diff_flush(opt);\n+\t\treturn !opt->loginfo;\n+\t}\n+\n \tparse_commit_or_die(commit);\n \toid = get_commit_tree_oid(commit);\n \n@@ -1179,10 +1185,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)\n \topt->loginfo = &log;\n \topt->diffopt.no_free = 1;\n \n-\t/* NEEDSWORK: no restoring of no_free?  Why? */\n-\tif (opt->line_level_traverse)\n-\t\treturn line_log_print(opt, commit);\n-\n \tif (opt->track_linear && !opt->linear && !opt->reverse_output_stage)\n \t\tfprintf(opt->diffopt.file, \"\\n%s\\n\", opt->break_bar);\n \tshown = log_tree_diff(opt, commit, &log);\ndiff --git a/revision.c b/revision.c\nindex 4a8e24bc38..c903f7a1b4 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3179,8 +3179,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n \n \tif (revs->line_level_traverse &&\n-\t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n-\t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n+\t    (revs->full_diff ||\n+\t     (revs->diffopt.output_format &\n+\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n+\t\tdie(_(\"-L does not yet support the requested diff format\"));\n \n \tif (revs->expand_tabs_in_log < 0)\n \t\trevs->expand_tabs_in_log = revs->expand_tabs_in_log_default;\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex aaf197d2ed..e3937138a9 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -368,7 +368,6 @@ test_expect_success '-L diff output includes index and new file mode' '\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@@ -377,7 +376,6 @@ test_expect_success '-L with --word-diff' '\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@@ -433,7 +431,6 @@ test_expect_success 'show line-log with graph' '\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@@ -445,7 +442,6 @@ test_expect_success 'show line-log with graph' '\n \t| +    return F2 + 2;\n \t|  }\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@@ -494,23 +490,17 @@ test_expect_success '-L --find-object does not crash with merge and rename' '\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+test_expect_success '-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+test_expect_success '-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+test_expect_success '-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@@ -711,4 +701,41 @@ test_expect_success '-L with -G filters to diff-text matches' '\n \tgrep \"F2 + 2\" actual\n '\n \n+test_expect_success '-L with --diff-filter=M excludes root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual &&\n+\t# Root commit is an Add (A), not a Modify (M), so it should\n+\t# be excluded; only the modification commit remains.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with --diff-filter=A shows only root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual &&\n+\techo \"Add func1() and func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with -S suppresses non-matching commits' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -S \"F2 + 2\" --format=%s --no-patch >actual &&\n+\t# Only the commit that changes the count of \"F2 + 2\" should appear.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--full-diff is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --full-diff 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '-L --oneline has no extra blank line before diff' '\n+\tgit checkout parent-oids &&\n+\tgit log --oneline -L:func2:file.c -1 >actual &&\n+\t# Oneline header on line 1, diff starts immediately on line 2\n+\tsed -n 2p actual >line2 &&\n+\ttest_grep \"^diff --git\" line2\n+'\n+\n test_done\ndiff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main\nindex 65a8cc673a..6d7a201036 100644\n--- a/t/t4211/sha1/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha1/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 6ce3c4ff690136099bb17e1a8766b75764726ea7\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\ndiff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main\nindex 3178989253..c93e03bef4 100644\n--- a/t/t4211/sha256/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha256/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\n-- \ngitgitgadget\n\n"},{"id":"544077","messageId":"10a3d8dde2dbc16fd7918f1196065b06151d496a.1779738059.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","subject":"[PATCH v2 3/3] line-log: allow non-patch diff formats with -L","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-25T19:40:58Z","receivedAt":"2026-05-25T19:41:06Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L flows through log_tree_diff_flush() and diff_flush(),\nmetadata-only diff formats work because they only read filepair\nfields (status, mode, path, oid) already set on the pre-computed\npairs.\n\nExpand the allowlist in setup_revisions() to also accept --raw,\n--name-only, --name-status, and --summary.  Diff stat formats\n(--stat, --numstat, --shortstat, --dirstat) remain blocked because\nthey call compute_diffstat() on full blob content and would show\nwhole-file statistics rather than range-scoped ones.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n Documentation/line-range-options.adoc | 10 +++---\n revision.c                            |  4 ++-\n t/t4211-line-log.sh                   | 47 +++++++++++++++++++++++++--\n 3 files changed, 54 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc\nindex ecb2c79fb9..72f639b5e7 100644\n--- a/Documentation/line-range-options.adoc\n+++ b/Documentation/line-range-options.adoc\n@@ -8,12 +8,14 @@\n \tgive zero or one positive revision arguments, and\n \t_<start>_ and _<end>_ (or _<funcname>_) must exist in the starting revision.\n \tYou can specify this option more than once. Implies `--patch`.\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+\tPatch output can be suppressed using `--no-patch`.\n+\tNon-patch diff formats `--raw`, `--name-only`, `--name-status`,\n+\tand `--summary` are supported.  Diff stat formats\n+\t(`--stat`, `--numstat`, `--shortstat`, `--dirstat`) are not\n+\tcurrently 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+as are pickaxe options (`-S`, `-G`) and `--diff-filter`.\n +\n include::line-range-format.adoc[]\ndiff --git a/revision.c b/revision.c\nindex c903f7a1b4..f26fc1f4d5 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3181,7 +3181,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->line_level_traverse &&\n \t    (revs->full_diff ||\n \t     (revs->diffopt.output_format &\n-\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n+\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n+\t\tDIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n+\t\tDIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY))))\n \t\tdie(_(\"-L does not yet support the requested diff format\"));\n \n \tif (revs->expand_tabs_in_log < 0)\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex e3937138a9..4722ec3e29 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -155,8 +155,45 @@ test_expect_success '-p shows the default patch output' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--raw is forbidden' '\n-\ttest_must_fail git log -L1,24:b.c --raw\n+test_expect_success '--raw shows mode, oid, status and path' '\n+\tgit log -L1,24:b.c --raw --format= >actual &&\n+\ttest_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n+\t! test_grep \"^diff --git\" actual &&\n+\t! test_grep \"^@@\" actual\n+'\n+\n+test_expect_success '--name-only shows path' '\n+\tgit log -L1,24:b.c --name-only --format= >actual &&\n+\ttest_grep \"^b.c$\" actual &&\n+\t! test_grep \"^diff --git\" actual &&\n+\t! test_grep \"^@@\" actual\n+'\n+\n+test_expect_success '--name-status shows status and path' '\n+\tgit log -L1,24:b.c --name-status --format= >actual &&\n+\ttest_grep \"^M\tb.c$\" actual &&\n+\t! test_grep \"^diff --git\" actual &&\n+\t! test_grep \"^@@\" actual\n+'\n+\n+test_expect_success '--stat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --stat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--numstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --numstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--shortstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --shortstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--dirstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --dirstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n '\n \n test_expect_success 'setup for checking fancy rename following' '\n@@ -738,4 +775,10 @@ test_expect_success '-L --oneline has no extra blank line before diff' '\n \ttest_grep \"^diff --git\" line2\n '\n \n+test_expect_success '--summary shows new file on root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --summary --format= >actual &&\n+\ttest_grep \"create mode 100644 file.c\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"544193","messageId":"CALnO6CA5GPS2CMv_x_=wOPBgspe31FkW=h832GCBpAUbBoJNLg@mail.gmail.com","threadId":"65560","inReplyTo":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"D. Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-05-27T20:20:37Z","receivedAt":"2026-05-27T20:20:49Z","isPatch":true,"body":"On Mon, May 25, 2026 at 3:41 PM Michael Montalbo via GitGitGadget\n<gitgitgadget@gmail.com> wrote:\n>\n> Since its introduction, git log -L has short-circuited from\n> log_tree_commit() into its own output function, bypassing log_tree_diff()\n> and log_tree_diff_flush(). This skips no_free save/restore,\n> always_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n> --find-object) and --diff-filter cannot suppress commits whose pairs are all\n> filtered out, because show_log() runs before diffcore_std().\n>\n> This series restructures the flow so that -L goes through the same\n> log_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\n> merge diffs, then uses that to enable several non-patch diff formats.\n>\n> Patch 1: revision: move -L setup before output_format-to-diff derivation\n>\n> Preparatory reorder in setup_revisions(). The -L block sets a default\n> DIFF_FORMAT_PATCH when no format is requested; move it before the derivation\n> of revs->diff from output_format so the default is visible to that check. No\n> behavior change on its own.\n>\n> Patch 2: line-log: integrate -L output with the standard log-tree pipeline\n>\n> Rename line_log_print() to line_log_queue_pairs(), stripping it down to only\n> queue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\n> diffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\n> suppression, and aligns the commit/diff separator with the rest of log\n> output. Rejects --full-diff, which is not yet supported when filepairs are\n> pre-computed.\n>\n> Patch 3: line-log: allow non-patch diff formats with -L\n>\n> Expand the allowlist to accept --raw, --name-only, --name-status, and\n> --summary. These only read filepair metadata already set by the line-log\n> machinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\n> remain blocked because they call compute_diffstat() on full blob content and\n> would show whole-file statistics rather than range-scoped ones.\n>\n> Changes since v1:\n>\n>  * Patch 2: use !opt->loginfo return convention in log_tree_diff() to match\n>    the existing single-parent and merge codepaths, instead of returning\n>    log_tree_diff_flush() directly.\n>  * Patch 2: reword the early-return removal to explicitly tie it to the\n>    pipeline change.\n>  * Patch 2: soften --full-diff rejection to \"not yet supported\".\n>  * Patches 2-3: use test_grep consistently in new tests.\n>  * Patch 2: replace sed | grep pipe with sed > file && test_grep for proper\n>    exit status handling.\n>\n> Michael Montalbo (3):\n>   revision: move -L setup before output_format-to-diff derivation\n>   line-log: integrate -L output with the standard log-tree pipeline\n>   line-log: allow non-patch diff formats with -L\n>\n>  Documentation/line-range-options.adoc         |  10 +-\n>  line-log.c                                    |  30 ++----\n>  line-log.h                                    |   2 +-\n>  log-tree.c                                    |  10 +-\n>  revision.c                                    |  24 +++--\n>  t/t4211-line-log.sh                           | 100 +++++++++++++++---\n>  t/t4211/sha1/expect.parallel-change-f-to-main |   1 -\n>  .../sha256/expect.parallel-change-f-to-main   |   1 -\n>  8 files changed, 121 insertions(+), 57 deletions(-)\n>\n>\n> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v2\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v2\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2094\n>\n> Range-diff vs v1:\n\nLooks good.\n\n>\n>  1:  9633eb62c6 = 1:  9633eb62c6 revision: move -L setup before output_format-to-diff derivation\n>  2:  2d9e0ca015 ! 2:  7acfc5376e line-log: integrate -L output with the standard log-tree pipeline\n>      @@ Commit message\n>              log_tree_diff_flush(), mirroring the diff_tree_oid() + flush\n>              pattern used by the single-parent and merge codepaths.\n>\n>      -     - Remove the early return in log_tree_commit() that bypassed\n>      -       no_free save/restore, always_show_header, and diff_free().\n>      +     - Remove the early return in log_tree_commit() that is no longer\n>      +       needed now that -L output flows through log_tree_diff() and\n>      +       log_tree_diff_flush(); this restores no_free save/restore,\n>      +       always_show_header, and diff_free() cleanup.\n>\n>           Because show_log() is now deferred until after diffcore_std() inside\n>           log_tree_diff_flush(), pickaxe (-S, -G, --find-object) and\n>      @@ Commit message\n>           log_tree_diff_flush() only emits one for verbose headers.  This\n>           matches the rest of log output.\n>\n>      -    Also reject --full-diff, which is meaningless with -L: the filepairs\n>      -    are pre-computed during the history walk and scoped to tracked paths,\n>      -    so there is no tree diff to widen.\n>      +    Also reject --full-diff, which is not yet supported with -L: the\n>      +    filepairs are pre-computed during the history walk and scoped to\n>      +    tracked line ranges, so there is currently no full-tree diff to\n>      +    fall back to for display.\n>\n>           Update tests accordingly.\n>\n>      @@ log-tree.c: static int log_tree_diff(struct rev_info *opt, struct commit *commit\n>\n>       + if (opt->line_level_traverse) {\n>       +         line_log_queue_pairs(opt, commit);\n>      -+         return log_tree_diff_flush(opt);\n>      ++         log_tree_diff_flush(opt);\n>      ++         return !opt->loginfo;\n>       + }\n>       +\n>         parse_commit_or_die(commit);\n>      @@ log-tree.c: int log_tree_commit(struct rev_info *opt, struct commit *commit)\n>\n>        ## revision.c ##\n>       @@ revision.c: int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n>      +          die(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n>      +\n>         if (revs->line_level_traverse &&\n>      -      (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n>      -          die(_(\"-L does not yet support diff formats besides -p and -s\"));\n>      -+ if (revs->line_level_traverse && revs->full_diff)\n>      -+         die(_(\"-L is not compatible with --full-diff\"));\n>      +-     (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n>      +-         die(_(\"-L does not yet support diff formats besides -p and -s\"));\n>      ++     (revs->full_diff ||\n>      ++      (revs->diffopt.output_format &\n>      ++       ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n>      ++         die(_(\"-L does not yet support the requested diff format\"));\n>\n>         if (revs->expand_tabs_in_log < 0)\n>                 revs->expand_tabs_in_log = revs->expand_tabs_in_log_default;\n>      @@ t/t4211-line-log.sh: test_expect_success '-L with -G filters to diff-text matche\n>       + test_cmp expect actual\n>       +'\n>       +\n>      -+test_expect_success '--full-diff is not supported with -L' '\n>      ++test_expect_success '--full-diff is not yet supported with -L' '\n>       + test_must_fail git log -L1,24:b.c --full-diff 2>err &&\n>      -+ test_grep \"not compatible with --full-diff\" err\n>      ++ test_grep \"does not yet support\" err\n>       +'\n>       +\n>       +test_expect_success '-L --oneline has no extra blank line before diff' '\n>       + git checkout parent-oids &&\n>       + git log --oneline -L:func2:file.c -1 >actual &&\n>       + # Oneline header on line 1, diff starts immediately on line 2\n>      -+ sed -n 2p actual | grep \"^diff --git\"\n>      ++ sed -n 2p actual >line2 &&\n>      ++ test_grep \"^diff --git\" line2\n>       +'\n>       +\n>        test_done\n>  3:  06c24b416f ! 3:  10a3d8dde2 line-log: allow non-patch diff formats with -L\n>      @@ Documentation/line-range-options.adoc\n>\n>        ## revision.c ##\n>       @@ revision.c: int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n>      -          die(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n>      -\n>         if (revs->line_level_traverse &&\n>      --     (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n>      --         die(_(\"-L does not yet support diff formats besides -p and -s\"));\n>      -+     (revs->diffopt.output_format &\n>      -+      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n>      -+        DIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n>      -+        DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY)))\n>      -+         die(_(\"-L does not yet support the requested diff format\"));\n>      -  if (revs->line_level_traverse && revs->full_diff)\n>      -          die(_(\"-L is not compatible with --full-diff\"));\n>      +      (revs->full_diff ||\n>      +       (revs->diffopt.output_format &\n>      +-       ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n>      ++       ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n>      ++         DIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n>      ++         DIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY))))\n>      +          die(_(\"-L does not yet support the requested diff format\"));\n>\n>      +  if (revs->expand_tabs_in_log < 0)\n>\n>        ## t/t4211-line-log.sh ##\n>       @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n>      @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n>       - test_must_fail git log -L1,24:b.c --raw\n>       +test_expect_success '--raw shows mode, oid, status and path' '\n>       + git log -L1,24:b.c --raw --format= >actual &&\n>      -+ grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M     b.c$\" actual &&\n>      -+ ! grep \"^diff --git\" actual &&\n>      -+ ! grep \"^@@\" actual\n>      ++ test_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M        b.c$\" actual &&\n>      ++ ! test_grep \"^diff --git\" actual &&\n>      ++ ! test_grep \"^@@\" actual\n\nI wish we had docs for all the little test helpers… in particular, I\nthink this is supposed to be \"test_grep !\" ?\n\n>       +'\n>       +\n>       +test_expect_success '--name-only shows path' '\n>       + git log -L1,24:b.c --name-only --format= >actual &&\n>      -+ grep \"^b.c$\" actual &&\n>      -+ ! grep \"^diff --git\" actual &&\n>      -+ ! grep \"^@@\" actual\n>      ++ test_grep \"^b.c$\" actual &&\n>      ++ ! test_grep \"^diff --git\" actual &&\n>      ++ ! test_grep \"^@@\" actual\n>       +'\n>       +\n>       +test_expect_success '--name-status shows status and path' '\n>       + git log -L1,24:b.c --name-status --format= >actual &&\n>      -+ grep \"^M        b.c$\" actual &&\n>      -+ ! grep \"^diff --git\" actual &&\n>      -+ ! grep \"^@@\" actual\n>      ++ test_grep \"^M   b.c$\" actual &&\n>      ++ ! test_grep \"^diff --git\" actual &&\n>      ++ ! test_grep \"^@@\" actual\n>       +'\n>       +\n>       +test_expect_success '--stat is not yet supported with -L' '\n>      @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n>\n>        test_expect_success 'setup for checking fancy rename following' '\n>       @@ t/t4211-line-log.sh: test_expect_success '-L --oneline has no extra blank line before diff' '\n>      -  sed -n 2p actual | grep \"^diff --git\"\n>      +  test_grep \"^diff --git\" line2\n>        '\n>\n>       +test_expect_success '--summary shows new file on root commit' '\n>       + git checkout parent-oids &&\n>       + git log -L:func2:file.c --summary --format= >actual &&\n>      -+ grep \"create mode 100644 file.c\" actual\n>      ++ test_grep \"create mode 100644 file.c\" actual\n>       +'\n>       +\n>        test_done\n>\n> --\n> gitgitgadget\n\nThanks\n\n-- \nD. Ben Knoble\n"},{"id":"544231","messageId":"xmqqo6hzjrde.fsf@gitster.g","threadId":"65560","inReplyTo":"CALnO6CA5GPS2CMv_x_=wOPBgspe31FkW=h832GCBpAUbBoJNLg@mail.gmail.com","subject":"Re: [PATCH v2 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-28T18:55:41Z","receivedAt":"2026-05-28T18:55:44Z","isPatch":true,"body":"\"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n\n>>      ++ ! test_grep \"^diff --git\" actual &&\n>>      ++ ! test_grep \"^@@\" actual\n>\n> I wish we had docs for all the little test helpers… in particular, I\n> think this is supposed to be \"test_grep !\" ?\n\nGood eyes.  I wonder if we teach test-lint to catch these.\n"},{"id":"544234","messageId":"CAC2QwmJZyVY0EZnr1144gRrAxBxVoxd1x64t60DL-SeWFyn6Rw@mail.gmail.com","threadId":"65560","inReplyTo":"xmqqo6hzjrde.fsf@gitster.g","subject":"Re: [PATCH v2 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Michael Montalbo","fromEmail":"mmontalbo@gmail.com","sentAt":"2026-05-28T19:31:03Z","receivedAt":"2026-05-28T19:31:16Z","isPatch":true,"body":"On Thu, May 28, 2026 at 11:55 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"D. Ben Knoble\" <ben.knoble@gmail.com> writes:\n>\n> >>      ++ ! test_grep \"^diff --git\" actual &&\n> >>      ++ ! test_grep \"^@@\" actual\n> >\n> > I wish we had docs for all the little test helpers… in particular, I\n> > think this is supposed to be \"test_grep !\" ?\n\nGood catch, thank you! Will submit a fix and try to address the lack\nof docs in a follow-up.\n\n>\n> Good eyes.  I wonder if we teach test-lint to catch these.\n\nI had the same thought, and started working on a series that does this\nand migrates\nexisting offenders. I should be able to submit it soon.\n"},{"id":"544241","messageId":"pull.2094.v3.git.1780001267.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v2.git.1779738059.gitgitgadget@gmail.com","subject":"[PATCH v3 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-28T20:47:43Z","receivedAt":"2026-05-28T20:47:49Z","isPatch":true,"body":"Since its introduction, git log -L has short-circuited from\nlog_tree_commit() into its own output function, bypassing log_tree_diff()\nand log_tree_diff_flush(). This skips no_free save/restore,\nalways_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n--find-object) and --diff-filter cannot suppress commits whose pairs are all\nfiltered out, because show_log() runs before diffcore_std().\n\nThis series restructures the flow so that -L goes through the same\nlog_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\nmerge diffs, then uses that to enable several non-patch diff formats.\n\nPatch 1: revision: move -L setup before output_format-to-diff derivation\n\nPreparatory reorder in setup_revisions(). The -L block sets a default\nDIFF_FORMAT_PATCH when no format is requested; move it before the derivation\nof revs->diff from output_format so the default is visible to that check. No\nbehavior change on its own.\n\nPatch 2: line-log: integrate -L output with the standard log-tree pipeline\n\nRename line_log_print() to line_log_queue_pairs(), stripping it down to only\nqueue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\ndiffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\nsuppression, and aligns the commit/diff separator with the rest of log\noutput. Rejects --full-diff, which is not yet supported when filepairs are\npre-computed.\n\nPatch 3: line-log: allow non-patch diff formats with -L\n\nExpand the allowlist to accept --raw, --name-only, --name-status, and\n--summary. These only read filepair metadata already set by the line-log\nmachinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\nremain blocked because they call compute_diffstat() on full blob content and\nwould show whole-file statistics rather than range-scoped ones.\n\nChanges since v2:\n\n * Switch \"! test_grep\" to \"test_grep !\" in tests.\n\nMichael Montalbo (3):\n  revision: move -L setup before output_format-to-diff derivation\n  line-log: integrate -L output with the standard log-tree pipeline\n  line-log: allow non-patch diff formats with -L\n\n Documentation/line-range-options.adoc         |  10 +-\n line-log.c                                    |  30 ++----\n line-log.h                                    |   2 +-\n log-tree.c                                    |  10 +-\n revision.c                                    |  24 +++--\n t/t4211-line-log.sh                           | 100 +++++++++++++++---\n t/t4211/sha1/expect.parallel-change-f-to-main |   1 -\n .../sha256/expect.parallel-change-f-to-main   |   1 -\n 8 files changed, 121 insertions(+), 57 deletions(-)\n\n\nbase-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/2094\n\nRange-diff vs v2:\n\n 1:  9633eb62c6 = 1:  9633eb62c6 revision: move -L setup before output_format-to-diff derivation\n 2:  7acfc5376e = 2:  7acfc5376e line-log: integrate -L output with the standard log-tree pipeline\n 3:  10a3d8dde2 ! 3:  ae0b7f3ca8 line-log: allow non-patch diff formats with -L\n     @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n      +test_expect_success '--raw shows mode, oid, status and path' '\n      +\tgit log -L1,24:b.c --raw --format= >actual &&\n      +\ttest_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n     -+\t! test_grep \"^diff --git\" actual &&\n     -+\t! test_grep \"^@@\" actual\n     ++\ttest_grep ! \"^diff --git\" actual &&\n     ++\ttest_grep ! \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--name-only shows path' '\n      +\tgit log -L1,24:b.c --name-only --format= >actual &&\n      +\ttest_grep \"^b.c$\" actual &&\n     -+\t! test_grep \"^diff --git\" actual &&\n     -+\t! test_grep \"^@@\" actual\n     ++\ttest_grep ! \"^diff --git\" actual &&\n     ++\ttest_grep ! \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--name-status shows status and path' '\n      +\tgit log -L1,24:b.c --name-status --format= >actual &&\n      +\ttest_grep \"^M\tb.c$\" actual &&\n     -+\t! test_grep \"^diff --git\" actual &&\n     -+\t! test_grep \"^@@\" actual\n     ++\ttest_grep ! \"^diff --git\" actual &&\n     ++\ttest_grep ! \"^@@\" actual\n      +'\n      +\n      +test_expect_success '--stat is not yet supported with -L' '\n\n-- \ngitgitgadget\n"},{"id":"544242","messageId":"9633eb62c620c7935f4e0aa87cae8808a6128c26.1780001267.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v3.git.1780001267.gitgitgadget@gmail.com","subject":"[PATCH v3 1/3] revision: move -L setup before output_format-to-diff derivation","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-28T20:47:44Z","receivedAt":"2026-05-28T20:47:50Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nThe line_level_traverse block sets a default DIFF_FORMAT_PATCH when\nno output format has been explicitly requested.  This default must\nbe visible to the \"Did the user ask for any diff output?\" check\nthat derives revs->diff from revs->diffopt.output_format.\n\nCurrently the -L block runs after that derivation, so revs->diff\nstays 0 when no explicit format is given.  This does not matter yet\nbecause log_tree_commit() short-circuits into line_log_print()\nbefore consulting revs->diff, but the next commit will route -L\nthrough the normal log_tree_diff() path, which checks revs->diff.\n\nMove the block above the derivation so the default DIFF_FORMAT_PATCH\nis in place when revs->diff is computed.  No behavior change on its\nown.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n revision.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/revision.c b/revision.c\nindex 599b3a66c3..4a8e24bc38 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3112,6 +3112,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tobject_context_release(&oc);\n \t}\n \n+\tif (revs->line_level_traverse) {\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 \t/* Did the user ask for any diff output? Run the diff! */\n \tif (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)\n \t\trevs->diff = 1;\n@@ -3125,14 +3133,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->diffopt.objfind)\n \t\trevs->simplify_history = 0;\n \n-\tif (revs->line_level_traverse) {\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))\n \t\trevs->limited = 1;\n \n-- \ngitgitgadget\n\n"},{"id":"544243","messageId":"7acfc5376e14fb3d69e06ab8a5ba68973bfe808f.1780001267.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v3.git.1780001267.gitgitgadget@gmail.com","subject":"[PATCH v3 2/3] line-log: integrate -L output with the standard log-tree pipeline","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-28T20:47:45Z","receivedAt":"2026-05-28T20:47:51Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\n`git log -L` has bypassed log_tree_diff() and log_tree_diff_flush()\nsince the feature was introduced, short-circuiting from\nlog_tree_commit() directly into line_log_print().  This skips the\nno_free save/restore (noted in a NEEDSWORK comment added by\nf8781bfda3), the always_show_header fallback, show_diff_of_diff(),\nand diff_free() cleanup.\n\nRestructure so that -L flows through log_tree_diff() ->\nlog_tree_diff_flush(), the same path used by the normal\nsingle-parent and merge diff codepaths:\n\n - Rename line_log_print() to line_log_queue_pairs() and strip it\n   down to just queuing pre-computed filepairs.  The show_log(),\n   separator, diffcore_std(), and diff_flush() calls are removed\n   since log_tree_diff_flush() handles all of those.\n\n - In log_tree_diff(), call line_log_queue_pairs() then\n   log_tree_diff_flush(), mirroring the diff_tree_oid() + flush\n   pattern used by the single-parent and merge codepaths.\n\n - Remove the early return in log_tree_commit() that is no longer\n   needed now that -L output flows through log_tree_diff() and\n   log_tree_diff_flush(); this restores no_free save/restore,\n   always_show_header, and diff_free() cleanup.\n\nBecause show_log() is now deferred until after diffcore_std() inside\nlog_tree_diff_flush(), pickaxe (-S, -G, --find-object) and\n--diff-filter now properly suppress commits when all pairs are\nfiltered out.\n\nThe blank-line separator between commit header and diff changes\nslightly: the old code printed one unconditionally, while\nlog_tree_diff_flush() only emits one for verbose headers.  This\nmatches the rest of log output.\n\nAlso reject --full-diff, which is not yet supported with -L: the\nfilepairs are pre-computed during the history walk and scoped to\ntracked line ranges, so there is currently no full-tree diff to\nfall back to for display.\n\nUpdate tests accordingly.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n line-log.c                                    | 30 ++++-------\n line-log.h                                    |  2 +-\n log-tree.c                                    | 10 ++--\n revision.c                                    |  6 ++-\n t/t4211-line-log.sh                           | 53 ++++++++++++++-----\n t/t4211/sha1/expect.parallel-change-f-to-main |  1 -\n .../sha256/expect.parallel-change-f-to-main   |  1 -\n 7 files changed, 60 insertions(+), 43 deletions(-)\n\ndiff --git a/line-log.c b/line-log.c\nindex 858a899cd2..7ee55b05cc 100644\n--- a/line-log.c\n+++ b/line-log.c\n@@ -13,7 +13,6 @@\n #include \"revision.h\"\n #include \"xdiff-interface.h\"\n #include \"strbuf.h\"\n-#include \"log-tree.h\"\n #include \"line-log.h\"\n #include \"setup.h\"\n #include \"strvec.h\"\n@@ -1004,29 +1003,18 @@ static int process_all_files(struct line_log_data **range_out,\n \treturn changed;\n }\n \n-int line_log_print(struct rev_info *rev, struct commit *commit)\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit)\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\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+\tstruct line_log_data *range = lookup_line_range(rev, commit);\n+\tstruct line_log_data *r;\n \n-\t\tdiffcore_std(&rev->diffopt);\n-\t\tdiff_flush(&rev->diffopt);\n+\tfor (r = range; r; r = r->next) {\n+\t\tif (r->pair) {\n+\t\t\tstruct diff_filepair *p = diff_filepair_dup(r->pair);\n+\t\t\tp->line_ranges = &r->ranges;\n+\t\t\tdiff_q(&diff_queued_diff, p);\n+\t\t}\n \t}\n-\treturn 1;\n }\n \n static int bloom_filter_check(struct rev_info *rev,\ndiff --git a/line-log.h b/line-log.h\nindex 04a6ea64d3..99e1755ce3 100644\n--- a/line-log.h\n+++ b/line-log.h\n@@ -46,7 +46,7 @@ int line_log_filter(struct rev_info *rev);\n int line_log_process_ranges_arbitrary_commit(struct rev_info *rev,\n \t\t\t\t\t\t    struct commit *commit);\n \n-int line_log_print(struct rev_info *rev, struct commit *commit);\n+void line_log_queue_pairs(struct rev_info *rev, struct commit *commit);\n \n void line_log_free(struct rev_info *rev);\n \ndiff --git a/log-tree.c b/log-tree.c\nindex 7e048701d0..88b3019293 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1105,6 +1105,12 @@ static int log_tree_diff(struct rev_info *opt, struct commit *commit, struct log\n \tif (!all_need_diff && !opt->merges_need_diff)\n \t\treturn 0;\n \n+\tif (opt->line_level_traverse) {\n+\t\tline_log_queue_pairs(opt, commit);\n+\t\tlog_tree_diff_flush(opt);\n+\t\treturn !opt->loginfo;\n+\t}\n+\n \tparse_commit_or_die(commit);\n \toid = get_commit_tree_oid(commit);\n \n@@ -1179,10 +1185,6 @@ int log_tree_commit(struct rev_info *opt, struct commit *commit)\n \topt->loginfo = &log;\n \topt->diffopt.no_free = 1;\n \n-\t/* NEEDSWORK: no restoring of no_free?  Why? */\n-\tif (opt->line_level_traverse)\n-\t\treturn line_log_print(opt, commit);\n-\n \tif (opt->track_linear && !opt->linear && !opt->reverse_output_stage)\n \t\tfprintf(opt->diffopt.file, \"\\n%s\\n\", opt->break_bar);\n \tshown = log_tree_diff(opt, commit, &log);\ndiff --git a/revision.c b/revision.c\nindex 4a8e24bc38..c903f7a1b4 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3179,8 +3179,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\tdie(_(\"the option '%s' requires '%s'\"), \"--grep-reflog\", \"--walk-reflogs\");\n \n \tif (revs->line_level_traverse &&\n-\t    (revs->diffopt.output_format & ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT)))\n-\t\tdie(_(\"-L does not yet support diff formats besides -p and -s\"));\n+\t    (revs->full_diff ||\n+\t     (revs->diffopt.output_format &\n+\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n+\t\tdie(_(\"-L does not yet support the requested diff format\"));\n \n \tif (revs->expand_tabs_in_log < 0)\n \t\trevs->expand_tabs_in_log = revs->expand_tabs_in_log_default;\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex aaf197d2ed..e3937138a9 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -368,7 +368,6 @@ test_expect_success '-L diff output includes index and new file mode' '\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@@ -377,7 +376,6 @@ test_expect_success '-L with --word-diff' '\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@@ -433,7 +431,6 @@ test_expect_success 'show line-log with graph' '\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@@ -445,7 +442,6 @@ test_expect_success 'show line-log with graph' '\n \t| +    return F2 + 2;\n \t|  }\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@@ -494,23 +490,17 @@ test_expect_success '-L --find-object does not crash with merge and rename' '\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+test_expect_success '-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+test_expect_success '-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+test_expect_success '-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@@ -711,4 +701,41 @@ test_expect_success '-L with -G filters to diff-text matches' '\n \tgrep \"F2 + 2\" actual\n '\n \n+test_expect_success '-L with --diff-filter=M excludes root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=M --format=%s --no-patch >actual &&\n+\t# Root commit is an Add (A), not a Modify (M), so it should\n+\t# be excluded; only the modification commit remains.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with --diff-filter=A shows only root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --diff-filter=A --format=%s --no-patch >actual &&\n+\techo \"Add func1() and func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '-L with -S suppresses non-matching commits' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c -S \"F2 + 2\" --format=%s --no-patch >actual &&\n+\t# Only the commit that changes the count of \"F2 + 2\" should appear.\n+\techo \"Modify func2() in file.c\" >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '--full-diff is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --full-diff 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '-L --oneline has no extra blank line before diff' '\n+\tgit checkout parent-oids &&\n+\tgit log --oneline -L:func2:file.c -1 >actual &&\n+\t# Oneline header on line 1, diff starts immediately on line 2\n+\tsed -n 2p actual >line2 &&\n+\ttest_grep \"^diff --git\" line2\n+'\n+\n test_done\ndiff --git a/t/t4211/sha1/expect.parallel-change-f-to-main b/t/t4211/sha1/expect.parallel-change-f-to-main\nindex 65a8cc673a..6d7a201036 100644\n--- a/t/t4211/sha1/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha1/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 6ce3c4ff690136099bb17e1a8766b75764726ea7\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\ndiff --git a/t/t4211/sha256/expect.parallel-change-f-to-main b/t/t4211/sha256/expect.parallel-change-f-to-main\nindex 3178989253..c93e03bef4 100644\n--- a/t/t4211/sha256/expect.parallel-change-f-to-main\n+++ b/t/t4211/sha256/expect.parallel-change-f-to-main\n@@ -5,7 +5,6 @@ Date:   Fri Apr 12 16:16:24 2013 +0200\n \n     Merge across the rename\n \n-\n commit 4f7a58195a92c400e28a2354328587f1ff14fb77f5cf894536f17ccbc72931b9\n Author: Thomas Rast <trast@student.ethz.ch>\n Date:   Thu Feb 28 10:49:50 2013 +0100\n-- \ngitgitgadget\n\n"},{"id":"544244","messageId":"ae0b7f3ca8a0cb103bd9f65e414f8ac970b7ecb2.1780001267.git.gitgitgadget@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v3.git.1780001267.gitgitgadget@gmail.com","subject":"[PATCH v3 3/3] line-log: allow non-patch diff formats with -L","fromName":"Michael Montalbo via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-28T20:47:46Z","receivedAt":"2026-05-28T20:47:52Z","isPatch":true,"body":"From: Michael Montalbo <mmontalbo@gmail.com>\n\nNow that -L flows through log_tree_diff_flush() and diff_flush(),\nmetadata-only diff formats work because they only read filepair\nfields (status, mode, path, oid) already set on the pre-computed\npairs.\n\nExpand the allowlist in setup_revisions() to also accept --raw,\n--name-only, --name-status, and --summary.  Diff stat formats\n(--stat, --numstat, --shortstat, --dirstat) remain blocked because\nthey call compute_diffstat() on full blob content and would show\nwhole-file statistics rather than range-scoped ones.\n\nSigned-off-by: Michael Montalbo <mmontalbo@gmail.com>\n---\n Documentation/line-range-options.adoc | 10 +++---\n revision.c                            |  4 ++-\n t/t4211-line-log.sh                   | 47 +++++++++++++++++++++++++--\n 3 files changed, 54 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/line-range-options.adoc b/Documentation/line-range-options.adoc\nindex ecb2c79fb9..72f639b5e7 100644\n--- a/Documentation/line-range-options.adoc\n+++ b/Documentation/line-range-options.adoc\n@@ -8,12 +8,14 @@\n \tgive zero or one positive revision arguments, and\n \t_<start>_ and _<end>_ (or _<funcname>_) must exist in the starting revision.\n \tYou can specify this option more than once. Implies `--patch`.\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+\tPatch output can be suppressed using `--no-patch`.\n+\tNon-patch diff formats `--raw`, `--name-only`, `--name-status`,\n+\tand `--summary` are supported.  Diff stat formats\n+\t(`--stat`, `--numstat`, `--shortstat`, `--dirstat`) are not\n+\tcurrently 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+as are pickaxe options (`-S`, `-G`) and `--diff-filter`.\n +\n include::line-range-format.adoc[]\ndiff --git a/revision.c b/revision.c\nindex c903f7a1b4..f26fc1f4d5 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3181,7 +3181,9 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \tif (revs->line_level_traverse &&\n \t    (revs->full_diff ||\n \t     (revs->diffopt.output_format &\n-\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT))))\n+\t      ~(DIFF_FORMAT_PATCH | DIFF_FORMAT_NO_OUTPUT |\n+\t\tDIFF_FORMAT_RAW | DIFF_FORMAT_NAME |\n+\t\tDIFF_FORMAT_NAME_STATUS | DIFF_FORMAT_SUMMARY))))\n \t\tdie(_(\"-L does not yet support the requested diff format\"));\n \n \tif (revs->expand_tabs_in_log < 0)\ndiff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh\nindex e3937138a9..ca4eb7bbc7 100755\n--- a/t/t4211-line-log.sh\n+++ b/t/t4211-line-log.sh\n@@ -155,8 +155,45 @@ test_expect_success '-p shows the default patch output' '\n \ttest_cmp expect actual\n '\n \n-test_expect_success '--raw is forbidden' '\n-\ttest_must_fail git log -L1,24:b.c --raw\n+test_expect_success '--raw shows mode, oid, status and path' '\n+\tgit log -L1,24:b.c --raw --format= >actual &&\n+\ttest_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M\tb.c$\" actual &&\n+\ttest_grep ! \"^diff --git\" actual &&\n+\ttest_grep ! \"^@@\" actual\n+'\n+\n+test_expect_success '--name-only shows path' '\n+\tgit log -L1,24:b.c --name-only --format= >actual &&\n+\ttest_grep \"^b.c$\" actual &&\n+\ttest_grep ! \"^diff --git\" actual &&\n+\ttest_grep ! \"^@@\" actual\n+'\n+\n+test_expect_success '--name-status shows status and path' '\n+\tgit log -L1,24:b.c --name-status --format= >actual &&\n+\ttest_grep \"^M\tb.c$\" actual &&\n+\ttest_grep ! \"^diff --git\" actual &&\n+\ttest_grep ! \"^@@\" actual\n+'\n+\n+test_expect_success '--stat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --stat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--numstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --numstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--shortstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --shortstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n+'\n+\n+test_expect_success '--dirstat is not yet supported with -L' '\n+\ttest_must_fail git log -L1,24:b.c --dirstat 2>err &&\n+\ttest_grep \"does not yet support\" err\n '\n \n test_expect_success 'setup for checking fancy rename following' '\n@@ -738,4 +775,10 @@ test_expect_success '-L --oneline has no extra blank line before diff' '\n \ttest_grep \"^diff --git\" line2\n '\n \n+test_expect_success '--summary shows new file on root commit' '\n+\tgit checkout parent-oids &&\n+\tgit log -L:func2:file.c --summary --format= >actual &&\n+\ttest_grep \"create mode 100644 file.c\" actual\n+'\n+\n test_done\n-- \ngitgitgadget\n"},{"id":"544266","messageId":"B59BA5B1-184D-48A8-8BAD-11EB6F8EB50C@gmail.com","threadId":"65560","inReplyTo":"pull.2094.v3.git.1780001267.gitgitgadget@gmail.com","subject":"Re: [PATCH v3 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Ben Knoble","fromEmail":"ben.knoble@gmail.com","sentAt":"2026-05-29T12:04:27Z","receivedAt":"2026-05-29T12:04:39Z","isPatch":true,"body":"\n> Le 28 mai 2026 à 16:47, Michael Montalbo via GitGitGadget <gitgitgadget@gmail.com> a écrit :\n> \n> ﻿Since its introduction, git log -L has short-circuited from\n> log_tree_commit() into its own output function, bypassing log_tree_diff()\n> and log_tree_diff_flush(). This skips no_free save/restore,\n> always_show_header, diff_free() cleanup, and means that pickaxe (-S, -G,\n> --find-object) and --diff-filter cannot suppress commits whose pairs are all\n> filtered out, because show_log() runs before diffcore_std().\n> \n> This series restructures the flow so that -L goes through the same\n> log_tree_diff() -> log_tree_diff_flush() path as normal single-parent and\n> merge diffs, then uses that to enable several non-patch diff formats.\n> \n> Patch 1: revision: move -L setup before output_format-to-diff derivation\n> \n> Preparatory reorder in setup_revisions(). The -L block sets a default\n> DIFF_FORMAT_PATCH when no format is requested; move it before the derivation\n> of revs->diff from output_format so the default is visible to that check. No\n> behavior change on its own.\n> \n> Patch 2: line-log: integrate -L output with the standard log-tree pipeline\n> \n> Rename line_log_print() to line_log_queue_pairs(), stripping it down to only\n> queue pre-computed filepairs. log_tree_diff_flush() handles show_log(),\n> diffcore_std(), and diff_flush(). This fixes pickaxe and --diff-filter\n> suppression, and aligns the commit/diff separator with the rest of log\n> output. Rejects --full-diff, which is not yet supported when filepairs are\n> pre-computed.\n> \n> Patch 3: line-log: allow non-patch diff formats with -L\n> \n> Expand the allowlist to accept --raw, --name-only, --name-status, and\n> --summary. These only read filepair metadata already set by the line-log\n> machinery. Diff stat formats (--stat, --numstat, --shortstat, --dirstat)\n> remain blocked because they call compute_diffstat() on full blob content and\n> would show whole-file statistics rather than range-scoped ones.\n> \n> Changes since v2:\n> \n> * Switch \"! test_grep\" to \"test_grep !\" in tests.\n\nThanks ! I did not read the tests carefully for semantic value, but the rationale and overall code looks good to me as discussed previously.\n\nThe range-diff here looks good, too. \n\n\n> Michael Montalbo (3):\n>  revision: move -L setup before output_format-to-diff derivation\n>  line-log: integrate -L output with the standard log-tree pipeline\n>  line-log: allow non-patch diff formats with -L\n> \n> Documentation/line-range-options.adoc         |  10 +-\n> line-log.c                                    |  30 ++----\n> line-log.h                                    |   2 +-\n> log-tree.c                                    |  10 +-\n> revision.c                                    |  24 +++--\n> t/t4211-line-log.sh                           | 100 +++++++++++++++---\n> t/t4211/sha1/expect.parallel-change-f-to-main |   1 -\n> .../sha256/expect.parallel-change-f-to-main   |   1 -\n> 8 files changed, 121 insertions(+), 57 deletions(-)\n> \n> \n> base-commit: 9f223ef1c026d91c7ac68cc0211bde255dda6199\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2094%2Fmmontalbo%2Fmm%2Fline-log-use-log-tree-diff-flush-v3\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2094/mmontalbo/mm/line-log-use-log-tree-diff-flush-v3\n> Pull-Request: https://github.com/gitgitgadget/git/pull/2094\n> \n> Range-diff vs v2:\n> \n> 1:  9633eb62c6 = 1:  9633eb62c6 revision: move -L setup before output_format-to-diff derivation\n> 2:  7acfc5376e = 2:  7acfc5376e line-log: integrate -L output with the standard log-tree pipeline\n> 3:  10a3d8dde2 ! 3:  ae0b7f3ca8 line-log: allow non-patch diff formats with -L\n>     @@ t/t4211-line-log.sh: test_expect_success '-p shows the default patch output' '\n>      +test_expect_success '--raw shows mode, oid, status and path' '\n>      +    git log -L1,24:b.c --raw --format= >actual &&\n>      +    test_grep \"^:100644 100644 [0-9a-f]\\{7\\} [0-9a-f]\\{7\\} M    b.c$\" actual &&\n>     -+    ! test_grep \"^diff --git\" actual &&\n>     -+    ! test_grep \"^@@\" actual\n>     ++    test_grep ! \"^diff --git\" actual &&\n>     ++    test_grep ! \"^@@\" actual\n>      +'\n>      +\n>      +test_expect_success '--name-only shows path' '\n>      +    git log -L1,24:b.c --name-only --format= >actual &&\n>      +    test_grep \"^b.c$\" actual &&\n>     -+    ! test_grep \"^diff --git\" actual &&\n>     -+    ! test_grep \"^@@\" actual\n>     ++    test_grep ! \"^diff --git\" actual &&\n>     ++    test_grep ! \"^@@\" actual\n>      +'\n>      +\n>      +test_expect_success '--name-status shows status and path' '\n>      +    git log -L1,24:b.c --name-status --format= >actual &&\n>      +    test_grep \"^M    b.c$\" actual &&\n>     -+    ! test_grep \"^diff --git\" actual &&\n>     -+    ! test_grep \"^@@\" actual\n>     ++    test_grep ! \"^diff --git\" actual &&\n>     ++    test_grep ! \"^@@\" actual\n>      +'\n>      +\n>      +test_expect_success '--stat is not yet supported with -L' '\n> \n> --\n> gitgitgadget\n"},{"id":"544429","messageId":"xmqqzf1e2aie.fsf@gitster.g","threadId":"65560","inReplyTo":"B59BA5B1-184D-48A8-8BAD-11EB6F8EB50C@gmail.com","subject":"Re: [PATCH v3 0/3] line-log: integrate -L with the standard log output pipeline","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-01T21:53:13Z","receivedAt":"2026-06-01T21:53:15Z","isPatch":true,"body":"Ben Knoble <ben.knoble@gmail.com> writes:\n\n>> Changes since v2:\n>> \n>> * Switch \"! test_grep\" to \"test_grep !\" in tests.\n>\n> Thanks ! I did not read the tests carefully for semantic value,\n> but the rationale and overall code looks good to me as discussed\n> previously.\n>\n> The range-diff here looks good, too. \n\nThanks, both.  Let's mark it for 'next' then.\n\n"}]}