git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 0/4] Change git-last-modified(1) default behavior and add documentation

From
Toon Claes <toon@iotcl.com>
Date
Jan 20, 2026, 21:47 UTC
Message-ID
<20260120-toon-last-modified-zzzz-v3-0-9bffd4968b0e@iotcl.com>
In-Reply-To
<20260116-toon-last-modified-zzzz-v2-0-79e44f2806fe@iotcl.com>

We have had several reports[1][2] from users that have been thoroughly confused by the default behaviour of git-last-modified(1). Most importantly, when using the command on trees other than the root tree the requests are seemingly ignoring that request and instead blame the top-level subtree. For example:

    $ git last-modified t/lib-bash.sh
    acdfea4394db8e8b42f48e36f7726d64a909a89d t

This result is completely unexpected and feels buggy to anybody who is not deeply familiar with git-last-modified(1). Furthermore, users typically have a hard time to figure out the correct parameters.

This series changes the behavior to something that intuitively makes more sense.

The change of the default behavior is done in the last commit, in the lead-up commits changes in the documentation are made.

[1]: <f0c508cc-5c6b-4c4b-a3f3-0cdd8d1071e5@app.fastmail.com> [2]: <03f96860-29fc-42a7-a220-c3ec65eb8516@codeberg.org>

---
Changes in v3:
- Rejoined the two commits about `-z and the two about `--max-depth`
- In the end, drop `struct last_modified::recursive`. This ensures
  the effect of `--recursive` or `--max-depth` depends on whatever comes
  last.
- Added a commit to rename `<path>` to `<pathspec>` in the docs.
- Link to v2: https://patch.msgid.link/20260116-toon-last-modified-zzzz-v2-0-79e44f2806fe@iotcl.com
Changes in v2:
- Change the default --max-depth.
- Split up commits in smaller pieces.
- Add more testss
- Link to v1: https://patch.msgid.link/20251126-toon-last-modified-zzzz-v1-0-608350df0caa@iotcl.com
Cc: Gusted <gusted@codeberg.org>
---
Toon Claes (4):
      last-modified: clarify in the docs the command takes a pathspec
      last-modified: document option '-z'
      last-modified: document option '--max-depth'
      last-modified: change default max-depth to 0
 Documentation/git-last-modified.adoc | 45 ++++++++++++++++++++++++++++--------
 builtin/last-modified.c              | 25 ++++++++++++--------
 t/t8020-last-modified.sh             | 35 ++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+), 18 deletions(-)
Range-diff versus v2:
-:  ---------- > 1:  1290dbb179 last-modified: clarify in the docs the command takes a pathspec
1:  2c9fa32799 ! 2:  8ba5c59ae8 last-modified: document NUL termination
    @@ Metadata
     Author: Toon Claes <toon@iotcl.com>
     
      ## Commit message ##
    -    last-modified: document NUL termination
    +    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 <toon@iotcl.com>
     
      ## Documentation/git-last-modified.adoc ##
    @@ Documentation/git-last-modified.adoc: git-last-modified - EXPERIMENTAL: Show whe
      SYNOPSIS
      --------
      [synopsis]
    --git last-modified [--recursive] [--show-trees] [<revision-range>] [[--] <path>...]
    +-git last-modified [--recursive] [--show-trees]
     +git last-modified [--recursive] [--show-trees] [-z]
    -+		  [<revision-range>] [[--] <path>...]
    + 		  [<revision-range>] [[--] <pathspec>...]
      
      DESCRIPTION
    - -----------
     @@ Documentation/git-last-modified.adoc: OPTIONS
      	Show tree entries even when recursing into them. It has no effect
      	without `--recursive`.
    @@ Documentation/git-last-modified.adoc: OPTIONS
      	Only traverse commits in the specified revision range. When no
      	`<revision-range>` is specified, it defaults to `HEAD` (i.e. the whole
     @@ Documentation/git-last-modified.adoc: OPTIONS
    - 	Without an optional path parameter, all files and subdirectories
    - 	in path traversal the are included in the output.
    + 	If no _<pathspec>_ is given, all files and subdirectories are included.
    + 	See linkgit:gitglossary[7] for details on pathspec syntax.
      
     +OUTPUT
     +------
    @@ Documentation/git-last-modified.adoc: OPTIONS
      linkgit:git-blame[1],
     
      ## builtin/last-modified.c ##
    +@@ builtin/last-modified.c: struct last_modified {
    + 	struct rev_info rev;
    + 	bool recursive;
    + 	bool show_trees;
    ++	bool nul_termination;
    + 
    + 	const char **all_paths;
    + 	size_t all_paths_nr;
    +@@ builtin/last-modified.c: 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,
     @@ builtin/last-modified.c: 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] "
    --		   "[<revision-range>] [[--] <path>...]"),
    +-		N_("git last-modified [--recursive] [--show-trees]\n"
     +		N_("git last-modified [--recursive] [--show-trees] [-z]\n"
    -+		   "                  [<revision-range>] [[--] <path>...]"),
    + 		   "                  [<revision-range>] [[--] <pathspec>...]"),
      		NULL
      	};
    +@@ builtin/last-modified.c: 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()
    + 	};
      
2:  04589b4ccc < -:  ---------- last-modified: add option '-z' to help output
3:  a45fe991ff < -:  ---------- last-modified: document option --max-depth
4:  3103084474 ! 3:  a1f0178263 last-modified: add option '--max-depth' to help output
    @@ Metadata
     Author: Toon Claes <toon@iotcl.com>
     
      ## Commit message ##
    -    last-modified: add option '--max-depth' to help output
    +    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.
     
    -    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.
    +    builtin/last-modified.c itself. This prepares for the change in default
    +    behavior in the next commit.
     
         Signed-off-by: Toon Claes <toon@iotcl.com>
     
    + ## Documentation/git-last-modified.adoc ##
    +@@ Documentation/git-last-modified.adoc: 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=<depth>] [-z]
    + 		  [<revision-range>] [[--] <pathspec>...]
    + 
    + DESCRIPTION
    +@@ Documentation/git-last-modified.adoc: OPTIONS
    + 	Show tree entries even when recursing into them. It has no effect
    + 	without `--recursive`.
    + 
    ++`--max-depth=<depth>`::
    ++	For each pathspec given on the command line, traverse at most `<depth>`
    ++	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.
    + 
    +
      ## builtin/last-modified.c ##
     @@ builtin/last-modified.c: struct last_modified {
      	bool recursive;
      	bool show_trees;
    - 	bool null_termination;
    + 	bool nul_termination;
     +	int max_depth;
      
      	const char **all_paths;
    @@ builtin/last-modified.c: static int last_modified_init(struct last_modified *lm,
      	argc = setup_revisions(argc, argv, &lm->rev, NULL);
      	if (argc > 1) {
      		error(_("unknown last-modified argument: %s"), argv[1]);
    +@@ builtin/last-modified.c: 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=<depth>] [-z]\n"
    + 		   "                  [<revision-range>] [[--] <pathspec>...]"),
    + 		NULL
    + 	};
     @@ builtin/last-modified.c: 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")),
    ++			      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()
      	};
      
5:  3795e85ab5 ! 4:  81ce108196 last-modified: change default max-depth to 0
    @@ Commit message
     
      ## Documentation/git-last-modified.adoc ##
     @@ Documentation/git-last-modified.adoc: OPTIONS
    + 
    + `-r`::
      `--recursive`::
    - 	Instead of showing tree entries, step into subtrees and show all entries
    - 	inside them recursively.
    -+	This is identical as setting `--max-depth=-1`.
    +-	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 `<pathspec>`. With this option, it
    ++	descends into subtrees and displays all entries within them.
    ++	Equivalent to `--max-depth=-1`.
      
      `-t`::
      `--show-trees`::
    -@@ Documentation/git-last-modified.adoc: OPTIONS
    - `--max-depth=<depth>`::
    - 	For each pathspec given on the command line, descend at most `<depth>`
    - 	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.
    +-	Show tree entries even when recursing into them. It has no effect
    +-	without `--recursive`.
    ++	Show tree entries even when recursing into them.
      
    - `-z`::
    + `--max-depth=<depth>`::
    + 	For each pathspec given on the command line, traverse at most `<depth>`
     
      ## builtin/last-modified.c ##
    +@@ builtin/last-modified.c: 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;
     @@ builtin/last-modified.c: static int last_modified_init(struct last_modified *lm, struct repository *r,
      	lm->rev.no_commit_id = 1;
      	lm->rev.diff = 1;
    @@ builtin/last-modified.c: static int last_modified_init(struct last_modified *lm,
     -		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;
    ++	lm->rev.diffopt.max_depth_valid = lm->max_depth >= 0;
      
      	argc = setup_revisions(argc, argv, &lm->rev, NULL);
      	if (argc > 1) {
    +@@ builtin/last-modified.c: 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,
     @@ builtin/last-modified.c: int cmd_last_modified(int argc, const char **argv, const char *prefix,
      		OPT_END()
      	};

--- base-commit: b5c409c40f1595e3e590760c6f14a16b6683e22c change-id: 20251114-toon-last-modified-zzzz-af9c1be74fc4

Previous: Toon ClaesNext: Toon Claes
Message 28 of 36 in “Expand and enhance git-last-modified(1) documentation”
  1. 0/3 Expand and enhance git-last-modified(1) documentationToon Claes, Nov 26, 2025
  2. 1/3 last-modified: handle and document NUL terminationToon Claes, Nov 26, 2025
  3. Karthik NayakNov 26, 2025
  4. Junio C HamanoNov 26, 2025
  5. Toon ClaesNov 28, 2025
  6. Patrick SteinhardtDec 1, 2025
  7. 2/3 last-modified: document option --max-depthToon Claes, Nov 26, 2025
  8. Karthik NayakNov 26, 2025
  9. Toon ClaesJan 16, 2026
  10. Junio C HamanoNov 26, 2025
  11. Toon ClaesNov 28, 2025
  12. 3/3 last-modified: better document how depth in handledToon Claes, Nov 26, 2025
  13. Eric SunshineNov 26, 2025
  14. Patrick SteinhardtDec 1, 2025
  15. Toon ClaesDec 2, 2025
  16. Patrick SteinhardtDec 2, 2025
  17. 0/5 Change git-last-modified(1) default behavior and add documentationToon Claes, Jan 16, 2026
  18. 1/5 last-modified: document NUL terminationToon Claes, Jan 16, 2026
  19. 2/5 last-modified: add option '-z' to help outputToon Claes, Jan 16, 2026
  20. Junio C HamanoJan 16, 2026
  21. 3/5 last-modified: document option --max-depthToon Claes, Jan 16, 2026
  22. 4/5 last-modified: add option '--max-depth' to help outputToon Claes, Jan 16, 2026
  23. Junio C HamanoJan 16, 2026
  24. 5/5 last-modified: change default max-depth to 0Toon Claes, Jan 16, 2026
  25. Junio C HamanoJan 16, 2026
  26. Kristoffer HaugsbakkJan 16, 2026
  27. Toon ClaesJan 20, 2026
  28. 0/4 Change git-last-modified(1) default behavior and add documentationToon Claes, Jan 20, 2026
  29. 1/4 last-modified: clarify in the docs the command takes a pathspecToon Claes, Jan 20, 2026
  30. 2/4 last-modified: document option '-z'Toon Claes, Jan 20, 2026
  31. 3/4 last-modified: document option '--max-depth'Toon Claes, Jan 20, 2026
  32. 4/4 last-modified: change default max-depth to 0Toon Claes, Jan 20, 2026
  33. Kristoffer HaugsbakkJan 25, 2026
  34. Junio C HamanoJan 21, 2026
  35. Karthik NayakFeb 3, 2026
  36. Junio C HamanoFeb 3, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.