From 8f5b52c62ae5fc38ac9f545539dd458977ebb235 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 16 Jan 2026 14:22:49 +0100 Subject: [PATCH 1/5] last-modified: document NUL termination 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. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 22 +++++++++++++++++++++- builtin/last-modified.c | 4 ++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 602843e095..2e5f370c15 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] [-z] + [] [[--] ...] DESCRIPTION ----------- @@ -32,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 @@ -44,6 +48,22 @@ OPTIONS Without an optional path parameter, all files and subdirectories in path traversal the are included in the output. +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 b0ecbdc540..80b805a589 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] [-z]\n" + " [] [[--] ...]"), NULL }; From f1136ddeb04c7198ce5e71a7ef6e9a1904d1eeb8 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 16 Jan 2026 14:22:50 +0100 Subject: [PATCH 2/5] last-modified: add option '-z' to help output The parsing of option '-z' is done by diff_opt_parse(), which is called by setup_revisions(), and ends up filling in `struct diff_options::line_termination`. But that field isn't used by the diff machinery itself, only by builtin/last-modified.c to format the output. 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. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- builtin/last-modified.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 80b805a589..bc51d16b92 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 null_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->null_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, @@ -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.null_termination, + N_("lines are separated with NUL character")), OPT_END() }; From cf3f547bb96757b32dcfba14ddf64c79b54c75b7 Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 16 Jan 2026 14:22:51 +0100 Subject: [PATCH 3/5] 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. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- Documentation/git-last-modified.adoc | 8 +++++++- builtin/last-modified.c | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index 2e5f370c15..a3992db3f2 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, descend at most `` + levels of directories. A negative value means no limit. + Setting a positive value implies `--recursive`. + Cannot be combined with wildcards in the pathspec. + `-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 bc51d16b92..630687b81f 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -511,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] [-z]\n" + N_("git last-modified [--recursive] [--show-trees] [--max-depth=] [-z]\n" " [] [[--] ...]"), NULL }; From 19ccdcd2a5349534f01c8611d7adcac0267a10ee Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 16 Jan 2026 14:22:52 +0100 Subject: [PATCH 4/5] last-modified: add option '--max-depth' to help output In previous commit option '--max-depth' was added to the documentation. 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. It enables us to change default behavior in a subsequent commit. Signed-off-by: Toon Claes Signed-off-by: Junio C Hamano --- builtin/last-modified.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/builtin/last-modified.c b/builtin/last-modified.c index 630687b81f..f6cbff30e7 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -56,6 +56,7 @@ struct last_modified { bool recursive; bool show_trees; bool null_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]); @@ -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.null_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 9aeda2827b3956b6466b17d64e4b2c2bee3cd8ea Mon Sep 17 00:00:00 2001 From: Toon Claes Date: Fri, 16 Jan 2026 14:22:53 +0100 Subject: [PATCH 5/5] 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 | 3 ++- builtin/last-modified.c | 16 +++---------- t/t8020-last-modified.sh | 35 ++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 14 deletions(-) diff --git a/Documentation/git-last-modified.adoc b/Documentation/git-last-modified.adoc index a3992db3f2..57136baf3b 100644 --- a/Documentation/git-last-modified.adoc +++ b/Documentation/git-last-modified.adoc @@ -27,6 +27,7 @@ OPTIONS `--recursive`:: Instead of showing tree entries, step into subtrees and show all entries inside them recursively. + This is identical as setting `--max-depth=-1`. `-t`:: `--show-trees`:: @@ -36,7 +37,7 @@ OPTIONS `--max-depth=`:: For each pathspec given on the command line, descend at most `` levels of directories. A negative value means no limit. - Setting a positive value implies `--recursive`. + The default depth is 0. Cannot be combined with wildcards in the pathspec. `-z`:: diff --git a/builtin/last-modified.c b/builtin/last-modified.c index f6cbff30e7..d8988e15c1 100644 --- a/builtin/last-modified.c +++ b/builtin/last-modified.c @@ -481,14 +481,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->recursive && lm->max_depth >= 0; argc = setup_revisions(argc, argv, &lm->rev, NULL); if (argc > 1) { @@ -535,12 +531,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