From b143f0f60816bbb2095eadc15d81b49c131f6a19 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 20 Jan 2026 22:47:08 +0100 Subject: [PATCH 1/4] last-modified: clarify in the docs the command takes a pathspec The documentation mentions git-last-modified(1) takes `...`, but that argument actually accepts a pathspec. Reword the documentation to reflect that. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 11 ++++++----- builtin/last-modified.c | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 602843e095..7c3fd844b8 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -9,7 +9,8 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified SYNOPSIS -------- [synopsis] -git last-modified [--recursive] [--show-trees] [] [[--] ...] +git last-modified [--recursive] [--show-trees] + [] [[--] ...] DESCRIPTION ----------- @@ -39,10 +40,10 @@ OPTIONS spell ``, see the 'Specifying Ranges' section of linkgit:gitrevisions[7]. -`[--] ...`:: - For each __ given, the commit which last modified it is returned. - Without an optional path parameter, all files and subdirectories - in path traversal the are included in the output. +`[--] ...`:: + Show the commit that last modified each path matching __. + If no __ is given, all files and subdirectories are included. + See linkgit:gitglossary[7] for details on pathspec syntax. SEE ALSO -------- diff --git a/builtin/last-modified.c b/builtin/last-modified.c index b0ecbdc540..781495f597 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -510,8 +510,8 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, struct last_modified lm = { 0 }; const char * const last_modified_usage[] = { - N_("git last-modified [--recursive] [--show-trees] " - "[] [[--] ...]"), + N_("git last-modified [--recursive] [--show-trees]\n" + " [] [[--] ...]"), NULL }; From 209574de2d2ab0a264522c8c44c3eebb6d03ec43 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 20 Jan 2026 22:47:09 +0100 Subject: [PATCH 2/4] last-modified: document option '-z' The command git-last-modified(1) already recognizes the option '-z', and similar to many other commands this will make the output NUL-terminated instead of using newlines. Although, this option is missing from the documentation, so add it. In addition to that, to have '-z' also appear in the help output of `git last-modified -h`, move the handling of '-z' to parse_options() in builtin/last-modified.c itself. Before, the parsing of option '-z' was done by diff_opt_parse(), which is called by setup_revisions(). That would fill in `struct diff_options::line_termination`, but that field was not used by the diff machinery itself. Thus it makes more sense to have the handling of that option completely in builtin/last-modified.c. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 21 ++++++++++++++++++++- builtin/last-modified.c | 11 +++++++---- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 7c3fd844b8..3760fd33a1 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -9,7 +9,7 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified SYNOPSIS -------- [synopsis] -git last-modified [--recursive] [--show-trees] +git last-modified [--recursive] [--show-trees] [-z] [] [[--] ...] DESCRIPTION @@ -33,6 +33,9 @@ OPTIONS Show tree entries even when recursing into them. It has no effect without `--recursive`. +`-z`:: + Terminate each line with a _NUL_ character rather than a newline. + ``:: Only traverse commits in the specified revision range. When no `` is specified, it defaults to `HEAD` (i.e. the whole @@ -45,6 +48,22 @@ OPTIONS If no __ is given, all files and subdirectories are included. See linkgit:gitglossary[7] for details on pathspec syntax. +OUTPUT +------ + +The output is in the format: + +------------ + TAB LF +------------ + +If a path contains any special characters, the path is C-style quoted. To +avoid quoting, pass option `-z` to terminate each line with a NUL. + +------------ + TAB NUL +------------ + SEE ALSO -------- linkgit:git-blame[1], diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 781495f597..46423b527e 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -55,6 +55,7 @@ struct last_modified { struct rev_info rev; bool recursive; bool show_trees; + bool nul_termination; const char **all_paths; size_t all_paths_nr; @@ -165,10 +166,10 @@ static void last_modified_emit(struct last_modified *lm, putchar('^'); printf("%s\t", oid_to_hex(&commit->object.oid)); - if (lm->rev.diffopt.line_termination) - write_name_quoted(path, stdout, '\n'); - else + if (lm->nul_termination) printf("%s%c", path, '\0'); + else + write_name_quoted(path, stdout, '\n'); } static void mark_path(const char *path, const struct object_id *oid, @@ -510,7 +511,7 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, struct last_modified lm = { 0 }; const char * const last_modified_usage[] = { - N_("git last-modified [--recursive] [--show-trees]\n" + N_("git last-modified [--recursive] [--show-trees] [-z]\n" " [] [[--] ...]"), NULL }; @@ -520,6 +521,8 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, N_("recurse into subtrees")), OPT_BOOL('t', "show-trees", &lm.show_trees, N_("show tree entries when recursing into subtrees")), + OPT_BOOL('z', NULL, &lm.nul_termination, + N_("lines are separated with NUL character")), OPT_END() }; From 9bfaf78cb28b26ab0538e2edd2229547d6be962f Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 20 Jan 2026 22:47:10 +0100 Subject: [PATCH 3/4] last-modified: document option '--max-depth' Option --max-depth is supported by git-last-modified(1), because it was added to the diff machinery in a1dfa5448d (diff: teach tree-diff a max-depth parameter, 2025-08-07). This option is useful for everyday use of the git-last-modified(1) command, so document it's existence in the man page. To have it also appear in the help output of `git last-modified -h`, move the handling of '--max-depth' to parse_options() in builtin/last-modified.c itself. This prepares for the change in default behavior in the next commit. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 8 +++++++- builtin/last-modified.c | 17 ++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 3760fd33a1..6f9b119bb6 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -9,7 +9,7 @@ git-last-modified - EXPERIMENTAL: Show when files were last modified SYNOPSIS -------- [synopsis] -git last-modified [--recursive] [--show-trees] [-z] +git last-modified [--recursive] [--show-trees] [--max-depth=] [-z] [] [[--] ...] DESCRIPTION @@ -33,6 +33,12 @@ OPTIONS Show tree entries even when recursing into them. It has no effect without `--recursive`. +`--max-depth=`:: + For each pathspec given on the command line, traverse at most `` + levels into subtrees. A negative value means no limit. + The default is 0, which shows all paths matching the pathspec + without descending into subtrees. + `-z`:: Terminate each line with a _NUL_ character rather than a newline. diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 46423b527e..797c1bb88b 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -56,6 +56,7 @@ struct last_modified { bool recursive; bool show_trees; bool nul_termination; + int max_depth; const char **all_paths; size_t all_paths_nr; @@ -483,6 +484,12 @@ static int last_modified_init(struct last_modified *lm, struct repository *r, lm->rev.diffopt.flags.recursive = lm->recursive; lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees; + if (lm->max_depth >= 0) { + lm->rev.diffopt.flags.recursive = 1; + lm->rev.diffopt.max_depth = lm->max_depth; + lm->rev.diffopt.max_depth_valid = 1; + } + argc = setup_revisions(argc, argv, &lm->rev, NULL); if (argc > 1) { error(_("unknown last-modified argument: %s"), argv[1]); @@ -511,7 +518,7 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, struct last_modified lm = { 0 }; const char * const last_modified_usage[] = { - N_("git last-modified [--recursive] [--show-trees] [-z]\n" + N_("git last-modified [--recursive] [--show-trees] [--max-depth=] [-z]\n" " [] [[--] ...]"), NULL }; @@ -521,11 +528,19 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, N_("recurse into subtrees")), OPT_BOOL('t', "show-trees", &lm.show_trees, N_("show tree entries when recursing into subtrees")), + OPT_INTEGER_F(0, "max-depth", &lm.max_depth, + N_("maximum tree depth to recurse"), PARSE_OPT_NONEG), OPT_BOOL('z', NULL, &lm.nul_termination, N_("lines are separated with NUL character")), OPT_END() }; + /* + * Set the default of a max-depth to "unset". This will change in a + * subsequent commit. + */ + lm.max_depth = -1; + argc = parse_options(argc, argv, prefix, last_modified_options, last_modified_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); From 9dcc09bed13aba0dc93d253f18ee2c7da5970c0c Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Tue, 20 Jan 2026 22:47:11 +0100 Subject: [PATCH 4/4] last-modified: change default max-depth to 0 By default git-last-modified(1) doesn't recurse into subtrees. So when the pathspec contained a path in a subtree, the command would only print the commit information about the parent tree of the path, like: $ git last-modified -- path/file aaa0aab1bbb2bcc3ccc4ddd5dde6eee7eff8fff9 path Change the default behavior to give commit information about the exact path instead: $ git last-modified -- path/file aaa0aab1bbb2bcc3ccc4ddd5dde6eee7eff8fff9 path/file To achieve this, the default max-depth is changed to 0 and recursive is always enabled. The handling of option '-r' is modified to disable a max-depth, resulting in the behavior of this option to remain unchanged. No existing tests were modified, because there didn't exist any tests covering the example above. But more tests are added to cover this now. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 9 +++---- builtin/last-modified.c | 21 ++++------------- t/t8020-last-modified.sh | 35 ++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 6f9b119bb6..d7d16fc4f7 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -25,13 +25,14 @@ OPTIONS `-r`:: `--recursive`:: - Instead of showing tree entries, step into subtrees and show all entries - inside them recursively. + Recursively traverse into all subtrees. By default, the command only + shows tree entries matching the ``. With this option, it + descends into subtrees and displays all entries within them. + Equivalent to `--max-depth=-1`. `-t`:: `--show-trees`:: - Show tree entries even when recursing into them. It has no effect - without `--recursive`. + Show tree entries even when recursing into them. `--max-depth=`:: For each pathspec given on the command line, traverse at most `` diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 797c1bb88b..e27f36b624 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -53,7 +53,6 @@ define_commit_slab(active_paths_for_commit, struct bitmap *); struct last_modified { struct hashmap paths; struct rev_info rev; - bool recursive; bool show_trees; bool nul_termination; int max_depth; @@ -481,14 +480,10 @@ static int last_modified_init(struct last_modified *lm, struct repository *r, lm->rev.no_commit_id = 1; lm->rev.diff = 1; lm->rev.diffopt.flags.no_recursive_diff_tree_combined = 1; - lm->rev.diffopt.flags.recursive = lm->recursive; + lm->rev.diffopt.flags.recursive = 1; lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees; - - if (lm->max_depth >= 0) { - lm->rev.diffopt.flags.recursive = 1; - lm->rev.diffopt.max_depth = lm->max_depth; - lm->rev.diffopt.max_depth_valid = 1; - } + lm->rev.diffopt.max_depth = lm->max_depth; + lm->rev.diffopt.max_depth_valid = lm->max_depth >= 0; argc = setup_revisions(argc, argv, &lm->rev, NULL); if (argc > 1) { @@ -524,8 +519,8 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, }; struct option last_modified_options[] = { - OPT_BOOL('r', "recursive", &lm.recursive, - N_("recurse into subtrees")), + OPT_SET_INT('r', "recursive", &lm.max_depth, + N_("recurse into subtrees"), -1), OPT_BOOL('t', "show-trees", &lm.show_trees, N_("show tree entries when recursing into subtrees")), OPT_INTEGER_F(0, "max-depth", &lm.max_depth, @@ -535,12 +530,6 @@ int cmd_last_modified(int argc, const char **argv, const char *prefix, OPT_END() }; - /* - * Set the default of a max-depth to "unset". This will change in a - * subsequent commit. - */ - lm.max_depth = -1; - argc = parse_options(argc, argv, prefix, last_modified_options, last_modified_usage, PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); diff --git a/t/t8020-last-modified.sh b/t/t8020-last-modified.sh index a4c1114ee2..43f38937ba 100755 --- a/t/t8020-last-modified.sh +++ b/t/t8020-last-modified.sh @@ -85,6 +85,41 @@ test_expect_success 'last-modified subdir recursive' ' EOF ' +test_expect_success 'last-modified subdir non-recursive' ' + check_last_modified a <<-\EOF + 3 a + EOF +' + +test_expect_success 'last-modified path in subdir non-recursive' ' + check_last_modified a/file <<-\EOF + 2 a/file + EOF +' + +test_expect_success 'last-modified subdir with wildcard non-recursive' ' + check_last_modified a/* <<-\EOF + 3 a/b + 2 a/file + EOF +' + +test_expect_success 'last-modified with negative max-depth' ' + check_last_modified --max-depth=-1 <<-\EOF + 3 a/b/file + 2 a/file + 1 file + EOF +' + +test_expect_success 'last-modified with max-depth of 1' ' + check_last_modified --max-depth=1 <<-\EOF + 3 a/b + 2 a/file + 1 file + EOF +' + test_expect_success 'last-modified from non-HEAD commit' ' check_last_modified HEAD^ <<-\EOF 2 a