threads / patch / 57383

v11, 13 partsls-tree: remove commented-out code

Subject: [PATCH v11 01/13] ls-tree: remove commented-out code

## tl;dr

107 messages between Feb 8, 2022 and Jun 9, 2022. Diffs are folded; open one to read it.

replies: 106people: 5as markdown or json

Teng Long· Feb 8, 2022, 12:14 UTC · lore

[PATCH v11 00/13] ls-tree: "--object-only" and "--format" opts

Main diffs from v10:
1. Remove commit b04188c822 (ls-tree: optimize naming and handling of "return" in
   show_tree()) and split to 2 new commits:
   		99e6d47108 (ls-tree: rename "retval" to "recurse" in "show_tree()")
		3816a65fe6 (simplify nesting if/else logic in "show_tree()")
   And also use existed `object_type()` instead of a new `get_type()` which bring by v10.
   These changes are based on Ævar Arnfjörð Bjarmason's suggestion on [1]. 
2. Better order of "--format" and "--object-only" features
   The v11 set `--format` before than `--object-only`, this make the patch progression
   much easier to read and let `--object-only` simplier to implement as a case of "--format".
   The change is based on Ævar Arnfjörð Bjarmason's suggestion on [2].
3. Replace to correct author names in partial commits.
   The change is based on Ævar Arnfjörð Bjarmason's suggestion on [3].
4. Changes on commit of "--format" feature.
   * Documentation bugfix:
     		  1) Add back the "\n" before "DESCRIPTION".
		  2) Use a simpler usage case instead of a fragile JSON example.
		  3) Other problems that be mentioned in [4].
   
   * Use a correspondingly uniform naming frame of the "fieldnames" instead of the old ones.
     	          1) mode   ->  objectmode
		  2) type   ->  objecttype
		  3) object ->  objectname
		  4) size   ->  objectsize
		  5) file   ->  path
   These changes are based on Ævar Arnfjörð Bjarmason's suggestion on [4].
5. Add more tests such as let "--format" combined with `-t`, `--full-tree` and `--full-name`. 
6. Add a new commit to introduce "fast_path()".

[1] https://public-inbox.org/git/220114.86ee5attm4.gmgdl@evledraar.gmail.com/ [2] https://public-inbox.org/git/CADMgQSSNQFHhf3=K+PiaoonBnheoDcoKpWy9-zjSu90d9rDY2w@mail.gmail.com/ [3] https://public-inbox.org/git/CADMgQSQaE4EtiNXyGKebPyPS_0YTQ=HN+dU89_jD6BgQ1C470A@mail.gmail.com/ [4] https://public-inbox.org/git/20220118125939.99956-1-dyroneteng@gmail.com/

Johannes Schindelin (1):
  cocci: allow padding with `strbuf_addf()`
Teng Long (6):
  ls-tree: rename "retval" to "recurse" in "show_tree()"
  ls-tree: simplify nesting if/else logic in "show_tree()"
  ls-tree: fix "--name-only" and "--long" combined use bug
  ls-tree: slightly refactor `show_tree()`
  ls-tree: introduce function "fast_path()"
  ls-tree.c: support --object-only option for "git-ls-tree"
Ævar Arnfjörð Bjarmason (6):
  ls-tree: remove commented-out code
  ls-tree: add missing braces to "else" arms
  ls-tree: use "enum object_type", not {blob,tree,commit}_type
  ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
  ls-tree: introduce struct "show_tree_data"
  ls-tree.c: introduce "--format" option
 Documentation/git-ls-tree.txt   |  64 ++++++-
 builtin/ls-tree.c               | 322 +++++++++++++++++++++++++-------
 contrib/coccinelle/strbuf.cocci |   6 +-
 t/t3104-ls-tree-format.sh       |  93 +++++++++
 t/t3105-ls-tree-oid.sh          |  51 +++++
 5 files changed, 465 insertions(+), 71 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
 create mode 100755 t/t3105-ls-tree-oid.sh
Range-diff against v10:
 1:  b04188c822 <  -:  ---------- ls-tree: optimize naming and handling of "return" in show_tree()
 -:  ---------- >  1:  2fcff7e0d4 ls-tree: remove commented-out code
 -:  ---------- >  2:  6fd1dd9383 ls-tree: add missing braces to "else" arms
 -:  ---------- >  3:  208654b5e2 ls-tree: use "enum object_type", not {blob,tree,commit}_type
 -:  ---------- >  4:  2637464fd8 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
 -:  ---------- >  5:  99e6d47108 ls-tree: rename "retval" to "recurse" in "show_tree()"
 -:  ---------- >  6:  3816a65fe6 ls-tree: simplify nesting if/else logic in "show_tree()"
 -:  ---------- >  7:  b22c2dc49e ls-tree: fix "--name-only" and "--long" combined use bug
 2:  bcfbc935b8 !  8:  41e8ed5047 ls-tree.c: support --object-only option for "git-ls-tree"
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    ls-tree.c: support --object-only option for "git-ls-tree"
    +    ls-treeMain diffs from v10:
1. Remove commit b04188c822 (ls-tree: optimize naming and handling of "return" in
   show_tree()) and split to 2 new commits:
                99e6d47108 (ls-tree: rename "retval" to "recurse" in "show_tree()")
                3816a65fe6 (simplify nesting if/else logic in "show_tree()")
   And also use existed `object_type()` instead of a new `get_type()` which bring by v10.
   These changes are based on Ævar Arnfjörð Bjarmason's suggestion on [1].
2. Better order of "--format" and "--object-only" features
   The v11 set `--format` before than `--object-only`, this make the patch progression
   much easier to read and let `--object-only` simplier to implement as a case of "--format".
   The change is based on Ævar Arnfjörð Bjarmason's suggestion on [2].
3. Fix the author'signs of som commits.
   The change is based on Ævar Arnfjörð Bjarmason's suggestion on [3].
4. Changes on commit of "--format" feature
   * Documentation bugfix:
                  1) Add back the "\n" before "DESCRIPTION".
                  2) Use a simplier usage case instead of a fragile JSON example.
                  3) Other problems that be mentioned in [4].
   * Use a correspondingly uniform naming frame of the "fieldnames" instead of the old ones.
                  1) mode   ->  objectmode
                  2) type   ->  objecttype
                  3) object ->  objectname
                  4) size   ->  objectsize
                  5) file   ->  path
   These changes are based on Ævar Arnfjörð Bjarmason's suggestion on [4].
5. Add more tests such like let "--format" combined with `-t`, `--full-tree` and `--full-name`.
6. Add a new commit to introduce "fast_path()".
[1] https://public-inbox.org/git/220114.86ee5attm4.gmgdl@evledraar.gmail.com/
[2] https://public-inbox.org/git/CADMgQSSNQFHhf3=K+PiaoonBnheoDcoKpWy9-zjSu90d9rDY2w@mail.gmail.com/
[3] https://public-inbox.org/git/CADMgQSQaE4EtiNXyGKebPyPS_0YTQ=HN+dU89_jD6BgQ1C470A@mail.gmail.com/
[4] https://public-inbox.org/git/20220118125939.99956-1-dyroneteng@gmail.com/: slightly refactor `show_tree()`
     
    -    We usually pipe the output from `git ls-trees` to tools like
    -    `sed` or `cut` when we only want to extract some fields.
    +    This is a non-functional change, we use a new int "shown_fields" to mark
    +    which columns to output, and `parse_shown_fields()` to calculate the
    +    value of "shown_fields".
     
    -    When we want only the pathname component, we can pass
    -    `--name-only` option to omit such a pipeline, but there are no
    -    options for extracting other fields.
    -
    -    Teach the "--object-only" option to the command to only show the
    -    object name. This option cannot be used together with
    -    "--name-only" or "--long" , they are mutually exclusive (actually
    -    "--name-only" and "--long" can be combined together before, this
    -    commit by the way fix this bug).
    -
    -    A simple refactoring was done to the "show_tree" function, intead by
    -    using bitwise operations to recognize the format for printing to
    -    stdout. The reason for doing this is that we don't want to increase
    -    the readability difficulty with the addition of "-object-only",
    -    making this part of the logic easier to read and expand.
    -
    -    In terms of performance, there is no loss comparing to the
    -    "master" (2ae0a9cb8298185a94e5998086f380a355dd8907), here are the
    -    results of the performance tests in my environment based on linux
    -    repository:
    -
    -        $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    -        Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    -        Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]
    -        Range (min … max):   101.5 ms … 111.3 ms    28 runs
    -
    -        $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD"
    -        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD
    -        Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]
    -        Range (min … max):    99.3 ms … 109.5 ms    27 runs
    -
    -        $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    -        Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    -        Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]
    -        Range (min … max):   323.0 ms … 355.0 ms    10 runs
    -
    -        $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD"
    -        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD
    -        Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]
    -        Range (min … max):   330.4 ms … 349.9 ms    10 runs
    +    This has the advantage of making the show_tree logic simpler and more
    +    readable, as well as making it easier to extend new options (for example,
    +    if we want to add a "--object-only" option, we just need to add a similar
    +    "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in
    +    "show_tree()").
     
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
    - ## Documentation/git-ls-tree.txt ##
    -@@ Documentation/git-ls-tree.txt: SYNOPSIS
    - --------
    - [verse]
    - 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
    --	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
    -+	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]
    - 	    <tree-ish> [<path>...]
    - 
    - DESCRIPTION
    -@@ Documentation/git-ls-tree.txt: OPTIONS
    - --name-only::
    - --name-status::
    - 	List only filenames (instead of the "long" output), one per line.
    -+	Cannot be combined with `--object-only`.
    -+
    -+--object-only::
    -+	List only names of the objects, one per line. Cannot be combined
    -+	with `--name-only` or `--name-status`.
    - 
    - --abbrev[=<n>]::
    - 	Instead of showing the full 40-byte hexadecimal object
    -
      ## builtin/ls-tree.c ##
     @@
      
    @@ builtin/ls-tree.c
     +#define LS_SHOW_TREES (1 << 2)
     +#define LS_NAME_ONLY (1 << 3)
     +#define LS_SHOW_SIZE (1 << 4)
    -+#define LS_OBJECT_ONLY (1 << 5)
      static int abbrev;
      static int ls_options;
      static struct pathspec pathspec;
      static int chomp_prefix;
      static const char *ls_tree_prefix;
     +static unsigned int shown_fields;
    -+#define FIELD_FILE_NAME 1
    ++#define FIELD_PATH_NAME 1
     +#define FIELD_SIZE (1 << 1)
     +#define FIELD_OBJECT_NAME (1 << 2)
     +#define FIELD_TYPE (1 << 3)
    @@ builtin/ls-tree.c
      	NULL
      };
      
    --static int show_recursive(const char *base, size_t baselen, const char *pathname)
     +enum {
     +	MODE_UNSPECIFIED = 0,
     +	MODE_NAME_ONLY,
    -+	MODE_OBJECT_ONLY,
     +	MODE_LONG,
     +};
     +
    @@ builtin/ls-tree.c
     +static int parse_shown_fields(void)
     +{
     +	if (cmdmode == MODE_NAME_ONLY) {
    -+		shown_fields = FIELD_FILE_NAME;
    -+		return 0;
    -+	}
    -+	if (cmdmode == MODE_OBJECT_ONLY) {
    -+		shown_fields = FIELD_OBJECT_NAME;
    ++		shown_fields = FIELD_PATH_NAME;
     +		return 0;
     +	}
    ++
     +	if (!ls_options || (ls_options & LS_RECURSIVE)
     +	    || (ls_options & LS_SHOW_TREES)
     +	    || (ls_options & LS_TREE_ONLY))
    @@ builtin/ls-tree.c
     +	return 1;
     +}
     +
    -+static int show_recursive(const char *base, size_t baselen,
    -+			  const char *pathname)
    + static int show_recursive(const char *base, size_t baselen, const char *pathname)
      {
      	int i;
    - 
    -@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
    - 	        : OBJ_BLOB);
    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
    + 	return 0;
      }
      
     +static int show_default(const struct object_id *oid, enum object_type type,
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -			printf("%06o %s %s\t", mode, type_name(type),
     -			       find_unique_abbrev(oid, abbrev));
     -		}
    -+	if (shown_fields == FIELD_OBJECT_NAME) {
    -+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
    ++	if (shown_fields == FIELD_PATH_NAME) {
    ++		baselen = base->len;
    ++		strbuf_addstr(base, pathname);
    ++		write_name_quoted_relative(base->buf,
    ++					   chomp_prefix ? ls_tree_prefix : NULL,
    ++					   stdout, line_termination);
    ++		strbuf_setlen(base, baselen);
     +		return recurse;
      	}
     -	baselen = base->len;
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -				   stdout, line_termination);
     -	strbuf_setlen(base, baselen);
     +
    -+	if (shown_fields == FIELD_FILE_NAME) {
    -+		baselen = base->len;
    -+		strbuf_addstr(base, pathname);
    -+		write_name_quoted_relative(base->buf,
    -+					   chomp_prefix ? ls_tree_prefix : NULL,
    -+					   stdout, line_termination);
    -+		strbuf_setlen(base, baselen);
    -+		return recurse;
    -+	}
    -+
     +	if (shown_fields >= FIELD_DEFAULT)
     +		show_default(oid, type, pathname, mode, base);
     +
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      			LS_SHOW_TREES),
      		OPT_SET_INT('z', NULL, &line_termination,
      			    N_("terminate entries with NUL byte"), 0),
    --		OPT_BIT('l', "long", &ls_options, N_("include object size"),
    --			LS_SHOW_SIZE),
    --		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
    --			LS_NAME_ONLY),
    --		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
    --			LS_NAME_ONLY),
    +-		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
    +-			    LS_SHOW_SIZE),
    +-		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
    +-			    LS_NAME_ONLY),
    +-		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
    +-			    LS_NAME_ONLY),
     +		OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"),
     +			    MODE_LONG),
     +		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
     +			    MODE_NAME_ONLY),
     +		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
     +			    MODE_NAME_ONLY),
    -+		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
    -+			    MODE_OBJECT_ONLY),
      		OPT_SET_INT(0, "full-name", &chomp_prefix,
      			    N_("use full path names"), 0),
      		OPT_BOOL(0, "full-tree", &full_tree,
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      	/*
      	 * show_recursive() rolls its own matching code and is
      	 * generally ignorant of 'struct pathspec'. The magic mask
    -
    - ## t/t3104-ls-tree-oid.sh (new) ##
    -@@
    -+#!/bin/sh
    -+
    -+test_description='git ls-tree objects handling.'
    -+
    -+. ./test-lib.sh
    -+
    -+test_expect_success 'setup' '
    -+	test_commit A &&
    -+	test_commit B &&
    -+	mkdir -p C &&
    -+	test_commit C/D.txt &&
    -+	find *.txt path* \( -type f -o -type l \) -print |
    -+	xargs git update-index --add &&
    -+	tree=$(git write-tree) &&
    -+	echo $tree
    -+'
    -+
    -+test_expect_success 'usage: --object-only' '
    -+	git ls-tree --object-only $tree >current &&
    -+	git ls-tree $tree >result &&
    -+	cut -f1 result | cut -d " " -f3 >expected &&
    -+	test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: --object-only with -r' '
    -+	git ls-tree --object-only -r $tree >current &&
    -+	git ls-tree -r $tree >result &&
    -+	cut -f1 result | cut -d " " -f3 >expected &&
    -+	test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: --object-only with --abbrev' '
    -+	git ls-tree --object-only --abbrev=6 $tree >current &&
    -+	git ls-tree --abbrev=6 $tree >result &&
    -+	cut -f1 result | cut -d " " -f3 >expected &&
    -+	test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --name-only with --object-only' '
    -+	test_expect_code 129 git ls-tree --object-only --name-only $tree
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --name-status with --object-only' '
    -+	test_expect_code 129 git ls-tree --object-only --name-status $tree
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --long with --object-only' '
    -+	test_expect_code 129 git ls-tree --object-only --long $tree
    -+'
    -+
    -+test_done
 3:  3ddffa1027 !  9:  46e10a5392 ls-tree.c: introduce struct "show_tree_data"
    @@
      ## Metadata ##
    -Author: Teng Long <dyroneteng@gmail.com>
    +Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Commit message ##
    -    ls-tree.c: introduce struct "show_tree_data"
    +    ls-tree: introduce struct "show_tree_data"
     
         "show_tree_data" is a struct that packages the necessary fields for
         "show_tree()". This commit is a pre-prepared commit for supporting
         "--format" option and it does not affect any existing functionality.
     
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## builtin/ls-tree.c ##
    @@ builtin/ls-tree.c: static unsigned int shown_fields;
      static const  char * const ls_tree_usage[] = {
      	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
      	NULL
    -@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
    - 	        : OBJ_BLOB);
    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
    + 	return 0;
      }
      
     -static int show_default(const struct object_id *oid, enum object_type type,
    @@ builtin/ls-tree.c: static int show_default(const struct object_id *oid, enum obj
      }
      
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    + 	int recurse = 0;
      	size_t baselen;
    - 	enum object_type type = get_type(mode);
    - 
    + 	enum object_type type = object_type(mode);
     +	struct show_tree_data data = {
     +		.mode = mode,
     +		.type = type,
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     +		.pathname = pathname,
     +		.base = base,
     +	};
    -+
    + 
      	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
      		recurse = READ_TREE_RECURSIVE;
    - 	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
      	}
      
 4:  4b58a707c2 ! 10:  c04320b801 cocci: allow padding with `strbuf_addf()`
    @@
      ## Metadata ##
    -Author: Teng Long <dyroneteng@gmail.com>
    +Author: Johannes Schindelin <Johannes.Schindelin@gmx.de>
     
      ## Commit message ##
         cocci: allow padding with `strbuf_addf()`
 5:  db058bf670 ! 11:  5936004f13 ls-tree.c: introduce "--format" option
    @@
      ## Metadata ##
    -Author: Teng Long <dyroneteng@gmail.com>
    +Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Commit message ##
         ls-tree.c: introduce "--format" option
    @@ Commit message
         Links:
                 [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
     
    +    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## Documentation/git-ls-tree.txt ##
    @@ Documentation/git-ls-tree.txt: SYNOPSIS
      --------
      [verse]
      'git ls-tree' [-d] [-r] [-t] [-l] [-z]
    --	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]
    --	    <tree-ish> [<path>...]
    --
    -+	    [--name-only] [--name-status] [--object-only]
    -+	    [--full-name] [--full-tree] [--abbrev[=<n>]]
    -+	    [--format=<format>] <tree-ish> [<path>...]
    +-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
    ++	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
    + 	    <tree-ish> [<path>...]
    + 
      DESCRIPTION
    - -----------
    - Lists the contents of a given tree object, like what "/bin/ls -a" does
     @@ Documentation/git-ls-tree.txt: OPTIONS
      	Do not limit the listing to the current working directory.
      	Implies --full-name.
    @@ Documentation/git-ls-tree.txt: OPTIONS
      
      Output Format
      -------------
    +-        <mode> SP <type> SP <object> TAB <file>
    ++
    ++The output format of `ls-tree` is determined by either the `--format`
    ++option, or other format-altering options such as `--name-only` etc.
    ++(see `--format` above).
     +
    -+Default format:
    ++The use of certain `--format` directives is equivalent to using those
    ++options, but invoking the full formatting machinery can be slower than
    ++using an appropriate formatting option.
     +
    -         <mode> SP <type> SP <object> TAB <file>
    ++In cases where the `--format` would exactly map to an existing option
    ++`ls-tree` will use the appropriate faster path. Thus the default format
    ++is equivalent to:
    ++
    ++        %(objectmode) %(objecttype) %(objectname)%x09%(path)
      
      This output format is compatible with what `--index-info --stdin` of
    + 'git update-index' expects.
    + 
    + When the `-l` option is used, format changes to
    + 
    +-        <mode> SP <type> SP <object> SP <object size> TAB <file>
    ++        %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
    + 
    +-Object size identified by <object> is given in bytes, and right-justified
    ++Object size identified by <objectname> is given in bytes, and right-justified
    + with minimum width of 7 characters.  Object size is given only for blobs
    + (file) entries; for other entries `-` character is used in place of size.
    + 
     @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variable `core.quotePath`
      (see linkgit:git-config[1]).  Using `-z` the filename is output
      verbatim and the line is terminated by a NUL byte.
    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl
     +
     +It is possible to print in a custom format by using the `--format` option,
     +which is able to interpolate different fields using a `%(fieldname)` notation.
    -+For example, if you want to only print the <object> and <file> fields with a
    -+JSON style, executing with a specific "--format" like
    -+
    -+        git ls-tree --format='{"object":"%(object)", "file":"%(file)"}' <tree-ish>
    ++For example, if you only care about the "objectname" and "path" fields, you
    ++can execute with a specific "--format" like
     +
    -+The output format changes to:
    -+
    -+        {"object":"<object>", "file":"<file>"}
    ++        git ls-tree --format='%(objectname) %(path)' <tree-ish>
     +
     +FIELD NAMES
     +-----------
    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl
     +into the resulting output. For each outputing line, the following
     +names can be used:
     +
    -+mode::
    ++objectmode::
     +	The mode of the object.
    -+type::
    ++objecttype::
     +	The type of the object (`blob` or `tree`).
    -+object::
    ++objectname::
     +	The name of the object.
    -+size[:padded]::
    ++objectsize[:padded]::
     +	The size of the object ("-" if it's a tree).
     +	It also supports a padded format of size with "%(size:padded)".
    -+file::
    -+	The filename of the object.
    ++path::
    ++	The pathname of the object.
     +
      GIT
      ---
      Part of the linkgit:git[1] suite
     
      ## builtin/ls-tree.c ##
    +@@ builtin/ls-tree.c: static unsigned int shown_fields;
    + #define FIELD_MODE (1 << 4)
    + #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
    + #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    +-
    ++static const char *format;
    ++static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
    ++static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    ++static const char *name_only_format = "%(path)";
    + struct show_tree_data {
    + 	unsigned mode;
    + 	enum object_type type;
     @@ builtin/ls-tree.c: enum {
      
      static int cmdmode = MODE_UNSPECIFIED;
      
    -+static const char *format;
    -+static const char *default_format = "%(mode) %(type) %(object)%x09%(file)";
    -+static const char *long_format = "%(mode) %(type) %(object) %(size:padded)%x09%(file)";
    -+static const char *name_only_format = "%(file)";
    -+static const char *object_only_format = "%(object)";
    -+
    - static int parse_shown_fields(void)
    - {
    - 	if (cmdmode == MODE_NAME_ONLY) {
    -@@ builtin/ls-tree.c: static int parse_shown_fields(void)
    - 	return 1;
    - }
    - 
     +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
     +			      const enum object_type type, unsigned int padded)
     +{
    @@ builtin/ls-tree.c: static int parse_shown_fields(void)
     +			die(_("could not get object info about '%s'"),
     +			    oid_to_hex(oid));
     +		if (padded)
    -+			strbuf_addf(line, "%7" PRIuMAX, (uintmax_t)size);
    ++			strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size);
     +		else
    -+			strbuf_addf(line, "%" PRIuMAX, (uintmax_t)size);
    ++			strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size);
     +	} else if (padded) {
     +		strbuf_addf(line, "%7s", "-");
     +	} else {
    @@ builtin/ls-tree.c: static int parse_shown_fields(void)
     +	}
     +}
     +
    -+static size_t expand_show_tree(struct strbuf *line, const char *start,
    ++static size_t expand_show_tree(struct strbuf *sb, const char *start,
     +			       void *context)
     +{
     +	struct show_tree_data *data = context;
     +	const char *end;
     +	const char *p;
     +	unsigned int errlen;
    -+	size_t len = strbuf_expand_literal_cb(line, start, NULL);
    ++	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
     +
     +	if (len)
     +		return len;
    @@ builtin/ls-tree.c: static int parse_shown_fields(void)
     +		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
     +
     +	len = end - start + 1;
    -+	if (skip_prefix(start, "(mode)", &p)) {
    -+		strbuf_addf(line, "%06o", data->mode);
    -+	} else if (skip_prefix(start, "(type)", &p)) {
    -+		strbuf_addstr(line, type_name(data->type));
    -+	} else if (skip_prefix(start, "(size:padded)", &p)) {
    -+		expand_objectsize(line, data->oid, data->type, 1);
    -+	} else if (skip_prefix(start, "(size)", &p)) {
    -+		expand_objectsize(line, data->oid, data->type, 0);
    -+	} else if (skip_prefix(start, "(object)", &p)) {
    -+		strbuf_add_unique_abbrev(line, data->oid, abbrev);
    -+	} else if (skip_prefix(start, "(file)", &p)) {
    ++	if (skip_prefix(start, "(objectmode)", &p)) {
    ++		strbuf_addf(sb, "%06o", data->mode);
    ++	} else if (skip_prefix(start, "(objecttype)", &p)) {
    ++		strbuf_addstr(sb, type_name(data->type));
    ++	} else if (skip_prefix(start, "(objectsize:padded)", &p)) {
    ++		expand_objectsize(sb, data->oid, data->type, 1);
    ++	} else if (skip_prefix(start, "(objectsize)", &p)) {
    ++		expand_objectsize(sb, data->oid, data->type, 0);
    ++	} else if (skip_prefix(start, "(objectname)", &p)) {
    ++		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
    ++	} else if (skip_prefix(start, "(path)", &p)) {
     +		const char *name = data->base->buf;
     +		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
     +		struct strbuf quoted = STRBUF_INIT;
    -+		struct strbuf sb = STRBUF_INIT;
    ++		struct strbuf sbuf = STRBUF_INIT;
     +		strbuf_addstr(data->base, data->pathname);
    -+		name = relative_path(data->base->buf, prefix, &sb);
    ++		name = relative_path(data->base->buf, prefix, &sbuf);
     +		quote_c_style(name, &quoted, NULL, 0);
    -+		strbuf_addbuf(line, &quoted);
    -+		strbuf_release(&sb);
    ++		strbuf_addbuf(sb, &quoted);
    ++		strbuf_release(&sbuf);
     +		strbuf_release(&quoted);
     +	} else {
     +		errlen = (unsigned long)len;
    @@ builtin/ls-tree.c: static int parse_shown_fields(void)
     +	return len;
     +}
     +
    - static int show_recursive(const char *base, size_t baselen,
    - 			  const char *pathname)
    + static int parse_shown_fields(void)
      {
    -@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
    - 	        : OBJ_BLOB);
    + 	if (cmdmode == MODE_NAME_ONLY) {
    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
    + 	return 0;
      }
      
     +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
    @@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
     +{
     +	size_t baselen;
     +	int recurse = 0;
    -+	struct strbuf line = STRBUF_INIT;
    -+	enum object_type type = get_type(mode);
    ++	struct strbuf sb = STRBUF_INIT;
    ++	enum object_type type = object_type(mode);
     +
     +	struct show_tree_data data = {
     +		.mode = mode,
    @@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
     +		return 0;
     +
     +	baselen = base->len;
    -+	strbuf_expand(&line, format, expand_show_tree, &data);
    -+	strbuf_addch(&line, line_termination);
    -+	fwrite(line.buf, line.len, 1, stdout);
    -+	strbuf_release(&line);
    ++	strbuf_expand(&sb, format, expand_show_tree, &data);
    ++	strbuf_addch(&sb, line_termination);
    ++	fwrite(sb.buf, sb.len, 1, stdout);
    ++	strbuf_release(&sb);
     +	strbuf_setlen(base, baselen);
     +	return recurse;
     +}
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      			 N_("list entire tree; not just current directory "
      			    "(implies --full-name)")),
     +		OPT_STRING_F(0, "format", &format, N_("format"),
    -+			     N_("format to use for the output"),
    -+			     PARSE_OPT_NONEG),
    ++					 N_("format to use for the output"),
    ++					 PARSE_OPT_NONEG),
      		OPT__ABBREV(&abbrev),
      		OPT_END()
      	};
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		die("not a tree object");
     -	return !!read_tree(the_repository, tree,
     -			   &pathspec, show_tree, NULL);
    -+
     +	/*
     +	 * The generic show_tree_fmt() is slower than show_tree(), so
     +	 * take the fast path if possible.
     +	 */
    -+	if (format &&
    -+	    (!strcmp(format, default_format) ||
    -+	     !strcmp(format, long_format) ||
    -+	     !strcmp(format, name_only_format) ||
    -+	     !strcmp(format, object_only_format)))
    ++	if (format && (!strcmp(format, default_format))) {
    ++		fn = show_tree;
    ++	} else if (format && (!strcmp(format, long_format))) {
    ++		shown_fields = shown_fields | FIELD_SIZE;
     +		fn = show_tree;
    -+	else if (format)
    ++	} else if (format && (!strcmp(format, name_only_format))) {
    ++		shown_fields = FIELD_PATH_NAME;
    ++		fn = show_tree;
    ++	} else if (format)
     +		fn = show_tree_fmt;
     +
     +	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
      }
     
    - ## t/t3105-ls-tree-format.sh (new) ##
    + ## t/t3104-ls-tree-format.sh (new) ##
     @@
     +#!/bin/sh
     +
    @@ t/t3105-ls-tree-format.sh (new)
     +test_expect_success 'ls-tree --format usage' '
     +	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
     +	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
    -+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD &&
    -+	test_expect_code 129 git ls-tree --format=fmt --object-only HEAD
    ++	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
     +'
     +
     +test_expect_success 'setup' '
    @@ t/t3105-ls-tree-format.sh (new)
     +test_ls_tree_format () {
     +	format=$1 &&
     +	opts=$2 &&
    ++	fmtopts=$3 &&
     +	shift 2 &&
     +	git ls-tree $opts -r HEAD >expect.raw &&
     +	sed "s/^/> /" >expect <expect.raw &&
    -+	git ls-tree --format="> $format" -r HEAD >actual &&
    ++	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
     +	test_cmp expect actual
     +}
     +
     +test_expect_success 'ls-tree --format=<default-like>' '
     +	test_ls_tree_format \
    -+		"%(mode) %(type) %(object)%x09%(file)" \
    ++		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
     +		""
     +'
     +
     +test_expect_success 'ls-tree --format=<long-like>' '
     +	test_ls_tree_format \
    -+		"%(mode) %(type) %(object) %(size:padded)%x09%(file)" \
    ++		"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
     +		"--long"
     +'
     +
     +test_expect_success 'ls-tree --format=<name-only-like>' '
     +	test_ls_tree_format \
    -+		"%(file)" \
    ++		"%(path)" \
     +		"--name-only"
     +'
     +
    -+test_expect_success 'ls-tree --format=<object-only-like>' '
    ++test_expect_success 'ls-tree combine --format=<default-like> and -t' '
    ++	test_ls_tree_format \
    ++	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    ++	"-t" \
    ++	"-t"
    ++'
    ++
    ++test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
     +	test_ls_tree_format \
    -+		"%(object)" \
    -+		"--object-only"
    ++	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    ++	"--full-name" \
    ++	"--full-name"
     +'
     +
    ++test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
    ++	test_ls_tree_format \
    ++	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    ++	"--full-tree" \
    ++	"--full-tree"
    ++'
    ++
    ++test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
    ++	git ls-tree -r HEAD >expect &&
    ++	git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
    ++	test_cmp expect actual
    ++'
    ++
    ++test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
    ++	git ls-tree -r --name-only HEAD >expect &&
    ++	git ls-tree --format="%(path)" -r HEAD >actual &&
    ++	test_cmp expect actual
    ++'
     +test_done
 -:  ---------- > 12:  6d26497749 ls-tree: introduce function "fast_path()"
 -:  ---------- > 13:  e6d98f2560 ls-tree.c: support --object-only option for "git-ls-tree"
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore
From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Remove code added in f35a6d3bce7 (Teach core object handling functions about gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output flushing before fork(), 2008-08-04), and then finally ending up in its current form in d3bee161fef (tree.c: allow read_tree_recursive() to traverse gitlink entries, 2009-01-25). All while being commented-out!

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
Show changes to builtin/ls-tree.c +0 −9
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3a442631c7..5f7c84950c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	const char *type = blob_type;
 
 	if (S_ISGITLINK(mode)) {
-		/*
-		 * Maybe we want to have some recursive version here?
-		 *
-		 * Something similar to this incomplete example:
-		 *
-		if (show_subprojects(base, baselen, pathname))
-			retval = READ_TREE_RECURSIVE;
-		 *
-		 */
 		type = commit_type;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 03/13] ls-tree: use "enum object_type", not {blob,tree,commit}_type

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Change the ls-tree.c code to use type_name() on the enum instead of using the string constants. This doesn't matter either way for performance, but makes this a bit easier to read as we'll no longer need a strcmp() here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/ls-tree.c +6 −6
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0a28f32ccb..3f0225b097 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int retval = 0;
 	int baselen;
-	const char *type = blob_type;
+	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-		type = commit_type;
+		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
 			retval = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
 				return retval;
 		}
-		type = tree_type;
+		type = OBJ_TREE;
 	}
 	else if (ls_options & LS_TREE_ONLY)
 		return 0;
@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
 			char size_text[24];
-			if (!strcmp(type, blob_type)) {
+			if (type == OBJ_BLOB) {
 				unsigned long size;
 				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
 					xsnprintf(size_text, sizeof(size_text),
@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
 			}
-			printf("%06o %s %s %7s\t", mode, type,
+			printf("%06o %s %s %7s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
 		} else {
-			printf("%06o %s %s\t", mode, type,
+			printf("%06o %s %s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev));
 		}
 	}
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 12/13] ls-tree: introduce function "fast_path()"

The generic "show_tree_fmt()" is slower than "show_tree()", so we want to take the fast path if possible.

when "--format=<format>" is passed, "fast_path()" will determine whether to use "show_tree()" or insist on using "show_tree_fmt()" by a try of finding out if the built-int format is hit.

This commit take out the related codes from "cmd_ls_tree()" and package them into a new funtion "fast_path()".

Explain it a little bit further, whether fast_path is hit or not, the final correctness should not break. Abstracting a separate method helps improve the readability of "cmd_ls_tree()" and the cohesiveness and extensibility of fast path logic.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 23 ++++++++++++++---------
 1 file changed, 14 insertions(+), 9 deletions(-)
Show changes to builtin/ls-tree.c +14 −9
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 1c71e5d543..ba96bcf602 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -58,6 +58,19 @@ enum {
 
 static int cmdmode = MODE_UNSPECIFIED;
 
+static int fast_path(void){
+	if (!strcmp(format, default_format)) {
+		return 1;
+	} else if (!strcmp(format, long_format)) {
+		shown_fields = shown_fields | FIELD_SIZE;
+		return 1;
+	} else if (!strcmp(format, name_only_format)) {
+		shown_fields = FIELD_PATH_NAME;
+		return 1;
+	}
+	return 0;
+}
+
 static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
 			      const enum object_type type, unsigned int padded)
 {
@@ -350,15 +363,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format && (!strcmp(format, default_format))) {
-		fn = show_tree;
-	} else if (format && (!strcmp(format, long_format))) {
-		shown_fields = shown_fields | FIELD_SIZE;
-		fn = show_tree;
-	} else if (format && (!strcmp(format, name_only_format))) {
-		shown_fields = FIELD_PATH_NAME;
-		fn = show_tree;
-	} else if (format)
+	if (format && !fast_path())
 		fn = show_tree_fmt;
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 05:32 UTC · re: Teng Long · lore

Re: [PATCH v11 12/13] ls-tree: introduce function "fast_path()"

On Tue, Feb 08 2022, Teng Long wrote:
Show 14 quoted lines
> The generic "show_tree_fmt()" is slower than "show_tree()", so
> we want to take the fast path if possible.
>
> when "--format=<format>" is passed, "fast_path()" will determine
> whether to use "show_tree()" or insist on using "show_tree_fmt()"
> by a try of finding out if the built-int format is hit.
>
> This commit take out the related codes from "cmd_ls_tree()" and
> package them into a new funtion "fast_path()".
>
> Explain it a little bit further, whether fast_path is hit or not,
> the final correctness should not break. Abstracting a separate method
> helps improve the readability of "cmd_ls_tree()" and the cohesiveness
> and extensibility of fast path logic.

This whole commit message sounds a bit like "we're introducing this fast path", but really it got added in 11/13, and this is just a refactoring to split that into a function to slightly reduce the size of cmd_ls_tree() itself.

Which I really don't mind, but it would be better if the commit message said so, e.g.:

    In a preceding commit a fast path selection was added to cmd_ls_tree(),
    split it into a utility function because ...
But I got stuck on "..." because I couldn't find a reason :)

I.e. in 13/13 this isn't used at all, except by adding a new brace arm to it, but then it could still live in cmd_ls_tree().

Personally I think the pre-image is a bit easier to read, but then again I wrote that so I'm biased. I don't mind changing this, but structurally for the series it seems better to squash it in if you'd want to keep it.

Show 25 quoted lines
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/ls-tree.c | 23 ++++++++++++++---------
>  1 file changed, 14 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 1c71e5d543..ba96bcf602 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -58,6 +58,19 @@ enum {
>  
>  static int cmdmode = MODE_UNSPECIFIED;
>  
> +static int fast_path(void){
> +	if (!strcmp(format, default_format)) {
> +		return 1;
> +	} else if (!strcmp(format, long_format)) {
> +		shown_fields = shown_fields | FIELD_SIZE;
> +		return 1;
> +	} else if (!strcmp(format, name_only_format)) {
> +		shown_fields = FIELD_PATH_NAME;
> +		return 1;
> +	}
> +	return 0;
> +}

Just in terms of arranging things if you add a static function and it's only used in one other function, here in cmd_ls_tree(), it's more readable to add it immediately before that function.

Show 20 quoted lines
>  static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
>  			      const enum object_type type, unsigned int padded)
>  {
> @@ -350,15 +363,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
>  	 * The generic show_tree_fmt() is slower than show_tree(), so
>  	 * take the fast path if possible.
>  	 */
> -	if (format && (!strcmp(format, default_format))) {
> -		fn = show_tree;
> -	} else if (format && (!strcmp(format, long_format))) {
> -		shown_fields = shown_fields | FIELD_SIZE;
> -		fn = show_tree;
> -	} else if (format && (!strcmp(format, name_only_format))) {
> -		shown_fields = FIELD_PATH_NAME;
> -		fn = show_tree;
> -	} else if (format)
> +	if (format && !fast_path())
>  		fn = show_tree_fmt;
>  
>  	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
Also in terms of structure wouldn't it be better to end up with this:
Show changes to builtin/ls-tree.c +2 −2
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 9819a241869..47f7e2136b0 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -355,8 +355,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
-	parse_shown_fields();
-
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
@@ -373,6 +371,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	tree = parse_tree_indirect(&oid);
 	if (!tree)
 		die("not a tree object");
+
+	parse_shown_fields();
 	/*
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.

I.e. have the whole "shown_fields" decisions be near one another.
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 13/13] ls-tree.c: support --object-only option for "git-ls-tree"

We usually pipe the output from `git ls-trees` to tools like `sed` or `cut` when we only want to extract some fields.

When we want only the pathname component, we can pass `--name-only` option to omit such a pipeline, but there are no options for extracting other fields.

Teach the "--object-only" option to the command to only show the object name. This option cannot be used together with "--name-only" or "--long" , they are mutually exclusive (actually "--name-only" and "--long" can be combined together before, this commit by the way fix this bug).

In terms of performance, there is no loss comparing to the "master" (2ae0a9c), here are the results of the performance tests in my environment based on linux repository:

    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]
    Range (min … max):   101.5 ms … 111.3 ms    28 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]
    Range (min … max):    99.3 ms … 109.5 ms    27 runs
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]
    Range (min … max):   323.0 ms … 355.0 ms    10 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]
    Range (min … max):   330.4 ms … 349.9 ms    10 runs
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |  7 ++++-
 builtin/ls-tree.c             | 16 ++++++++++-
 t/t3104-ls-tree-format.sh     | 12 +++++++++
 t/t3105-ls-tree-oid.sh        | 51 +++++++++++++++++++++++++++++++++++
 4 files changed, 84 insertions(+), 2 deletions(-)
 create mode 100755 t/t3105-ls-tree-oid.sh
Show changes to 4 files +84 −2

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3104-ls-tree-format.sh, t/t3105-ls-tree-oid.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db29a9efb5..21045dd163 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
+	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -59,6 +59,11 @@ OPTIONS
 --name-only::
 --name-status::
 	List only filenames (instead of the "long" output), one per line.
+	Cannot be combined with `--object-only`.
+
+--object-only::
+	List only names of the objects, one per line. Cannot be combined
+	with `--name-only` or `--name-status`.
 
 --abbrev[=<n>]::
 	Instead of showing the full 40-byte hexadecimal object
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ba96bcf602..9819a24186 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -20,6 +20,7 @@ static int line_termination = '\n';
 #define LS_SHOW_TREES (1 << 2)
 #define LS_NAME_ONLY (1 << 3)
 #define LS_SHOW_SIZE (1 << 4)
+#define LS_OBJECT_ONLY (1 << 5)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
@@ -37,6 +38,7 @@ static const char *format;
 static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
 static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
 static const char *name_only_format = "%(path)";
+static const char *object_only_format = "%(objectname)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -53,6 +55,7 @@ static const  char * const ls_tree_usage[] = {
 enum {
 	MODE_UNSPECIFIED = 0,
 	MODE_NAME_ONLY,
+	MODE_OBJECT_ONLY,
 	MODE_LONG,
 };
 
@@ -67,6 +70,8 @@ static int fast_path(void){
 	} else if (!strcmp(format, name_only_format)) {
 		shown_fields = FIELD_PATH_NAME;
 		return 1;
+	} else if (!strcmp(format, object_only_format)) {
+		shown_fields = FIELD_OBJECT_NAME;
 	}
 	return 0;
 }
@@ -143,7 +148,10 @@ static int parse_shown_fields(void)
 		shown_fields = FIELD_PATH_NAME;
 		return 0;
 	}
-
+	if (cmdmode == MODE_OBJECT_ONLY) {
+		shown_fields = FIELD_OBJECT_NAME;
+		return 0;
+	}
 	if (!ls_options || (ls_options & LS_RECURSIVE)
 	    || (ls_options & LS_SHOW_TREES)
 	    || (ls_options & LS_TREE_ONLY))
@@ -267,6 +275,10 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
 		return 0;
 
+	if (shown_fields == FIELD_OBJECT_NAME) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		return recurse;
+	}
 	if (shown_fields == FIELD_PATH_NAME) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
@@ -304,6 +316,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
+			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index e08c83dc47..c0ffc8e1c3 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -46,6 +46,12 @@ test_expect_success 'ls-tree --format=<name-only-like>' '
 		"--name-only"
 '
 
+test_expect_success 'ls-tree --format=<object-only-like>' '
+	test_ls_tree_format \
+		"%(objectname)" \
+		"--object-only"
+'
+
 test_expect_success 'ls-tree combine --format=<default-like> and -t' '
 	test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
@@ -78,4 +84,10 @@ test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
 	git ls-tree --format="%(path)" -r HEAD >actual &&
 	test_cmp expect actual
 '
+
+test_expect_success 'ls-tree hit fast-path with --format=<object-only-like>' '
+	git ls-tree -r --object-only HEAD >expect &&
+	git ls-tree --format="%(objectname)" -r HEAD >actual &&
+	test_cmp expect actual
+'
 test_done
diff --git a/t/t3105-ls-tree-oid.sh b/t/t3105-ls-tree-oid.sh
new file mode 100755
index 0000000000..992bb26bfa
--- /dev/null
+++ b/t/t3105-ls-tree-oid.sh
@@ -0,0 +1,51 @@
+#!/bin/sh
+
+test_description='git ls-tree objects handling.'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+test_commit A &&
+test_commit B &&
+mkdir -p C &&
+test_commit C/D.txt &&
+find *.txt path* \( -type f -o -type l \) -print |
+xargs git update-index --add &&
+tree=$(git write-tree) &&
+echo $tree
+'
+
+test_expect_success 'usage: --object-only' '
+git ls-tree --object-only $tree >current &&
+git ls-tree $tree >result &&
+cut -f1 result | cut -d " " -f3 >expected &&
+test_cmp current expected
+'
+
+test_expect_success 'usage: --object-only with -r' '
+git ls-tree --object-only -r $tree >current &&
+git ls-tree -r $tree >result &&
+cut -f1 result | cut -d " " -f3 >expected &&
+test_cmp current expected
+'
+
+test_expect_success 'usage: --object-only with --abbrev' '
+git ls-tree --object-only --abbrev=6 $tree >current &&
+git ls-tree --abbrev=6 $tree >result &&
+cut -f1 result | cut -d " " -f3 >expected &&
+test_cmp current expected
+'
+
+test_expect_success 'usage: incompatible options: --name-only with --object-only' '
+test_expect_code 129 git ls-tree --object-only --name-only $tree
+'
+
+test_expect_success 'usage: incompatible options: --name-status with --object-only' '
+test_expect_code 129 git ls-tree --object-only --name-status $tree
+'
+
+test_expect_success 'usage: incompatible options: --long with --object-only' '
+test_expect_code 129 git ls-tree --object-only --long $tree
+'
+
+test_done
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 05:24 UTC · re: Teng Long · lore

Re: [PATCH v11 13/13] ls-tree.c: support --object-only option for "git-ls-tree"

On Tue, Feb 08 2022, Teng Long wrote:
Show 17 quoted lines
> We usually pipe the output from `git ls-trees` to tools like
> `sed` or `cut` when we only want to extract some fields.
>
> When we want only the pathname component, we can pass
> `--name-only` option to omit such a pipeline, but there are no
> options for extracting other fields.
>
> Teach the "--object-only" option to the command to only show the
> object name. This option cannot be used together with
> "--name-only" or "--long" , they are mutually exclusive (actually
> "--name-only" and "--long" can be combined together before, this
> commit by the way fix this bug).
>
> In terms of performance, there is no loss comparing to the
> "master" (2ae0a9c), here are the
> results of the performance tests in my environment based on linux
> repository:

I think given the re-arrangement in this v11 it would make sense to change the commit messageto say:

 * This is an alias for --format=%(objectname)
 * Per benchmark XYZ it's faster
I.e. this:
Show 19 quoted lines
>     $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
>     Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
>     Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]
>     Range (min … max):   101.5 ms … 111.3 ms    28 runs
>
>     $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD"
>     Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD
>     Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]
>     Range (min … max):    99.3 ms … 109.5 ms    27 runs
>
>     $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
>     Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
>     Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]
>     Range (min … max):   323.0 ms … 355.0 ms    10 runs
>
>     $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD"
>     Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD
>     Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]
>     Range (min … max):   330.4 ms … 349.9 ms    10 runs
Is surely more relevant if compared to master & that --format.
Show 31 quoted lines
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  Documentation/git-ls-tree.txt |  7 ++++-
>  builtin/ls-tree.c             | 16 ++++++++++-
>  t/t3104-ls-tree-format.sh     | 12 +++++++++
>  t/t3105-ls-tree-oid.sh        | 51 +++++++++++++++++++++++++++++++++++
>  4 files changed, 84 insertions(+), 2 deletions(-)
>  create mode 100755 t/t3105-ls-tree-oid.sh
>
> diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
> index db29a9efb5..21045dd163 100644
> --- a/Documentation/git-ls-tree.txt
> +++ b/Documentation/git-ls-tree.txt
> @@ -10,7 +10,7 @@ SYNOPSIS
>  --------
>  [verse]
>  'git ls-tree' [-d] [-r] [-t] [-l] [-z]
> -	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
> +	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
>  	    <tree-ish> [<path>...]
>  
>  DESCRIPTION
> @@ -59,6 +59,11 @@ OPTIONS
>  --name-only::
>  --name-status::
>  	List only filenames (instead of the "long" output), one per line.
> +	Cannot be combined with `--object-only`.
> +
> +--object-only::
> +	List only names of the objects, one per line. Cannot be combined
> +	with `--name-only` or `--name-status`.
Hrm, I regret that in my version of v11 11/13 I didn't add to all of these something like:
    
    This is equivalent to specifying `--format=...`, but for both this
    option and that exact format the command takes a hand-optimized
    codepath instead of going through the generic formatting mechanism.
Or whatever, and perhaps have everything after ", but for[...]" part of
the generic FORMAT section (no need to say it for every option).
    
Show 79 quoted lines
>  
>  --abbrev[=<n>]::
>  	Instead of showing the full 40-byte hexadecimal object
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index ba96bcf602..9819a24186 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -20,6 +20,7 @@ static int line_termination = '\n';
>  #define LS_SHOW_TREES (1 << 2)
>  #define LS_NAME_ONLY (1 << 3)
>  #define LS_SHOW_SIZE (1 << 4)
> +#define LS_OBJECT_ONLY (1 << 5)
>  static int abbrev;
>  static int ls_options;
>  static struct pathspec pathspec;
> @@ -37,6 +38,7 @@ static const char *format;
>  static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
>  static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
>  static const char *name_only_format = "%(path)";
> +static const char *object_only_format = "%(objectname)";
>  struct show_tree_data {
>  	unsigned mode;
>  	enum object_type type;
> @@ -53,6 +55,7 @@ static const  char * const ls_tree_usage[] = {
>  enum {
>  	MODE_UNSPECIFIED = 0,
>  	MODE_NAME_ONLY,
> +	MODE_OBJECT_ONLY,
>  	MODE_LONG,
>  };
>  
> @@ -67,6 +70,8 @@ static int fast_path(void){
>  	} else if (!strcmp(format, name_only_format)) {
>  		shown_fields = FIELD_PATH_NAME;
>  		return 1;
> +	} else if (!strcmp(format, object_only_format)) {
> +		shown_fields = FIELD_OBJECT_NAME;
>  	}
>  	return 0;
>  }
> @@ -143,7 +148,10 @@ static int parse_shown_fields(void)
>  		shown_fields = FIELD_PATH_NAME;
>  		return 0;
>  	}
> -
> +	if (cmdmode == MODE_OBJECT_ONLY) {
> +		shown_fields = FIELD_OBJECT_NAME;
> +		return 0;
> +	}
>  	if (!ls_options || (ls_options & LS_RECURSIVE)
>  	    || (ls_options & LS_SHOW_TREES)
>  	    || (ls_options & LS_TREE_ONLY))
> @@ -267,6 +275,10 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
>  	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
>  		return 0;
>  
> +	if (shown_fields == FIELD_OBJECT_NAME) {
> +		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
> +		return recurse;
> +	}
>  	if (shown_fields == FIELD_PATH_NAME) {
>  		baselen = base->len;
>  		strbuf_addstr(base, pathname);
> @@ -304,6 +316,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
>  			    MODE_NAME_ONLY),
>  		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
>  			    MODE_NAME_ONLY),
> +		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
> +			    MODE_OBJECT_ONLY),
>  		OPT_SET_INT(0, "full-name", &chomp_prefix,
>  			    N_("use full path names"), 0),
>  		OPT_BOOL(0, "full-tree", &full_tree,
> diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
> index e08c83dc47..c0ffc8e1c3 100755
> --- a/t/t3104-ls-tree-format.sh
> +++ b/t/t3104-ls-tree-format.sh
> @@ -46,6 +46,12 @@ test_expect_success 'ls-tree --format=<name-only-like>' '
>  		"--name-only"
>  '
This looks much better/less complex than in earlier rounds.
Show 20 quoted lines
> +test_expect_success 'ls-tree --format=<object-only-like>' '
> +	test_ls_tree_format \
> +		"%(objectname)" \
> +		"--object-only"
> +'
> +
>  test_expect_success 'ls-tree combine --format=<default-like> and -t' '
>  	test_ls_tree_format \
>  	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
> @@ -78,4 +84,10 @@ test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
>  	git ls-tree --format="%(path)" -r HEAD >actual &&
>  	test_cmp expect actual
>  '
> +
> +test_expect_success 'ls-tree hit fast-path with --format=<object-only-like>' '
> +	git ls-tree -r --object-only HEAD >expect &&
> +	git ls-tree --format="%(objectname)" -r HEAD >actual &&
> +	test_cmp expect actual
> +'
>  test_done
So, you and I came up with independent tests for these two.

I wonder if this can be re-arranged so that we can share the tests, and perhaps test all for both the --format and --object-onnly in some for-loop, or maybe it's not worth it.

Show 57 quoted lines
> diff --git a/t/t3105-ls-tree-oid.sh b/t/t3105-ls-tree-oid.sh
> new file mode 100755
> index 0000000000..992bb26bfa
> --- /dev/null
> +++ b/t/t3105-ls-tree-oid.sh
> @@ -0,0 +1,51 @@
> +#!/bin/sh
> +
> +test_description='git ls-tree objects handling.'
> +
> +. ./test-lib.sh
> +
> +test_expect_success 'setup' '
> +test_commit A &&
> +test_commit B &&
> +mkdir -p C &&
> +test_commit C/D.txt &&
> +find *.txt path* \( -type f -o -type l \) -print |
> +xargs git update-index --add &&
> +tree=$(git write-tree) &&
> +echo $tree
> +'
> +
> +test_expect_success 'usage: --object-only' '
> +git ls-tree --object-only $tree >current &&
> +git ls-tree $tree >result &&
> +cut -f1 result | cut -d " " -f3 >expected &&
> +test_cmp current expected
> +'
> +
> +test_expect_success 'usage: --object-only with -r' '
> +git ls-tree --object-only -r $tree >current &&
> +git ls-tree -r $tree >result &&
> +cut -f1 result | cut -d " " -f3 >expected &&
> +test_cmp current expected
> +'
> +
> +test_expect_success 'usage: --object-only with --abbrev' '
> +git ls-tree --object-only --abbrev=6 $tree >current &&
> +git ls-tree --abbrev=6 $tree >result &&
> +cut -f1 result | cut -d " " -f3 >expected &&
> +test_cmp current expected
> +'
> +
> +test_expect_success 'usage: incompatible options: --name-only with --object-only' '
> +test_expect_code 129 git ls-tree --object-only --name-only $tree
> +'
> +
> +test_expect_success 'usage: incompatible options: --name-status with --object-only' '
> +test_expect_code 129 git ls-tree --object-only --name-status $tree
> +'
> +
> +test_expect_success 'usage: incompatible options: --long with --object-only' '
> +test_expect_code 129 git ls-tree --object-only --long $tree
> +'
> +
> +test_done
This whole test block seems to have lost its indentation since the v10.
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 04/13] ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The "struct strbuf"'s "len" member is a "size_t", not an "int", so let's change our corresponding types accordingly. This also changes the "len" and "speclen" variables, which are likewise used to store the return value of strlen(), which returns "size_t", not "int".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/ls-tree.c +3 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3f0225b097..eecc7482d5 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
-static int show_recursive(const char *base, int baselen, const char *pathname)
+static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
 
@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
 
 	for (i = 0; i < pathspec.nr; i++) {
 		const char *spec = pathspec.items[i].match;
-		int len, speclen;
+		size_t len, speclen;
 
 		if (strncmp(base, spec, baselen))
 			continue;
@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
 	int retval = 0;
-	int baselen;
+	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 02/13] ls-tree: add missing braces to "else" arms

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add missing {} to the "else" arms in show_tree() per the CodingGuidelines.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/ls-tree.c +4 −2
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5f7c84950c..0a28f32ccb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				else
 					xsnprintf(size_text, sizeof(size_text),
 						  "%"PRIuMAX, (uintmax_t)size);
-			} else
+			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
+			}
 			printf("%06o %s %s %7s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
-		} else
+		} else {
 			printf("%06o %s %s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev));
+		}
 	}
 	baselen = base->len;
 	strbuf_addstr(base, pathname);
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 06/13] ls-tree: simplify nesting if/else logic in "show_tree()"

This commit use "object_type()" to get the type, then remove some of the nested if to let the codes here become more cleaner.

Signed-off-by: Teng Long <dyronetengb@gmail.com>
---
 builtin/ls-tree.c | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)
Show changes to builtin/ls-tree.c +6 −12
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61..9c57a36c8c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,19 +66,13 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
-		if (show_recursive(base->buf, base->len, pathname)) {
-			recurse = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return recurse;
-		}
-		type = OBJ_TREE;
-	}
-	else if (ls_options & LS_TREE_ONLY)
+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+		recurse = READ_TREE_RECURSIVE;
+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+		return recurse;
+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
 		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 06:06 UTC · re: Teng Long · lore

Re: [PATCH v11 06/13] ls-tree: simplify nesting if/else logic in "show_tree()"

On Tue, Feb 08 2022, Teng Long wrote:
Show 38 quoted lines
> This commit use "object_type()" to get the type, then remove
> some of the nested if to let the codes here become more cleaner.
>
> Signed-off-by: Teng Long <dyronetengb@gmail.com>
> ---
>  builtin/ls-tree.c | 18 ++++++------------
>  1 file changed, 6 insertions(+), 12 deletions(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index ef8c414f61..9c57a36c8c 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -66,19 +66,13 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
>  {
>  	int recurse = 0;
>  	size_t baselen;
> -	enum object_type type = OBJ_BLOB;
> +	enum object_type type = object_type(mode);
>  
> -	if (S_ISGITLINK(mode)) {
> -		type = OBJ_COMMIT;
> -	} else if (S_ISDIR(mode)) {
> -		if (show_recursive(base->buf, base->len, pathname)) {
> -			recurse = READ_TREE_RECURSIVE;
> -			if (!(ls_options & LS_SHOW_TREES))
> -				return recurse;
> -		}
> -		type = OBJ_TREE;
> -	}
> -	else if (ls_options & LS_TREE_ONLY)
> +	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
> +		recurse = READ_TREE_RECURSIVE;
> +	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
> +		return recurse;
> +	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
>  		return 0;
>  
>  	if (!(ls_options & LS_NAME_ONLY)) {

I think the use of object_type() here is good, but in any case I think doing a minimal change first for the "type" and then this proposed refactoring would be easier to look at, and to independently decide on the two.

I find this much easier to read, both as a diff and as end-state:
Show changes to builtin/ls-tree.c +14 −19
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61a..0af09e94a03 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,20 +66,18 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
+	if (type == OBJ_BLOB) {
+		if (ls_options & LS_TREE_ONLY)
+			return 0;
+	} else if (type == OBJ_TREE) { 
 		if (show_recursive(base->buf, base->len, pathname)) {
 			recurse = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
 				return recurse;
 		}
-		type = OBJ_TREE;
 	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {

Unrolling this from a logical if/else if to an if/if/if I think also
doesn't make sense. At the cost of a slightly larger diff (could be done
on top) we get rid of the show_recursive() branch too:

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61a..d4be71bad24 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,20 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
-		if (show_recursive(base->buf, base->len, pathname)) {
-			recurse = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return recurse;
-		}
-		type = OBJ_TREE;
+	if (type == OBJ_BLOB) {
+		if (ls_options & LS_TREE_ONLY)
+			return 0;
+	} else if (type == OBJ_TREE &&
+		   show_recursive(base->buf, base->len, pathname)) {
+		recurse = READ_TREE_RECURSIVE;
+		if (!(ls_options & LS_SHOW_TREES))
+			return recurse;
 	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {

Which, I think is also nicer to read, we're not checking "is it a
tree?", setting "recursive", and then using that "recursive" as a
boolean for no reason. Let's just continue on that "else if" chain we're
already in instead...
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 05/13] ls-tree: rename "retval" to "recurse" in "show_tree()"

The variable which "show_tree()" return is named "retval", a name that's a little hard to understand. The commit rename "retval" to "recurse" which is a more meaningful name than before in the context. We do not need to take a look at "read_tree_at()" in "tree.c" to make sure what does "retval" mean.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/ls-tree.c +4 −4
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..ef8c414f61 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -64,7 +64,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
-	int retval = 0;
+	int recurse = 0;
 	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
@@ -72,9 +72,9 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-			retval = READ_TREE_RECURSIVE;
+			recurse = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
-				return retval;
+				return recurse;
 		}
 		type = OBJ_TREE;
 	}
@@ -109,7 +109,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				   chomp_prefix ? ls_tree_prefix : NULL,
 				   stdout, line_termination);
 	strbuf_setlen(base, baselen);
-	return retval;
+	return recurse;
 }
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 07/13] ls-tree: fix "--name-only" and "--long" combined use bug

If we execute "git ls-tree" with combined "--name-only" and "--long" , only the pathname will be printed, the size is omitted (the original discoverer was Peff in [1]).

This commit fix this issue by using `OPT_CMDMODE()` instead to make both of them mutually exclusive.

[1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/ls-tree.c +6 −6
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 9c57a36c8c..32147e75e6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -120,12 +120,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_BIT('l', "long", &ls_options, N_("include object size"),
-			LS_SHOW_SIZE),
-		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
-		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
+			    LS_SHOW_SIZE),
+		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 06:04 UTC · re: Teng Long · lore

Re: [PATCH v11 07/13] ls-tree: fix "--name-only" and "--long" combined use bug

On Tue, Feb 08 2022, Teng Long wrote:
Show 37 quoted lines
> If we execute "git ls-tree" with combined "--name-only" and "--long"
> , only the pathname will be printed, the size is omitted (the original
> discoverer was Peff in [1]).
>
> This commit fix this issue by using `OPT_CMDMODE()` instead to make both
> of them mutually exclusive.
>
> [1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/ls-tree.c | 12 ++++++------
>  1 file changed, 6 insertions(+), 6 deletions(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 9c57a36c8c..32147e75e6 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -120,12 +120,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
>  			LS_SHOW_TREES),
>  		OPT_SET_INT('z', NULL, &line_termination,
>  			    N_("terminate entries with NUL byte"), 0),
> -		OPT_BIT('l', "long", &ls_options, N_("include object size"),
> -			LS_SHOW_SIZE),
> -		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
> -			LS_NAME_ONLY),
> -		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
> -			LS_NAME_ONLY),
> +		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
> +			    LS_SHOW_SIZE),
> +		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
> +			    LS_NAME_ONLY),
> +		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
> +			    LS_NAME_ONLY),
>  		OPT_SET_INT(0, "full-name", &chomp_prefix,
>  			    N_("use full path names"), 0),
>  		OPT_BOOL(0, "full-tree", &full_tree,
This seems like a sensible fix, but let's add a test for it. See:
    git grep 'test_expect_code 129'
For examples.
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 08/13] ls-tree: slightly refactor `show_tree()`

This is a non-functional change, we use a new int "shown_fields" to mark which columns to output, and `parse_shown_fields()` to calculate the value of "shown_fields".

This has the advantage of making the show_tree logic simpler and more readable, as well as making it easier to extend new options (for example, if we want to add a "--object-only" option, we just need to add a similar "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in "show_tree()").

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 126 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 89 insertions(+), 37 deletions(-)
Show changes to builtin/ls-tree.c +89 −37
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 32147e75e6..8baab7c83e 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,21 +16,53 @@
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
-#define LS_TREE_ONLY 2
-#define LS_SHOW_TREES 4
-#define LS_NAME_ONLY 8
-#define LS_SHOW_SIZE 16
+#define LS_TREE_ONLY (1 << 1)
+#define LS_SHOW_TREES (1 << 2)
+#define LS_NAME_ONLY (1 << 3)
+#define LS_SHOW_SIZE (1 << 4)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
+static unsigned int shown_fields;
+#define FIELD_PATH_NAME 1
+#define FIELD_SIZE (1 << 1)
+#define FIELD_OBJECT_NAME (1 << 2)
+#define FIELD_TYPE (1 << 3)
+#define FIELD_MODE (1 << 4)
+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
 };
 
+enum {
+	MODE_UNSPECIFIED = 0,
+	MODE_NAME_ONLY,
+	MODE_LONG,
+};
+
+static int cmdmode = MODE_UNSPECIFIED;
+
+static int parse_shown_fields(void)
+{
+	if (cmdmode == MODE_NAME_ONLY) {
+		shown_fields = FIELD_PATH_NAME;
+		return 0;
+	}
+
+	if (!ls_options || (ls_options & LS_RECURSIVE)
+	    || (ls_options & LS_SHOW_TREES)
+	    || (ls_options & LS_TREE_ONLY))
+		shown_fields = FIELD_DEFAULT;
+	if (cmdmode == MODE_LONG)
+		shown_fields = FIELD_LONG_DEFAULT;
+	return 1;
+}
+
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -61,6 +93,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_default(const struct object_id *oid, enum object_type type,
+			const char *pathname, unsigned mode,
+			struct strbuf *base)
+{
+	size_t baselen = base->len;
+
+	if (shown_fields & FIELD_SIZE) {
+		char size_text[24];
+		if (type == OBJ_BLOB) {
+			unsigned long size;
+			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+				xsnprintf(size_text, sizeof(size_text), "BAD");
+			else
+				xsnprintf(size_text, sizeof(size_text),
+					  "%" PRIuMAX, (uintmax_t)size);
+		} else {
+			xsnprintf(size_text, sizeof(size_text), "-");
+		}
+		printf("%06o %s %s %7s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev), size_text);
+	} else {
+		printf("%06o %s %s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev));
+	}
+	baselen = base->len;
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+	return 1;
+}
+
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
@@ -75,34 +140,19 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
 		return 0;
 
-	if (!(ls_options & LS_NAME_ONLY)) {
-		if (ls_options & LS_SHOW_SIZE) {
-			char size_text[24];
-			if (type == OBJ_BLOB) {
-				unsigned long size;
-				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
-					xsnprintf(size_text, sizeof(size_text),
-						  "BAD");
-				else
-					xsnprintf(size_text, sizeof(size_text),
-						  "%"PRIuMAX, (uintmax_t)size);
-			} else {
-				xsnprintf(size_text, sizeof(size_text), "-");
-			}
-			printf("%06o %s %s %7s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev),
-			       size_text);
-		} else {
-			printf("%06o %s %s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev));
-		}
+	if (shown_fields == FIELD_PATH_NAME) {
+		baselen = base->len;
+		strbuf_addstr(base, pathname);
+		write_name_quoted_relative(base->buf,
+					   chomp_prefix ? ls_tree_prefix : NULL,
+					   stdout, line_termination);
+		strbuf_setlen(base, baselen);
+		return recurse;
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL,
-				   stdout, line_termination);
-	strbuf_setlen(base, baselen);
+
+	if (shown_fields >= FIELD_DEFAULT)
+		show_default(oid, type, pathname, mode, base);
+
 	return recurse;
 }
 
@@ -120,12 +170,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
-			    LS_SHOW_SIZE),
-		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
-		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"),
+			    MODE_LONG),
+		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
@@ -156,6 +206,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
+	parse_shown_fields();
+
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 05:56 UTC · re: Teng Long · lore

Re: [PATCH v11 08/13] ls-tree: slightly refactor `show_tree()`

On Tue, Feb 08 2022, Teng Long wrote:
Show 9 quoted lines
> This is a non-functional change, we use a new int "shown_fields" to mark
> which columns to output, and `parse_shown_fields()` to calculate the
> value of "shown_fields".
>
> This has the advantage of making the show_tree logic simpler and more
> readable, as well as making it easier to extend new options (for example,
> if we want to add a "--object-only" option, we just need to add a similar
> "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in
> "show_tree()").
I think this and the 09/13 really don't make sense in combination.

Now, I clearly prefer to put options for the command into its own little struct to pass around, I think it makes for easier reading than the globals you end up with.

But tastes differ, and some built-ins use one, and some the other pattern.

But this is really the worst of both worlds, let's just pick one or the other, not effectively some some ptions in that struct in 09/13, and some in globals here...

Show 7 quoted lines
> +static unsigned int shown_fields;
> +#define FIELD_PATH_NAME 1
> +#define FIELD_SIZE (1 << 1)
> +#define FIELD_OBJECT_NAME (1 << 2)
> +#define FIELD_TYPE (1 << 3)
> +#define FIELD_MODE (1 << 4)
> +#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */

Why do we need some FIELD_DEFAULT here as opposed to just having it by an enum field with a valu of 0?

Show 7 quoted lines
> +enum {
> +	MODE_UNSPECIFIED = 0,
> +	MODE_NAME_ONLY,
> +	MODE_LONG,
> +};
> +
> +static int cmdmode = MODE_UNSPECIFIED;

let's name this enum type and use it, see e.g. builtin/help.c's "static enum help_action" for an example.

Show 16 quoted lines
> +
> +static int parse_shown_fields(void)
> +{
> +	if (cmdmode == MODE_NAME_ONLY) {
> +		shown_fields = FIELD_PATH_NAME;
> +		return 0;
> +	}
> +
> +	if (!ls_options || (ls_options & LS_RECURSIVE)
> +	    || (ls_options & LS_SHOW_TREES)
> +	    || (ls_options & LS_TREE_ONLY))
> +		shown_fields = FIELD_DEFAULT;
> +	if (cmdmode == MODE_LONG)
> +		shown_fields = FIELD_LONG_DEFAULT;
> +	return 1;
> +}

I still don't really get why we can't just use the one MODE_* here. E.g. doesn't MODE_LONG map to FIELD_LONG_DEFAULT, MODE_NAME_ONLY to FIELD_PATH_NAME etc?

Is this all so we can do "shown_fields & FIELD_SIZE" in show_default() as opposed to e.g. checking "default format or long format?" ?

Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 10/13] cocci: allow padding with `strbuf_addf()`

From: Johannes Schindelin <johannes.schindelin@gmx.de>

A convenient way to pad strings is to use something like `strbuf_addf(&buf, "%20s", "Hello, world!")`.

However, the Coccinelle rule that forbids a format `"%s"` with a constant string argument cast too wide a net, and also forbade such padding.

The original rule was introduced by commit:
    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to contrib/coccinelle/strbuf.cocci +3 −3
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
index d9ada69b43..0970d98ad7 100644
--- a/contrib/coccinelle/strbuf.cocci
+++ b/contrib/coccinelle/strbuf.cocci
@@ -15,7 +15,7 @@ constant fmt !~ "%";
 @@
 expression E;
 struct strbuf SB;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SB.buf);
 + strbuf_addbuf(E, &SB);
@@ -23,7 +23,7 @@ format F =~ "s";
 @@
 expression E;
 struct strbuf *SBP;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SBP->buf);
 + strbuf_addbuf(E, SBP);
@@ -44,7 +44,7 @@ struct strbuf *SBP;
 
 @@
 expression E1, E2;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E1, "%@F@", E2);
 + strbuf_addstr(E1, E2);
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 09/13] ls-tree: introduce struct "show_tree_data"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

"show_tree_data" is a struct that packages the necessary fields for "show_tree()". This commit is a pre-prepared commit for supporting "--format" option and it does not affect any existing functionality.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 43 ++++++++++++++++++++++++++++---------------
 1 file changed, 28 insertions(+), 15 deletions(-)
Show changes to builtin/ls-tree.c +28 −15
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 8baab7c83e..293b8f9dfb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -34,6 +34,14 @@ static unsigned int shown_fields;
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
+struct show_tree_data {
+	unsigned mode;
+	enum object_type type;
+	const struct object_id *oid;
+	const char *pathname;
+	struct strbuf *base;
+};
+
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
@@ -93,17 +101,15 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
-static int show_default(const struct object_id *oid, enum object_type type,
-			const char *pathname, unsigned mode,
-			struct strbuf *base)
+static int show_default(struct show_tree_data *data)
 {
-	size_t baselen = base->len;
+	size_t baselen = data->base->len;
 
 	if (shown_fields & FIELD_SIZE) {
 		char size_text[24];
-		if (type == OBJ_BLOB) {
+		if (data->type == OBJ_BLOB) {
 			unsigned long size;
-			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
 				xsnprintf(size_text, sizeof(size_text), "BAD");
 			else
 				xsnprintf(size_text, sizeof(size_text),
@@ -111,18 +117,18 @@ static int show_default(const struct object_id *oid, enum object_type type,
 		} else {
 			xsnprintf(size_text, sizeof(size_text), "-");
 		}
-		printf("%06o %s %s %7s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev), size_text);
+		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev), size_text);
 	} else {
-		printf("%06o %s %s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev));
+		printf("%06o %s %s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev));
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
+	baselen = data->base->len;
+	strbuf_addstr(data->base, data->pathname);
+	write_name_quoted_relative(data->base->buf,
 				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
 				   line_termination);
-	strbuf_setlen(base, baselen);
+	strbuf_setlen(data->base, baselen);
 	return 1;
 }
 
@@ -132,6 +138,13 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
 
 	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
 		recurse = READ_TREE_RECURSIVE;
@@ -151,7 +164,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	}
 
 	if (shown_fields >= FIELD_DEFAULT)
-		show_default(oid, type, pathname, mode, base);
+		show_default(&data);
 
 	return recurse;
 }
-- 
2.34.1.403.gb35f2687cf.dirty
Teng Long· Feb 8, 2022, 12:14 UTC · re: Teng Long · lore

[PATCH v11 11/13] ls-tree.c: introduce "--format" option

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add a --format option to ls-tree. It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths.

Rather than add --type-only, --object-only etc. we can just support a --format using a strbuf_expand() similar to "for-each-ref --format". We might still add such options in the future for convenience.

The --format implementation is slower than the existing code, but this change does not cause any performance regressions. We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided.

I.e. something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout.

The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community [1].

Here is the statistics about performance tests:
1. Default format (hitten the builtin formats):
    "git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]
    Range (min … max):    99.2 ms … 113.2 ms    28 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD
    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]
    Range (min … max):   100.2 ms … 110.5 ms    29 runs
2. Default format includes object size (hitten the builtin formats):
    "git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]
    Range (min … max):   327.5 ms … 348.4 ms    10 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD
    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]
    Range (min … max):   328.8 ms … 349.4 ms    10 runs
Links:
	[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |  59 ++++++++++++++--
 builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-
 t/t3104-ls-tree-format.sh     |  81 +++++++++++++++++++++
 3 files changed, 262 insertions(+), 7 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Show changes to 3 files +262 −7

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..db29a9efb5 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -74,6 +74,16 @@ OPTIONS
 	Do not limit the listing to the current working directory.
 	Implies --full-name.
 
+--format=<format>::
+	A string that interpolates `%(fieldname)` from the result
+	being shown. It also interpolates `%%` to `%`, and
+	`%xx` where `xx` are hex digits interpolates to character
+	with hex code `xx`; for example `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+	When specified, `--format` cannot be combined with other
+	format-altering options, including `--long`, `--name-only`
+	and `--object-only`.
+
 [<path>...]::
 	When paths are given, show them (note that this isn't really raw
 	pathnames, but rather a list of patterns to match).  Otherwise
@@ -82,16 +92,29 @@ OPTIONS
 
 Output Format
 -------------
-        <mode> SP <type> SP <object> TAB <file>
+
+The output format of `ls-tree` is determined by either the `--format`
+option, or other format-altering options such as `--name-only` etc.
+(see `--format` above).
+
+The use of certain `--format` directives is equivalent to using those
+options, but invoking the full formatting machinery can be slower than
+using an appropriate formatting option.
+
+In cases where the `--format` would exactly map to an existing option
+`ls-tree` will use the appropriate faster path. Thus the default format
+is equivalent to:
+
+        %(objectmode) %(objecttype) %(objectname)%x09%(path)
 
 This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
-        <mode> SP <type> SP <object> SP <object size> TAB <file>
+        %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
 
-Object size identified by <object> is given in bytes, and right-justified
+Object size identified by <objectname> is given in bytes, and right-justified
 with minimum width of 7 characters.  Object size is given only for blobs
 (file) entries; for other entries `-` character is used in place of size.
 
@@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]).  Using `-z` the filename is output
 verbatim and the line is terminated by a NUL byte.
 
+Customized format:
+
+It is possible to print in a custom format by using the `--format` option,
+which is able to interpolate different fields using a `%(fieldname)` notation.
+For example, if you only care about the "objectname" and "path" fields, you
+can execute with a specific "--format" like
+
+        git ls-tree --format='%(objectname) %(path)' <tree-ish>
+
+FIELD NAMES
+-----------
+
+Various values from structured fields can be used to interpolate
+into the resulting output. For each outputing line, the following
+names can be used:
+
+objectmode::
+	The mode of the object.
+objecttype::
+	The type of the object (`blob` or `tree`).
+objectname::
+	The name of the object.
+objectsize[:padded]::
+	The size of the object ("-" if it's a tree).
+	It also supports a padded format of size with "%(size:padded)".
+path::
+	The pathname of the object.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 293b8f9dfb..1c71e5d543 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -33,7 +33,10 @@ static unsigned int shown_fields;
 #define FIELD_MODE (1 << 4)
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
-
+static const char *format;
+static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
+static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
+static const char *name_only_format = "%(path)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -55,6 +58,72 @@ enum {
 
 static int cmdmode = MODE_UNSPECIFIED;
 
+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
+			      const enum object_type type, unsigned int padded)
+{
+	if (type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, oid, &size) < 0)
+			die(_("could not get object info about '%s'"),
+			    oid_to_hex(oid));
+		if (padded)
+			strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size);
+		else
+			strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size);
+	} else if (padded) {
+		strbuf_addf(line, "%7s", "-");
+	} else {
+		strbuf_addstr(line, "-");
+	}
+}
+
+static size_t expand_show_tree(struct strbuf *sb, const char *start,
+			       void *context)
+{
+	struct show_tree_data *data = context;
+	const char *end;
+	const char *p;
+	unsigned int errlen;
+	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+
+	if (len)
+		return len;
+	if (*start != '(')
+		die(_("bad ls-tree format: as '%s'"), start);
+
+	end = strchr(start + 1, ')');
+	if (!end)
+		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
+
+	len = end - start + 1;
+	if (skip_prefix(start, "(objectmode)", &p)) {
+		strbuf_addf(sb, "%06o", data->mode);
+	} else if (skip_prefix(start, "(objecttype)", &p)) {
+		strbuf_addstr(sb, type_name(data->type));
+	} else if (skip_prefix(start, "(objectsize:padded)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 1);
+	} else if (skip_prefix(start, "(objectsize)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 0);
+	} else if (skip_prefix(start, "(objectname)", &p)) {
+		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
+	} else if (skip_prefix(start, "(path)", &p)) {
+		const char *name = data->base->buf;
+		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
+		struct strbuf quoted = STRBUF_INIT;
+		struct strbuf sbuf = STRBUF_INIT;
+		strbuf_addstr(data->base, data->pathname);
+		name = relative_path(data->base->buf, prefix, &sbuf);
+		quote_c_style(name, &quoted, NULL, 0);
+		strbuf_addbuf(sb, &quoted);
+		strbuf_release(&sbuf);
+		strbuf_release(&quoted);
+	} else {
+		errlen = (unsigned long)len;
+		die(_("bad ls-tree format: %%%.*s"), errlen, start);
+	}
+	return len;
+}
+
 static int parse_shown_fields(void)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
@@ -101,6 +170,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
+			 const char *pathname, unsigned mode, void *context)
+{
+	size_t baselen;
+	int recurse = 0;
+	struct strbuf sb = STRBUF_INIT;
+	enum object_type type = object_type(mode);
+
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
+
+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+		recurse = READ_TREE_RECURSIVE;
+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+		return recurse;
+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
+		return 0;
+
+	baselen = base->len;
+	strbuf_expand(&sb, format, expand_show_tree, &data);
+	strbuf_addch(&sb, line_termination);
+	fwrite(sb.buf, sb.len, 1, stdout);
+	strbuf_release(&sb);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
 static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
@@ -174,6 +275,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct object_id oid;
 	struct tree *tree;
 	int i, full_tree = 0;
+	read_tree_fn_t fn = show_tree;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -194,6 +296,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "full-tree", &full_tree,
 			 N_("list entire tree; not just current directory "
 			    "(implies --full-name)")),
+		OPT_STRING_F(0, "format", &format, N_("format"),
+					 N_("format to use for the output"),
+					 PARSE_OPT_NONEG),
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
@@ -214,6 +319,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
 		ls_options |= LS_SHOW_TREES;
 
+	if (format && cmdmode)
+		usage_msg_opt(
+			_("--format can't be combined with other format-altering options"),
+			ls_tree_usage, ls_tree_options);
 	if (argc < 1)
 		usage_with_options(ls_tree_usage, ls_tree_options);
 	if (get_oid(argv[0], &oid))
@@ -237,6 +346,20 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	tree = parse_tree_indirect(&oid);
 	if (!tree)
 		die("not a tree object");
-	return !!read_tree(the_repository, tree,
-			   &pathspec, show_tree, NULL);
+	/*
+	 * The generic show_tree_fmt() is slower than show_tree(), so
+	 * take the fast path if possible.
+	 */
+	if (format && (!strcmp(format, default_format))) {
+		fn = show_tree;
+	} else if (format && (!strcmp(format, long_format))) {
+		shown_fields = shown_fields | FIELD_SIZE;
+		fn = show_tree;
+	} else if (format && (!strcmp(format, name_only_format))) {
+		shown_fields = FIELD_PATH_NAME;
+		fn = show_tree;
+	} else if (format)
+		fn = show_tree_fmt;
+
+	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
 }
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
new file mode 100755
index 0000000000..e08c83dc47
--- /dev/null
+++ b/t/t3104-ls-tree-format.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='ls-tree --format'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	fmtopts=$3 &&
+	shift 2 &&
+	git ls-tree $opts -r HEAD >expect.raw &&
+	sed "s/^/> /" >expect <expect.raw &&
+	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'ls-tree --format=<default-like>' '
+	test_ls_tree_format \
+		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+		""
+'
+
+test_expect_success 'ls-tree --format=<long-like>' '
+	test_ls_tree_format \
+		"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+		"--long"
+'
+
+test_expect_success 'ls-tree --format=<name-only-like>' '
+	test_ls_tree_format \
+		"%(path)" \
+		"--name-only"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and -t' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"-t" \
+	"-t"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-name" \
+	"--full-name"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-tree" \
+	"--full-tree"
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
+	git ls-tree -r HEAD >expect &&
+	git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
+	git ls-tree -r --name-only HEAD >expect &&
+	git ls-tree --format="%(path)" -r HEAD >actual &&
+	test_cmp expect actual
+'
+test_done
-- 
2.34.1.403.gb35f2687cf.dirty
Ævar Arnfjörð Bjarmason· Feb 19, 2022, 05:44 UTC · re: Teng Long · lore

Re: [PATCH v11 11/13] ls-tree.c: introduce "--format" option

On Tue, Feb 08 2022, Teng Long wrote:
> +	if (*start != '(')
> +		die(_("bad ls-tree format: as '%s'"), start);
My typo surely, but I think I menat "as of" not just "as" there:
    $ ./git ls-tree --format="%[blah)" -r HEAD
    fatal: bad ls-tree format: as of '[blah)'
> +
> +	end = strchr(start + 1, ')');
> +	if (!end)
> +		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
Or actually:
    $ ./git ls-tree --format="%(blah]" -r HEAD
    fatal: bad ls-tree format: element '(blah]' does not end in ')'
We could rather say for the first one:
    $ ./git ls-tree --format="%[blah)" -r HEAD
    fatal: bad ls-tree format: element '[blah)' does not start with '('
> [...]
> +		errlen = (unsigned long)len;
> +		die(_("bad ls-tree format: %%%.*s"), errlen, start);

I wondered why that %% is there (and I probably wrote this in the first place, I didn't check:). But it makes sense, because strbuf_expand() skips past the % for us, and we'd like to say e.g. %(foobar) here, not (foobar) or whatever.

Show 10 quoted lines
> new file mode 100755
> index 0000000000..e08c83dc47
> --- /dev/null
> +++ b/t/t3104-ls-tree-format.sh
> @@ -0,0 +1,81 @@
> +#!/bin/sh
> +
> +test_description='ls-tree --format'
> +
> +TEST_PASSES_SANITIZE_LEAK=true

I notice now after commenting on your 13/13 that you should add TEST_PASSES_SANITIZE_LEAK=true to it (assuming it doesn't leak, which I don't think it does, but test with SANITIZE=leak first!)

Show 10 quoted lines
> +test_ls_tree_format () {
> +	format=$1 &&
> +	opts=$2 &&
> +	fmtopts=$3 &&
> +	shift 2 &&
> +	git ls-tree $opts -r HEAD >expect.raw &&
> +	sed "s/^/> /" >expect <expect.raw &&
> +	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
> +	test_cmp expect actual
> +}

I also forgot I wrote this, but also per my comment on 13/13 you can just add your tests added in 13/13 to this file, then we'll assert that -r etc. work the same for both.

Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 00/12] ls-tree: "--object-only" and "--format" opts

Sorry for the late reply.
Main diff from V11:
1. [v12][06/12] simplify nesting if/else logic in "show_tree()"
  * Unrolling this from a logical if/if/if/ to if/else
  It's from Ævar Arnfjörð Bjarmason's suggestion at:
  https://public-inbox.org/git/220219.86ee3ze5kz.gmgdl@evledraar.gmail.com/#t
  By the way, Ævar also think to doing a minimal change first for use
  "object_type", the reason I didn't is because after the unroll, the related
  codes here is almost the same. So, I think this situation here is ok now.
2. [v12][07/12] ls-tree: fix "--name-only" and "--long" combined use bug
  * Add 2 tests for the bugfix
  It's from Ævar Arnfjörð Bjarmason's suggestion at:
  https://public-inbox.org/git/220219.86iltbe6i2.gmgdl@evledraar.gmail.com/
3. [v12][08/12] ls-tree: slightly refactor `show_tree()
   * Name the cmdmode enum
   It's from Ævar Arnfjörð Bjarmason's suggestion at:
   https://public-inbox.org/git/220219.86mtine6jb.gmgdl@evledraar.gmail.com/
4. [v12][09/12] ls-tree: introduce struct "show_tree_data"
   * Move global "show_fields into struct "show_data", avoid to use both global
     value and local value to save the related data for showing.
   It's from Ævar Arnfjörð Bjarmason's suggestion at:
   https://public-inbox.org/git/220219.86mtine6jb.gmgdl@evledraar.gmail.com/       
5. [v12][11/12] ls-tree.c: introduce "--format" option
   * Replace "die(_("bad ls-tree format: as '%s'"), start);" with
     "die(_("bad ls-tree format: element '%s' does not start with '('"), start)"
   It's from Ævar Arnfjörð Bjarmason's suggestion at:
   https://public-inbox.org/git/220219.86r17ze72s.gmgdl@evledraar.gmail.com/
6. ls-tree: introduce function "fast_path()"
   * This commit is removed now.
7. [v12][12/12] ls-tree.c: support --object-only option for "git-ls-tree"
   * Optimize the commit message
   * Add some extra contents under "--object-only:" in "git-ls-tree.txt"
   * Tidy the tests. I remove t3105-ls-tree-oid.sh, move the format-checking
     tests to t3104-ls-tree-format.sh and move the option-compatiblilty-checking
     test to t3103-ls-tree-misc.sh.
Thanks.
Johannes Schindelin (1):
  cocci: allow padding with `strbuf_addf()`
Teng Long (5):
  ls-tree: rename "retval" to "recurse" in "show_tree()"
  ls-tree: simplify nesting if/else logic in "show_tree()"
  ls-tree: fix "--name-only" and "--long" combined use bug
  ls-tree: slightly refactor `show_tree()`
  ls-tree: support --object-only option for "git-ls-tree"
Ævar Arnfjörð Bjarmason (6):
  ls-tree: remove commented-out code
  ls-tree: add missing braces to "else" arms
  ls-tree: use "enum object_type", not {blob,tree,commit}_type
  ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
  ls-tree: introduce struct "show_tree_data"
  ls-tree: introduce "--format" option
 Documentation/git-ls-tree.txt   |  68 ++++++-
 builtin/ls-tree.c               | 324 +++++++++++++++++++++++++-------
 contrib/coccinelle/strbuf.cocci |   6 +-
 t/t3103-ls-tree-misc.sh         |  12 ++
 t/t3104-ls-tree-format.sh       | 100 ++++++++++
 5 files changed, 439 insertions(+), 71 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Range-diff against v11:
 -:  ---------- >  1:  2fcff7e0d4 ls-tree: remove commented-out code
 -:  ---------- >  2:  6fd1dd9383 ls-tree: add missing braces to "else" arms
 -:  ---------- >  3:  208654b5e2 ls-tree: use "enum object_type", not {blob,tree,commit}_type
 -:  ---------- >  4:  2637464fd8 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
 -:  ---------- >  5:  99e6d47108 ls-tree: rename "retval" to "recurse" in "show_tree()"
 1:  3816a65fe6 !  6:  a8d9b78dea ls-tree: simplify nesting if/else logic in "show_tree()"
    @@ Metadata
      ## Commit message ##
         ls-tree: simplify nesting if/else logic in "show_tree()"
     
    -    This commit use "object_type()" to get the type, then remove
    -    some of the nested if to let the codes here become more cleaner.
    +    Use the object_type() function to determine the object type from the
    +    "mode" passed to us by read_tree(), instead of doing so with the S_*()
    +    macros.
     
         Signed-off-by: Teng Long <dyronetengb@gmail.com>
     
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -				return recurse;
     -		}
     -		type = OBJ_TREE;
    --	}
    --	else if (ls_options & LS_TREE_ONLY)
    -+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
    ++	if (type == OBJ_BLOB) {
    ++		if (ls_options & LS_TREE_ONLY)
    ++			return 0;
    ++	} else if (type == OBJ_TREE &&
    ++		   show_recursive(base->buf, base->len, pathname)) {
     +		recurse = READ_TREE_RECURSIVE;
    -+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
    -+		return recurse;
    -+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    - 		return 0;
    ++		if (!(ls_options & LS_SHOW_TREES))
    ++			return recurse;
    + 	}
    +-	else if (ls_options & LS_TREE_ONLY)
    +-		return 0;
      
      	if (!(ls_options & LS_NAME_ONLY)) {
    + 		if (ls_options & LS_SHOW_SIZE) {
 2:  b22c2dc49e !  7:  25a07e048f ls-tree: fix "--name-only" and "--long" combined use bug
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		OPT_SET_INT(0, "full-name", &chomp_prefix,
      			    N_("use full path names"), 0),
      		OPT_BOOL(0, "full-tree", &full_tree,
    +
    + ## t/t3103-ls-tree-misc.sh ##
    +@@ t/t3103-ls-tree-misc.sh: test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
    + 	test_must_fail git ls-tree -r HEAD
    + '
    + 
    ++for opts in \
    ++	"--name-only --long" \
    ++	"--name-status --long"
    ++do
    ++	test_expect_success "usage: incompatible options: $opts" '
    ++		test_expect_code 129 git ls-tree $opts $tree
    ++    '
    ++done
    ++
    + test_done
 3:  41e8ed5047 !  8:  f449146b4d ls-tree: slightly refactor `show_tree()`
    @@ builtin/ls-tree.c
      	NULL
      };
      
    -+enum {
    ++static enum mutx_option {
     +	MODE_UNSPECIFIED = 0,
     +	MODE_NAME_ONLY,
     +	MODE_LONG,
    -+};
    -+
    -+static int cmdmode = MODE_UNSPECIFIED;
    ++} cmdmode;
     +
     +static int parse_shown_fields(void)
     +{
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
      		const char *pathname, unsigned mode, void *context)
      {
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    - 	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    - 		return 0;
    + 			return recurse;
    + 	}
      
     -	if (!(ls_options & LS_NAME_ONLY)) {
     -		if (ls_options & LS_SHOW_SIZE) {
 4:  46e10a5392 !  9:  80311adc7c ls-tree: introduce struct "show_tree_data"
    @@ Commit message
         "show_tree()". This commit is a pre-prepared commit for supporting
         "--format" option and it does not affect any existing functionality.
     
    +    At the same time, we also move global "shown_fields" into "show_tree_data"
    +    , the purpose of this is to avoid use both global variable and pass-around
    +    struct.
    +
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## builtin/ls-tree.c ##
    +@@ builtin/ls-tree.c: static int ls_options;
    + static struct pathspec pathspec;
    + static int chomp_prefix;
    + static const char *ls_tree_prefix;
    +-static unsigned int shown_fields;
    + #define FIELD_PATH_NAME 1
    + #define FIELD_SIZE (1 << 1)
    + #define FIELD_OBJECT_NAME (1 << 2)
     @@ builtin/ls-tree.c: static unsigned int shown_fields;
      #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
      #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    @@ builtin/ls-tree.c: static unsigned int shown_fields;
     +	const struct object_id *oid;
     +	const char *pathname;
     +	struct strbuf *base;
    ++	unsigned int shown_fields;
     +};
     +
      static const  char * const ls_tree_usage[] = {
      	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
      	NULL
    +@@ builtin/ls-tree.c: static enum mutx_option {
    + 	MODE_LONG,
    + } cmdmode;
    + 
    +-static int parse_shown_fields(void)
    ++static int parse_shown_fields(unsigned int *shown_fields)
    + {
    + 	if (cmdmode == MODE_NAME_ONLY) {
    +-		shown_fields = FIELD_PATH_NAME;
    ++		*shown_fields = FIELD_PATH_NAME;
    + 		return 0;
    + 	}
    + 
    + 	if (!ls_options || (ls_options & LS_RECURSIVE)
    + 	    || (ls_options & LS_SHOW_TREES)
    + 	    || (ls_options & LS_TREE_ONLY))
    +-		shown_fields = FIELD_DEFAULT;
    ++		*shown_fields = FIELD_DEFAULT;
    + 	if (cmdmode == MODE_LONG)
    +-		shown_fields = FIELD_LONG_DEFAULT;
    ++		*shown_fields = FIELD_LONG_DEFAULT;
    + 	return 1;
    + }
    + 
     @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
      	return 0;
      }
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     -	size_t baselen = base->len;
     +	size_t baselen = data->base->len;
      
    - 	if (shown_fields & FIELD_SIZE) {
    +-	if (shown_fields & FIELD_SIZE) {
    ++	if (data->shown_fields & FIELD_SIZE) {
      		char size_text[24];
     -		if (type == OBJ_BLOB) {
     +		if (data->type == OBJ_BLOB) {
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
      	int recurse = 0;
      	size_t baselen;
      	enum object_type type = object_type(mode);
    ++	unsigned int shown_fields = *(unsigned int *)context;
     +	struct show_tree_data data = {
     +		.mode = mode,
     +		.type = type,
     +		.oid = oid,
     +		.pathname = pathname,
     +		.base = base,
    ++		.shown_fields = shown_fields,
     +	};
      
    - 	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
    - 		recurse = READ_TREE_RECURSIVE;
    + 	if (type == OBJ_BLOB) {
    + 		if (ls_options & LS_TREE_ONLY)
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    + 		return recurse;
      	}
      
    - 	if (shown_fields >= FIELD_DEFAULT)
    +-	if (shown_fields >= FIELD_DEFAULT)
     -		show_default(oid, type, pathname, mode, base);
    ++	if (shown_fields>= FIELD_DEFAULT)
     +		show_default(&data);
      
      	return recurse;
      }
    +@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + 	struct object_id oid;
    + 	struct tree *tree;
    + 	int i, full_tree = 0;
    ++	unsigned int shown_fields = 0;
    + 	const struct option ls_tree_options[] = {
    + 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
    + 			LS_TREE_ONLY),
    +@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + 	if (get_oid(argv[0], &oid))
    + 		die("Not a valid object name %s", argv[0]);
    + 
    +-	parse_shown_fields();
    +-
    + 	/*
    + 	 * show_recursive() rolls its own matching code and is
    + 	 * generally ignorant of 'struct pathspec'. The magic mask
    +@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + 	tree = parse_tree_indirect(&oid);
    + 	if (!tree)
    + 		die("not a tree object");
    +-	return !!read_tree(the_repository, tree,
    +-			   &pathspec, show_tree, NULL);
    ++
    ++	parse_shown_fields(&shown_fields);
    ++
    ++	return !!read_tree(the_repository, tree, &pathspec, show_tree,
    ++			   &shown_fields);
    + }
 5:  c04320b801 = 10:  459080f549 cocci: allow padding with `strbuf_addf()`
 6:  5936004f13 ! 11:  cb717d08be ls-tree.c: introduce "--format" option
    @@ Metadata
     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Commit message ##
    -    ls-tree.c: introduce "--format" option
    +    ls-tree: introduce "--format" option
     
         Add a --format option to ls-tree. It has an existing default output,
         and then --long and --name-only options to emit the default output
    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl
      Part of the linkgit:git[1] suite
     
      ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static unsigned int shown_fields;
    +@@ builtin/ls-tree.c: static const char *ls_tree_prefix;
      #define FIELD_MODE (1 << 4)
      #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
      #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    @@ builtin/ls-tree.c: static unsigned int shown_fields;
      struct show_tree_data {
      	unsigned mode;
      	enum object_type type;
    -@@ builtin/ls-tree.c: enum {
    - 
    - static int cmdmode = MODE_UNSPECIFIED;
    +@@ builtin/ls-tree.c: static enum mutx_option {
    + 	MODE_LONG,
    + } cmdmode;
      
     +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
     +			      const enum object_type type, unsigned int padded)
    @@ builtin/ls-tree.c: enum {
     +	if (len)
     +		return len;
     +	if (*start != '(')
    -+		die(_("bad ls-tree format: as '%s'"), start);
    ++		die(_("bad ls-tree format: element '%s' does not start with '('"), start);
     +
     +	end = strchr(start + 1, ')');
     +	if (!end)
    @@ builtin/ls-tree.c: enum {
     +	return len;
     +}
     +
    - static int parse_shown_fields(void)
    + static int parse_shown_fields(unsigned int *shown_fields)
      {
      	if (cmdmode == MODE_NAME_ONLY) {
     @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
      {
      	size_t baselen = data->base->len;
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	struct object_id oid;
      	struct tree *tree;
      	int i, full_tree = 0;
    + 	unsigned int shown_fields = 0;
     +	read_tree_fn_t fn = show_tree;
      	const struct option ls_tree_options[] = {
      		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		usage_with_options(ls_tree_usage, ls_tree_options);
      	if (get_oid(argv[0], &oid))
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	tree = parse_tree_indirect(&oid);
    - 	if (!tree)
    - 		die("not a tree object");
    --	return !!read_tree(the_repository, tree,
    --			   &pathspec, show_tree, NULL);
    + 
    + 	parse_shown_fields(&shown_fields);
    + 
    +-	return !!read_tree(the_repository, tree, &pathspec, show_tree,
    +-			   &shown_fields);
     +	/*
     +	 * The generic show_tree_fmt() is slower than show_tree(), so
     +	 * take the fast path if possible.
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
     +	} else if (format)
     +		fn = show_tree_fmt;
     +
    -+	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
    ++	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
      }
     
      ## t/t3104-ls-tree-format.sh (new) ##
 7:  6d26497749 <  -:  ---------- ls-tree: introduce function "fast_path()"
 8:  e6d98f2560 ! 12:  55b1c4379d ls-tree.c: support --object-only option for "git-ls-tree"
    @@ Metadata
     Author: Teng Long <dyroneteng@gmail.com>
     
      ## Commit message ##
    -    ls-tree.c: support --object-only option for "git-ls-tree"
    +    ls-tree: support --object-only option for "git-ls-tree"
     
    -    We usually pipe the output from `git ls-trees` to tools like
    -    `sed` or `cut` when we only want to extract some fields.
    +    '--object-only' is an alias for '--format=%(objectname)'. It cannot
    +    be used together other format-altering options like '--name-only',
    +    '--long' or '--format', they are mutually exclusive.
     
    -    When we want only the pathname component, we can pass
    -    `--name-only` option to omit such a pipeline, but there are no
    -    options for extracting other fields.
    +    The "--name-only" option outputs <filepath> only. Likewise, <objectName>
    +    is another high frequency used field, so implement '--object-only' option
    +    will bring intuitive and clear semantics for this scenario. Using
    +    '--format=%(objectname)' we can achieve a similar effect, but the former
    +    is with a lower learning cost(without knowing the format requirement
    +    of '--format' option).
     
    -    Teach the "--object-only" option to the command to only show the
    -    object name. This option cannot be used together with
    -    "--name-only" or "--long" , they are mutually exclusive (actually
    -    "--name-only" and "--long" can be combined together before, this
    -    commit by the way fix this bug).
    +    Even so, if a user is prefer to use "--format=%(objectname)", this is entirely
    +    welcome because they are not only equivalent in function, but also have almost
    +    identical performance. The reason is this commit also add the specific of
    +    "--format=%(objectname)" to the current fast-pathes (builtin formats) to
    +    avoid running unnecessary parsing mechanisms.
     
    -    In terms of performance, there is no loss comparing to the
    -    "master" (2ae0a9c), here are the
    -    results of the performance tests in my environment based on linux
    -    repository:
    +    The following performance benchmarks are based on torvalds/linux.git:
     
    -        $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    -        Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    -        Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]
    -        Range (min … max):   101.5 ms … 111.3 ms    28 runs
    +      When hit the fast-path:
     
    -        $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD"
    -        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD
    -        Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]
    -        Range (min … max):    99.3 ms … 109.5 ms    27 runs
    +          Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --object-only HEAD
    +            Time (mean ± σ):      83.6 ms ±   2.0 ms    [User: 59.4 ms, System: 24.1 ms]
    +            Range (min … max):    80.4 ms …  87.2 ms    35 runs
     
    -        $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    -        Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    -        Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]
    -        Range (min … max):   323.0 ms … 355.0 ms    10 runs
    +          Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(objectname)' HEAD
    +            Time (mean ± σ):      84.1 ms ±   1.8 ms    [User: 61.7 ms, System: 22.3 ms]
    +            Range (min … max):    80.9 ms …  87.5 ms    35 runs
     
    -        $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD"
    -        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD
    -        Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]
    -        Range (min … max):   330.4 ms … 349.9 ms    10 runs
    +      But for a customized format, it will be slower:
    +
    +           Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='oid: %(objectname)' HEAD
    +             Time (mean ± σ):      96.5 ms ±   2.5 ms    [User: 72.9 ms, System: 23.5 ms]
    +             Range (min … max):    93.1 ms … 104.1 ms    31 runs
     
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
    @@ Documentation/git-ls-tree.txt: OPTIONS
     +--object-only::
     +	List only names of the objects, one per line. Cannot be combined
     +	with `--name-only` or `--name-status`.
    ++	This is equivalent to specifying `--format='%(objectname)'`, but
    ++	for both this option and that exact format the command takes a
    ++	hand-optimized codepath instead of going through the generic
    ++	formatting mechanism.
      
      --abbrev[=<n>]::
      	Instead of showing the full 40-byte hexadecimal object
    @@ builtin/ls-tree.c: static const char *format;
      	unsigned mode;
      	enum object_type type;
     @@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
    - enum {
    + static enum mutx_option {
      	MODE_UNSPECIFIED = 0,
      	MODE_NAME_ONLY,
     +	MODE_OBJECT_ONLY,
      	MODE_LONG,
    - };
    + } cmdmode;
      
    -@@ builtin/ls-tree.c: static int fast_path(void){
    - 	} else if (!strcmp(format, name_only_format)) {
    - 		shown_fields = FIELD_PATH_NAME;
    - 		return 1;
    -+	} else if (!strcmp(format, object_only_format)) {
    -+		shown_fields = FIELD_OBJECT_NAME;
    - 	}
    - 	return 0;
    - }
    -@@ builtin/ls-tree.c: static int parse_shown_fields(void)
    - 		shown_fields = FIELD_PATH_NAME;
    +@@ builtin/ls-tree.c: static int parse_shown_fields(unsigned int *shown_fields)
    + 		*shown_fields = FIELD_PATH_NAME;
      		return 0;
      	}
     -
     +	if (cmdmode == MODE_OBJECT_ONLY) {
    -+		shown_fields = FIELD_OBJECT_NAME;
    ++		*shown_fields = FIELD_OBJECT_NAME;
     +		return 0;
     +	}
      	if (!ls_options || (ls_options & LS_RECURSIVE)
      	    || (ls_options & LS_SHOW_TREES)
      	    || (ls_options & LS_TREE_ONLY))
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    - 	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    - 		return 0;
    + 			return recurse;
    + 	}
      
     +	if (shown_fields == FIELD_OBJECT_NAME) {
     +		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
     +		return recurse;
     +	}
    ++
      	if (shown_fields == FIELD_PATH_NAME) {
      		baselen = base->len;
      		strbuf_addstr(base, pathname);
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		OPT_SET_INT(0, "full-name", &chomp_prefix,
      			    N_("use full path names"), 0),
      		OPT_BOOL(0, "full-tree", &full_tree,
    +@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + 	} else if (format && (!strcmp(format, name_only_format))) {
    + 		shown_fields = FIELD_PATH_NAME;
    + 		fn = show_tree;
    ++	} else if (format && (!strcmp(format, object_only_format))) {
    ++		shown_fields = FIELD_OBJECT_NAME;
    ++		fn = show_tree;
    + 	} else if (format)
    + 		fn = show_tree_fmt;
    + 
    +
    + ## t/t3103-ls-tree-misc.sh ##
    +@@ t/t3103-ls-tree-misc.sh: test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
    + 
    + for opts in \
    + 	"--name-only --long" \
    +-	"--name-status --long"
    ++	"--name-status --long" \
    ++	"--name-only --object-only" \
    ++	"--name-status --object-only" \
    ++	"--object-only --long" \
    ++	"--object-only --format"
    + do
    + 	test_expect_success "usage: incompatible options: $opts" '
    + 		test_expect_code 129 git ls-tree $opts $tree
    +     '
    + done
    +-
    + test_done
     
      ## t/t3104-ls-tree-format.sh ##
     @@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree --format=<name-only-like>' '
    @@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree --format=<name-only-like
     +		"%(objectname)" \
     +		"--object-only"
     +'
    ++
    ++test_expect_success 'ls-tree --format=<object-only-like> --abbrev' '
    ++	test_ls_tree_format \
    ++		"%(objectname)" \
    ++		"--object-only --abbrev" \
    ++		"--abbrev"
    ++'
     +
      test_expect_success 'ls-tree combine --format=<default-like> and -t' '
      	test_ls_tree_format \
    @@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree hit fast-path with --for
     +	test_cmp expect actual
     +'
      test_done
    -
    - ## t/t3105-ls-tree-oid.sh (new) ##
    -@@
    -+#!/bin/sh
    -+
    -+test_description='git ls-tree objects handling.'
    -+
    -+. ./test-lib.sh
    -+
    -+test_expect_success 'setup' '
    -+test_commit A &&
    -+test_commit B &&
    -+mkdir -p C &&
    -+test_commit C/D.txt &&
    -+find *.txt path* \( -type f -o -type l \) -print |
    -+xargs git update-index --add &&
    -+tree=$(git write-tree) &&
    -+echo $tree
    -+'
    -+
    -+test_expect_success 'usage: --object-only' '
    -+git ls-tree --object-only $tree >current &&
    -+git ls-tree $tree >result &&
    -+cut -f1 result | cut -d " " -f3 >expected &&
    -+test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: --object-only with -r' '
    -+git ls-tree --object-only -r $tree >current &&
    -+git ls-tree -r $tree >result &&
    -+cut -f1 result | cut -d " " -f3 >expected &&
    -+test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: --object-only with --abbrev' '
    -+git ls-tree --object-only --abbrev=6 $tree >current &&
    -+git ls-tree --abbrev=6 $tree >result &&
    -+cut -f1 result | cut -d " " -f3 >expected &&
    -+test_cmp current expected
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --name-only with --object-only' '
    -+test_expect_code 129 git ls-tree --object-only --name-only $tree
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --name-status with --object-only' '
    -+test_expect_code 129 git ls-tree --object-only --name-status $tree
    -+'
    -+
    -+test_expect_success 'usage: incompatible options: --long with --object-only' '
    -+test_expect_code 129 git ls-tree --object-only --long $tree
    -+'
    -+
    -+test_done
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 01/12] ls-tree: remove commented-out code

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Remove code added in f35a6d3bce7 (Teach core object handling functions about gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output flushing before fork(), 2008-08-04), and then finally ending up in its current form in d3bee161fef (tree.c: allow read_tree_recursive() to traverse gitlink entries, 2009-01-25). All while being commented-out!

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
Show changes to builtin/ls-tree.c +0 −9
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3a442631c7..5f7c84950c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	const char *type = blob_type;
 
 	if (S_ISGITLINK(mode)) {
-		/*
-		 * Maybe we want to have some recursive version here?
-		 *
-		 * Something similar to this incomplete example:
-		 *
-		if (show_subprojects(base, baselen, pathname))
-			retval = READ_TREE_RECURSIVE;
-		 *
-		 */
 		type = commit_type;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 02/12] ls-tree: add missing braces to "else" arms

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add missing {} to the "else" arms in show_tree() per the CodingGuidelines.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/ls-tree.c +4 −2
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5f7c84950c..0a28f32ccb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				else
 					xsnprintf(size_text, sizeof(size_text),
 						  "%"PRIuMAX, (uintmax_t)size);
-			} else
+			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
+			}
 			printf("%06o %s %s %7s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
-		} else
+		} else {
 			printf("%06o %s %s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev));
+		}
 	}
 	baselen = base->len;
 	strbuf_addstr(base, pathname);
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 03/12] ls-tree: use "enum object_type", not {blob,tree,commit}_type

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Change the ls-tree.c code to use type_name() on the enum instead of using the string constants. This doesn't matter either way for performance, but makes this a bit easier to read as we'll no longer need a strcmp() here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/ls-tree.c +6 −6
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0a28f32ccb..3f0225b097 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int retval = 0;
 	int baselen;
-	const char *type = blob_type;
+	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-		type = commit_type;
+		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
 			retval = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
 				return retval;
 		}
-		type = tree_type;
+		type = OBJ_TREE;
 	}
 	else if (ls_options & LS_TREE_ONLY)
 		return 0;
@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
 			char size_text[24];
-			if (!strcmp(type, blob_type)) {
+			if (type == OBJ_BLOB) {
 				unsigned long size;
 				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
 					xsnprintf(size_text, sizeof(size_text),
@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
 			}
-			printf("%06o %s %s %7s\t", mode, type,
+			printf("%06o %s %s %7s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
 		} else {
-			printf("%06o %s %s\t", mode, type,
+			printf("%06o %s %s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev));
 		}
 	}
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 04/12] ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The "struct strbuf"'s "len" member is a "size_t", not an "int", so let's change our corresponding types accordingly. This also changes the "len" and "speclen" variables, which are likewise used to store the return value of strlen(), which returns "size_t", not "int".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/ls-tree.c +3 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3f0225b097..eecc7482d5 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
-static int show_recursive(const char *base, int baselen, const char *pathname)
+static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
 
@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
 
 	for (i = 0; i < pathspec.nr; i++) {
 		const char *spec = pathspec.items[i].match;
-		int len, speclen;
+		size_t len, speclen;
 
 		if (strncmp(base, spec, baselen))
 			continue;
@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
 	int retval = 0;
-	int baselen;
+	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 05/12] ls-tree: rename "retval" to "recurse" in "show_tree()"

The variable which "show_tree()" return is named "retval", a name that's a little hard to understand. The commit rename "retval" to "recurse" which is a more meaningful name than before in the context. We do not need to take a look at "read_tree_at()" in "tree.c" to make sure what does "retval" mean.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/ls-tree.c +4 −4
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..ef8c414f61 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -64,7 +64,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
-	int retval = 0;
+	int recurse = 0;
 	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
@@ -72,9 +72,9 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-			retval = READ_TREE_RECURSIVE;
+			recurse = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
-				return retval;
+				return recurse;
 		}
 		type = OBJ_TREE;
 	}
@@ -109,7 +109,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				   chomp_prefix ? ls_tree_prefix : NULL,
 				   stdout, line_termination);
 	strbuf_setlen(base, baselen);
-	return retval;
+	return recurse;
 }
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 06/12] ls-tree: simplify nesting if/else logic in "show_tree()"

Use the object_type() function to determine the object type from the "mode" passed to us by read_tree(), instead of doing so with the S_*() macros.

Signed-off-by: Teng Long <dyronetengb@gmail.com>
---
 builtin/ls-tree.c | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)
Show changes to builtin/ls-tree.c +9 −12
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61..d4be71bad2 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,20 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
-		if (show_recursive(base->buf, base->len, pathname)) {
-			recurse = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return recurse;
-		}
-		type = OBJ_TREE;
+	if (type == OBJ_BLOB) {
+		if (ls_options & LS_TREE_ONLY)
+			return 0;
+	} else if (type == OBJ_TREE &&
+		   show_recursive(base->buf, base->len, pathname)) {
+		recurse = READ_TREE_RECURSIVE;
+		if (!(ls_options & LS_SHOW_TREES))
+			return recurse;
 	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 07/12] ls-tree: fix "--name-only" and "--long" combined use bug

If we execute "git ls-tree" with combined "--name-only" and "--long" , only the pathname will be printed, the size is omitted (the original discoverer was Peff in [1]).

This commit fix this issue by using `OPT_CMDMODE()` instead to make both of them mutually exclusive.

[1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c       | 12 ++++++------
 t/t3103-ls-tree-misc.sh |  9 +++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)
Show changes to 2 files +15 −6

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index d4be71bad2..7be4c13228 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -123,12 +123,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_BIT('l', "long", &ls_options, N_("include object size"),
-			LS_SHOW_SIZE),
-		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
-		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
+			    LS_SHOW_SIZE),
+		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d18ba1bd84..d9d7fa932f 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -23,4 +23,13 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 	test_must_fail git ls-tree -r HEAD
 '
 
+for opts in \
+	"--name-only --long" \
+	"--name-status --long"
+do
+	test_expect_success "usage: incompatible options: $opts" '
+		test_expect_code 129 git ls-tree $opts $tree
+    '
+done
+
 test_done
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 08/12] ls-tree: slightly refactor `show_tree()`

This is a non-functional change, we use a new int "shown_fields" to mark which columns to output, and `parse_shown_fields()` to calculate the value of "shown_fields".

This has the advantage of making the show_tree logic simpler and more readable, as well as making it easier to extend new options (for example, if we want to add a "--object-only" option, we just need to add a similar "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in "show_tree()").

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 124 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 87 insertions(+), 37 deletions(-)
Show changes to builtin/ls-tree.c +87 −37
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 7be4c13228..c70e31ac3d 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,21 +16,51 @@
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
-#define LS_TREE_ONLY 2
-#define LS_SHOW_TREES 4
-#define LS_NAME_ONLY 8
-#define LS_SHOW_SIZE 16
+#define LS_TREE_ONLY (1 << 1)
+#define LS_SHOW_TREES (1 << 2)
+#define LS_NAME_ONLY (1 << 3)
+#define LS_SHOW_SIZE (1 << 4)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
+static unsigned int shown_fields;
+#define FIELD_PATH_NAME 1
+#define FIELD_SIZE (1 << 1)
+#define FIELD_OBJECT_NAME (1 << 2)
+#define FIELD_TYPE (1 << 3)
+#define FIELD_MODE (1 << 4)
+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
 };
 
+static enum mutx_option {
+	MODE_UNSPECIFIED = 0,
+	MODE_NAME_ONLY,
+	MODE_LONG,
+} cmdmode;
+
+static int parse_shown_fields(void)
+{
+	if (cmdmode == MODE_NAME_ONLY) {
+		shown_fields = FIELD_PATH_NAME;
+		return 0;
+	}
+
+	if (!ls_options || (ls_options & LS_RECURSIVE)
+	    || (ls_options & LS_SHOW_TREES)
+	    || (ls_options & LS_TREE_ONLY))
+		shown_fields = FIELD_DEFAULT;
+	if (cmdmode == MODE_LONG)
+		shown_fields = FIELD_LONG_DEFAULT;
+	return 1;
+}
+
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -61,6 +91,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_default(const struct object_id *oid, enum object_type type,
+			const char *pathname, unsigned mode,
+			struct strbuf *base)
+{
+	size_t baselen = base->len;
+
+	if (shown_fields & FIELD_SIZE) {
+		char size_text[24];
+		if (type == OBJ_BLOB) {
+			unsigned long size;
+			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+				xsnprintf(size_text, sizeof(size_text), "BAD");
+			else
+				xsnprintf(size_text, sizeof(size_text),
+					  "%" PRIuMAX, (uintmax_t)size);
+		} else {
+			xsnprintf(size_text, sizeof(size_text), "-");
+		}
+		printf("%06o %s %s %7s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev), size_text);
+	} else {
+		printf("%06o %s %s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev));
+	}
+	baselen = base->len;
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+	return 1;
+}
+
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
@@ -78,34 +141,19 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
-	if (!(ls_options & LS_NAME_ONLY)) {
-		if (ls_options & LS_SHOW_SIZE) {
-			char size_text[24];
-			if (type == OBJ_BLOB) {
-				unsigned long size;
-				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
-					xsnprintf(size_text, sizeof(size_text),
-						  "BAD");
-				else
-					xsnprintf(size_text, sizeof(size_text),
-						  "%"PRIuMAX, (uintmax_t)size);
-			} else {
-				xsnprintf(size_text, sizeof(size_text), "-");
-			}
-			printf("%06o %s %s %7s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev),
-			       size_text);
-		} else {
-			printf("%06o %s %s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev));
-		}
+	if (shown_fields == FIELD_PATH_NAME) {
+		baselen = base->len;
+		strbuf_addstr(base, pathname);
+		write_name_quoted_relative(base->buf,
+					   chomp_prefix ? ls_tree_prefix : NULL,
+					   stdout, line_termination);
+		strbuf_setlen(base, baselen);
+		return recurse;
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL,
-				   stdout, line_termination);
-	strbuf_setlen(base, baselen);
+
+	if (shown_fields >= FIELD_DEFAULT)
+		show_default(oid, type, pathname, mode, base);
+
 	return recurse;
 }
 
@@ -123,12 +171,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
-			    LS_SHOW_SIZE),
-		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
-		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"),
+			    MODE_LONG),
+		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
@@ -159,6 +207,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
+	parse_shown_fields();
+
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 09/12] ls-tree: introduce struct "show_tree_data"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

"show_tree_data" is a struct that packages the necessary fields for "show_tree()". This commit is a pre-prepared commit for supporting "--format" option and it does not affect any existing functionality.

At the same time, we also move global "shown_fields" into "show_tree_data" , the purpose of this is to avoid use both global variable and pass-around struct.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 69 +++++++++++++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 26 deletions(-)
Show changes to builtin/ls-tree.c +43 −26
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index c70e31ac3d..04aefc6106 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -25,7 +25,6 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
-static unsigned int shown_fields;
 #define FIELD_PATH_NAME 1
 #define FIELD_SIZE (1 << 1)
 #define FIELD_OBJECT_NAME (1 << 2)
@@ -34,6 +33,15 @@ static unsigned int shown_fields;
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
+struct show_tree_data {
+	unsigned mode;
+	enum object_type type;
+	const struct object_id *oid;
+	const char *pathname;
+	struct strbuf *base;
+	unsigned int shown_fields;
+};
+
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
@@ -45,19 +53,19 @@ static enum mutx_option {
 	MODE_LONG,
 } cmdmode;
 
-static int parse_shown_fields(void)
+static int parse_shown_fields(unsigned int *shown_fields)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
-		shown_fields = FIELD_PATH_NAME;
+		*shown_fields = FIELD_PATH_NAME;
 		return 0;
 	}
 
 	if (!ls_options || (ls_options & LS_RECURSIVE)
 	    || (ls_options & LS_SHOW_TREES)
 	    || (ls_options & LS_TREE_ONLY))
-		shown_fields = FIELD_DEFAULT;
+		*shown_fields = FIELD_DEFAULT;
 	if (cmdmode == MODE_LONG)
-		shown_fields = FIELD_LONG_DEFAULT;
+		*shown_fields = FIELD_LONG_DEFAULT;
 	return 1;
 }
 
@@ -91,17 +99,15 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
-static int show_default(const struct object_id *oid, enum object_type type,
-			const char *pathname, unsigned mode,
-			struct strbuf *base)
+static int show_default(struct show_tree_data *data)
 {
-	size_t baselen = base->len;
+	size_t baselen = data->base->len;
 
-	if (shown_fields & FIELD_SIZE) {
+	if (data->shown_fields & FIELD_SIZE) {
 		char size_text[24];
-		if (type == OBJ_BLOB) {
+		if (data->type == OBJ_BLOB) {
 			unsigned long size;
-			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
 				xsnprintf(size_text, sizeof(size_text), "BAD");
 			else
 				xsnprintf(size_text, sizeof(size_text),
@@ -109,18 +115,18 @@ static int show_default(const struct object_id *oid, enum object_type type,
 		} else {
 			xsnprintf(size_text, sizeof(size_text), "-");
 		}
-		printf("%06o %s %s %7s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev), size_text);
+		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev), size_text);
 	} else {
-		printf("%06o %s %s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev));
+		printf("%06o %s %s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev));
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
+	baselen = data->base->len;
+	strbuf_addstr(data->base, data->pathname);
+	write_name_quoted_relative(data->base->buf,
 				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
 				   line_termination);
-	strbuf_setlen(base, baselen);
+	strbuf_setlen(data->base, baselen);
 	return 1;
 }
 
@@ -130,6 +136,15 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
+	unsigned int shown_fields = *(unsigned int *)context;
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+		.shown_fields = shown_fields,
+	};
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
@@ -151,8 +166,8 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		return recurse;
 	}
 
-	if (shown_fields >= FIELD_DEFAULT)
-		show_default(oid, type, pathname, mode, base);
+	if (shown_fields>= FIELD_DEFAULT)
+		show_default(&data);
 
 	return recurse;
 }
@@ -162,6 +177,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct object_id oid;
 	struct tree *tree;
 	int i, full_tree = 0;
+	unsigned int shown_fields = 0;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -207,8 +223,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
-	parse_shown_fields();
-
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
@@ -225,6 +239,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	tree = parse_tree_indirect(&oid);
 	if (!tree)
 		die("not a tree object");
-	return !!read_tree(the_repository, tree,
-			   &pathspec, show_tree, NULL);
+
+	parse_shown_fields(&shown_fields);
+
+	return !!read_tree(the_repository, tree, &pathspec, show_tree,
+			   &shown_fields);
 }
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 10/12] cocci: allow padding with `strbuf_addf()`

From: Johannes Schindelin <johannes.schindelin@gmx.de>

A convenient way to pad strings is to use something like `strbuf_addf(&buf, "%20s", "Hello, world!")`.

However, the Coccinelle rule that forbids a format `"%s"` with a constant string argument cast too wide a net, and also forbade such padding.

The original rule was introduced by commit:
    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to contrib/coccinelle/strbuf.cocci +3 −3
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
index d9ada69b43..0970d98ad7 100644
--- a/contrib/coccinelle/strbuf.cocci
+++ b/contrib/coccinelle/strbuf.cocci
@@ -15,7 +15,7 @@ constant fmt !~ "%";
 @@
 expression E;
 struct strbuf SB;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SB.buf);
 + strbuf_addbuf(E, &SB);
@@ -23,7 +23,7 @@ format F =~ "s";
 @@
 expression E;
 struct strbuf *SBP;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SBP->buf);
 + strbuf_addbuf(E, SBP);
@@ -44,7 +44,7 @@ struct strbuf *SBP;
 
 @@
 expression E1, E2;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E1, "%@F@", E2);
 + strbuf_addstr(E1, E2);
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 11/12] ls-tree: introduce "--format" option

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add a --format option to ls-tree. It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths.

Rather than add --type-only, --object-only etc. we can just support a --format using a strbuf_expand() similar to "for-each-ref --format". We might still add such options in the future for convenience.

The --format implementation is slower than the existing code, but this change does not cause any performance regressions. We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided.

I.e. something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout.

The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community [1].

Here is the statistics about performance tests:
1. Default format (hitten the builtin formats):
    "git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]
    Range (min … max):    99.2 ms … 113.2 ms    28 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD
    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]
    Range (min … max):   100.2 ms … 110.5 ms    29 runs
2. Default format includes object size (hitten the builtin formats):
    "git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]
    Range (min … max):   327.5 ms … 348.4 ms    10 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD
    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]
    Range (min … max):   328.8 ms … 349.4 ms    10 runs
Links:
	[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |  59 ++++++++++++++--
 builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-
 t/t3104-ls-tree-format.sh     |  81 +++++++++++++++++++++
 3 files changed, 262 insertions(+), 7 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Show changes to 3 files +262 −7

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..db29a9efb5 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -74,6 +74,16 @@ OPTIONS
 	Do not limit the listing to the current working directory.
 	Implies --full-name.
 
+--format=<format>::
+	A string that interpolates `%(fieldname)` from the result
+	being shown. It also interpolates `%%` to `%`, and
+	`%xx` where `xx` are hex digits interpolates to character
+	with hex code `xx`; for example `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+	When specified, `--format` cannot be combined with other
+	format-altering options, including `--long`, `--name-only`
+	and `--object-only`.
+
 [<path>...]::
 	When paths are given, show them (note that this isn't really raw
 	pathnames, but rather a list of patterns to match).  Otherwise
@@ -82,16 +92,29 @@ OPTIONS
 
 Output Format
 -------------
-        <mode> SP <type> SP <object> TAB <file>
+
+The output format of `ls-tree` is determined by either the `--format`
+option, or other format-altering options such as `--name-only` etc.
+(see `--format` above).
+
+The use of certain `--format` directives is equivalent to using those
+options, but invoking the full formatting machinery can be slower than
+using an appropriate formatting option.
+
+In cases where the `--format` would exactly map to an existing option
+`ls-tree` will use the appropriate faster path. Thus the default format
+is equivalent to:
+
+        %(objectmode) %(objecttype) %(objectname)%x09%(path)
 
 This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
-        <mode> SP <type> SP <object> SP <object size> TAB <file>
+        %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
 
-Object size identified by <object> is given in bytes, and right-justified
+Object size identified by <objectname> is given in bytes, and right-justified
 with minimum width of 7 characters.  Object size is given only for blobs
 (file) entries; for other entries `-` character is used in place of size.
 
@@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]).  Using `-z` the filename is output
 verbatim and the line is terminated by a NUL byte.
 
+Customized format:
+
+It is possible to print in a custom format by using the `--format` option,
+which is able to interpolate different fields using a `%(fieldname)` notation.
+For example, if you only care about the "objectname" and "path" fields, you
+can execute with a specific "--format" like
+
+        git ls-tree --format='%(objectname) %(path)' <tree-ish>
+
+FIELD NAMES
+-----------
+
+Various values from structured fields can be used to interpolate
+into the resulting output. For each outputing line, the following
+names can be used:
+
+objectmode::
+	The mode of the object.
+objecttype::
+	The type of the object (`blob` or `tree`).
+objectname::
+	The name of the object.
+objectsize[:padded]::
+	The size of the object ("-" if it's a tree).
+	It also supports a padded format of size with "%(size:padded)".
+path::
+	The pathname of the object.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 04aefc6106..a46fa73bb3 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -32,7 +32,10 @@ static const char *ls_tree_prefix;
 #define FIELD_MODE (1 << 4)
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
-
+static const char *format;
+static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
+static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
+static const char *name_only_format = "%(path)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -53,6 +56,72 @@ static enum mutx_option {
 	MODE_LONG,
 } cmdmode;
 
+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
+			      const enum object_type type, unsigned int padded)
+{
+	if (type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, oid, &size) < 0)
+			die(_("could not get object info about '%s'"),
+			    oid_to_hex(oid));
+		if (padded)
+			strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size);
+		else
+			strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size);
+	} else if (padded) {
+		strbuf_addf(line, "%7s", "-");
+	} else {
+		strbuf_addstr(line, "-");
+	}
+}
+
+static size_t expand_show_tree(struct strbuf *sb, const char *start,
+			       void *context)
+{
+	struct show_tree_data *data = context;
+	const char *end;
+	const char *p;
+	unsigned int errlen;
+	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+
+	if (len)
+		return len;
+	if (*start != '(')
+		die(_("bad ls-tree format: element '%s' does not start with '('"), start);
+
+	end = strchr(start + 1, ')');
+	if (!end)
+		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
+
+	len = end - start + 1;
+	if (skip_prefix(start, "(objectmode)", &p)) {
+		strbuf_addf(sb, "%06o", data->mode);
+	} else if (skip_prefix(start, "(objecttype)", &p)) {
+		strbuf_addstr(sb, type_name(data->type));
+	} else if (skip_prefix(start, "(objectsize:padded)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 1);
+	} else if (skip_prefix(start, "(objectsize)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 0);
+	} else if (skip_prefix(start, "(objectname)", &p)) {
+		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
+	} else if (skip_prefix(start, "(path)", &p)) {
+		const char *name = data->base->buf;
+		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
+		struct strbuf quoted = STRBUF_INIT;
+		struct strbuf sbuf = STRBUF_INIT;
+		strbuf_addstr(data->base, data->pathname);
+		name = relative_path(data->base->buf, prefix, &sbuf);
+		quote_c_style(name, &quoted, NULL, 0);
+		strbuf_addbuf(sb, &quoted);
+		strbuf_release(&sbuf);
+		strbuf_release(&quoted);
+	} else {
+		errlen = (unsigned long)len;
+		die(_("bad ls-tree format: %%%.*s"), errlen, start);
+	}
+	return len;
+}
+
 static int parse_shown_fields(unsigned int *shown_fields)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
@@ -99,6 +168,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
+			 const char *pathname, unsigned mode, void *context)
+{
+	size_t baselen;
+	int recurse = 0;
+	struct strbuf sb = STRBUF_INIT;
+	enum object_type type = object_type(mode);
+
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
+
+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+		recurse = READ_TREE_RECURSIVE;
+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+		return recurse;
+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
+		return 0;
+
+	baselen = base->len;
+	strbuf_expand(&sb, format, expand_show_tree, &data);
+	strbuf_addch(&sb, line_termination);
+	fwrite(sb.buf, sb.len, 1, stdout);
+	strbuf_release(&sb);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
 static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
@@ -178,6 +279,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct tree *tree;
 	int i, full_tree = 0;
 	unsigned int shown_fields = 0;
+	read_tree_fn_t fn = show_tree;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -198,6 +300,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "full-tree", &full_tree,
 			 N_("list entire tree; not just current directory "
 			    "(implies --full-name)")),
+		OPT_STRING_F(0, "format", &format, N_("format"),
+					 N_("format to use for the output"),
+					 PARSE_OPT_NONEG),
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
@@ -218,6 +323,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
 		ls_options |= LS_SHOW_TREES;
 
+	if (format && cmdmode)
+		usage_msg_opt(
+			_("--format can't be combined with other format-altering options"),
+			ls_tree_usage, ls_tree_options);
 	if (argc < 1)
 		usage_with_options(ls_tree_usage, ls_tree_options);
 	if (get_oid(argv[0], &oid))
@@ -242,6 +351,20 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 
 	parse_shown_fields(&shown_fields);
 
-	return !!read_tree(the_repository, tree, &pathspec, show_tree,
-			   &shown_fields);
+	/*
+	 * The generic show_tree_fmt() is slower than show_tree(), so
+	 * take the fast path if possible.
+	 */
+	if (format && (!strcmp(format, default_format))) {
+		fn = show_tree;
+	} else if (format && (!strcmp(format, long_format))) {
+		shown_fields = shown_fields | FIELD_SIZE;
+		fn = show_tree;
+	} else if (format && (!strcmp(format, name_only_format))) {
+		shown_fields = FIELD_PATH_NAME;
+		fn = show_tree;
+	} else if (format)
+		fn = show_tree_fmt;
+
+	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
 }
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
new file mode 100755
index 0000000000..e08c83dc47
--- /dev/null
+++ b/t/t3104-ls-tree-format.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='ls-tree --format'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	fmtopts=$3 &&
+	shift 2 &&
+	git ls-tree $opts -r HEAD >expect.raw &&
+	sed "s/^/> /" >expect <expect.raw &&
+	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'ls-tree --format=<default-like>' '
+	test_ls_tree_format \
+		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+		""
+'
+
+test_expect_success 'ls-tree --format=<long-like>' '
+	test_ls_tree_format \
+		"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+		"--long"
+'
+
+test_expect_success 'ls-tree --format=<name-only-like>' '
+	test_ls_tree_format \
+		"%(path)" \
+		"--name-only"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and -t' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"-t" \
+	"-t"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-name" \
+	"--full-name"
+'
+
+test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
+	test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-tree" \
+	"--full-tree"
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
+	git ls-tree -r HEAD >expect &&
+	git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
+	git ls-tree -r --name-only HEAD >expect &&
+	git ls-tree --format="%(path)" -r HEAD >actual &&
+	test_cmp expect actual
+'
+test_done
-- 
2.34.1.402.gef0f5bd184
Teng Long· Mar 4, 2022, 10:42 UTC · re: Teng Long · lore

[PATCH v12 12/12] ls-tree: support --object-only option for "git-ls-tree"

'--object-only' is an alias for '--format=%(objectname)'. It cannot be used together other format-altering options like '--name-only', '--long' or '--format', they are mutually exclusive.

The "--name-only" option outputs <filepath> only. Likewise, <objectName> is another high frequency used field, so implement '--object-only' option will bring intuitive and clear semantics for this scenario. Using '--format=%(objectname)' we can achieve a similar effect, but the former is with a lower learning cost(without knowing the format requirement of '--format' option).

Even so, if a user is prefer to use "--format=%(objectname)", this is entirely welcome because they are not only equivalent in function, but also have almost identical performance. The reason is this commit also add the specific of "--format=%(objectname)" to the current fast-pathes (builtin formats) to avoid running unnecessary parsing mechanisms.

The following performance benchmarks are based on torvalds/linux.git:
  When hit the fast-path:
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --object-only HEAD
        Time (mean ± σ):      83.6 ms ±   2.0 ms    [User: 59.4 ms, System: 24.1 ms]
        Range (min … max):    80.4 ms …  87.2 ms    35 runs
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(objectname)' HEAD
        Time (mean ± σ):      84.1 ms ±   1.8 ms    [User: 61.7 ms, System: 22.3 ms]
        Range (min … max):    80.9 ms …  87.5 ms    35 runs
  But for a customized format, it will be slower:
       Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='oid: %(objectname)' HEAD
         Time (mean ± σ):      96.5 ms ±   2.5 ms    [User: 72.9 ms, System: 23.5 ms]
  	 Range (min … max):    93.1 ms … 104.1 ms    31 runs
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt | 11 ++++++++++-
 builtin/ls-tree.c             | 18 +++++++++++++++++-
 t/t3103-ls-tree-misc.sh       |  7 +++++--
 t/t3104-ls-tree-format.sh     | 19 +++++++++++++++++++
 4 files changed, 51 insertions(+), 4 deletions(-)
Show changes to 4 files +51 −4

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3103-ls-tree-misc.sh, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db29a9efb5..1aea998e72 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
+	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -59,6 +59,15 @@ OPTIONS
 --name-only::
 --name-status::
 	List only filenames (instead of the "long" output), one per line.
+	Cannot be combined with `--object-only`.
+
+--object-only::
+	List only names of the objects, one per line. Cannot be combined
+	with `--name-only` or `--name-status`.
+	This is equivalent to specifying `--format='%(objectname)'`, but
+	for both this option and that exact format the command takes a
+	hand-optimized codepath instead of going through the generic
+	formatting mechanism.
 
 --abbrev[=<n>]::
 	Instead of showing the full 40-byte hexadecimal object
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index a46fa73bb3..7da21d9963 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -20,6 +20,7 @@ static int line_termination = '\n';
 #define LS_SHOW_TREES (1 << 2)
 #define LS_NAME_ONLY (1 << 3)
 #define LS_SHOW_SIZE (1 << 4)
+#define LS_OBJECT_ONLY (1 << 5)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
@@ -36,6 +37,7 @@ static const char *format;
 static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
 static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
 static const char *name_only_format = "%(path)";
+static const char *object_only_format = "%(objectname)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -53,6 +55,7 @@ static const  char * const ls_tree_usage[] = {
 static enum mutx_option {
 	MODE_UNSPECIFIED = 0,
 	MODE_NAME_ONLY,
+	MODE_OBJECT_ONLY,
 	MODE_LONG,
 } cmdmode;
 
@@ -128,7 +131,10 @@ static int parse_shown_fields(unsigned int *shown_fields)
 		*shown_fields = FIELD_PATH_NAME;
 		return 0;
 	}
-
+	if (cmdmode == MODE_OBJECT_ONLY) {
+		*shown_fields = FIELD_OBJECT_NAME;
+		return 0;
+	}
 	if (!ls_options || (ls_options & LS_RECURSIVE)
 	    || (ls_options & LS_SHOW_TREES)
 	    || (ls_options & LS_TREE_ONLY))
@@ -257,6 +263,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
+	if (shown_fields == FIELD_OBJECT_NAME) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		return recurse;
+	}
+
 	if (shown_fields == FIELD_PATH_NAME) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
@@ -295,6 +306,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
+			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
@@ -363,6 +376,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	} else if (format && (!strcmp(format, name_only_format))) {
 		shown_fields = FIELD_PATH_NAME;
 		fn = show_tree;
+	} else if (format && (!strcmp(format, object_only_format))) {
+		shown_fields = FIELD_OBJECT_NAME;
+		fn = show_tree;
 	} else if (format)
 		fn = show_tree_fmt;
 
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d9d7fa932f..d979c0df5d 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -25,11 +25,14 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 
 for opts in \
 	"--name-only --long" \
-	"--name-status --long"
+	"--name-status --long" \
+	"--name-only --object-only" \
+	"--name-status --object-only" \
+	"--object-only --long" \
+	"--object-only --format"
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
     '
 done
-
 test_done
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index e08c83dc47..b86941c202 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -46,6 +46,19 @@ test_expect_success 'ls-tree --format=<name-only-like>' '
 		"--name-only"
 '
 
+test_expect_success 'ls-tree --format=<object-only-like>' '
+	test_ls_tree_format \
+		"%(objectname)" \
+		"--object-only"
+'
+
+test_expect_success 'ls-tree --format=<object-only-like> --abbrev' '
+	test_ls_tree_format \
+		"%(objectname)" \
+		"--object-only --abbrev" \
+		"--abbrev"
+'
+
 test_expect_success 'ls-tree combine --format=<default-like> and -t' '
 	test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
@@ -78,4 +91,10 @@ test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
 	git ls-tree --format="%(path)" -r HEAD >actual &&
 	test_cmp expect actual
 '
+
+test_expect_success 'ls-tree hit fast-path with --format=<object-only-like>' '
+	git ls-tree -r --object-only HEAD >expect &&
+	git ls-tree --format="%(objectname)" -r HEAD >actual &&
+	test_cmp expect actual
+'
 test_done
-- 
2.34.1.402.gef0f5bd184
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:56 UTC · re: Teng Long · lore

[RFC/REVIEW 0/7] fixups/suggestions/musings for tl/ls-tree-oid-only

Re https://lore.kernel.org/git/20220308080551.18538-1-dyroneteng@gmail.com/ sorry about the delay in reviewing this. I did look it over & come up with the below almost a week ago, but forgot/didn't have time to turn it into inline comments.

I don't think all of these need to be squashed or fixed up into the proposed series, but are just various small issues/questions I came up with while reviewing it. Brief notes below:

Ævar Arnfjörð Bjarmason (7):
  ls-tree tests: add tests for --name-status

Optional, but maybe a good idea to add to the series. I noticed the zero --name-status test coverage when testing it.

  ls-tree tests: exhaustively test fast & slow path for --format

Probably a good idea to add to it. I.e. the tests for the --format drifted from the RFC version I had so that we weren't testing the optimized v.s. non-optimized path.

  ls-tree: remove dead labels
Removes dead code in the proposed series (from an earlier iteration?)
  ls-tree: remove unused "MODE_UNSPECIFIED"
The MODE_UNSPECIFIED is also dead code in this series, but...
  ls-tree: detect and error on --name-only --name-status

We should die on --name-status --name-only (two option synonyms), but don't on "master", so. This does make some subsequent code here slightly simpler though...

  ls-tree: remove FIELD_*, just use MODE_*

The main outstanding thing for me when trying to understand the code in this series is why we ended up with FIELD_* defines *and* the MODE_*. This moves to just using MODE_*. I think it makes things easier to understand. I.e. it's one less thing to pass around, and what was in parse_shown_fields() is just embedded in show_tree() now without the indirection.

There's also a change there to add an array of format <-> fast path fn mappings, instead of the current if/else if chain with harcdoded strcmp().

  ls-tree: split up "fast path" callbacks

Maybe a good idea, maybe not. Splits up all the "fast path" callbacks into individual functions, instead of a monolithic show_default().

I think the resulting code is easier to read, since it's clear what data we need to set up for what, but maybe we're off into the weeds here...

 builtin/ls-tree.c          | 279 ++++++++++++++++++++++---------------
 t/t3101-ls-tree-dirname.sh |  55 ++++----
 t/t3103-ls-tree-misc.sh    |  15 +-
 t/t3104-ls-tree-format.sh  |  89 +++++-------
 4 files changed, 235 insertions(+), 203 deletions(-)
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 1/7] ls-tree tests: add tests for --name-status

The --name-status synonym for --name-only added in c639a5548a5 (ls-tree: --name-only, 2005-12-01) had no tests, let's make sure it works the same way as its sibling.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t3101-ls-tree-dirname.sh | 55 ++++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 26 deletions(-)
Show changes to t/t3101-ls-tree-dirname.sh +29 −26
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 05fde642259..217006d1bfb 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -201,31 +201,34 @@ EOF
 	test_cmp expected check
 '
 
-test_expect_success 'ls-tree --name-only' '
-	git ls-tree --name-only $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0
-path1
-path2
-path3
-EOF
-	test_output
-'
-
-test_expect_success 'ls-tree --name-only -r' '
-	git ls-tree --name-only -r $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0/a/b/c/1.txt
-path1/b/c/1.txt
-path2/1.txt
-path3/1.txt
-path3/2.txt
-EOF
-	test_output
-'
+for opt in --name-only --name-status
+do
+	test_expect_success "ls-tree $opt" '
+		git ls-tree $opt $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0
+		path1
+		path2
+		path3
+		EOF
+		test_output
+	'
+
+	test_expect_success "ls-tree $opt -r" '
+		git ls-tree $opt -r $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0/a/b/c/1.txt
+		path1/b/c/1.txt
+		path2/1.txt
+		path3/1.txt
+		path3/2.txt
+		EOF
+		test_output
+	'
+done
 
 test_done
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 2/7] ls-tree tests: exhaustively test fast & slow path for --format

Change these tests adapted from my initial version in [1] to exhaustively test both the fast and slow paths of --format.

In [1] there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to ensure that we had test coverage for passing tests that would otherwise use show_tree() through show_tree_fmt(), and thus that the formatting mechanism could handle all the same cases as the non-formatting options.

Somewhere in subsequent re-rolls of that we seem to have drifted away from what the goal of these tests should be. We're trying to ensure correctness of show_tree_fmt(). We can't tell if we "hit [the] fast-path" here, and instead of having an explicit test for that, we can just add it to something our "test_ls_tree_format" tests for.

1. https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
2. https://lore.kernel.org/git/cb717d08be87e3239117c6c667cb32caabaad33d.1646390152.git.dyroneteng@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 t/t3104-ls-tree-format.sh | 89 +++++++++++++++------------------------
 1 file changed, 33 insertions(+), 56 deletions(-)
Show changes to t/t3104-ls-tree-format.sh +33 −56
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index b86941c2024..12105115eaf 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -22,79 +22,56 @@ test_ls_tree_format () {
 	opts=$2 &&
 	fmtopts=$3 &&
 	shift 2 &&
-	git ls-tree $opts -r HEAD >expect.raw &&
-	sed "s/^/> /" >expect <expect.raw &&
-	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
-	test_cmp expect actual
+
+	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
+		git ls-tree $opts -r HEAD >expect &&
+		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success "ls-tree '--format=<$format>' on optimized v.s. non-optimized path" '
+		git ls-tree --format="$format" -r $fmtopts HEAD >expect &&
+		git ls-tree --format="> $format" -r $fmtopts HEAD >actual.raw &&
+		sed "s/^> //" >actual <actual.raw &&
+		test_cmp expect actual
+	'
 }
 
-test_expect_success 'ls-tree --format=<default-like>' '
-	test_ls_tree_format \
-		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
-		""
-'
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	""
 
-test_expect_success 'ls-tree --format=<long-like>' '
-	test_ls_tree_format \
-		"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
-		"--long"
-'
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+	"--long"
 
-test_expect_success 'ls-tree --format=<name-only-like>' '
-	test_ls_tree_format \
-		"%(path)" \
-		"--name-only"
-'
+test_ls_tree_format \
+	"%(path)" \
+	"--name-only"
 
-test_expect_success 'ls-tree --format=<object-only-like>' '
-	test_ls_tree_format \
-		"%(objectname)" \
-		"--object-only"
-'
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only"
+
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only --abbrev" \
+	"--abbrev"
 
-test_expect_success 'ls-tree --format=<object-only-like> --abbrev' '
-	test_ls_tree_format \
-		"%(objectname)" \
-		"--object-only --abbrev" \
-		"--abbrev"
-'
 
-test_expect_success 'ls-tree combine --format=<default-like> and -t' '
-	test_ls_tree_format \
+test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"-t" \
 	"-t"
-'
 
-test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
-	test_ls_tree_format \
+test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"--full-name" \
 	"--full-name"
-'
 
-test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
-	test_ls_tree_format \
+test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"--full-tree" \
 	"--full-tree"
-'
-
-test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
-	git ls-tree -r HEAD >expect &&
-	git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
-	test_cmp expect actual
-'
-
-test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
-	git ls-tree -r --name-only HEAD >expect &&
-	git ls-tree --format="%(path)" -r HEAD >actual &&
-	test_cmp expect actual
-'
 
-test_expect_success 'ls-tree hit fast-path with --format=<object-only-like>' '
-	git ls-tree -r --object-only HEAD >expect &&
-	git ls-tree --format="%(objectname)" -r HEAD >actual &&
-	test_cmp expect actual
-'
 test_done
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:57 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 5/7] ls-tree: detect and error on --name-only --name-status

The --name-only and --name-status options are synonyms, but let's detect and error if both are provided.

In addition let's add explicit --format tests for the combination of these various options.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c       | 11 ++++++++++-
 t/t3103-ls-tree-misc.sh | 15 +++++++++------
 2 files changed, 19 insertions(+), 7 deletions(-)
Show changes to 2 files +19 −7

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 95816709d38..528b4e5b5f6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -52,6 +52,7 @@ static const  char * const ls_tree_usage[] = {
 static enum ls_tree_cmdmode {
 	MODE_LONG = 1,
 	MODE_NAME_ONLY,
+	MODE_NAME_STATUS,
 	MODE_OBJECT_ONLY,
 } cmdmode;
 
@@ -301,7 +302,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
-			    MODE_NAME_ONLY),
+			    MODE_NAME_STATUS),
 		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
 			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
@@ -327,6 +328,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		ls_tree_prefix = prefix = NULL;
 		chomp_prefix = 0;
 	}
+	/*
+	 * We wanted to detect conflicts between --name-only and
+	 * --name-status, but once we're done with that subsequent
+	 * code should only need to check the primary name.
+	 */
+	if (cmdmode == MODE_NAME_STATUS)
+		cmdmode = MODE_NAME_ONLY;
+
 	/* -d -r should imply -t, but -d by itself should not have to. */
 	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index ce39158338a..81c63439623 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -24,15 +24,18 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 '
 
 for opts in \
-	"--name-only --long" \
-	"--name-status --long" \
-	"--name-only --object-only" \
+	"--long --name-only" \
+	"--name-only --name-status" \
 	"--name-status --object-only" \
-	"--object-only --long" \
-	" --object-only --format"
+	"--object-only --long"
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
-    '
+	'
+
+	one_opt=$(echo "$opts" | cut -d' '  -f1)
+	test_expect_success "usage: incompatible options: $one_opt and --format" '
+		test_expect_code 129 git ls-tree $one_opt --format=fmt $tree
+	'
 done
 test_done
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:57 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 4/7] ls-tree: remove unused "MODE_UNSPECIFIED"

Remove the "MODE_UNSPECIFIED" added in a preceding commit. We don't need it, instead let's start the "command mode" enum at 1, as e.g. builtin/help.c does for its similar pattern.

In addition let's change this from "mutx_option" to a more obvious "ls-tree_cmdmode", even if the type name isn't used anywhere else. The previous name sounded as though it had something to do with a (thread?) mutex.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
Show changes to builtin/ls-tree.c +2 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index f04733a9be1..95816709d38 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -49,11 +49,10 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
-static enum mutx_option {
-	MODE_UNSPECIFIED = 0,
+static enum ls_tree_cmdmode {
+	MODE_LONG = 1,
 	MODE_NAME_ONLY,
 	MODE_OBJECT_ONLY,
-	MODE_LONG,
 } cmdmode;
 
 static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 3/7] ls-tree: remove dead labels

These have not been used since a preceding "ls-tree: slightly refactor `show_tree()`" commit, and should be squashed into that.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 3 ---
 1 file changed, 3 deletions(-)
Show changes to builtin/ls-tree.c +0 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 23ec2f0fe76..f04733a9be1 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -18,9 +18,6 @@ static int line_termination = '\n';
 #define LS_RECURSIVE 1
 #define LS_TREE_ONLY (1 << 1)
 #define LS_SHOW_TREES (1 << 2)
-#define LS_NAME_ONLY (1 << 3)
-#define LS_SHOW_SIZE (1 << 4)
-#define LS_OBJECT_ONLY (1 << 5)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:57 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 6/7] ls-tree: remove FIELD_*, just use MODE_*

When we're picking where we should go in the optimized "show_tree" path there's no reason for why we need to convert our "cmdmode" of e.g. MODE_LONG into a FIELD_LONG_DEFAULT. Instead we can simply do those checks in the show_tree() function itself.

Let's also make this code more future-proof by unrolling the hardcoded strmp() if/else if chain into something that checks a new "static struct" providing a bidirectional mapping between optimized formats and the ls_tree_cmdmode.

A preceding commit removed "MODE_UNSPECIFIED", now there is a use for such a thing, as we'll need to add this to our mapping so we'll choose the "show_tree" fast-path here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 98 ++++++++++++++++++++++-------------------------
 1 file changed, 46 insertions(+), 52 deletions(-)
Show changes to builtin/ls-tree.c +46 −52
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 528b4e5b5f6..820baddf9a9 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -23,25 +23,13 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
-#define FIELD_PATH_NAME 1
-#define FIELD_SIZE (1 << 1)
-#define FIELD_OBJECT_NAME (1 << 2)
-#define FIELD_TYPE (1 << 3)
-#define FIELD_MODE (1 << 4)
-#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
-#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 static const char *format;
-static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
-static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
-static const char *name_only_format = "%(path)";
-static const char *object_only_format = "%(objectname)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
 	const struct object_id *oid;
 	const char *pathname;
 	struct strbuf *base;
-	unsigned int shown_fields;
 };
 
 static const  char * const ls_tree_usage[] = {
@@ -50,7 +38,8 @@ static const  char * const ls_tree_usage[] = {
 };
 
 static enum ls_tree_cmdmode {
-	MODE_LONG = 1,
+	MODE_DEFAULT = 0,
+	MODE_LONG,
 	MODE_NAME_ONLY,
 	MODE_NAME_STATUS,
 	MODE_OBJECT_ONLY,
@@ -122,25 +111,6 @@ static size_t expand_show_tree(struct strbuf *sb, const char *start,
 	return len;
 }
 
-static int parse_shown_fields(unsigned int *shown_fields)
-{
-	if (cmdmode == MODE_NAME_ONLY) {
-		*shown_fields = FIELD_PATH_NAME;
-		return 0;
-	}
-	if (cmdmode == MODE_OBJECT_ONLY) {
-		*shown_fields = FIELD_OBJECT_NAME;
-		return 0;
-	}
-	if (!ls_options || (ls_options & LS_RECURSIVE)
-	    || (ls_options & LS_SHOW_TREES)
-	    || (ls_options & LS_TREE_ONLY))
-		*shown_fields = FIELD_DEFAULT;
-	if (cmdmode == MODE_LONG)
-		*shown_fields = FIELD_LONG_DEFAULT;
-	return 1;
-}
-
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -207,7 +177,7 @@ static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
 
-	if (data->shown_fields & FIELD_SIZE) {
+	if (cmdmode == MODE_LONG) {
 		char size_text[24];
 		if (data->type == OBJ_BLOB) {
 			unsigned long size;
@@ -240,14 +210,12 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
-	unsigned int shown_fields = *(unsigned int *)context;
 	struct show_tree_data data = {
 		.mode = mode,
 		.type = type,
 		.oid = oid,
 		.pathname = pathname,
 		.base = base,
-		.shown_fields = shown_fields,
 	};
 
 	if (type == OBJ_BLOB) {
@@ -260,12 +228,12 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
-	if (shown_fields == FIELD_OBJECT_NAME) {
+	if (cmdmode == MODE_OBJECT_ONLY) {
 		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
 		return recurse;
 	}
 
-	if (shown_fields == FIELD_PATH_NAME) {
+	if (cmdmode == MODE_NAME_ONLY) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
 		write_name_quoted_relative(base->buf,
@@ -275,12 +243,40 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		return recurse;
 	}
 
-	if (shown_fields>= FIELD_DEFAULT)
+	if (cmdmode == MODE_LONG ||
+	    (!ls_options || (ls_options & LS_RECURSIVE)
+	     || (ls_options & LS_SHOW_TREES)
+	     || (ls_options & LS_TREE_ONLY)))
 		show_default(&data);
 
 	return recurse;
 }
 
+struct ls_tree_cmdmode_to_fmt {
+	enum ls_tree_cmdmode mode;
+	const char *const fmt;
+};
+
+static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
+	{
+		.mode = MODE_DEFAULT,
+		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+	},
+	{
+		.mode = MODE_LONG,
+		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+	},
+	{
+		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
+		.fmt = "%(path)",
+	},
+	{
+		.mode = MODE_OBJECT_ONLY,
+		.fmt = "%(objectname)",
+	},
+	{ 0 },
+};
+
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 {
 	struct object_id oid;
@@ -367,25 +363,23 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (!tree)
 		die("not a tree object");
 
-	parse_shown_fields(&shown_fields);
-
 	/*
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format && (!strcmp(format, default_format))) {
-		fn = show_tree;
-	} else if (format && (!strcmp(format, long_format))) {
-		shown_fields = shown_fields | FIELD_SIZE;
-		fn = show_tree;
-	} else if (format && (!strcmp(format, name_only_format))) {
-		shown_fields = FIELD_PATH_NAME;
-		fn = show_tree;
-	} else if (format && (!strcmp(format, object_only_format))) {
-		shown_fields = FIELD_OBJECT_NAME;
-		fn = show_tree;
-	} else if (format)
+	if (format) {
+		struct ls_tree_cmdmode_to_fmt *m2f;
+
 		fn = show_tree_fmt;
+		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
+			if (strcmp(format, m2f->fmt))
+				continue;
+
+			cmdmode = m2f->mode;
+			fn = show_tree;
+			break;
+		}
+	}
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
 }
-- 
2.35.1.1295.g6b025d3e231
Ævar Arnfjörð Bjarmason· Mar 10, 2022, 13:57 UTC · re: Ævar Arnfjörð Bjarmason · lore

[RFC/REVIEW 7/7] ls-tree: split up "fast path" callbacks

Make the various if/else in the callbacks for the "fast path" a lot easier to read by just using common functions for the parts that are common, and have per-format callbacks for those parts that are different.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 198 +++++++++++++++++++++++++++++-----------------
 1 file changed, 124 insertions(+), 74 deletions(-)
Show changes to builtin/ls-tree.c +124 −74
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 820baddf9a9..ee69b8c2702 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -173,108 +173,157 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
 	return recurse;
 }
 
-static int show_default(struct show_tree_data *data)
+static int show_tree_common(struct show_tree_data *data, int *recurse,
+			    const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode)
 {
-	size_t baselen = data->base->len;
-
-	if (cmdmode == MODE_LONG) {
-		char size_text[24];
-		if (data->type == OBJ_BLOB) {
-			unsigned long size;
-			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
-				xsnprintf(size_text, sizeof(size_text), "BAD");
-			else
-				xsnprintf(size_text, sizeof(size_text),
-					  "%" PRIuMAX, (uintmax_t)size);
-		} else {
-			xsnprintf(size_text, sizeof(size_text), "-");
-		}
-		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev), size_text);
-	} else {
-		printf("%06o %s %s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev));
-	}
-	baselen = data->base->len;
-	strbuf_addstr(data->base, data->pathname);
-	write_name_quoted_relative(data->base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
-				   line_termination);
-	strbuf_setlen(data->base, baselen);
-	return 1;
-}
-
-static int show_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, void *context)
-{
-	int recurse = 0;
-	size_t baselen;
 	enum object_type type = object_type(mode);
-	struct show_tree_data data = {
-		.mode = mode,
-		.type = type,
-		.oid = oid,
-		.pathname = pathname,
-		.base = base,
-	};
+	int ret = -1;
+
+	*recurse = 0;
+	data->mode = mode;
+	data->type = type;
+	data->oid = oid;
+	data->pathname = pathname;
+	data->base = base;
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
-			return 0;
+			ret = 0;
 	} else if (type == OBJ_TREE &&
 		   show_recursive(base->buf, base->len, pathname)) {
-		recurse = READ_TREE_RECURSIVE;
+		*recurse = READ_TREE_RECURSIVE;
 		if (!(ls_options & LS_SHOW_TREES))
-			return recurse;
+			ret = *recurse;
 	}
 
-	if (cmdmode == MODE_OBJECT_ONLY) {
-		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
-		return recurse;
-	}
+	return ret;
+}
 
-	if (cmdmode == MODE_NAME_ONLY) {
-		baselen = base->len;
-		strbuf_addstr(base, pathname);
-		write_name_quoted_relative(base->buf,
-					   chomp_prefix ? ls_tree_prefix : NULL,
-					   stdout, line_termination);
-		strbuf_setlen(base, baselen);
-		return recurse;
+static void show_tree_common_default_long(struct strbuf *base,
+					  const char *pathname,
+					  const size_t baselen)
+{
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+}
+
+static int show_tree_default(const struct object_id *oid, struct strbuf *base,
+			     const char *pathname, unsigned mode,
+			     void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%06o %s %s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev));
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return recurse;
+}
+
+static int show_tree_long(const struct object_id *oid, struct strbuf *base,
+			  const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+	char size_text[24];
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	if (data.type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD)
+			xsnprintf(size_text, sizeof(size_text), "BAD");
+		else
+			xsnprintf(size_text, sizeof(size_text),
+				  "%" PRIuMAX, (uintmax_t)size);
+	} else {
+		xsnprintf(size_text, sizeof(size_text), "-");
 	}
 
-	if (cmdmode == MODE_LONG ||
-	    (!ls_options || (ls_options & LS_RECURSIVE)
-	     || (ls_options & LS_SHOW_TREES)
-	     || (ls_options & LS_TREE_ONLY)))
-		show_default(&data);
+	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev), size_text);
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return 1;
+}
 
+static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
+			       const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	const size_t baselen = base->len;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL,
+				   stdout, line_termination);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
+static int show_tree_object(const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
 	return recurse;
 }
 
 struct ls_tree_cmdmode_to_fmt {
 	enum ls_tree_cmdmode mode;
 	const char *const fmt;
+	read_tree_fn_t fn;
 };
 
 static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
 	{
 		.mode = MODE_DEFAULT,
 		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+		.fn = show_tree_default,
 	},
 	{
 		.mode = MODE_LONG,
 		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+		.fn = show_tree_long,
 	},
 	{
 		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
 		.fmt = "%(path)",
+		.fn = show_tree_name_only,
 	},
 	{
 		.mode = MODE_OBJECT_ONLY,
 		.fmt = "%(objectname)",
+		.fn = show_tree_object
+	},
+	{
+		/* fallback */
+		.fn = show_tree_default,
 	},
-	{ 0 },
 };
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
@@ -283,7 +332,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct tree *tree;
 	int i, full_tree = 0;
 	unsigned int shown_fields = 0;
-	read_tree_fn_t fn = show_tree;
+	read_tree_fn_t fn = NULL;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -312,6 +361,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
+	struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;
 
 	git_config(git_default_config, NULL);
 	ls_tree_prefix = prefix;
@@ -367,18 +417,18 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format) {
-		struct ls_tree_cmdmode_to_fmt *m2f;
-
-		fn = show_tree_fmt;
-		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
-			if (strcmp(format, m2f->fmt))
-				continue;
-
+	while (m2f++) {
+		if (!m2f->fmt) {
+			fn = format ? show_tree_fmt : show_tree_default;
+		} else if (format && !strcmp(format, m2f->fmt)) {
 			cmdmode = m2f->mode;
-			fn = show_tree;
-			break;
+			fn = m2f->fn;
+		} else if (!format && cmdmode == m2f->mode) {
+			fn = m2f->fn;
+		} else {
+			continue;
 		}
+		break;
 	}
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
-- 
2.35.1.1295.g6b025d3e231
Teng Long· Mar 17, 2022, 09:51 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [RFC/REVIEW 0/7] fixups/suggestions/musings for tl/ls-tree-oid-only

Ævar Arnfjörð Bjarmason wrote on Thu, 10 Mar 2022 14:56:56 +0100
Show 6 quoted lines
> I don't think all of these need to be squashed or fixed up into the
> proposed series, but are just various small issues/questions I came 
> with while reviewing it. Brief notes 
>
> Ævar Arnfjörð Bjarmason (7):
> ...

I looked these commits in "RFC/REVIEW" and I think each one is a good improvement. So next, to make the squashes or keep it alone I think maybe it's like:

(1) ls-tree tests: add tests for --name-status
    They are new test codes that better than old ones. 
    Action: Keep it individually.
(2) ls-tree tests: exhaustively test fast & slow path for --format
    They are better test codes for the correctness formatting mechanism.
    Action: Squash into commit 'ls-tree: introduce "--format" option'
(3) ls-tree: remove dead labels
    They remove the dead labels, and you mentioned it should be squash into
    the commit which brought them in.
    
    Action: Squash into commit 'ls-tree: slightly refactor `show_tree()`'
(4) ls-tree: remove unused "MODE_UNSPECIFIED"
    As the subject describes, remove unused "MODE_UNSPECIFIED" and make
    "mutx_option" to a better name "mutx_option". It's the prepared commit
    for 'ls-tree: remove FIELD_*, just use MODE_*''
    Action: Squash into commit 'ls-tree: slightly refactor `show_tree()`'
(5) ls-tree: detect and error on --name-only --name-status
    Optimized the incompatible detecting tests codes in "t/t3103-ls-tree-misc.sh"
    and add a new 'MODE_NAME_STATUS'.
    Action: Keep it individually.
(6) ls-tree: remove FIELD_*, just use MODE_*
    Using MODE directlly and make a format-mode mappings for fast-path detection.
    Action: Keep it individually.
(7) ls-tree: split up "fast path" callbacks
    Expand "ls_tree_cmdmode_format" structure for each formats with the specific 
    show function and split up the current show functions name.
    Action: Keep it individually.
This is the way I try to continue this work, please let me know if I understand you wrong.

I look forward to your reply and I will decide the next step based on the reply. If we have a consistent understanding of the next actions, I will try to make a pull request to your Git fork first. If there is no problem, I will continue to send patches to the mailing list.

Thanks.
Ævar Arnfjörð Bjarmason· Mar 17, 2022, 10:04 UTC · re: Teng Long · lore

Re: [RFC/REVIEW 0/7] fixups/suggestions/musings for tl/ls-tree-oid-only

On Thu, Mar 17 2022, Teng Long wrote:
Show 61 quoted lines
> Ævar Arnfjörð Bjarmason wrote on Thu, 10 Mar 2022 14:56:56 +0100
>
>> I don't think all of these need to be squashed or fixed up into the
>> proposed series, but are just various small issues/questions I came 
>> with while reviewing it. Brief notes 
>>
>> Ævar Arnfjörð Bjarmason (7):
>> ...
>
>
> I looked these commits in "RFC/REVIEW" and I think each one is a good
> improvement. So next, to make the squashes or keep it alone  I think
> maybe it's like: 
>
>
> (1) ls-tree tests: add tests for --name-status
>
>     They are new test codes that better than old ones. 
>
>     Action: Keep it individually.
>
> (2) ls-tree tests: exhaustively test fast & slow path for --format
>
>     They are better test codes for the correctness formatting mechanism.
>
>     Action: Squash into commit 'ls-tree: introduce "--format" option'
>
> (3) ls-tree: remove dead labels
>
>     They remove the dead labels, and you mentioned it should be squash into
>     the commit which brought them in.
>     
>     Action: Squash into commit 'ls-tree: slightly refactor `show_tree()`'
>
> (4) ls-tree: remove unused "MODE_UNSPECIFIED"
>
>     As the subject describes, remove unused "MODE_UNSPECIFIED" and make
>     "mutx_option" to a better name "mutx_option". It's the prepared commit
>     for 'ls-tree: remove FIELD_*, just use MODE_*''
>
>     Action: Squash into commit 'ls-tree: slightly refactor `show_tree()`'
>
> (5) ls-tree: detect and error on --name-only --name-status
>
>     Optimized the incompatible detecting tests codes in "t/t3103-ls-tree-misc.sh"
>     and add a new 'MODE_NAME_STATUS'.
>
>     Action: Keep it individually.
>
> (6) ls-tree: remove FIELD_*, just use MODE_*
>
>     Using MODE directlly and make a format-mode mappings for fast-path detection.
>
>     Action: Keep it individually.
>
> (7) ls-tree: split up "fast path" callbacks
>
>     Expand "ls_tree_cmdmode_format" structure for each formats with the specific 
>     show function and split up the current show functions name.
>
>     Action: Keep it individually.

All sounds good, or rather. I really meant those as "hey maybe it's useful, you decide what to do with it". So I'm happy with whatever you picked here :)

Show 5 quoted lines
> This is the way I try to continue this work, please let me know if I understand you wrong.
>
> I look forward to your reply and I will decide the next step based on the reply. If we have
> a consistent understanding of the next actions, I will try to make a pull request to your
> Git fork first. If there is no problem, I will continue to send patches to the mailing list.

Hi, thanks for looking at it. I think it's better to just post a re-roll on the list for discussion, with whatever changed you think are appropriate. Thanks!

Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 00/16] ls-tree: "--object-only" and "--format" opts

Diff from v12:
Current patch is based by the review patch of Ævar Arnfjörð Bjarmason in [1].

In terms of the review commits, I replied Ævar Arnfjörð Bjarmason [2] and listed the actions, therefore this v13 patch was implemented by these actions.

Links:
1. https://public-inbox.org/git/cover-0.7-00000000000-20220310T134811Z-avarab@gmail.com/
2. https://public-inbox.org/git/20220317095129.86790-1-dyroneteng@gmail.com/
Thanks.
Johannes Schindelin (1):
  cocci: allow padding with `strbuf_addf()`
Teng Long (5):
  ls-tree: rename "retval" to "recurse" in "show_tree()"
  ls-tree: simplify nesting if/else logic in "show_tree()"
  ls-tree: fix "--name-only" and "--long" combined use bug
  ls-tree: slightly refactor `show_tree()`
  ls-tree: support --object-only option for "git-ls-tree"
Ævar Arnfjörð Bjarmason (10):
  ls-tree: remove commented-out code
  ls-tree: add missing braces to "else" arms
  ls-tree: use "enum object_type", not {blob,tree,commit}_type
  ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
  ls-tree: introduce struct "show_tree_data"
  ls-tree: introduce "--format" option
  ls-tree tests: add tests for --name-status
  ls-tree: detect and error on --name-only --name-status
  ls-tree: remove FIELD_*, just use MODE_*
  ls-tree: split up "fast path" callbacks
 Documentation/git-ls-tree.txt   |  68 +++++-
 builtin/ls-tree.c               | 367 ++++++++++++++++++++++++++------
 contrib/coccinelle/strbuf.cocci |   6 +-
 t/t3101-ls-tree-dirname.sh      |  55 ++---
 t/t3103-ls-tree-misc.sh         |  15 ++
 t/t3104-ls-tree-format.sh       |  76 +++++++
 6 files changed, 493 insertions(+), 94 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Range-diff against v12:
 -:  ---------- >  1:  2fcff7e0d4 ls-tree: remove commented-out code
 -:  ---------- >  2:  6fd1dd9383 ls-tree: add missing braces to "else" arms
 -:  ---------- >  3:  208654b5e2 ls-tree: use "enum object_type", not {blob,tree,commit}_type
 -:  ---------- >  4:  2637464fd8 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
 -:  ---------- >  5:  99e6d47108 ls-tree: rename "retval" to "recurse" in "show_tree()"
 -:  ---------- >  6:  a8d9b78dea ls-tree: simplify nesting if/else logic in "show_tree()"
 -:  ---------- >  7:  25a07e048f ls-tree: fix "--name-only" and "--long" combined use bug
 1:  f449146b4d !  8:  55f1e10d7e ls-tree: slightly refactor `show_tree()`
    @@ builtin/ls-tree.c
     -#define LS_SHOW_SIZE 16
     +#define LS_TREE_ONLY (1 << 1)
     +#define LS_SHOW_TREES (1 << 2)
    -+#define LS_NAME_ONLY (1 << 3)
    -+#define LS_SHOW_SIZE (1 << 4)
      static int abbrev;
      static int ls_options;
      static struct pathspec pathspec;
    @@ builtin/ls-tree.c
      	NULL
      };
      
    -+static enum mutx_option {
    -+	MODE_UNSPECIFIED = 0,
    ++static enum ls_tree_cmdmode {
    ++	MODE_LONG = 1,
     +	MODE_NAME_ONLY,
    -+	MODE_LONG,
     +} cmdmode;
     +
     +static int parse_shown_fields(void)
 2:  80311adc7c !  9:  85d0b0da1d ls-tree: introduce struct "show_tree_data"
    @@ builtin/ls-tree.c: static unsigned int shown_fields;
      static const  char * const ls_tree_usage[] = {
      	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
      	NULL
    -@@ builtin/ls-tree.c: static enum mutx_option {
    - 	MODE_LONG,
    +@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    + 	MODE_NAME_ONLY,
      } cmdmode;
      
     -static int parse_shown_fields(void)
 3:  459080f549 = 10:  eddcf903ad cocci: allow padding with `strbuf_addf()`
 4:  cb717d08be ! 11:  7451242daa ls-tree: introduce "--format" option
    @@ Commit message
         idea and suggestion, this commit makes modifications in terms of the
         original discussion on community [1].
     
    +    In [1] there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to
    +    ensure that we had test coverage for passing tests that would
    +    otherwise use show_tree() through show_tree_fmt(), and thus that the
    +    formatting mechanism could handle all the same cases as the
    +    non-formatting options.
    +
    +    Somewhere in subsequent re-rolls of that we seem to have drifted away
    +    from what the goal of these tests should be. We're trying to ensure
    +    correctness of show_tree_fmt(). We can't tell if we "hit [the]
    +    fast-path" here, and instead of having an explicit test for that, we
    +    can just add it to something our "test_ls_tree_format" tests for.
    +
         Here is the statistics about performance tests:
     
         1. Default format (hitten the builtin formats):
    @@ Commit message
     
         Links:
                 [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
    +            [2] https://lore.kernel.org/git/cb717d08be87e3239117c6c667cb32caabaad33d.1646390152.git.dyroneteng@gmail.com/
     
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
    @@ builtin/ls-tree.c: static const char *ls_tree_prefix;
      struct show_tree_data {
      	unsigned mode;
      	enum object_type type;
    -@@ builtin/ls-tree.c: static enum mutx_option {
    - 	MODE_LONG,
    +@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    + 	MODE_NAME_ONLY,
      } cmdmode;
      
     +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
    @@ t/t3104-ls-tree-format.sh (new)
     +	opts=$2 &&
     +	fmtopts=$3 &&
     +	shift 2 &&
    -+	git ls-tree $opts -r HEAD >expect.raw &&
    -+	sed "s/^/> /" >expect <expect.raw &&
    -+	git ls-tree --format="> $format" -r $fmtopts HEAD >actual &&
    -+	test_cmp expect actual
    ++
    ++	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
    ++		git ls-tree $opts -r HEAD >expect &&
    ++		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
    ++		test_cmp expect actual
    ++	'
    ++
    ++	test_expect_success "ls-tree '--format=<$format>' on optimized v.s. non-optimized path" '
    ++		git ls-tree --format="$format" -r $fmtopts HEAD >expect &&
    ++		git ls-tree --format="> $format" -r $fmtopts HEAD >actual.raw &&
    ++		sed "s/^> //" >actual <actual.raw &&
    ++		test_cmp expect actual
    ++	'
     +}
     +
    -+test_expect_success 'ls-tree --format=<default-like>' '
    -+	test_ls_tree_format \
    -+		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    -+		""
    -+'
    ++test_ls_tree_format \
    ++	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    ++	""
     +
    -+test_expect_success 'ls-tree --format=<long-like>' '
    -+	test_ls_tree_format \
    -+		"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
    -+		"--long"
    -+'
    ++test_ls_tree_format \
    ++	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
    ++	"--long"
     +
    -+test_expect_success 'ls-tree --format=<name-only-like>' '
    -+	test_ls_tree_format \
    -+		"%(path)" \
    -+		"--name-only"
    -+'
    ++test_ls_tree_format \
    ++	"%(path)" \
    ++	"--name-only"
     +
    -+test_expect_success 'ls-tree combine --format=<default-like> and -t' '
    -+	test_ls_tree_format \
    ++test_ls_tree_format \
     +	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
     +	"-t" \
     +	"-t"
    -+'
     +
    -+test_expect_success 'ls-tree combine --format=<default-like> and --full-name' '
    -+	test_ls_tree_format \
    ++test_ls_tree_format \
     +	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
     +	"--full-name" \
     +	"--full-name"
    -+'
     +
    -+test_expect_success 'ls-tree combine --format=<default-like> and --full-tree' '
    -+	test_ls_tree_format \
    ++test_ls_tree_format \
     +	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
     +	"--full-tree" \
     +	"--full-tree"
    -+'
     +
    -+test_expect_success 'ls-tree hit fast-path with --format=<default-like>' '
    -+	git ls-tree -r HEAD >expect &&
    -+	git ls-tree --format="%(objectmode) %(objecttype) %(objectname)%x09%(path)" -r HEAD >actual &&
    -+	test_cmp expect actual
    -+'
    -+
    -+test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
    -+	git ls-tree -r --name-only HEAD >expect &&
    -+	git ls-tree --format="%(path)" -r HEAD >actual &&
    -+	test_cmp expect actual
    -+'
     +test_done
 5:  55b1c4379d ! 12:  2bcd08ebd0 ls-tree: support --object-only option for "git-ls-tree"
    @@ Documentation/git-ls-tree.txt: OPTIONS
      	Instead of showing the full 40-byte hexadecimal object
     
      ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static int line_termination = '\n';
    - #define LS_SHOW_TREES (1 << 2)
    - #define LS_NAME_ONLY (1 << 3)
    - #define LS_SHOW_SIZE (1 << 4)
    -+#define LS_OBJECT_ONLY (1 << 5)
    - static int abbrev;
    - static int ls_options;
    - static struct pathspec pathspec;
     @@ builtin/ls-tree.c: static const char *format;
      static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
      static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    @@ builtin/ls-tree.c: static const char *format;
      	unsigned mode;
      	enum object_type type;
     @@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
    - static enum mutx_option {
    - 	MODE_UNSPECIFIED = 0,
    + static enum ls_tree_cmdmode {
    + 	MODE_LONG = 1,
      	MODE_NAME_ONLY,
     +	MODE_OBJECT_ONLY,
    - 	MODE_LONG,
      } cmdmode;
      
    + static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
     @@ builtin/ls-tree.c: static int parse_shown_fields(unsigned int *shown_fields)
      		*shown_fields = FIELD_PATH_NAME;
      		return 0;
    @@ t/t3103-ls-tree-misc.sh: test_expect_success 'ls-tree fails with non-zero exit c
      test_done
     
      ## t/t3104-ls-tree-format.sh ##
    -@@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree --format=<name-only-like>' '
    - 		"--name-only"
    - '
    +@@ t/t3104-ls-tree-format.sh: test_ls_tree_format \
    + 	"%(path)" \
    + 	"--name-only"
      
    -+test_expect_success 'ls-tree --format=<object-only-like>' '
    -+	test_ls_tree_format \
    -+		"%(objectname)" \
    -+		"--object-only"
    -+'
    ++test_ls_tree_format \
    ++	"%(objectname)" \
    ++	"--object-only"
     +
    -+test_expect_success 'ls-tree --format=<object-only-like> --abbrev' '
    -+	test_ls_tree_format \
    -+		"%(objectname)" \
    -+		"--object-only --abbrev" \
    -+		"--abbrev"
    -+'
    ++test_ls_tree_format \
    ++	"%(objectname)" \
    ++	"--object-only --abbrev" \
    ++	"--abbrev"
     +
    - test_expect_success 'ls-tree combine --format=<default-like> and -t' '
    - 	test_ls_tree_format \
    + test_ls_tree_format \
      	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    -@@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree hit fast-path with --format=<name-only-like>' '
    - 	git ls-tree --format="%(path)" -r HEAD >actual &&
    - 	test_cmp expect actual
    - '
    -+
    -+test_expect_success 'ls-tree hit fast-path with --format=<object-only-like>' '
    -+	git ls-tree -r --object-only HEAD >expect &&
    -+	git ls-tree --format="%(objectname)" -r HEAD >actual &&
    -+	test_cmp expect actual
    -+'
    - test_done
    + 	"-t" \
 -:  ---------- > 13:  a5c73de057 ls-tree tests: add tests for --name-status
 -:  ---------- > 14:  89402a8518 ls-tree: detect and error on --name-only --name-status
 -:  ---------- > 15:  b8afca193a ls-tree: remove FIELD_*, just use MODE_*
 -:  ---------- > 16:  010e3c0ece ls-tree: split up "fast path" callbacks
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 01/16] ls-tree: remove commented-out code

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Remove code added in f35a6d3bce7 (Teach core object handling functions about gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output flushing before fork(), 2008-08-04), and then finally ending up in its current form in d3bee161fef (tree.c: allow read_tree_recursive() to traverse gitlink entries, 2009-01-25). All while being commented-out!

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
Show changes to builtin/ls-tree.c +0 −9
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3a442631c7..5f7c84950c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	const char *type = blob_type;
 
 	if (S_ISGITLINK(mode)) {
-		/*
-		 * Maybe we want to have some recursive version here?
-		 *
-		 * Something similar to this incomplete example:
-		 *
-		if (show_subprojects(base, baselen, pathname))
-			retval = READ_TREE_RECURSIVE;
-		 *
-		 */
 		type = commit_type;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 02/16] ls-tree: add missing braces to "else" arms

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add missing {} to the "else" arms in show_tree() per the CodingGuidelines.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/ls-tree.c +4 −2
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5f7c84950c..0a28f32ccb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				else
 					xsnprintf(size_text, sizeof(size_text),
 						  "%"PRIuMAX, (uintmax_t)size);
-			} else
+			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
+			}
 			printf("%06o %s %s %7s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
-		} else
+		} else {
 			printf("%06o %s %s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev));
+		}
 	}
 	baselen = base->len;
 	strbuf_addstr(base, pathname);
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 03/16] ls-tree: use "enum object_type", not {blob,tree,commit}_type

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Change the ls-tree.c code to use type_name() on the enum instead of using the string constants. This doesn't matter either way for performance, but makes this a bit easier to read as we'll no longer need a strcmp() here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/ls-tree.c +6 −6
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0a28f32ccb..3f0225b097 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int retval = 0;
 	int baselen;
-	const char *type = blob_type;
+	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-		type = commit_type;
+		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
 			retval = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
 				return retval;
 		}
-		type = tree_type;
+		type = OBJ_TREE;
 	}
 	else if (ls_options & LS_TREE_ONLY)
 		return 0;
@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
 			char size_text[24];
-			if (!strcmp(type, blob_type)) {
+			if (type == OBJ_BLOB) {
 				unsigned long size;
 				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
 					xsnprintf(size_text, sizeof(size_text),
@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
 			}
-			printf("%06o %s %s %7s\t", mode, type,
+			printf("%06o %s %s %7s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
 		} else {
-			printf("%06o %s %s\t", mode, type,
+			printf("%06o %s %s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev));
 		}
 	}
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 04/16] ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The "struct strbuf"'s "len" member is a "size_t", not an "int", so let's change our corresponding types accordingly. This also changes the "len" and "speclen" variables, which are likewise used to store the return value of strlen(), which returns "size_t", not "int".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/ls-tree.c +3 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3f0225b097..eecc7482d5 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
-static int show_recursive(const char *base, int baselen, const char *pathname)
+static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
 
@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
 
 	for (i = 0; i < pathspec.nr; i++) {
 		const char *spec = pathspec.items[i].match;
-		int len, speclen;
+		size_t len, speclen;
 
 		if (strncmp(base, spec, baselen))
 			continue;
@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
 	int retval = 0;
-	int baselen;
+	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 05/16] ls-tree: rename "retval" to "recurse" in "show_tree()"

The variable which "show_tree()" return is named "retval", a name that's a little hard to understand. The commit rename "retval" to "recurse" which is a more meaningful name than before in the context. We do not need to take a look at "read_tree_at()" in "tree.c" to make sure what does "retval" mean.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/ls-tree.c +4 −4
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..ef8c414f61 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -64,7 +64,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
-	int retval = 0;
+	int recurse = 0;
 	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
@@ -72,9 +72,9 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-			retval = READ_TREE_RECURSIVE;
+			recurse = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
-				return retval;
+				return recurse;
 		}
 		type = OBJ_TREE;
 	}
@@ -109,7 +109,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				   chomp_prefix ? ls_tree_prefix : NULL,
 				   stdout, line_termination);
 	strbuf_setlen(base, baselen);
-	return retval;
+	return recurse;
 }
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 06/16] ls-tree: simplify nesting if/else logic in "show_tree()"

Use the object_type() function to determine the object type from the "mode" passed to us by read_tree(), instead of doing so with the S_*() macros.

Signed-off-by: Teng Long <dyronetengb@gmail.com>
---
 builtin/ls-tree.c | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)
Show changes to builtin/ls-tree.c +9 −12
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61..d4be71bad2 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,20 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
-		if (show_recursive(base->buf, base->len, pathname)) {
-			recurse = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return recurse;
-		}
-		type = OBJ_TREE;
+	if (type == OBJ_BLOB) {
+		if (ls_options & LS_TREE_ONLY)
+			return 0;
+	} else if (type == OBJ_TREE &&
+		   show_recursive(base->buf, base->len, pathname)) {
+		recurse = READ_TREE_RECURSIVE;
+		if (!(ls_options & LS_SHOW_TREES))
+			return recurse;
 	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 07/16] ls-tree: fix "--name-only" and "--long" combined use bug

If we execute "git ls-tree" with combined "--name-only" and "--long" , only the pathname will be printed, the size is omitted (the original discoverer was Peff in [1]).

This commit fix this issue by using `OPT_CMDMODE()` instead to make both of them mutually exclusive.

[1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c       | 12 ++++++------
 t/t3103-ls-tree-misc.sh |  9 +++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)
Show changes to 2 files +15 −6

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index d4be71bad2..7be4c13228 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -123,12 +123,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_BIT('l', "long", &ls_options, N_("include object size"),
-			LS_SHOW_SIZE),
-		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
-		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
+			    LS_SHOW_SIZE),
+		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d18ba1bd84..d9d7fa932f 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -23,4 +23,13 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 	test_must_fail git ls-tree -r HEAD
 '
 
+for opts in \
+	"--name-only --long" \
+	"--name-status --long"
+do
+	test_expect_success "usage: incompatible options: $opts" '
+		test_expect_code 129 git ls-tree $opts $tree
+    '
+done
+
 test_done
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 08/16] ls-tree: slightly refactor `show_tree()`

This is a non-functional change, we use a new int "shown_fields" to mark which columns to output, and `parse_shown_fields()` to calculate the value of "shown_fields".

This has the advantage of making the show_tree logic simpler and more readable, as well as making it easier to extend new options (for example, if we want to add a "--object-only" option, we just need to add a similar "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in "show_tree()").

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 121 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 84 insertions(+), 37 deletions(-)
Show changes to builtin/ls-tree.c +84 −37
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 7be4c13228..4364416e1c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,21 +16,48 @@
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
-#define LS_TREE_ONLY 2
-#define LS_SHOW_TREES 4
-#define LS_NAME_ONLY 8
-#define LS_SHOW_SIZE 16
+#define LS_TREE_ONLY (1 << 1)
+#define LS_SHOW_TREES (1 << 2)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
+static unsigned int shown_fields;
+#define FIELD_PATH_NAME 1
+#define FIELD_SIZE (1 << 1)
+#define FIELD_OBJECT_NAME (1 << 2)
+#define FIELD_TYPE (1 << 3)
+#define FIELD_MODE (1 << 4)
+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
 };
 
+static enum ls_tree_cmdmode {
+	MODE_LONG = 1,
+	MODE_NAME_ONLY,
+} cmdmode;
+
+static int parse_shown_fields(void)
+{
+	if (cmdmode == MODE_NAME_ONLY) {
+		shown_fields = FIELD_PATH_NAME;
+		return 0;
+	}
+
+	if (!ls_options || (ls_options & LS_RECURSIVE)
+	    || (ls_options & LS_SHOW_TREES)
+	    || (ls_options & LS_TREE_ONLY))
+		shown_fields = FIELD_DEFAULT;
+	if (cmdmode == MODE_LONG)
+		shown_fields = FIELD_LONG_DEFAULT;
+	return 1;
+}
+
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -61,6 +88,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_default(const struct object_id *oid, enum object_type type,
+			const char *pathname, unsigned mode,
+			struct strbuf *base)
+{
+	size_t baselen = base->len;
+
+	if (shown_fields & FIELD_SIZE) {
+		char size_text[24];
+		if (type == OBJ_BLOB) {
+			unsigned long size;
+			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+				xsnprintf(size_text, sizeof(size_text), "BAD");
+			else
+				xsnprintf(size_text, sizeof(size_text),
+					  "%" PRIuMAX, (uintmax_t)size);
+		} else {
+			xsnprintf(size_text, sizeof(size_text), "-");
+		}
+		printf("%06o %s %s %7s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev), size_text);
+	} else {
+		printf("%06o %s %s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev));
+	}
+	baselen = base->len;
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+	return 1;
+}
+
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
@@ -78,34 +138,19 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
-	if (!(ls_options & LS_NAME_ONLY)) {
-		if (ls_options & LS_SHOW_SIZE) {
-			char size_text[24];
-			if (type == OBJ_BLOB) {
-				unsigned long size;
-				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
-					xsnprintf(size_text, sizeof(size_text),
-						  "BAD");
-				else
-					xsnprintf(size_text, sizeof(size_text),
-						  "%"PRIuMAX, (uintmax_t)size);
-			} else {
-				xsnprintf(size_text, sizeof(size_text), "-");
-			}
-			printf("%06o %s %s %7s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev),
-			       size_text);
-		} else {
-			printf("%06o %s %s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev));
-		}
+	if (shown_fields == FIELD_PATH_NAME) {
+		baselen = base->len;
+		strbuf_addstr(base, pathname);
+		write_name_quoted_relative(base->buf,
+					   chomp_prefix ? ls_tree_prefix : NULL,
+					   stdout, line_termination);
+		strbuf_setlen(base, baselen);
+		return recurse;
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL,
-				   stdout, line_termination);
-	strbuf_setlen(base, baselen);
+
+	if (shown_fields >= FIELD_DEFAULT)
+		show_default(oid, type, pathname, mode, base);
+
 	return recurse;
 }
 
@@ -123,12 +168,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
-			    LS_SHOW_SIZE),
-		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
-		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"),
+			    MODE_LONG),
+		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
@@ -159,6 +204,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
+	parse_shown_fields();
+
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 09/16] ls-tree: introduce struct "show_tree_data"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

"show_tree_data" is a struct that packages the necessary fields for "show_tree()". This commit is a pre-prepared commit for supporting "--format" option and it does not affect any existing functionality.

At the same time, we also move global "shown_fields" into "show_tree_data" , the purpose of this is to avoid use both global variable and pass-around struct.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 69 +++++++++++++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 26 deletions(-)
Show changes to builtin/ls-tree.c +43 −26
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 4364416e1c..0491847b43 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -23,7 +23,6 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
-static unsigned int shown_fields;
 #define FIELD_PATH_NAME 1
 #define FIELD_SIZE (1 << 1)
 #define FIELD_OBJECT_NAME (1 << 2)
@@ -32,6 +31,15 @@ static unsigned int shown_fields;
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 
+struct show_tree_data {
+	unsigned mode;
+	enum object_type type;
+	const struct object_id *oid;
+	const char *pathname;
+	struct strbuf *base;
+	unsigned int shown_fields;
+};
+
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
 	NULL
@@ -42,19 +50,19 @@ static enum ls_tree_cmdmode {
 	MODE_NAME_ONLY,
 } cmdmode;
 
-static int parse_shown_fields(void)
+static int parse_shown_fields(unsigned int *shown_fields)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
-		shown_fields = FIELD_PATH_NAME;
+		*shown_fields = FIELD_PATH_NAME;
 		return 0;
 	}
 
 	if (!ls_options || (ls_options & LS_RECURSIVE)
 	    || (ls_options & LS_SHOW_TREES)
 	    || (ls_options & LS_TREE_ONLY))
-		shown_fields = FIELD_DEFAULT;
+		*shown_fields = FIELD_DEFAULT;
 	if (cmdmode == MODE_LONG)
-		shown_fields = FIELD_LONG_DEFAULT;
+		*shown_fields = FIELD_LONG_DEFAULT;
 	return 1;
 }
 
@@ -88,17 +96,15 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
-static int show_default(const struct object_id *oid, enum object_type type,
-			const char *pathname, unsigned mode,
-			struct strbuf *base)
+static int show_default(struct show_tree_data *data)
 {
-	size_t baselen = base->len;
+	size_t baselen = data->base->len;
 
-	if (shown_fields & FIELD_SIZE) {
+	if (data->shown_fields & FIELD_SIZE) {
 		char size_text[24];
-		if (type == OBJ_BLOB) {
+		if (data->type == OBJ_BLOB) {
 			unsigned long size;
-			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
 				xsnprintf(size_text, sizeof(size_text), "BAD");
 			else
 				xsnprintf(size_text, sizeof(size_text),
@@ -106,18 +112,18 @@ static int show_default(const struct object_id *oid, enum object_type type,
 		} else {
 			xsnprintf(size_text, sizeof(size_text), "-");
 		}
-		printf("%06o %s %s %7s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev), size_text);
+		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev), size_text);
 	} else {
-		printf("%06o %s %s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev));
+		printf("%06o %s %s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev));
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
+	baselen = data->base->len;
+	strbuf_addstr(data->base, data->pathname);
+	write_name_quoted_relative(data->base->buf,
 				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
 				   line_termination);
-	strbuf_setlen(base, baselen);
+	strbuf_setlen(data->base, baselen);
 	return 1;
 }
 
@@ -127,6 +133,15 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
+	unsigned int shown_fields = *(unsigned int *)context;
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+		.shown_fields = shown_fields,
+	};
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
@@ -148,8 +163,8 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		return recurse;
 	}
 
-	if (shown_fields >= FIELD_DEFAULT)
-		show_default(oid, type, pathname, mode, base);
+	if (shown_fields>= FIELD_DEFAULT)
+		show_default(&data);
 
 	return recurse;
 }
@@ -159,6 +174,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct object_id oid;
 	struct tree *tree;
 	int i, full_tree = 0;
+	unsigned int shown_fields = 0;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -204,8 +220,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (get_oid(argv[0], &oid))
 		die("Not a valid object name %s", argv[0]);
 
-	parse_shown_fields();
-
 	/*
 	 * show_recursive() rolls its own matching code and is
 	 * generally ignorant of 'struct pathspec'. The magic mask
@@ -222,6 +236,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	tree = parse_tree_indirect(&oid);
 	if (!tree)
 		die("not a tree object");
-	return !!read_tree(the_repository, tree,
-			   &pathspec, show_tree, NULL);
+
+	parse_shown_fields(&shown_fields);
+
+	return !!read_tree(the_repository, tree, &pathspec, show_tree,
+			   &shown_fields);
 }
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 10/16] cocci: allow padding with `strbuf_addf()`

From: Johannes Schindelin <johannes.schindelin@gmx.de>

A convenient way to pad strings is to use something like `strbuf_addf(&buf, "%20s", "Hello, world!")`.

However, the Coccinelle rule that forbids a format `"%s"` with a constant string argument cast too wide a net, and also forbade such padding.

The original rule was introduced by commit:
    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to contrib/coccinelle/strbuf.cocci +3 −3
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
index d9ada69b43..0970d98ad7 100644
--- a/contrib/coccinelle/strbuf.cocci
+++ b/contrib/coccinelle/strbuf.cocci
@@ -15,7 +15,7 @@ constant fmt !~ "%";
 @@
 expression E;
 struct strbuf SB;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SB.buf);
 + strbuf_addbuf(E, &SB);
@@ -23,7 +23,7 @@ format F =~ "s";
 @@
 expression E;
 struct strbuf *SBP;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SBP->buf);
 + strbuf_addbuf(E, SBP);
@@ -44,7 +44,7 @@ struct strbuf *SBP;
 
 @@
 expression E1, E2;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E1, "%@F@", E2);
 + strbuf_addstr(E1, E2);
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 11/16] ls-tree: introduce "--format" option

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add a --format option to ls-tree. It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths.

Rather than add --type-only, --object-only etc. we can just support a --format using a strbuf_expand() similar to "for-each-ref --format". We might still add such options in the future for convenience.

The --format implementation is slower than the existing code, but this change does not cause any performance regressions. We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided.

I.e. something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout.

The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community [1].

In [1] there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to ensure that we had test coverage for passing tests that would otherwise use show_tree() through show_tree_fmt(), and thus that the formatting mechanism could handle all the same cases as the non-formatting options.

Somewhere in subsequent re-rolls of that we seem to have drifted away from what the goal of these tests should be. We're trying to ensure correctness of show_tree_fmt(). We can't tell if we "hit [the] fast-path" here, and instead of having an explicit test for that, we can just add it to something our "test_ls_tree_format" tests for.

Here is the statistics about performance tests:
1. Default format (hitten the builtin formats):
    "git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]
    Range (min … max):    99.2 ms … 113.2 ms    28 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD
    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]
    Range (min … max):   100.2 ms … 110.5 ms    29 runs
2. Default format includes object size (hitten the builtin formats):
    "git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]
    Range (min … max):   327.5 ms … 348.4 ms    10 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD
    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]
    Range (min … max):   328.8 ms … 349.4 ms    10 runs
Links:
	[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
	[2] https://lore.kernel.org/git/cb717d08be87e3239117c6c667cb32caabaad33d.1646390152.git.dyroneteng@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |  59 ++++++++++++++--
 builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-
 t/t3104-ls-tree-format.sh     |  67 ++++++++++++++++++
 3 files changed, 248 insertions(+), 7 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Show changes to 3 files +248 −7

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..db29a9efb5 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -74,6 +74,16 @@ OPTIONS
 	Do not limit the listing to the current working directory.
 	Implies --full-name.
 
+--format=<format>::
+	A string that interpolates `%(fieldname)` from the result
+	being shown. It also interpolates `%%` to `%`, and
+	`%xx` where `xx` are hex digits interpolates to character
+	with hex code `xx`; for example `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+	When specified, `--format` cannot be combined with other
+	format-altering options, including `--long`, `--name-only`
+	and `--object-only`.
+
 [<path>...]::
 	When paths are given, show them (note that this isn't really raw
 	pathnames, but rather a list of patterns to match).  Otherwise
@@ -82,16 +92,29 @@ OPTIONS
 
 Output Format
 -------------
-        <mode> SP <type> SP <object> TAB <file>
+
+The output format of `ls-tree` is determined by either the `--format`
+option, or other format-altering options such as `--name-only` etc.
+(see `--format` above).
+
+The use of certain `--format` directives is equivalent to using those
+options, but invoking the full formatting machinery can be slower than
+using an appropriate formatting option.
+
+In cases where the `--format` would exactly map to an existing option
+`ls-tree` will use the appropriate faster path. Thus the default format
+is equivalent to:
+
+        %(objectmode) %(objecttype) %(objectname)%x09%(path)
 
 This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
-        <mode> SP <type> SP <object> SP <object size> TAB <file>
+        %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
 
-Object size identified by <object> is given in bytes, and right-justified
+Object size identified by <objectname> is given in bytes, and right-justified
 with minimum width of 7 characters.  Object size is given only for blobs
 (file) entries; for other entries `-` character is used in place of size.
 
@@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]).  Using `-z` the filename is output
 verbatim and the line is terminated by a NUL byte.
 
+Customized format:
+
+It is possible to print in a custom format by using the `--format` option,
+which is able to interpolate different fields using a `%(fieldname)` notation.
+For example, if you only care about the "objectname" and "path" fields, you
+can execute with a specific "--format" like
+
+        git ls-tree --format='%(objectname) %(path)' <tree-ish>
+
+FIELD NAMES
+-----------
+
+Various values from structured fields can be used to interpolate
+into the resulting output. For each outputing line, the following
+names can be used:
+
+objectmode::
+	The mode of the object.
+objecttype::
+	The type of the object (`blob` or `tree`).
+objectname::
+	The name of the object.
+objectsize[:padded]::
+	The size of the object ("-" if it's a tree).
+	It also supports a padded format of size with "%(size:padded)".
+path::
+	The pathname of the object.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0491847b43..182705e894 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -30,7 +30,10 @@ static const char *ls_tree_prefix;
 #define FIELD_MODE (1 << 4)
 #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
 #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
-
+static const char *format;
+static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
+static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
+static const char *name_only_format = "%(path)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -50,6 +53,72 @@ static enum ls_tree_cmdmode {
 	MODE_NAME_ONLY,
 } cmdmode;
 
+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
+			      const enum object_type type, unsigned int padded)
+{
+	if (type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, oid, &size) < 0)
+			die(_("could not get object info about '%s'"),
+			    oid_to_hex(oid));
+		if (padded)
+			strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size);
+		else
+			strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size);
+	} else if (padded) {
+		strbuf_addf(line, "%7s", "-");
+	} else {
+		strbuf_addstr(line, "-");
+	}
+}
+
+static size_t expand_show_tree(struct strbuf *sb, const char *start,
+			       void *context)
+{
+	struct show_tree_data *data = context;
+	const char *end;
+	const char *p;
+	unsigned int errlen;
+	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+
+	if (len)
+		return len;
+	if (*start != '(')
+		die(_("bad ls-tree format: element '%s' does not start with '('"), start);
+
+	end = strchr(start + 1, ')');
+	if (!end)
+		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
+
+	len = end - start + 1;
+	if (skip_prefix(start, "(objectmode)", &p)) {
+		strbuf_addf(sb, "%06o", data->mode);
+	} else if (skip_prefix(start, "(objecttype)", &p)) {
+		strbuf_addstr(sb, type_name(data->type));
+	} else if (skip_prefix(start, "(objectsize:padded)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 1);
+	} else if (skip_prefix(start, "(objectsize)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 0);
+	} else if (skip_prefix(start, "(objectname)", &p)) {
+		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
+	} else if (skip_prefix(start, "(path)", &p)) {
+		const char *name = data->base->buf;
+		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
+		struct strbuf quoted = STRBUF_INIT;
+		struct strbuf sbuf = STRBUF_INIT;
+		strbuf_addstr(data->base, data->pathname);
+		name = relative_path(data->base->buf, prefix, &sbuf);
+		quote_c_style(name, &quoted, NULL, 0);
+		strbuf_addbuf(sb, &quoted);
+		strbuf_release(&sbuf);
+		strbuf_release(&quoted);
+	} else {
+		errlen = (unsigned long)len;
+		die(_("bad ls-tree format: %%%.*s"), errlen, start);
+	}
+	return len;
+}
+
 static int parse_shown_fields(unsigned int *shown_fields)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
@@ -96,6 +165,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
+			 const char *pathname, unsigned mode, void *context)
+{
+	size_t baselen;
+	int recurse = 0;
+	struct strbuf sb = STRBUF_INIT;
+	enum object_type type = object_type(mode);
+
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
+
+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+		recurse = READ_TREE_RECURSIVE;
+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+		return recurse;
+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
+		return 0;
+
+	baselen = base->len;
+	strbuf_expand(&sb, format, expand_show_tree, &data);
+	strbuf_addch(&sb, line_termination);
+	fwrite(sb.buf, sb.len, 1, stdout);
+	strbuf_release(&sb);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
 static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
@@ -175,6 +276,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct tree *tree;
 	int i, full_tree = 0;
 	unsigned int shown_fields = 0;
+	read_tree_fn_t fn = show_tree;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -195,6 +297,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "full-tree", &full_tree,
 			 N_("list entire tree; not just current directory "
 			    "(implies --full-name)")),
+		OPT_STRING_F(0, "format", &format, N_("format"),
+					 N_("format to use for the output"),
+					 PARSE_OPT_NONEG),
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
@@ -215,6 +320,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
 		ls_options |= LS_SHOW_TREES;
 
+	if (format && cmdmode)
+		usage_msg_opt(
+			_("--format can't be combined with other format-altering options"),
+			ls_tree_usage, ls_tree_options);
 	if (argc < 1)
 		usage_with_options(ls_tree_usage, ls_tree_options);
 	if (get_oid(argv[0], &oid))
@@ -239,6 +348,20 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 
 	parse_shown_fields(&shown_fields);
 
-	return !!read_tree(the_repository, tree, &pathspec, show_tree,
-			   &shown_fields);
+	/*
+	 * The generic show_tree_fmt() is slower than show_tree(), so
+	 * take the fast path if possible.
+	 */
+	if (format && (!strcmp(format, default_format))) {
+		fn = show_tree;
+	} else if (format && (!strcmp(format, long_format))) {
+		shown_fields = shown_fields | FIELD_SIZE;
+		fn = show_tree;
+	} else if (format && (!strcmp(format, name_only_format))) {
+		shown_fields = FIELD_PATH_NAME;
+		fn = show_tree;
+	} else if (format)
+		fn = show_tree_fmt;
+
+	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
 }
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
new file mode 100755
index 0000000000..7f1eb699d3
--- /dev/null
+++ b/t/t3104-ls-tree-format.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='ls-tree --format'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	fmtopts=$3 &&
+	shift 2 &&
+
+	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
+		git ls-tree $opts -r HEAD >expect &&
+		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success "ls-tree '--format=<$format>' on optimized v.s. non-optimized path" '
+		git ls-tree --format="$format" -r $fmtopts HEAD >expect &&
+		git ls-tree --format="> $format" -r $fmtopts HEAD >actual.raw &&
+		sed "s/^> //" >actual <actual.raw &&
+		test_cmp expect actual
+	'
+}
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	""
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+	"--long"
+
+test_ls_tree_format \
+	"%(path)" \
+	"--name-only"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"-t" \
+	"-t"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-name" \
+	"--full-name"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-tree" \
+	"--full-tree"
+
+test_done
-- 
2.34.1.406.g2e0e55130e
Ævar Arnfjörð Bjarmason· Mar 21, 2022, 09:22 UTC · re: Teng Long · lore

Re: [PATCH v13 11/16] ls-tree: introduce "--format" option

On Mon, Mar 21 2022, Teng Long wrote:
Show 7 quoted lines
> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> [...]
> +	} else if (format && (!strcmp(format, name_only_format))) {
> +		shown_fields = FIELD_PATH_NAME;
> +		fn = show_tree;
> +	} else if (format)
> +		fn = show_tree_fmt;
Missing {} braces for this "else if" arm.
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 12/16] ls-tree: support --object-only option for "git-ls-tree"

'--object-only' is an alias for '--format=%(objectname)'. It cannot be used together other format-altering options like '--name-only', '--long' or '--format', they are mutually exclusive.

The "--name-only" option outputs <filepath> only. Likewise, <objectName> is another high frequency used field, so implement '--object-only' option will bring intuitive and clear semantics for this scenario. Using '--format=%(objectname)' we can achieve a similar effect, but the former is with a lower learning cost(without knowing the format requirement of '--format' option).

Even so, if a user is prefer to use "--format=%(objectname)", this is entirely welcome because they are not only equivalent in function, but also have almost identical performance. The reason is this commit also add the specific of "--format=%(objectname)" to the current fast-pathes (builtin formats) to avoid running unnecessary parsing mechanisms.

The following performance benchmarks are based on torvalds/linux.git:
  When hit the fast-path:
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --object-only HEAD
        Time (mean ± σ):      83.6 ms ±   2.0 ms    [User: 59.4 ms, System: 24.1 ms]
        Range (min … max):    80.4 ms …  87.2 ms    35 runs
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(objectname)' HEAD
        Time (mean ± σ):      84.1 ms ±   1.8 ms    [User: 61.7 ms, System: 22.3 ms]
        Range (min … max):    80.9 ms …  87.5 ms    35 runs
  But for a customized format, it will be slower:
       Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='oid: %(objectname)' HEAD
         Time (mean ± σ):      96.5 ms ±   2.5 ms    [User: 72.9 ms, System: 23.5 ms]
  	 Range (min … max):    93.1 ms … 104.1 ms    31 runs
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt | 11 ++++++++++-
 builtin/ls-tree.c             | 17 ++++++++++++++++-
 t/t3103-ls-tree-misc.sh       |  7 +++++--
 t/t3104-ls-tree-format.sh     |  9 +++++++++
 4 files changed, 40 insertions(+), 4 deletions(-)
Show changes to 4 files +40 −4

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3103-ls-tree-misc.sh, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db29a9efb5..1aea998e72 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
+	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -59,6 +59,15 @@ OPTIONS
 --name-only::
 --name-status::
 	List only filenames (instead of the "long" output), one per line.
+	Cannot be combined with `--object-only`.
+
+--object-only::
+	List only names of the objects, one per line. Cannot be combined
+	with `--name-only` or `--name-status`.
+	This is equivalent to specifying `--format='%(objectname)'`, but
+	for both this option and that exact format the command takes a
+	hand-optimized codepath instead of going through the generic
+	formatting mechanism.
 
 --abbrev[=<n>]::
 	Instead of showing the full 40-byte hexadecimal object
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 182705e894..1aa65cc87f 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -34,6 +34,7 @@ static const char *format;
 static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
 static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
 static const char *name_only_format = "%(path)";
+static const char *object_only_format = "%(objectname)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -51,6 +52,7 @@ static const  char * const ls_tree_usage[] = {
 static enum ls_tree_cmdmode {
 	MODE_LONG = 1,
 	MODE_NAME_ONLY,
+	MODE_OBJECT_ONLY,
 } cmdmode;
 
 static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
@@ -125,7 +127,10 @@ static int parse_shown_fields(unsigned int *shown_fields)
 		*shown_fields = FIELD_PATH_NAME;
 		return 0;
 	}
-
+	if (cmdmode == MODE_OBJECT_ONLY) {
+		*shown_fields = FIELD_OBJECT_NAME;
+		return 0;
+	}
 	if (!ls_options || (ls_options & LS_RECURSIVE)
 	    || (ls_options & LS_SHOW_TREES)
 	    || (ls_options & LS_TREE_ONLY))
@@ -254,6 +259,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
+	if (shown_fields == FIELD_OBJECT_NAME) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		return recurse;
+	}
+
 	if (shown_fields == FIELD_PATH_NAME) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
@@ -292,6 +302,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
+			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
@@ -360,6 +372,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	} else if (format && (!strcmp(format, name_only_format))) {
 		shown_fields = FIELD_PATH_NAME;
 		fn = show_tree;
+	} else if (format && (!strcmp(format, object_only_format))) {
+		shown_fields = FIELD_OBJECT_NAME;
+		fn = show_tree;
 	} else if (format)
 		fn = show_tree_fmt;
 
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d9d7fa932f..d979c0df5d 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -25,11 +25,14 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 
 for opts in \
 	"--name-only --long" \
-	"--name-status --long"
+	"--name-status --long" \
+	"--name-only --object-only" \
+	"--name-status --object-only" \
+	"--object-only --long" \
+	"--object-only --format"
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
     '
 done
-
 test_done
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 7f1eb699d3..0769a933d6 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -49,6 +49,15 @@ test_ls_tree_format \
 	"%(path)" \
 	"--name-only"
 
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only"
+
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only --abbrev" \
+	"--abbrev"
+
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"-t" \
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 13/16] ls-tree tests: add tests for --name-status

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The --name-status synonym for --name-only added in c639a5548a5 (ls-tree: --name-only, 2005-12-01) had no tests, let's make sure it works the same way as its sibling.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 t/t3101-ls-tree-dirname.sh | 55 ++++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 26 deletions(-)
Show changes to t/t3101-ls-tree-dirname.sh +29 −26
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 05fde64225..217006d1bf 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -201,31 +201,34 @@ EOF
 	test_cmp expected check
 '
 
-test_expect_success 'ls-tree --name-only' '
-	git ls-tree --name-only $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0
-path1
-path2
-path3
-EOF
-	test_output
-'
-
-test_expect_success 'ls-tree --name-only -r' '
-	git ls-tree --name-only -r $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0/a/b/c/1.txt
-path1/b/c/1.txt
-path2/1.txt
-path3/1.txt
-path3/2.txt
-EOF
-	test_output
-'
+for opt in --name-only --name-status
+do
+	test_expect_success "ls-tree $opt" '
+		git ls-tree $opt $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0
+		path1
+		path2
+		path3
+		EOF
+		test_output
+	'
+
+	test_expect_success "ls-tree $opt -r" '
+		git ls-tree $opt -r $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0/a/b/c/1.txt
+		path1/b/c/1.txt
+		path2/1.txt
+		path3/1.txt
+		path3/2.txt
+		EOF
+		test_output
+	'
+done
 
 test_done
-- 
2.34.1.406.g2e0e55130e
Ævar Arnfjörð Bjarmason· Mar 21, 2022, 09:21 UTC · re: Teng Long · lore

Re: [PATCH v13 13/16] ls-tree tests: add tests for --name-status

On Mon, Mar 21 2022, Teng Long wrote:
Show 5 quoted lines
> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>
> The --name-status synonym for --name-only added in
> c639a5548a5 (ls-tree: --name-only, 2005-12-01) had no tests, let's
> make sure it works the same way as its sibling.

Let's move this to the front of the series, or rather have any test-only changes go first. Then it's clear with:

    git rebase -i -x 'make test' @{u}

What does and doesn't introduce behavior changes, i.e. this is just testing existing functionality.

Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 14/16] ls-tree: detect and error on --name-only --name-status

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The --name-only and --name-status options are synonyms, but let's detect and error if both are provided.

In addition let's add explicit --format tests for the combination of these various options.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c       | 11 ++++++++++-
 t/t3103-ls-tree-misc.sh | 15 +++++++++------
 2 files changed, 19 insertions(+), 7 deletions(-)
Show changes to 2 files +19 −7

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 1aa65cc87f..a271941540 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -52,6 +52,7 @@ static const  char * const ls_tree_usage[] = {
 static enum ls_tree_cmdmode {
 	MODE_LONG = 1,
 	MODE_NAME_ONLY,
+	MODE_NAME_STATUS,
 	MODE_OBJECT_ONLY,
 } cmdmode;
 
@@ -301,7 +302,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
-			    MODE_NAME_ONLY),
+			    MODE_NAME_STATUS),
 		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
 			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
@@ -327,6 +328,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		ls_tree_prefix = prefix = NULL;
 		chomp_prefix = 0;
 	}
+	/*
+	 * We wanted to detect conflicts between --name-only and
+	 * --name-status, but once we're done with that subsequent
+	 * code should only need to check the primary name.
+	 */
+	if (cmdmode == MODE_NAME_STATUS)
+		cmdmode = MODE_NAME_ONLY;
+
 	/* -d -r should imply -t, but -d by itself should not have to. */
 	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d979c0df5d..7454ebbb00 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -24,15 +24,18 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 '
 
 for opts in \
-	"--name-only --long" \
-	"--name-status --long" \
-	"--name-only --object-only" \
+	"--long --name-only" \
+	"--name-only --name-status" \
 	"--name-status --object-only" \
-	"--object-only --long" \
-	"--object-only --format"
+	"--object-only --long" 
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
-    '
+	'
+
+	one_opt=$(echo "$opts" | cut -d' '  -f1)
+	test_expect_success "usage: incompatible options: $one_opt and --format" '
+		test_expect_code 129 git ls-tree $one_opt --format=fmt $tree
+	'
 done
 test_done
-- 
2.34.1.406.g2e0e55130e
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 15/16] ls-tree: remove FIELD_*, just use MODE_*

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

When we're picking where we should go in the optimized "show_tree" path there's no reason for why we need to convert our "cmdmode" of e.g. MODE_LONG into a FIELD_LONG_DEFAULT. Instead we can simply do those checks in the show_tree() function itself.

Let's also make this code more future-proof by unrolling the hardcoded strmp() if/else if chain into something that checks a new "static struct" providing a bidirectional mapping between optimized formats and the ls_tree_cmdmode.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 98 ++++++++++++++++++++++-------------------------
 1 file changed, 46 insertions(+), 52 deletions(-)
Show changes to builtin/ls-tree.c +46 −52
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index a271941540..3e756b5eee 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -23,25 +23,13 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
-#define FIELD_PATH_NAME 1
-#define FIELD_SIZE (1 << 1)
-#define FIELD_OBJECT_NAME (1 << 2)
-#define FIELD_TYPE (1 << 3)
-#define FIELD_MODE (1 << 4)
-#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
-#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
 static const char *format;
-static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
-static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
-static const char *name_only_format = "%(path)";
-static const char *object_only_format = "%(objectname)";
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
 	const struct object_id *oid;
 	const char *pathname;
 	struct strbuf *base;
-	unsigned int shown_fields;
 };
 
 static const  char * const ls_tree_usage[] = {
@@ -50,7 +38,8 @@ static const  char * const ls_tree_usage[] = {
 };
 
 static enum ls_tree_cmdmode {
-	MODE_LONG = 1,
+	MODE_DEFAULT = 0,
+	MODE_LONG,
 	MODE_NAME_ONLY,
 	MODE_NAME_STATUS,
 	MODE_OBJECT_ONLY,
@@ -122,25 +111,6 @@ static size_t expand_show_tree(struct strbuf *sb, const char *start,
 	return len;
 }
 
-static int parse_shown_fields(unsigned int *shown_fields)
-{
-	if (cmdmode == MODE_NAME_ONLY) {
-		*shown_fields = FIELD_PATH_NAME;
-		return 0;
-	}
-	if (cmdmode == MODE_OBJECT_ONLY) {
-		*shown_fields = FIELD_OBJECT_NAME;
-		return 0;
-	}
-	if (!ls_options || (ls_options & LS_RECURSIVE)
-	    || (ls_options & LS_SHOW_TREES)
-	    || (ls_options & LS_TREE_ONLY))
-		*shown_fields = FIELD_DEFAULT;
-	if (cmdmode == MODE_LONG)
-		*shown_fields = FIELD_LONG_DEFAULT;
-	return 1;
-}
-
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -207,7 +177,7 @@ static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
 
-	if (data->shown_fields & FIELD_SIZE) {
+	if (cmdmode == MODE_LONG) {
 		char size_text[24];
 		if (data->type == OBJ_BLOB) {
 			unsigned long size;
@@ -240,14 +210,12 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
-	unsigned int shown_fields = *(unsigned int *)context;
 	struct show_tree_data data = {
 		.mode = mode,
 		.type = type,
 		.oid = oid,
 		.pathname = pathname,
 		.base = base,
-		.shown_fields = shown_fields,
 	};
 
 	if (type == OBJ_BLOB) {
@@ -260,12 +228,12 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
-	if (shown_fields == FIELD_OBJECT_NAME) {
+	if (cmdmode == MODE_OBJECT_ONLY) {
 		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
 		return recurse;
 	}
 
-	if (shown_fields == FIELD_PATH_NAME) {
+	if (cmdmode == MODE_NAME_ONLY) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
 		write_name_quoted_relative(base->buf,
@@ -275,12 +243,40 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		return recurse;
 	}
 
-	if (shown_fields>= FIELD_DEFAULT)
+	if (cmdmode == MODE_LONG ||
+	    (!ls_options || (ls_options & LS_RECURSIVE)
+	     || (ls_options & LS_SHOW_TREES)
+	     || (ls_options & LS_TREE_ONLY)))
 		show_default(&data);
 
 	return recurse;
 }
 
+struct ls_tree_cmdmode_to_fmt {
+	enum ls_tree_cmdmode mode;
+	const char *const fmt;
+};
+
+static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
+	{
+		.mode = MODE_DEFAULT,
+		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+	},
+	{
+		.mode = MODE_LONG,
+		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+	},
+	{
+		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
+		.fmt = "%(path)",
+	},
+	{
+		.mode = MODE_OBJECT_ONLY,
+		.fmt = "%(objectname)",
+	},
+	{ 0 },
+};
+
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 {
 	struct object_id oid;
@@ -367,25 +363,23 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	if (!tree)
 		die("not a tree object");
 
-	parse_shown_fields(&shown_fields);
-
 	/*
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format && (!strcmp(format, default_format))) {
-		fn = show_tree;
-	} else if (format && (!strcmp(format, long_format))) {
-		shown_fields = shown_fields | FIELD_SIZE;
-		fn = show_tree;
-	} else if (format && (!strcmp(format, name_only_format))) {
-		shown_fields = FIELD_PATH_NAME;
-		fn = show_tree;
-	} else if (format && (!strcmp(format, object_only_format))) {
-		shown_fields = FIELD_OBJECT_NAME;
-		fn = show_tree;
-	} else if (format)
+	if (format) {
+		struct ls_tree_cmdmode_to_fmt *m2f;
+
 		fn = show_tree_fmt;
+		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
+			if (strcmp(format, m2f->fmt))
+				continue;
+
+			cmdmode = m2f->mode;
+			fn = show_tree;
+			break;
+		}
+	}
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
 }
-- 
2.34.1.406.g2e0e55130e
Ævar Arnfjörð Bjarmason· Mar 21, 2022, 09:15 UTC · re: Teng Long · lore

Re: [PATCH v13 15/16] ls-tree: remove FIELD_*, just use MODE_*

On Mon, Mar 21 2022, Teng Long wrote:
Show 14 quoted lines
> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>
> When we're picking where we should go in the optimized "show_tree"
> path there's no reason for why we need to convert our "cmdmode" of
> e.g. MODE_LONG into a FIELD_LONG_DEFAULT. Instead we can simply do
> those checks in the show_tree() function itself.
>
> Let's also make this code more future-proof by unrolling the hardcoded
> strmp() if/else if chain into something that checks a new "static
> struct" providing a bidirectional mapping between optimized formats
> and the ls_tree_cmdmode.
>
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>

For this & any other changes I suggested which amend code introduced earlier in the series: Let's just fix that up into the respective commits, if you agree this is a good direction that is.

I.e. if it's good to do Y let's not first do X and then change it to Y, we can just start with Y.

Maybe that means none of these fix-up commitsa are left at the end, and that's OK.

Show 19 quoted lines
> ---
>  builtin/ls-tree.c | 98 ++++++++++++++++++++++-------------------------
>  1 file changed, 46 insertions(+), 52 deletions(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index a271941540..3e756b5eee 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -23,25 +23,13 @@ static int ls_options;
>  static struct pathspec pathspec;
>  static int chomp_prefix;
>  static const char *ls_tree_prefix;
> -#define FIELD_PATH_NAME 1
> -#define FIELD_SIZE (1 << 1)
> -#define FIELD_OBJECT_NAME (1 << 2)
> -#define FIELD_TYPE (1 << 3)
> -#define FIELD_MODE (1 << 4)
> -#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
> -#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
I.e. this whole thing.
Show 5 quoted lines
>  static const char *format;
> -static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
> -static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
> -static const char *name_only_format = "%(path)";
> -static const char *object_only_format = "%(objectname)";
And turning this into a loop can go with the initial --format change.
> [...]
All the changes below seemed related...
Teng Long· Mar 21, 2022, 07:33 UTC · re: Teng Long · lore

[PATCH v13 16/16] ls-tree: split up "fast path" callbacks

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Make the various if/else in the callbacks for the "fast path" a lot easier to read by just using common functions for the parts that are common, and have per-format callbacks for those parts that are different.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 198 +++++++++++++++++++++++++++++-----------------
 1 file changed, 124 insertions(+), 74 deletions(-)
Show changes to builtin/ls-tree.c +124 −74
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3e756b5eee..52ae71efaa 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -173,108 +173,157 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
 	return recurse;
 }
 
-static int show_default(struct show_tree_data *data)
+static int show_tree_common(struct show_tree_data *data, int *recurse,
+			    const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode)
 {
-	size_t baselen = data->base->len;
-
-	if (cmdmode == MODE_LONG) {
-		char size_text[24];
-		if (data->type == OBJ_BLOB) {
-			unsigned long size;
-			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
-				xsnprintf(size_text, sizeof(size_text), "BAD");
-			else
-				xsnprintf(size_text, sizeof(size_text),
-					  "%" PRIuMAX, (uintmax_t)size);
-		} else {
-			xsnprintf(size_text, sizeof(size_text), "-");
-		}
-		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev), size_text);
-	} else {
-		printf("%06o %s %s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev));
-	}
-	baselen = data->base->len;
-	strbuf_addstr(data->base, data->pathname);
-	write_name_quoted_relative(data->base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
-				   line_termination);
-	strbuf_setlen(data->base, baselen);
-	return 1;
-}
-
-static int show_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, void *context)
-{
-	int recurse = 0;
-	size_t baselen;
 	enum object_type type = object_type(mode);
-	struct show_tree_data data = {
-		.mode = mode,
-		.type = type,
-		.oid = oid,
-		.pathname = pathname,
-		.base = base,
-	};
+	int ret = -1;
+
+	*recurse = 0;
+	data->mode = mode;
+	data->type = type;
+	data->oid = oid;
+	data->pathname = pathname;
+	data->base = base;
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
-			return 0;
+			ret = 0;
 	} else if (type == OBJ_TREE &&
 		   show_recursive(base->buf, base->len, pathname)) {
-		recurse = READ_TREE_RECURSIVE;
+		*recurse = READ_TREE_RECURSIVE;
 		if (!(ls_options & LS_SHOW_TREES))
-			return recurse;
+			ret = *recurse;
 	}
 
-	if (cmdmode == MODE_OBJECT_ONLY) {
-		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
-		return recurse;
-	}
+	return ret;
+}
 
-	if (cmdmode == MODE_NAME_ONLY) {
-		baselen = base->len;
-		strbuf_addstr(base, pathname);
-		write_name_quoted_relative(base->buf,
-					   chomp_prefix ? ls_tree_prefix : NULL,
-					   stdout, line_termination);
-		strbuf_setlen(base, baselen);
-		return recurse;
+static void show_tree_common_default_long(struct strbuf *base,
+					  const char *pathname,
+					  const size_t baselen)
+{
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+}
+
+static int show_tree_default(const struct object_id *oid, struct strbuf *base,
+			     const char *pathname, unsigned mode,
+			     void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%06o %s %s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev));
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return recurse;
+}
+
+static int show_tree_long(const struct object_id *oid, struct strbuf *base,
+			  const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+	char size_text[24];
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	if (data.type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD)
+			xsnprintf(size_text, sizeof(size_text), "BAD");
+		else
+			xsnprintf(size_text, sizeof(size_text),
+				  "%" PRIuMAX, (uintmax_t)size);
+	} else {
+		xsnprintf(size_text, sizeof(size_text), "-");
 	}
 
-	if (cmdmode == MODE_LONG ||
-	    (!ls_options || (ls_options & LS_RECURSIVE)
-	     || (ls_options & LS_SHOW_TREES)
-	     || (ls_options & LS_TREE_ONLY)))
-		show_default(&data);
+	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev), size_text);
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return 1;
+}
 
+static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
+			       const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	const size_t baselen = base->len;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL,
+				   stdout, line_termination);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
+static int show_tree_object(const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
 	return recurse;
 }
 
 struct ls_tree_cmdmode_to_fmt {
 	enum ls_tree_cmdmode mode;
 	const char *const fmt;
+	read_tree_fn_t fn;
 };
 
 static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
 	{
 		.mode = MODE_DEFAULT,
 		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+		.fn = show_tree_default,
 	},
 	{
 		.mode = MODE_LONG,
 		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+		.fn = show_tree_long,
 	},
 	{
 		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
 		.fmt = "%(path)",
+		.fn = show_tree_name_only,
 	},
 	{
 		.mode = MODE_OBJECT_ONLY,
 		.fmt = "%(objectname)",
+		.fn = show_tree_object
+	},
+	{
+		/* fallback */
+		.fn = show_tree_default,
 	},
-	{ 0 },
 };
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
@@ -283,7 +332,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct tree *tree;
 	int i, full_tree = 0;
 	unsigned int shown_fields = 0;
-	read_tree_fn_t fn = show_tree;
+	read_tree_fn_t fn = NULL;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -312,6 +361,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
+	struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;
 
 	git_config(git_default_config, NULL);
 	ls_tree_prefix = prefix;
@@ -367,18 +417,18 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format) {
-		struct ls_tree_cmdmode_to_fmt *m2f;
-
-		fn = show_tree_fmt;
-		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
-			if (strcmp(format, m2f->fmt))
-				continue;
-
+	while (m2f++) {
+		if (!m2f->fmt) {
+			fn = format ? show_tree_fmt : show_tree_default;
+		} else if (format && !strcmp(format, m2f->fmt)) {
 			cmdmode = m2f->mode;
-			fn = show_tree;
-			break;
+			fn = m2f->fn;
+		} else if (!format && cmdmode == m2f->mode) {
+			fn = m2f->fn;
+		} else {
+			continue;
 		}
+		break;
 	}
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
-- 
2.34.1.406.g2e0e55130e
Ævar Arnfjörð Bjarmason· Mar 21, 2022, 09:20 UTC · re: Teng Long · lore

Re: [PATCH v13 16/16] ls-tree: split up "fast path" callbacks

On Mon, Mar 21 2022, Teng Long wrote:
Show 6 quoted lines
> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
>
> Make the various if/else in the callbacks for the "fast path" a lot
> easier to read by just using common functions for the parts that are
> common, and have per-format callbacks for those parts that are
> different.

FWIW I didn't do any exhaustive benchmarks of this, but I checked a few things against origin/master on linux.git and all the reported "hyperfine" timings were the same/within the +/- interval.

Teng Long· Mar 23, 2022, 09:58 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v13 16/16] ls-tree: split up "fast path" callbacks

On Mon, 21 Mar 2022 10:20:34 +0100, Ævar Arnfjörð Bjarmason wrote:
 
> FWIW I didn't do any exhaustive benchmarks of this, but I checked a few
> things against origin/master on linux.git and all the reported
> "hyperfine" timings were the same/within the +/- interval.

I sended a reply on gmail but seems not updated on public-inbox, so I sended again use git send-mail this time. If the duplicated message appears, sorry for that.

I tested the performance between fast-paths and non-fast-paths again on linux.git. And I found the default format appeared a performance regression which maybe was brought by this commit:

    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
      Time (mean ± σ):     111.3 ms ±   2.3 ms    [User: 86.2 ms, System: 25.0 ms]
      Range (min … max):   107.8 ms … 115.1 ms    25 runs
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r --format='%(objectmode) %(objecttype) %(objectname)%x09%(path)' HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r --format='%(objectmode) %(objecttype) %(objectname)%x09%(path)' HEAD
      Time (mean ± σ):     159.1 ms ±   4.7 ms    [User: 131.8 ms, System: 27.3 ms]
      Range (min … max):   152.3 ms … 170.4 ms    19 runs
Further, the other fast-paths except "default format" seem like what we expect, then I made this change:
Show changes to builtin/ls-tree.c +3 −1
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index c9eb7f7243..7d784f97c7 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -406,15 +406,17 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
         * The generic show_tree_fmt() is slower than show_tree(), so
         * take the fast path if possible.
         */
-       while (m2f++) {
+       while (m2f) {
                if (!m2f->fmt) {
                        fn = format ? show_tree_fmt : show_tree_default;
                } else if (format && !strcmp(format, m2f->fmt)) {
+                       fprintf(stderr, "[dyrone] format: %s\n", format);
                        cmdmode = m2f->mode;
                        fn = m2f->fn;
                } else if (!format && cmdmode == m2f->mode) {
                        fn = m2f->fn;
                } else {
+                       m2f++;
                        continue;
                }
                break;

----------end diff---------------

After the scenario has been tested agin, the result seems OK:

    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r --format='%(objectmode) %(objecttype) %(objectname)%x09%(path)' HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r --format='%(objectmode) %(objecttype) %(objectname)%x09%(path)' HEAD
      Time (mean ± σ):     112.2 ms ±   2.5 ms    [User: 86.3 ms, System: 25.9 ms]
      Range (min … max):   108.8 ms … 117.4 ms    25 runs


Thanks.
Junio C Hamano· Mar 21, 2022, 19:07 UTC · re: Teng Long · lore

Re: [PATCH v13 00/16] ls-tree: "--object-only" and "--format" opts

Teng Long <dyroneteng@gmail.com> writes:
Show 13 quoted lines
> Diff from v12:
>
> Current patch is based by the review patch of Ævar Arnfjörð Bjarmason in [1].
>
> In terms of the review commits, I replied Ævar Arnfjörð Bjarmason [2] and listed the
> actions, therefore this v13 patch was implemented by these actions.  
>
> Links:
>
> 1. https://public-inbox.org/git/cover-0.7-00000000000-20220310T134811Z-avarab@gmail.com/
> 2. https://public-inbox.org/git/20220317095129.86790-1-dyroneteng@gmail.com/
>
> Thanks.
Thanks for sticking to this topic.  Will queue.
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 00/15] ls-tree: "--object-only" and "--format" opts

Main diff from v13:
1. fix missing {} braces in "else if" arm.
   https://public-inbox.org/git/220321.86wngnbqwx.gmgdl@evledraar.gmail.com/
2. move "ls-tree tests: add tests for --name-status" in front of the series.
   https://public-inbox.org/git/220321.861qyvd5j1.gmgdl@evledraar.gmail.com/
3. squash "ls-tree: remove FIELD_*, just use MODE_*" into previous commit "ls-tree: slightly refactor `show_tree()`".
   https://public-inbox.org/git/220321.86a6djd5m4.gmgdl@evledraar.gmail.com/
4. make a fix on the performance regression when hit default format fast-path.
   https://public-inbox.org/git/220321.865yo7d5ki.gmgdl@evledraar.gmail.com/#t
These changes are based on Ævar Arnfjörð Bjarmason's advices and I really appreciate it.
Thanks.
Johannes Schindelin (1):
  cocci: allow padding with `strbuf_addf()`
Teng Long (5):
  ls-tree: rename "retval" to "recurse" in "show_tree()"
  ls-tree: simplify nesting if/else logic in "show_tree()"
  ls-tree: fix "--name-only" and "--long" combined use bug
  ls-tree: slightly refactor `show_tree()`
  ls-tree: support --object-only option for "git-ls-tree"
Ævar Arnfjörð Bjarmason (9):
  ls-tree tests: add tests for --name-status
  ls-tree: remove commented-out code
  ls-tree: add missing braces to "else" arms
  ls-tree: use "enum object_type", not {blob,tree,commit}_type
  ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
  ls-tree: introduce struct "show_tree_data"
  ls-tree: introduce "--format" option
  ls-tree: detect and error on --name-only --name-status
  ls-tree: split up "fast path" callbacks
 Documentation/git-ls-tree.txt   |  68 +++++-
 builtin/ls-tree.c               | 366 ++++++++++++++++++++++++++------
 contrib/coccinelle/strbuf.cocci |   6 +-
 t/t3101-ls-tree-dirname.sh      |  55 ++---
 t/t3103-ls-tree-misc.sh         |  15 ++
 t/t3104-ls-tree-format.sh       |  76 +++++++
 6 files changed, 492 insertions(+), 94 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Range-diff against v13:
13:  a5c73de057 =  1:  ef1cf83d5e ls-tree tests: add tests for --name-status
 1:  2fcff7e0d4 =  2:  02779bfd5d ls-tree: remove commented-out code
 2:  6fd1dd9383 =  3:  93484b102d ls-tree: add missing braces to "else" arms
 3:  208654b5e2 =  4:  2b746624ec ls-tree: use "enum object_type", not {blob,tree,commit}_type
 4:  2637464fd8 =  5:  ca90461e03 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"
 5:  99e6d47108 =  6:  f51d4fab0a ls-tree: rename "retval" to "recurse" in "show_tree()"
 6:  a8d9b78dea !  7:  bff68d994f ls-tree: simplify nesting if/else logic in "show_tree()"
    @@ Commit message
         "mode" passed to us by read_tree(), instead of doing so with the S_*()
         macros.
     
    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyronetengb@gmail.com>
     
      ## builtin/ls-tree.c ##
 7:  25a07e048f !  8:  1d1808bdf0 ls-tree: fix "--name-only" and "--long" combined use bug
    @@ Commit message
     
         [1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
     
    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## builtin/ls-tree.c ##
 8:  55f1e10d7e !  9:  f4fc377dfa ls-tree: slightly refactor `show_tree()`
    @@ Metadata
      ## Commit message ##
         ls-tree: slightly refactor `show_tree()`
     
    -    This is a non-functional change, we use a new int "shown_fields" to mark
    -    which columns to output, and `parse_shown_fields()` to calculate the
    -    value of "shown_fields".
    +    This is a non-functional change, we introduce an enum "ls_tree_cmdmode"
    +    then use it to mark which columns to output.
     
         This has the advantage of making the show_tree logic simpler and more
         readable, as well as making it easier to extend new options (for example,
         if we want to add a "--object-only" option, we just need to add a similar
    -    "if (shown_fields == FIELD_OBJECT_NAME)" short-circuit logic in
    -    "show_tree()").
    +    "short-circuit logic in "show_tree()").
     
    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## builtin/ls-tree.c ##
    @@ builtin/ls-tree.c
      static int abbrev;
      static int ls_options;
      static struct pathspec pathspec;
    - static int chomp_prefix;
    - static const char *ls_tree_prefix;
    -+static unsigned int shown_fields;
    -+#define FIELD_PATH_NAME 1
    -+#define FIELD_SIZE (1 << 1)
    -+#define FIELD_OBJECT_NAME (1 << 2)
    -+#define FIELD_TYPE (1 << 3)
    -+#define FIELD_MODE (1 << 4)
    -+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
    -+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    - 
    - static const  char * const ls_tree_usage[] = {
    - 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
    +@@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
      	NULL
      };
      
    @@ builtin/ls-tree.c
     +	MODE_LONG = 1,
     +	MODE_NAME_ONLY,
     +} cmdmode;
    -+
    -+static int parse_shown_fields(void)
    -+{
    -+	if (cmdmode == MODE_NAME_ONLY) {
    -+		shown_fields = FIELD_PATH_NAME;
    -+		return 0;
    -+	}
    -+
    -+	if (!ls_options || (ls_options & LS_RECURSIVE)
    -+	    || (ls_options & LS_SHOW_TREES)
    -+	    || (ls_options & LS_TREE_ONLY))
    -+		shown_fields = FIELD_DEFAULT;
    -+	if (cmdmode == MODE_LONG)
    -+		shown_fields = FIELD_LONG_DEFAULT;
    -+	return 1;
    -+}
     +
      static int show_recursive(const char *base, size_t baselen, const char *pathname)
      {
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     +{
     +	size_t baselen = base->len;
     +
    -+	if (shown_fields & FIELD_SIZE) {
    ++	if (cmdmode == MODE_LONG) {
     +		char size_text[24];
     +		if (type == OBJ_BLOB) {
     +			unsigned long size;
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -			printf("%06o %s %s\t", mode, type_name(type),
     -			       find_unique_abbrev(oid, abbrev));
     -		}
    -+	if (shown_fields == FIELD_PATH_NAME) {
    ++	if (cmdmode == MODE_NAME_ONLY) {
     +		baselen = base->len;
     +		strbuf_addstr(base, pathname);
     +		write_name_quoted_relative(base->buf,
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -				   stdout, line_termination);
     -	strbuf_setlen(base, baselen);
     +
    -+	if (shown_fields >= FIELD_DEFAULT)
    -+		show_default(oid, type, pathname, mode, base);
    ++	if (cmdmode == MODE_LONG ||
    ++		(!ls_options || (ls_options & LS_RECURSIVE)
    ++		 || (ls_options & LS_SHOW_TREES)
    ++		 || (ls_options & LS_TREE_ONLY)))
    ++			 show_default(oid, type, pathname, mode, base);
     +
      	return recurse;
      }
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		OPT_SET_INT(0, "full-name", &chomp_prefix,
      			    N_("use full path names"), 0),
      		OPT_BOOL(0, "full-tree", &full_tree,
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	if (get_oid(argv[0], &oid))
    - 		die("Not a valid object name %s", argv[0]);
    - 
    -+	parse_shown_fields();
    -+
    - 	/*
    - 	 * show_recursive() rolls its own matching code and is
    - 	 * generally ignorant of 'struct pathspec'. The magic mask
 9:  85d0b0da1d ! 10:  5131c0dcff ls-tree: introduce struct "show_tree_data"
    @@ Commit message
         "show_tree()". This commit is a pre-prepared commit for supporting
         "--format" option and it does not affect any existing functionality.
     
    -    At the same time, we also move global "shown_fields" into "show_tree_data"
    -    , the purpose of this is to avoid use both global variable and pass-around
    -    struct.
    -
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
    @@ builtin/ls-tree.c: static int ls_options;
      static struct pathspec pathspec;
      static int chomp_prefix;
      static const char *ls_tree_prefix;
    --static unsigned int shown_fields;
    - #define FIELD_PATH_NAME 1
    - #define FIELD_SIZE (1 << 1)
    - #define FIELD_OBJECT_NAME (1 << 2)
    -@@ builtin/ls-tree.c: static unsigned int shown_fields;
    - #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
    - #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    - 
     +struct show_tree_data {
     +	unsigned mode;
     +	enum object_type type;
     +	const struct object_id *oid;
     +	const char *pathname;
     +	struct strbuf *base;
    -+	unsigned int shown_fields;
     +};
    -+
    + 
      static const  char * const ls_tree_usage[] = {
      	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
    - 	NULL
    -@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    - 	MODE_NAME_ONLY,
    - } cmdmode;
    - 
    --static int parse_shown_fields(void)
    -+static int parse_shown_fields(unsigned int *shown_fields)
    - {
    - 	if (cmdmode == MODE_NAME_ONLY) {
    --		shown_fields = FIELD_PATH_NAME;
    -+		*shown_fields = FIELD_PATH_NAME;
    - 		return 0;
    - 	}
    - 
    - 	if (!ls_options || (ls_options & LS_RECURSIVE)
    - 	    || (ls_options & LS_SHOW_TREES)
    - 	    || (ls_options & LS_TREE_ONLY))
    --		shown_fields = FIELD_DEFAULT;
    -+		*shown_fields = FIELD_DEFAULT;
    - 	if (cmdmode == MODE_LONG)
    --		shown_fields = FIELD_LONG_DEFAULT;
    -+		*shown_fields = FIELD_LONG_DEFAULT;
    - 	return 1;
    - }
    - 
     @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
      	return 0;
      }
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     -	size_t baselen = base->len;
     +	size_t baselen = data->base->len;
      
    --	if (shown_fields & FIELD_SIZE) {
    -+	if (data->shown_fields & FIELD_SIZE) {
    + 	if (cmdmode == MODE_LONG) {
      		char size_text[24];
     -		if (type == OBJ_BLOB) {
     +		if (data->type == OBJ_BLOB) {
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
      	int recurse = 0;
      	size_t baselen;
      	enum object_type type = object_type(mode);
    -+	unsigned int shown_fields = *(unsigned int *)context;
     +	struct show_tree_data data = {
     +		.mode = mode,
     +		.type = type,
     +		.oid = oid,
     +		.pathname = pathname,
     +		.base = base,
    -+		.shown_fields = shown_fields,
     +	};
      
      	if (type == OBJ_BLOB) {
      		if (ls_options & LS_TREE_ONLY)
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    - 		return recurse;
    - 	}
    - 
    --	if (shown_fields >= FIELD_DEFAULT)
    --		show_default(oid, type, pathname, mode, base);
    -+	if (shown_fields>= FIELD_DEFAULT)
    -+		show_default(&data);
    + 		(!ls_options || (ls_options & LS_RECURSIVE)
    + 		 || (ls_options & LS_SHOW_TREES)
    + 		 || (ls_options & LS_TREE_ONLY)))
    +-			 show_default(oid, type, pathname, mode, base);
    ++			 show_default(&data);
      
      	return recurse;
      }
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	struct object_id oid;
    - 	struct tree *tree;
    - 	int i, full_tree = 0;
    -+	unsigned int shown_fields = 0;
    - 	const struct option ls_tree_options[] = {
    - 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
    - 			LS_TREE_ONLY),
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	if (get_oid(argv[0], &oid))
    - 		die("Not a valid object name %s", argv[0]);
    - 
    --	parse_shown_fields();
    --
    - 	/*
    - 	 * show_recursive() rolls its own matching code and is
    - 	 * generally ignorant of 'struct pathspec'. The magic mask
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	tree = parse_tree_indirect(&oid);
    - 	if (!tree)
    - 		die("not a tree object");
    --	return !!read_tree(the_repository, tree,
    --			   &pathspec, show_tree, NULL);
    -+
    -+	parse_shown_fields(&shown_fields);
    -+
    -+	return !!read_tree(the_repository, tree, &pathspec, show_tree,
    -+			   &shown_fields);
    - }
10:  eddcf903ad = 11:  e4769b64c9 cocci: allow padding with `strbuf_addf()`
11:  7451242daa ! 12:  8b21b204a5 ls-tree: introduce "--format" option
    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl
      Part of the linkgit:git[1] suite
     
      ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static const char *ls_tree_prefix;
    - #define FIELD_MODE (1 << 4)
    - #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
    - #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)
    --
    +@@ builtin/ls-tree.c: static int ls_options;
    + static struct pathspec pathspec;
    + static int chomp_prefix;
    + static const char *ls_tree_prefix;
     +static const char *format;
    -+static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
    -+static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    -+static const char *name_only_format = "%(path)";
    ++
      struct show_tree_data {
      	unsigned mode;
      	enum object_type type;
    -@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    +@@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
    + };
    + 
    + static enum ls_tree_cmdmode {
    +-	MODE_LONG = 1,
    ++	MODE_DEFAULT = 0,
    ++	MODE_LONG,
      	MODE_NAME_ONLY,
      } cmdmode;
      
    @@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
     +	return len;
     +}
     +
    - static int parse_shown_fields(unsigned int *shown_fields)
    + static int show_recursive(const char *base, size_t baselen, const char *pathname)
      {
    - 	if (cmdmode == MODE_NAME_ONLY) {
    + 	int i;
     @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
      	return 0;
      }
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
      static int show_default(struct show_tree_data *data)
      {
      	size_t baselen = data->base->len;
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    +@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    + 	return recurse;
    + }
    + 
    ++struct ls_tree_cmdmode_to_fmt {
    ++	enum ls_tree_cmdmode mode;
    ++	const char *const fmt;
    ++};
    ++
    ++static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
    ++	{
    ++		.mode = MODE_DEFAULT,
    ++		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
    ++	},
    ++	{
    ++		.mode = MODE_LONG,
    ++		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
    ++	},
    ++	{
    ++		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
    ++		.fmt = "%(path)",
    ++	},
    ++	{ 0 },
    ++};
    ++
    + int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + {
    + 	struct object_id oid;
      	struct tree *tree;
      	int i, full_tree = 0;
    - 	unsigned int shown_fields = 0;
     +	read_tree_fn_t fn = show_tree;
      	const struct option ls_tree_options[] = {
      		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		usage_with_options(ls_tree_usage, ls_tree_options);
      	if (get_oid(argv[0], &oid))
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 
    - 	parse_shown_fields(&shown_fields);
    - 
    --	return !!read_tree(the_repository, tree, &pathspec, show_tree,
    --			   &shown_fields);
    + 	tree = parse_tree_indirect(&oid);
    + 	if (!tree)
    + 		die("not a tree object");
    +-	return !!read_tree(the_repository, tree,
    +-			   &pathspec, show_tree, NULL);
     +	/*
     +	 * The generic show_tree_fmt() is slower than show_tree(), so
     +	 * take the fast path if possible.
     +	 */
    -+	if (format && (!strcmp(format, default_format))) {
    -+		fn = show_tree;
    -+	} else if (format && (!strcmp(format, long_format))) {
    -+		shown_fields = shown_fields | FIELD_SIZE;
    -+		fn = show_tree;
    -+	} else if (format && (!strcmp(format, name_only_format))) {
    -+		shown_fields = FIELD_PATH_NAME;
    -+		fn = show_tree;
    -+	} else if (format)
    ++	if (format) {
    ++		struct ls_tree_cmdmode_to_fmt *m2f;
    ++
     +		fn = show_tree_fmt;
    ++		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
    ++			if (strcmp(format, m2f->fmt))
    ++				continue;
    ++
    ++			cmdmode = m2f->mode;
    ++			fn = show_tree;
    ++			break;
    ++		}
    ++	}
     +
    -+	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
    ++	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
      }
     
      ## t/t3104-ls-tree-format.sh (new) ##
12:  2bcd08ebd0 ! 13:  27fd1d9e2c ls-tree: support --object-only option for "git-ls-tree"
    @@ Commit message
                  Time (mean ± σ):      96.5 ms ±   2.5 ms    [User: 72.9 ms, System: 23.5 ms]
                  Range (min … max):    93.1 ms … 104.1 ms    31 runs
     
    +    Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## Documentation/git-ls-tree.txt ##
    @@ Documentation/git-ls-tree.txt: OPTIONS
      	Instead of showing the full 40-byte hexadecimal object
     
      ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static const char *format;
    - static const char *default_format = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
    - static const char *long_format = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    - static const char *name_only_format = "%(path)";
    -+static const char *object_only_format = "%(objectname)";
    +@@ builtin/ls-tree.c: static struct pathspec pathspec;
    + static int chomp_prefix;
    + static const char *ls_tree_prefix;
    + static const char *format;
    +-
      struct show_tree_data {
      	unsigned mode;
      	enum object_type type;
    -@@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
    - static enum ls_tree_cmdmode {
    - 	MODE_LONG = 1,
    +@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    + 	MODE_DEFAULT = 0,
    + 	MODE_LONG,
      	MODE_NAME_ONLY,
     +	MODE_OBJECT_ONLY,
      } cmdmode;
      
      static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
    -@@ builtin/ls-tree.c: static int parse_shown_fields(unsigned int *shown_fields)
    - 		*shown_fields = FIELD_PATH_NAME;
    - 		return 0;
    - 	}
    --
    -+	if (cmdmode == MODE_OBJECT_ONLY) {
    -+		*shown_fields = FIELD_OBJECT_NAME;
    -+		return 0;
    -+	}
    - 	if (!ls_options || (ls_options & LS_RECURSIVE)
    - 	    || (ls_options & LS_SHOW_TREES)
    - 	    || (ls_options & LS_TREE_ONLY))
     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
      			return recurse;
      	}
      
    -+	if (shown_fields == FIELD_OBJECT_NAME) {
    ++	if (cmdmode == MODE_OBJECT_ONLY) {
     +		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
     +		return recurse;
     +	}
     +
    - 	if (shown_fields == FIELD_PATH_NAME) {
    + 	if (cmdmode == MODE_NAME_ONLY) {
      		baselen = base->len;
      		strbuf_addstr(base, pathname);
    +@@ builtin/ls-tree.c: static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
    + 		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
    + 		.fmt = "%(path)",
    + 	},
    ++	{
    ++		.mode = MODE_OBJECT_ONLY,
    ++		.fmt = "%(objectname)",
    ++	},
    + 	{ 0 },
    + };
    + 
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
      			    MODE_NAME_ONLY),
      		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		OPT_SET_INT(0, "full-name", &chomp_prefix,
      			    N_("use full path names"), 0),
      		OPT_BOOL(0, "full-tree", &full_tree,
    -@@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	} else if (format && (!strcmp(format, name_only_format))) {
    - 		shown_fields = FIELD_PATH_NAME;
    - 		fn = show_tree;
    -+	} else if (format && (!strcmp(format, object_only_format))) {
    -+		shown_fields = FIELD_OBJECT_NAME;
    -+		fn = show_tree;
    - 	} else if (format)
    - 		fn = show_tree_fmt;
    - 
     
      ## t/t3103-ls-tree-misc.sh ##
     @@ t/t3103-ls-tree-misc.sh: test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
14:  89402a8518 ! 14:  1d5d9470ab ls-tree: detect and error on --name-only --name-status
    @@ Commit message
         Signed-off-by: Teng Long <dyroneteng@gmail.com>
     
      ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static const  char * const ls_tree_usage[] = {
    - static enum ls_tree_cmdmode {
    - 	MODE_LONG = 1,
    +@@ builtin/ls-tree.c: static enum ls_tree_cmdmode {
    + 	MODE_DEFAULT = 0,
    + 	MODE_LONG,
      	MODE_NAME_ONLY,
     +	MODE_NAME_STATUS,
      	MODE_OBJECT_ONLY,
15:  b8afca193a <  -:  ---------- ls-tree: remove FIELD_*, just use MODE_*
16:  010e3c0ece ! 15:  55a96e933b ls-tree: split up "fast path" callbacks
    @@ builtin/ls-tree.c: static int show_tree_fmt(const struct object_id *oid, struct
      	}
      
     -	if (cmdmode == MODE_LONG ||
    --	    (!ls_options || (ls_options & LS_RECURSIVE)
    --	     || (ls_options & LS_SHOW_TREES)
    --	     || (ls_options & LS_TREE_ONLY)))
    --		show_default(&data);
    +-		(!ls_options || (ls_options & LS_RECURSIVE)
    +-		 || (ls_options & LS_SHOW_TREES)
    +-		 || (ls_options & LS_TREE_ONLY)))
    +-			 show_default(&data);
     +	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
     +	       find_unique_abbrev(data.oid, abbrev), size_text);
     +	show_tree_common_default_long(base, pathname, data.base->len);
    @@ builtin/ls-tree.c: static int show_tree_fmt(const struct object_id *oid, struct
      
      int cmd_ls_tree(int argc, const char **argv, const char *prefix)
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    + 	struct object_id oid;
      	struct tree *tree;
      	int i, full_tree = 0;
    - 	unsigned int shown_fields = 0;
     -	read_tree_fn_t fn = show_tree;
     +	read_tree_fn_t fn = NULL;
      	const struct option ls_tree_options[] = {
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
     -			if (strcmp(format, m2f->fmt))
     -				continue;
     -
    -+	while (m2f++) {
    ++	while (m2f) {
     +		if (!m2f->fmt) {
     +			fn = format ? show_tree_fmt : show_tree_default;
     +		} else if (format && !strcmp(format, m2f->fmt)) {
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
     +		} else if (!format && cmdmode == m2f->mode) {
     +			fn = m2f->fn;
     +		} else {
    ++			m2f++;
     +			continue;
      		}
     +		break;
      	}
      
    - 	return !!read_tree(the_repository, tree, &pathspec, fn, &shown_fields);
    + 	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 01/15] ls-tree tests: add tests for --name-status

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The --name-status synonym for --name-only added in c639a5548a5 (ls-tree: --name-only, 2005-12-01) had no tests, let's make sure it works the same way as its sibling.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 t/t3101-ls-tree-dirname.sh | 55 ++++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 26 deletions(-)
Show changes to t/t3101-ls-tree-dirname.sh +29 −26
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 05fde64225..217006d1bf 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -201,31 +201,34 @@ EOF
 	test_cmp expected check
 '
 
-test_expect_success 'ls-tree --name-only' '
-	git ls-tree --name-only $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0
-path1
-path2
-path3
-EOF
-	test_output
-'
-
-test_expect_success 'ls-tree --name-only -r' '
-	git ls-tree --name-only -r $tree >current &&
-	cat >expected <<\EOF &&
-1.txt
-2.txt
-path0/a/b/c/1.txt
-path1/b/c/1.txt
-path2/1.txt
-path3/1.txt
-path3/2.txt
-EOF
-	test_output
-'
+for opt in --name-only --name-status
+do
+	test_expect_success "ls-tree $opt" '
+		git ls-tree $opt $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0
+		path1
+		path2
+		path3
+		EOF
+		test_output
+	'
+
+	test_expect_success "ls-tree $opt -r" '
+		git ls-tree $opt -r $tree >current &&
+		cat >expected <<-\EOF &&
+		1.txt
+		2.txt
+		path0/a/b/c/1.txt
+		path1/b/c/1.txt
+		path2/1.txt
+		path3/1.txt
+		path3/2.txt
+		EOF
+		test_output
+	'
+done
 
 test_done
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 02/15] ls-tree: remove commented-out code

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Remove code added in f35a6d3bce7 (Teach core object handling functions about gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output flushing before fork(), 2008-08-04), and then finally ending up in its current form in d3bee161fef (tree.c: allow read_tree_recursive() to traverse gitlink entries, 2009-01-25). All while being commented-out!

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
Show changes to builtin/ls-tree.c +0 −9
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3a442631c7..5f7c84950c 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	const char *type = blob_type;
 
 	if (S_ISGITLINK(mode)) {
-		/*
-		 * Maybe we want to have some recursive version here?
-		 *
-		 * Something similar to this incomplete example:
-		 *
-		if (show_subprojects(base, baselen, pathname))
-			retval = READ_TREE_RECURSIVE;
-		 *
-		 */
 		type = commit_type;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 03/15] ls-tree: add missing braces to "else" arms

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add missing {} to the "else" arms in show_tree() per the CodingGuidelines.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
Show changes to builtin/ls-tree.c +4 −2
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5f7c84950c..0a28f32ccb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				else
 					xsnprintf(size_text, sizeof(size_text),
 						  "%"PRIuMAX, (uintmax_t)size);
-			} else
+			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
+			}
 			printf("%06o %s %s %7s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
-		} else
+		} else {
 			printf("%06o %s %s\t", mode, type,
 			       find_unique_abbrev(oid, abbrev));
+		}
 	}
 	baselen = base->len;
 	strbuf_addstr(base, pathname);
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 04/15] ls-tree: use "enum object_type", not {blob,tree,commit}_type

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Change the ls-tree.c code to use type_name() on the enum instead of using the string constants. This doesn't matter either way for performance, but makes this a bit easier to read as we'll no longer need a strcmp() here.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)
Show changes to builtin/ls-tree.c +6 −6
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 0a28f32ccb..3f0225b097 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int retval = 0;
 	int baselen;
-	const char *type = blob_type;
+	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-		type = commit_type;
+		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
 			retval = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
 				return retval;
 		}
-		type = tree_type;
+		type = OBJ_TREE;
 	}
 	else if (ls_options & LS_TREE_ONLY)
 		return 0;
@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
 			char size_text[24];
-			if (!strcmp(type, blob_type)) {
+			if (type == OBJ_BLOB) {
 				unsigned long size;
 				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
 					xsnprintf(size_text, sizeof(size_text),
@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			} else {
 				xsnprintf(size_text, sizeof(size_text), "-");
 			}
-			printf("%06o %s %s %7s\t", mode, type,
+			printf("%06o %s %s %7s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev),
 			       size_text);
 		} else {
-			printf("%06o %s %s\t", mode, type,
+			printf("%06o %s %s\t", mode, type_name(type),
 			       find_unique_abbrev(oid, abbrev));
 		}
 	}
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 05/15] ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The "struct strbuf"'s "len" member is a "size_t", not an "int", so let's change our corresponding types accordingly. This also changes the "len" and "speclen" variables, which are likewise used to store the return value of strlen(), which returns "size_t", not "int".

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to builtin/ls-tree.c +3 −3
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3f0225b097..eecc7482d5 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
-static int show_recursive(const char *base, int baselen, const char *pathname)
+static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
 
@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)
 
 	for (i = 0; i < pathspec.nr; i++) {
 		const char *spec = pathspec.items[i].match;
-		int len, speclen;
+		size_t len, speclen;
 
 		if (strncmp(base, spec, baselen))
 			continue;
@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
 	int retval = 0;
-	int baselen;
+	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
 	if (S_ISGITLINK(mode)) {
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 06/15] ls-tree: rename "retval" to "recurse" in "show_tree()"

The variable which "show_tree()" return is named "retval", a name that's a little hard to understand. The commit rename "retval" to "recurse" which is a more meaningful name than before in the context. We do not need to take a look at "read_tree_at()" in "tree.c" to make sure what does "retval" mean.

Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
Show changes to builtin/ls-tree.c +4 −4
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..ef8c414f61 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -64,7 +64,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
-	int retval = 0;
+	int recurse = 0;
 	size_t baselen;
 	enum object_type type = OBJ_BLOB;
 
@@ -72,9 +72,9 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		type = OBJ_COMMIT;
 	} else if (S_ISDIR(mode)) {
 		if (show_recursive(base->buf, base->len, pathname)) {
-			retval = READ_TREE_RECURSIVE;
+			recurse = READ_TREE_RECURSIVE;
 			if (!(ls_options & LS_SHOW_TREES))
-				return retval;
+				return recurse;
 		}
 		type = OBJ_TREE;
 	}
@@ -109,7 +109,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 				   chomp_prefix ? ls_tree_prefix : NULL,
 				   stdout, line_termination);
 	strbuf_setlen(base, baselen);
-	return retval;
+	return recurse;
 }
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 07/15] ls-tree: simplify nesting if/else logic in "show_tree()"

Use the object_type() function to determine the object type from the "mode" passed to us by read_tree(), instead of doing so with the S_*() macros.

Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyronetengb@gmail.com>
---
 builtin/ls-tree.c | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)
Show changes to builtin/ls-tree.c +9 −12
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index ef8c414f61..d4be71bad2 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -66,20 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 {
 	int recurse = 0;
 	size_t baselen;
-	enum object_type type = OBJ_BLOB;
+	enum object_type type = object_type(mode);
 
-	if (S_ISGITLINK(mode)) {
-		type = OBJ_COMMIT;
-	} else if (S_ISDIR(mode)) {
-		if (show_recursive(base->buf, base->len, pathname)) {
-			recurse = READ_TREE_RECURSIVE;
-			if (!(ls_options & LS_SHOW_TREES))
-				return recurse;
-		}
-		type = OBJ_TREE;
+	if (type == OBJ_BLOB) {
+		if (ls_options & LS_TREE_ONLY)
+			return 0;
+	} else if (type == OBJ_TREE &&
+		   show_recursive(base->buf, base->len, pathname)) {
+		recurse = READ_TREE_RECURSIVE;
+		if (!(ls_options & LS_SHOW_TREES))
+			return recurse;
 	}
-	else if (ls_options & LS_TREE_ONLY)
-		return 0;
 
 	if (!(ls_options & LS_NAME_ONLY)) {
 		if (ls_options & LS_SHOW_SIZE) {
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 08/15] ls-tree: fix "--name-only" and "--long" combined use bug

If we execute "git ls-tree" with combined "--name-only" and "--long" , only the pathname will be printed, the size is omitted (the original discoverer was Peff in [1]).

This commit fix this issue by using `OPT_CMDMODE()` instead to make both of them mutually exclusive.

[1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c       | 12 ++++++------
 t/t3103-ls-tree-misc.sh |  9 +++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)
Show changes to 2 files +15 −6

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index d4be71bad2..7be4c13228 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -123,12 +123,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_BIT('l', "long", &ls_options, N_("include object size"),
-			LS_SHOW_SIZE),
-		OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
-		OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"),
-			LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
+			    LS_SHOW_SIZE),
+		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
+			    LS_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d18ba1bd84..d9d7fa932f 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -23,4 +23,13 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 	test_must_fail git ls-tree -r HEAD
 '
 
+for opts in \
+	"--name-only --long" \
+	"--name-status --long"
+do
+	test_expect_success "usage: incompatible options: $opts" '
+		test_expect_code 129 git ls-tree $opts $tree
+    '
+done
+
 test_done
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 09/15] ls-tree: slightly refactor `show_tree()`

This is a non-functional change, we introduce an enum "ls_tree_cmdmode" then use it to mark which columns to output.

This has the advantage of making the show_tree logic simpler and more readable, as well as making it easier to extend new options (for example, if we want to add a "--object-only" option, we just need to add a similar "short-circuit logic in "show_tree()").

Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 98 +++++++++++++++++++++++++++++------------------
 1 file changed, 61 insertions(+), 37 deletions(-)
Show changes to builtin/ls-tree.c +61 −37
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 7be4c13228..173a534e92 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,10 +16,8 @@
 
 static int line_termination = '\n';
 #define LS_RECURSIVE 1
-#define LS_TREE_ONLY 2
-#define LS_SHOW_TREES 4
-#define LS_NAME_ONLY 8
-#define LS_SHOW_SIZE 16
+#define LS_TREE_ONLY (1 << 1)
+#define LS_SHOW_TREES (1 << 2)
 static int abbrev;
 static int ls_options;
 static struct pathspec pathspec;
@@ -31,6 +29,11 @@ static const  char * const ls_tree_usage[] = {
 	NULL
 };
 
+static enum ls_tree_cmdmode {
+	MODE_LONG = 1,
+	MODE_NAME_ONLY,
+} cmdmode;
+
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -61,6 +64,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_default(const struct object_id *oid, enum object_type type,
+			const char *pathname, unsigned mode,
+			struct strbuf *base)
+{
+	size_t baselen = base->len;
+
+	if (cmdmode == MODE_LONG) {
+		char size_text[24];
+		if (type == OBJ_BLOB) {
+			unsigned long size;
+			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+				xsnprintf(size_text, sizeof(size_text), "BAD");
+			else
+				xsnprintf(size_text, sizeof(size_text),
+					  "%" PRIuMAX, (uintmax_t)size);
+		} else {
+			xsnprintf(size_text, sizeof(size_text), "-");
+		}
+		printf("%06o %s %s %7s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev), size_text);
+	} else {
+		printf("%06o %s %s\t", mode, type_name(type),
+		find_unique_abbrev(oid, abbrev));
+	}
+	baselen = base->len;
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+	return 1;
+}
+
 static int show_tree(const struct object_id *oid, struct strbuf *base,
 		const char *pathname, unsigned mode, void *context)
 {
@@ -78,34 +114,22 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
-	if (!(ls_options & LS_NAME_ONLY)) {
-		if (ls_options & LS_SHOW_SIZE) {
-			char size_text[24];
-			if (type == OBJ_BLOB) {
-				unsigned long size;
-				if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
-					xsnprintf(size_text, sizeof(size_text),
-						  "BAD");
-				else
-					xsnprintf(size_text, sizeof(size_text),
-						  "%"PRIuMAX, (uintmax_t)size);
-			} else {
-				xsnprintf(size_text, sizeof(size_text), "-");
-			}
-			printf("%06o %s %s %7s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev),
-			       size_text);
-		} else {
-			printf("%06o %s %s\t", mode, type_name(type),
-			       find_unique_abbrev(oid, abbrev));
-		}
+	if (cmdmode == MODE_NAME_ONLY) {
+		baselen = base->len;
+		strbuf_addstr(base, pathname);
+		write_name_quoted_relative(base->buf,
+					   chomp_prefix ? ls_tree_prefix : NULL,
+					   stdout, line_termination);
+		strbuf_setlen(base, baselen);
+		return recurse;
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL,
-				   stdout, line_termination);
-	strbuf_setlen(base, baselen);
+
+	if (cmdmode == MODE_LONG ||
+		(!ls_options || (ls_options & LS_RECURSIVE)
+		 || (ls_options & LS_SHOW_TREES)
+		 || (ls_options & LS_TREE_ONLY)))
+			 show_default(oid, type, pathname, mode, base);
+
 	return recurse;
 }
 
@@ -123,12 +147,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			LS_SHOW_TREES),
 		OPT_SET_INT('z', NULL, &line_termination,
 			    N_("terminate entries with NUL byte"), 0),
-		OPT_CMDMODE('l', "long", &ls_options, N_("include object size"),
-			    LS_SHOW_SIZE),
-		OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
-		OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"),
-			    LS_NAME_ONLY),
+		OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"),
+			    MODE_LONG),
+		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
+			    MODE_NAME_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 10/15] ls-tree: introduce struct "show_tree_data"

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

"show_tree_data" is a struct that packages the necessary fields for "show_tree()". This commit is a pre-prepared commit for supporting "--format" option and it does not affect any existing functionality.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 42 +++++++++++++++++++++++++++---------------
 1 file changed, 27 insertions(+), 15 deletions(-)
Show changes to builtin/ls-tree.c +27 −15
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 173a534e92..edc8bf53a6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -23,6 +23,13 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
+struct show_tree_data {
+	unsigned mode;
+	enum object_type type;
+	const struct object_id *oid;
+	const char *pathname;
+	struct strbuf *base;
+};
 
 static const  char * const ls_tree_usage[] = {
 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
@@ -64,17 +71,15 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
-static int show_default(const struct object_id *oid, enum object_type type,
-			const char *pathname, unsigned mode,
-			struct strbuf *base)
+static int show_default(struct show_tree_data *data)
 {
-	size_t baselen = base->len;
+	size_t baselen = data->base->len;
 
 	if (cmdmode == MODE_LONG) {
 		char size_text[24];
-		if (type == OBJ_BLOB) {
+		if (data->type == OBJ_BLOB) {
 			unsigned long size;
-			if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)
+			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
 				xsnprintf(size_text, sizeof(size_text), "BAD");
 			else
 				xsnprintf(size_text, sizeof(size_text),
@@ -82,18 +87,18 @@ static int show_default(const struct object_id *oid, enum object_type type,
 		} else {
 			xsnprintf(size_text, sizeof(size_text), "-");
 		}
-		printf("%06o %s %s %7s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev), size_text);
+		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev), size_text);
 	} else {
-		printf("%06o %s %s\t", mode, type_name(type),
-		find_unique_abbrev(oid, abbrev));
+		printf("%06o %s %s\t", data->mode, type_name(data->type),
+		find_unique_abbrev(data->oid, abbrev));
 	}
-	baselen = base->len;
-	strbuf_addstr(base, pathname);
-	write_name_quoted_relative(base->buf,
+	baselen = data->base->len;
+	strbuf_addstr(data->base, data->pathname);
+	write_name_quoted_relative(data->base->buf,
 				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
 				   line_termination);
-	strbuf_setlen(base, baselen);
+	strbuf_setlen(data->base, baselen);
 	return 1;
 }
 
@@ -103,6 +108,13 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	int recurse = 0;
 	size_t baselen;
 	enum object_type type = object_type(mode);
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
@@ -128,7 +140,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 		(!ls_options || (ls_options & LS_RECURSIVE)
 		 || (ls_options & LS_SHOW_TREES)
 		 || (ls_options & LS_TREE_ONLY)))
-			 show_default(oid, type, pathname, mode, base);
+			 show_default(&data);
 
 	return recurse;
 }
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 11/15] cocci: allow padding with `strbuf_addf()`

From: Johannes Schindelin <johannes.schindelin@gmx.de>

A convenient way to pad strings is to use something like `strbuf_addf(&buf, "%20s", "Hello, world!")`.

However, the Coccinelle rule that forbids a format `"%s"` with a constant string argument cast too wide a net, and also forbade such padding.

The original rule was introduced by commit:
    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
Show changes to contrib/coccinelle/strbuf.cocci +3 −3
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
index d9ada69b43..0970d98ad7 100644
--- a/contrib/coccinelle/strbuf.cocci
+++ b/contrib/coccinelle/strbuf.cocci
@@ -15,7 +15,7 @@ constant fmt !~ "%";
 @@
 expression E;
 struct strbuf SB;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SB.buf);
 + strbuf_addbuf(E, &SB);
@@ -23,7 +23,7 @@ format F =~ "s";
 @@
 expression E;
 struct strbuf *SBP;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E, "%@F@", SBP->buf);
 + strbuf_addbuf(E, SBP);
@@ -44,7 +44,7 @@ struct strbuf *SBP;
 
 @@
 expression E1, E2;
-format F =~ "s";
+format F =~ "^s$";
 @@
 - strbuf_addf(E1, "%@F@", E2);
 + strbuf_addstr(E1, E2);
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 12/15] ls-tree: introduce "--format" option

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Add a --format option to ls-tree. It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths.

Rather than add --type-only, --object-only etc. we can just support a --format using a strbuf_expand() similar to "for-each-ref --format". We might still add such options in the future for convenience.

The --format implementation is slower than the existing code, but this change does not cause any performance regressions. We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided.

I.e. something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout.

The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community [1].

In [1] there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to ensure that we had test coverage for passing tests that would otherwise use show_tree() through show_tree_fmt(), and thus that the formatting mechanism could handle all the same cases as the non-formatting options.

Somewhere in subsequent re-rolls of that we seem to have drifted away from what the goal of these tests should be. We're trying to ensure correctness of show_tree_fmt(). We can't tell if we "hit [the] fast-path" here, and instead of having an explicit test for that, we can just add it to something our "test_ls_tree_format" tests for.

Here is the statistics about performance tests:
1. Default format (hitten the builtin formats):
    "git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD
    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]
    Range (min … max):    99.2 ms … 113.2 ms    28 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD
    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]
    Range (min … max):   100.2 ms … 110.5 ms    29 runs
2. Default format includes object size (hitten the builtin formats):
    "git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"
    $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD"
    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD
    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]
    Range (min … max):   327.5 ms … 348.4 ms    10 runs
    $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD"
    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD
    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]
    Range (min … max):   328.8 ms … 349.4 ms    10 runs
Links:
	[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
	[2] https://lore.kernel.org/git/cb717d08be87e3239117c6c667cb32caabaad33d.1646390152.git.dyroneteng@gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |  59 ++++++++++++-
 builtin/ls-tree.c             | 153 +++++++++++++++++++++++++++++++++-
 t/t3104-ls-tree-format.sh     |  67 +++++++++++++++
 3 files changed, 272 insertions(+), 7 deletions(-)
 create mode 100755 t/t3104-ls-tree-format.sh
Show changes to 3 files +272 −7

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..db29a9efb5 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]
+	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -74,6 +74,16 @@ OPTIONS
 	Do not limit the listing to the current working directory.
 	Implies --full-name.
 
+--format=<format>::
+	A string that interpolates `%(fieldname)` from the result
+	being shown. It also interpolates `%%` to `%`, and
+	`%xx` where `xx` are hex digits interpolates to character
+	with hex code `xx`; for example `%00` interpolates to
+	`\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF).
+	When specified, `--format` cannot be combined with other
+	format-altering options, including `--long`, `--name-only`
+	and `--object-only`.
+
 [<path>...]::
 	When paths are given, show them (note that this isn't really raw
 	pathnames, but rather a list of patterns to match).  Otherwise
@@ -82,16 +92,29 @@ OPTIONS
 
 Output Format
 -------------
-        <mode> SP <type> SP <object> TAB <file>
+
+The output format of `ls-tree` is determined by either the `--format`
+option, or other format-altering options such as `--name-only` etc.
+(see `--format` above).
+
+The use of certain `--format` directives is equivalent to using those
+options, but invoking the full formatting machinery can be slower than
+using an appropriate formatting option.
+
+In cases where the `--format` would exactly map to an existing option
+`ls-tree` will use the appropriate faster path. Thus the default format
+is equivalent to:
+
+        %(objectmode) %(objecttype) %(objectname)%x09%(path)
 
 This output format is compatible with what `--index-info --stdin` of
 'git update-index' expects.
 
 When the `-l` option is used, format changes to
 
-        <mode> SP <type> SP <object> SP <object size> TAB <file>
+        %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)
 
-Object size identified by <object> is given in bytes, and right-justified
+Object size identified by <objectname> is given in bytes, and right-justified
 with minimum width of 7 characters.  Object size is given only for blobs
 (file) entries; for other entries `-` character is used in place of size.
 
@@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath`
 (see linkgit:git-config[1]).  Using `-z` the filename is output
 verbatim and the line is terminated by a NUL byte.
 
+Customized format:
+
+It is possible to print in a custom format by using the `--format` option,
+which is able to interpolate different fields using a `%(fieldname)` notation.
+For example, if you only care about the "objectname" and "path" fields, you
+can execute with a specific "--format" like
+
+        git ls-tree --format='%(objectname) %(path)' <tree-ish>
+
+FIELD NAMES
+-----------
+
+Various values from structured fields can be used to interpolate
+into the resulting output. For each outputing line, the following
+names can be used:
+
+objectmode::
+	The mode of the object.
+objecttype::
+	The type of the object (`blob` or `tree`).
+objectname::
+	The name of the object.
+objectsize[:padded]::
+	The size of the object ("-" if it's a tree).
+	It also supports a padded format of size with "%(size:padded)".
+path::
+	The pathname of the object.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index edc8bf53a6..8a1bf2fa4d 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -23,6 +23,8 @@ static int ls_options;
 static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
+static const char *format;
+
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -37,10 +39,77 @@ static const  char * const ls_tree_usage[] = {
 };
 
 static enum ls_tree_cmdmode {
-	MODE_LONG = 1,
+	MODE_DEFAULT = 0,
+	MODE_LONG,
 	MODE_NAME_ONLY,
 } cmdmode;
 
+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
+			      const enum object_type type, unsigned int padded)
+{
+	if (type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, oid, &size) < 0)
+			die(_("could not get object info about '%s'"),
+			    oid_to_hex(oid));
+		if (padded)
+			strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size);
+		else
+			strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size);
+	} else if (padded) {
+		strbuf_addf(line, "%7s", "-");
+	} else {
+		strbuf_addstr(line, "-");
+	}
+}
+
+static size_t expand_show_tree(struct strbuf *sb, const char *start,
+			       void *context)
+{
+	struct show_tree_data *data = context;
+	const char *end;
+	const char *p;
+	unsigned int errlen;
+	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
+
+	if (len)
+		return len;
+	if (*start != '(')
+		die(_("bad ls-tree format: element '%s' does not start with '('"), start);
+
+	end = strchr(start + 1, ')');
+	if (!end)
+		die(_("bad ls-tree format: element '%s' does not end in ')'"), start);
+
+	len = end - start + 1;
+	if (skip_prefix(start, "(objectmode)", &p)) {
+		strbuf_addf(sb, "%06o", data->mode);
+	} else if (skip_prefix(start, "(objecttype)", &p)) {
+		strbuf_addstr(sb, type_name(data->type));
+	} else if (skip_prefix(start, "(objectsize:padded)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 1);
+	} else if (skip_prefix(start, "(objectsize)", &p)) {
+		expand_objectsize(sb, data->oid, data->type, 0);
+	} else if (skip_prefix(start, "(objectname)", &p)) {
+		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
+	} else if (skip_prefix(start, "(path)", &p)) {
+		const char *name = data->base->buf;
+		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
+		struct strbuf quoted = STRBUF_INIT;
+		struct strbuf sbuf = STRBUF_INIT;
+		strbuf_addstr(data->base, data->pathname);
+		name = relative_path(data->base->buf, prefix, &sbuf);
+		quote_c_style(name, &quoted, NULL, 0);
+		strbuf_addbuf(sb, &quoted);
+		strbuf_release(&sbuf);
+		strbuf_release(&quoted);
+	} else {
+		errlen = (unsigned long)len;
+		die(_("bad ls-tree format: %%%.*s"), errlen, start);
+	}
+	return len;
+}
+
 static int show_recursive(const char *base, size_t baselen, const char *pathname)
 {
 	int i;
@@ -71,6 +140,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
+			 const char *pathname, unsigned mode, void *context)
+{
+	size_t baselen;
+	int recurse = 0;
+	struct strbuf sb = STRBUF_INIT;
+	enum object_type type = object_type(mode);
+
+	struct show_tree_data data = {
+		.mode = mode,
+		.type = type,
+		.oid = oid,
+		.pathname = pathname,
+		.base = base,
+	};
+
+	if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
+		recurse = READ_TREE_RECURSIVE;
+	if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
+		return recurse;
+	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
+		return 0;
+
+	baselen = base->len;
+	strbuf_expand(&sb, format, expand_show_tree, &data);
+	strbuf_addch(&sb, line_termination);
+	fwrite(sb.buf, sb.len, 1, stdout);
+	strbuf_release(&sb);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
 static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
@@ -145,11 +246,33 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	return recurse;
 }
 
+struct ls_tree_cmdmode_to_fmt {
+	enum ls_tree_cmdmode mode;
+	const char *const fmt;
+};
+
+static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
+	{
+		.mode = MODE_DEFAULT,
+		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+	},
+	{
+		.mode = MODE_LONG,
+		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+	},
+	{
+		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
+		.fmt = "%(path)",
+	},
+	{ 0 },
+};
+
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 {
 	struct object_id oid;
 	struct tree *tree;
 	int i, full_tree = 0;
+	read_tree_fn_t fn = show_tree;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -170,6 +293,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_BOOL(0, "full-tree", &full_tree,
 			 N_("list entire tree; not just current directory "
 			    "(implies --full-name)")),
+		OPT_STRING_F(0, "format", &format, N_("format"),
+					 N_("format to use for the output"),
+					 PARSE_OPT_NONEG),
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
@@ -190,6 +316,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
 		ls_options |= LS_SHOW_TREES;
 
+	if (format && cmdmode)
+		usage_msg_opt(
+			_("--format can't be combined with other format-altering options"),
+			ls_tree_usage, ls_tree_options);
 	if (argc < 1)
 		usage_with_options(ls_tree_usage, ls_tree_options);
 	if (get_oid(argv[0], &oid))
@@ -211,6 +341,23 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	tree = parse_tree_indirect(&oid);
 	if (!tree)
 		die("not a tree object");
-	return !!read_tree(the_repository, tree,
-			   &pathspec, show_tree, NULL);
+	/*
+	 * The generic show_tree_fmt() is slower than show_tree(), so
+	 * take the fast path if possible.
+	 */
+	if (format) {
+		struct ls_tree_cmdmode_to_fmt *m2f;
+
+		fn = show_tree_fmt;
+		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
+			if (strcmp(format, m2f->fmt))
+				continue;
+
+			cmdmode = m2f->mode;
+			fn = show_tree;
+			break;
+		}
+	}
+
+	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
 }
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
new file mode 100755
index 0000000000..7f1eb699d3
--- /dev/null
+++ b/t/t3104-ls-tree-format.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+test_description='ls-tree --format'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	fmtopts=$3 &&
+	shift 2 &&
+
+	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
+		git ls-tree $opts -r HEAD >expect &&
+		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
+		test_cmp expect actual
+	'
+
+	test_expect_success "ls-tree '--format=<$format>' on optimized v.s. non-optimized path" '
+		git ls-tree --format="$format" -r $fmtopts HEAD >expect &&
+		git ls-tree --format="> $format" -r $fmtopts HEAD >actual.raw &&
+		sed "s/^> //" >actual <actual.raw &&
+		test_cmp expect actual
+	'
+}
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	""
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
+	"--long"
+
+test_ls_tree_format \
+	"%(path)" \
+	"--name-only"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"-t" \
+	"-t"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-name" \
+	"--full-name"
+
+test_ls_tree_format \
+	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
+	"--full-tree" \
+	"--full-tree"
+
+test_done
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 13/15] ls-tree: support --object-only option for "git-ls-tree"

'--object-only' is an alias for '--format=%(objectname)'. It cannot be used together other format-altering options like '--name-only', '--long' or '--format', they are mutually exclusive.

The "--name-only" option outputs <filepath> only. Likewise, <objectName> is another high frequency used field, so implement '--object-only' option will bring intuitive and clear semantics for this scenario. Using '--format=%(objectname)' we can achieve a similar effect, but the former is with a lower learning cost(without knowing the format requirement of '--format' option).

Even so, if a user is prefer to use "--format=%(objectname)", this is entirely welcome because they are not only equivalent in function, but also have almost identical performance. The reason is this commit also add the specific of "--format=%(objectname)" to the current fast-pathes (builtin formats) to avoid running unnecessary parsing mechanisms.

The following performance benchmarks are based on torvalds/linux.git:
  When hit the fast-path:
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --object-only HEAD
        Time (mean ± σ):      83.6 ms ±   2.0 ms    [User: 59.4 ms, System: 24.1 ms]
        Range (min … max):    80.4 ms …  87.2 ms    35 runs
      Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(objectname)' HEAD
        Time (mean ± σ):      84.1 ms ±   1.8 ms    [User: 61.7 ms, System: 22.3 ms]
        Range (min … max):    80.9 ms …  87.5 ms    35 runs
  But for a customized format, it will be slower:
       Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='oid: %(objectname)' HEAD
         Time (mean ± σ):      96.5 ms ±   2.5 ms    [User: 72.9 ms, System: 23.5 ms]
  	 Range (min … max):    93.1 ms … 104.1 ms    31 runs
Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt | 11 ++++++++++-
 builtin/ls-tree.c             | 13 ++++++++++++-
 t/t3103-ls-tree-misc.sh       |  7 +++++--
 t/t3104-ls-tree-format.sh     |  9 +++++++++
 4 files changed, 36 insertions(+), 4 deletions(-)
Show changes to 4 files +36 −4

Documentation/git-ls-tree.txt, builtin/ls-tree.c, t/t3103-ls-tree-misc.sh, t/t3104-ls-tree-format.sh

diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db29a9efb5..1aea998e72 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git ls-tree' [-d] [-r] [-t] [-l] [-z]
-	    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
+	    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]
 	    <tree-ish> [<path>...]
 
 DESCRIPTION
@@ -59,6 +59,15 @@ OPTIONS
 --name-only::
 --name-status::
 	List only filenames (instead of the "long" output), one per line.
+	Cannot be combined with `--object-only`.
+
+--object-only::
+	List only names of the objects, one per line. Cannot be combined
+	with `--name-only` or `--name-status`.
+	This is equivalent to specifying `--format='%(objectname)'`, but
+	for both this option and that exact format the command takes a
+	hand-optimized codepath instead of going through the generic
+	formatting mechanism.
 
 --abbrev[=<n>]::
 	Instead of showing the full 40-byte hexadecimal object
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 8a1bf2fa4d..3474f8c3d6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -24,7 +24,6 @@ static struct pathspec pathspec;
 static int chomp_prefix;
 static const char *ls_tree_prefix;
 static const char *format;
-
 struct show_tree_data {
 	unsigned mode;
 	enum object_type type;
@@ -42,6 +41,7 @@ static enum ls_tree_cmdmode {
 	MODE_DEFAULT = 0,
 	MODE_LONG,
 	MODE_NAME_ONLY,
+	MODE_OBJECT_ONLY,
 } cmdmode;
 
 static void expand_objectsize(struct strbuf *line, const struct object_id *oid,
@@ -227,6 +227,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 			return recurse;
 	}
 
+	if (cmdmode == MODE_OBJECT_ONLY) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		return recurse;
+	}
+
 	if (cmdmode == MODE_NAME_ONLY) {
 		baselen = base->len;
 		strbuf_addstr(base, pathname);
@@ -264,6 +269,10 @@ static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
 		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
 		.fmt = "%(path)",
 	},
+	{
+		.mode = MODE_OBJECT_ONLY,
+		.fmt = "%(objectname)",
+	},
 	{ 0 },
 };
 
@@ -288,6 +297,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
+		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
+			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
 			    N_("use full path names"), 0),
 		OPT_BOOL(0, "full-tree", &full_tree,
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d9d7fa932f..d979c0df5d 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -25,11 +25,14 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 
 for opts in \
 	"--name-only --long" \
-	"--name-status --long"
+	"--name-status --long" \
+	"--name-only --object-only" \
+	"--name-status --object-only" \
+	"--object-only --long" \
+	"--object-only --format"
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
     '
 done
-
 test_done
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 7f1eb699d3..0769a933d6 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -49,6 +49,15 @@ test_ls_tree_format \
 	"%(path)" \
 	"--name-only"
 
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only"
+
+test_ls_tree_format \
+	"%(objectname)" \
+	"--object-only --abbrev" \
+	"--abbrev"
+
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"-t" \
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 14/15] ls-tree: detect and error on --name-only --name-status

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

The --name-only and --name-status options are synonyms, but let's detect and error if both are provided.

In addition let's add explicit --format tests for the combination of these various options.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c       | 11 ++++++++++-
 t/t3103-ls-tree-misc.sh | 15 +++++++++------
 2 files changed, 19 insertions(+), 7 deletions(-)
Show changes to 2 files +19 −7

builtin/ls-tree.c, t/t3103-ls-tree-misc.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 3474f8c3d6..6550f27dfe 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -41,6 +41,7 @@ static enum ls_tree_cmdmode {
 	MODE_DEFAULT = 0,
 	MODE_LONG,
 	MODE_NAME_ONLY,
+	MODE_NAME_STATUS,
 	MODE_OBJECT_ONLY,
 } cmdmode;
 
@@ -296,7 +297,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"),
 			    MODE_NAME_ONLY),
 		OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"),
-			    MODE_NAME_ONLY),
+			    MODE_NAME_STATUS),
 		OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"),
 			    MODE_OBJECT_ONLY),
 		OPT_SET_INT(0, "full-name", &chomp_prefix,
@@ -322,6 +323,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		ls_tree_prefix = prefix = NULL;
 		chomp_prefix = 0;
 	}
+	/*
+	 * We wanted to detect conflicts between --name-only and
+	 * --name-status, but once we're done with that subsequent
+	 * code should only need to check the primary name.
+	 */
+	if (cmdmode == MODE_NAME_STATUS)
+		cmdmode = MODE_NAME_ONLY;
+
 	/* -d -r should imply -t, but -d by itself should not have to. */
 	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
 	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh
index d979c0df5d..7454ebbb00 100755
--- a/t/t3103-ls-tree-misc.sh
+++ b/t/t3103-ls-tree-misc.sh
@@ -24,15 +24,18 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '
 '
 
 for opts in \
-	"--name-only --long" \
-	"--name-status --long" \
-	"--name-only --object-only" \
+	"--long --name-only" \
+	"--name-only --name-status" \
 	"--name-status --object-only" \
-	"--object-only --long" \
-	"--object-only --format"
+	"--object-only --long" 
 do
 	test_expect_success "usage: incompatible options: $opts" '
 		test_expect_code 129 git ls-tree $opts $tree
-    '
+	'
+
+	one_opt=$(echo "$opts" | cut -d' '  -f1)
+	test_expect_success "usage: incompatible options: $one_opt and --format" '
+		test_expect_code 129 git ls-tree $one_opt --format=fmt $tree
+	'
 done
 test_done
-- 
2.34.1.404.g92931a4a2c.dirty
Teng Long· Mar 23, 2022, 09:13 UTC · re: Teng Long · lore

[PATCH v14 15/15] ls-tree: split up "fast path" callbacks

From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>

Make the various if/else in the callbacks for the "fast path" a lot easier to read by just using common functions for the parts that are common, and have per-format callbacks for those parts that are different.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
Signed-off-by: Teng Long <dyroneteng@gmail.com>
---
 builtin/ls-tree.c | 199 +++++++++++++++++++++++++++++-----------------
 1 file changed, 125 insertions(+), 74 deletions(-)
Show changes to builtin/ls-tree.c +125 −74
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 6550f27dfe..44a91cf9d0 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -173,108 +173,157 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
 	return recurse;
 }
 
-static int show_default(struct show_tree_data *data)
+static int show_tree_common(struct show_tree_data *data, int *recurse,
+			    const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode)
 {
-	size_t baselen = data->base->len;
-
-	if (cmdmode == MODE_LONG) {
-		char size_text[24];
-		if (data->type == OBJ_BLOB) {
-			unsigned long size;
-			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
-				xsnprintf(size_text, sizeof(size_text), "BAD");
-			else
-				xsnprintf(size_text, sizeof(size_text),
-					  "%" PRIuMAX, (uintmax_t)size);
-		} else {
-			xsnprintf(size_text, sizeof(size_text), "-");
-		}
-		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev), size_text);
-	} else {
-		printf("%06o %s %s\t", data->mode, type_name(data->type),
-		find_unique_abbrev(data->oid, abbrev));
-	}
-	baselen = data->base->len;
-	strbuf_addstr(data->base, data->pathname);
-	write_name_quoted_relative(data->base->buf,
-				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
-				   line_termination);
-	strbuf_setlen(data->base, baselen);
-	return 1;
-}
-
-static int show_tree(const struct object_id *oid, struct strbuf *base,
-		const char *pathname, unsigned mode, void *context)
-{
-	int recurse = 0;
-	size_t baselen;
 	enum object_type type = object_type(mode);
-	struct show_tree_data data = {
-		.mode = mode,
-		.type = type,
-		.oid = oid,
-		.pathname = pathname,
-		.base = base,
-	};
+	int ret = -1;
+
+	*recurse = 0;
+	data->mode = mode;
+	data->type = type;
+	data->oid = oid;
+	data->pathname = pathname;
+	data->base = base;
 
 	if (type == OBJ_BLOB) {
 		if (ls_options & LS_TREE_ONLY)
-			return 0;
+			ret = 0;
 	} else if (type == OBJ_TREE &&
 		   show_recursive(base->buf, base->len, pathname)) {
-		recurse = READ_TREE_RECURSIVE;
+		*recurse = READ_TREE_RECURSIVE;
 		if (!(ls_options & LS_SHOW_TREES))
-			return recurse;
+			ret = *recurse;
 	}
 
-	if (cmdmode == MODE_OBJECT_ONLY) {
-		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
-		return recurse;
-	}
+	return ret;
+}
 
-	if (cmdmode == MODE_NAME_ONLY) {
-		baselen = base->len;
-		strbuf_addstr(base, pathname);
-		write_name_quoted_relative(base->buf,
-					   chomp_prefix ? ls_tree_prefix : NULL,
-					   stdout, line_termination);
-		strbuf_setlen(base, baselen);
-		return recurse;
+static void show_tree_common_default_long(struct strbuf *base,
+					  const char *pathname,
+					  const size_t baselen)
+{
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
+				   line_termination);
+	strbuf_setlen(base, baselen);
+}
+
+static int show_tree_default(const struct object_id *oid, struct strbuf *base,
+			     const char *pathname, unsigned mode,
+			     void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%06o %s %s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev));
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return recurse;
+}
+
+static int show_tree_long(const struct object_id *oid, struct strbuf *base,
+			  const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+	char size_text[24];
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	if (data.type == OBJ_BLOB) {
+		unsigned long size;
+		if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD)
+			xsnprintf(size_text, sizeof(size_text), "BAD");
+		else
+			xsnprintf(size_text, sizeof(size_text),
+				  "%" PRIuMAX, (uintmax_t)size);
+	} else {
+		xsnprintf(size_text, sizeof(size_text), "-");
 	}
 
-	if (cmdmode == MODE_LONG ||
-		(!ls_options || (ls_options & LS_RECURSIVE)
-		 || (ls_options & LS_SHOW_TREES)
-		 || (ls_options & LS_TREE_ONLY)))
-			 show_default(&data);
+	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
+	       find_unique_abbrev(data.oid, abbrev), size_text);
+	show_tree_common_default_long(base, pathname, data.base->len);
+	return 1;
+}
 
+static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
+			       const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	const size_t baselen = base->len;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	strbuf_addstr(base, pathname);
+	write_name_quoted_relative(base->buf,
+				   chomp_prefix ? ls_tree_prefix : NULL,
+				   stdout, line_termination);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
+static int show_tree_object(const struct object_id *oid, struct strbuf *base,
+			    const char *pathname, unsigned mode, void *context)
+{
+	int early;
+	int recurse;
+	struct show_tree_data data = { 0 };
+
+	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
+	if (early >= 0)
+		return early;
+
+	printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
 	return recurse;
 }
 
 struct ls_tree_cmdmode_to_fmt {
 	enum ls_tree_cmdmode mode;
 	const char *const fmt;
+	read_tree_fn_t fn;
 };
 
 static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = {
 	{
 		.mode = MODE_DEFAULT,
 		.fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)",
+		.fn = show_tree_default,
 	},
 	{
 		.mode = MODE_LONG,
 		.fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)",
+		.fn = show_tree_long,
 	},
 	{
 		.mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */
 		.fmt = "%(path)",
+		.fn = show_tree_name_only,
 	},
 	{
 		.mode = MODE_OBJECT_ONLY,
 		.fmt = "%(objectname)",
+		.fn = show_tree_object
+	},
+	{
+		/* fallback */
+		.fn = show_tree_default,
 	},
-	{ 0 },
 };
 
 int cmd_ls_tree(int argc, const char **argv, const char *prefix)
@@ -282,7 +331,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	struct object_id oid;
 	struct tree *tree;
 	int i, full_tree = 0;
-	read_tree_fn_t fn = show_tree;
+	read_tree_fn_t fn = NULL;
 	const struct option ls_tree_options[] = {
 		OPT_BIT('d', NULL, &ls_options, N_("only show trees"),
 			LS_TREE_ONLY),
@@ -311,6 +360,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 		OPT__ABBREV(&abbrev),
 		OPT_END()
 	};
+	struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;
 
 	git_config(git_default_config, NULL);
 	ls_tree_prefix = prefix;
@@ -365,18 +415,19 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 	 * The generic show_tree_fmt() is slower than show_tree(), so
 	 * take the fast path if possible.
 	 */
-	if (format) {
-		struct ls_tree_cmdmode_to_fmt *m2f;
-
-		fn = show_tree_fmt;
-		for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) {
-			if (strcmp(format, m2f->fmt))
-				continue;
-
+	while (m2f) {
+		if (!m2f->fmt) {
+			fn = format ? show_tree_fmt : show_tree_default;
+		} else if (format && !strcmp(format, m2f->fmt)) {
 			cmdmode = m2f->mode;
-			fn = show_tree;
-			break;
+			fn = m2f->fn;
+		} else if (!format && cmdmode == m2f->mode) {
+			fn = m2f->fn;
+		} else {
+			m2f++;
+			continue;
 		}
+		break;
 	}
 
 	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
-- 
2.34.1.404.g92931a4a2c.dirty
Josh Steadmon· Apr 4, 2022, 20:06 UTC · re: Teng Long · lore

Re: [PATCH v14 15/15] ls-tree: split up "fast path" callbacks

On 2022.03.23 17:13, Teng Long wrote:
Show 167 quoted lines
> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> 
> Make the various if/else in the callbacks for the "fast path" a lot
> easier to read by just using common functions for the parts that are
> common, and have per-format callbacks for those parts that are
> different.
> 
> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
> Signed-off-by: Teng Long <dyroneteng@gmail.com>
> ---
>  builtin/ls-tree.c | 199 +++++++++++++++++++++++++++++-----------------
>  1 file changed, 125 insertions(+), 74 deletions(-)
> 
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 6550f27dfe..44a91cf9d0 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -173,108 +173,157 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
>  	return recurse;
>  }
>  
> -static int show_default(struct show_tree_data *data)
> +static int show_tree_common(struct show_tree_data *data, int *recurse,
> +			    const struct object_id *oid, struct strbuf *base,
> +			    const char *pathname, unsigned mode)
>  {
> -	size_t baselen = data->base->len;
> -
> -	if (cmdmode == MODE_LONG) {
> -		char size_text[24];
> -		if (data->type == OBJ_BLOB) {
> -			unsigned long size;
> -			if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)
> -				xsnprintf(size_text, sizeof(size_text), "BAD");
> -			else
> -				xsnprintf(size_text, sizeof(size_text),
> -					  "%" PRIuMAX, (uintmax_t)size);
> -		} else {
> -			xsnprintf(size_text, sizeof(size_text), "-");
> -		}
> -		printf("%06o %s %s %7s\t", data->mode, type_name(data->type),
> -		find_unique_abbrev(data->oid, abbrev), size_text);
> -	} else {
> -		printf("%06o %s %s\t", data->mode, type_name(data->type),
> -		find_unique_abbrev(data->oid, abbrev));
> -	}
> -	baselen = data->base->len;
> -	strbuf_addstr(data->base, data->pathname);
> -	write_name_quoted_relative(data->base->buf,
> -				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
> -				   line_termination);
> -	strbuf_setlen(data->base, baselen);
> -	return 1;
> -}
> -
> -static int show_tree(const struct object_id *oid, struct strbuf *base,
> -		const char *pathname, unsigned mode, void *context)
> -{
> -	int recurse = 0;
> -	size_t baselen;
>  	enum object_type type = object_type(mode);
> -	struct show_tree_data data = {
> -		.mode = mode,
> -		.type = type,
> -		.oid = oid,
> -		.pathname = pathname,
> -		.base = base,
> -	};
> +	int ret = -1;
> +
> +	*recurse = 0;
> +	data->mode = mode;
> +	data->type = type;
> +	data->oid = oid;
> +	data->pathname = pathname;
> +	data->base = base;
>  
>  	if (type == OBJ_BLOB) {
>  		if (ls_options & LS_TREE_ONLY)
> -			return 0;
> +			ret = 0;
>  	} else if (type == OBJ_TREE &&
>  		   show_recursive(base->buf, base->len, pathname)) {
> -		recurse = READ_TREE_RECURSIVE;
> +		*recurse = READ_TREE_RECURSIVE;
>  		if (!(ls_options & LS_SHOW_TREES))
> -			return recurse;
> +			ret = *recurse;
>  	}
>  
> -	if (cmdmode == MODE_OBJECT_ONLY) {
> -		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
> -		return recurse;
> -	}
> +	return ret;
> +}
>  
> -	if (cmdmode == MODE_NAME_ONLY) {
> -		baselen = base->len;
> -		strbuf_addstr(base, pathname);
> -		write_name_quoted_relative(base->buf,
> -					   chomp_prefix ? ls_tree_prefix : NULL,
> -					   stdout, line_termination);
> -		strbuf_setlen(base, baselen);
> -		return recurse;
> +static void show_tree_common_default_long(struct strbuf *base,
> +					  const char *pathname,
> +					  const size_t baselen)
> +{
> +	strbuf_addstr(base, pathname);
> +	write_name_quoted_relative(base->buf,
> +				   chomp_prefix ? ls_tree_prefix : NULL, stdout,
> +				   line_termination);
> +	strbuf_setlen(base, baselen);
> +}
> +
> +static int show_tree_default(const struct object_id *oid, struct strbuf *base,
> +			     const char *pathname, unsigned mode,
> +			     void *context)
> +{
> +	int early;
> +	int recurse;
> +	struct show_tree_data data = { 0 };
> +
> +	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
> +	if (early >= 0)
> +		return early;
> +
> +	printf("%06o %s %s\t", data.mode, type_name(data.type),
> +	       find_unique_abbrev(data.oid, abbrev));
> +	show_tree_common_default_long(base, pathname, data.base->len);
> +	return recurse;
> +}
> +
> +static int show_tree_long(const struct object_id *oid, struct strbuf *base,
> +			  const char *pathname, unsigned mode, void *context)
> +{
> +	int early;
> +	int recurse;
> +	struct show_tree_data data = { 0 };
> +	char size_text[24];
> +
> +	early = show_tree_common(&data, &recurse, oid, base, pathname, mode);
> +	if (early >= 0)
> +		return early;
> +
> +	if (data.type == OBJ_BLOB) {
> +		unsigned long size;
> +		if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD)
> +			xsnprintf(size_text, sizeof(size_text), "BAD");
> +		else
> +			xsnprintf(size_text, sizeof(size_text),
> +				  "%" PRIuMAX, (uintmax_t)size);
> +	} else {
> +		xsnprintf(size_text, sizeof(size_text), "-");
>  	}
>  
> -	if (cmdmode == MODE_LONG ||
> -		(!ls_options || (ls_options & LS_RECURSIVE)
> -		 || (ls_options & LS_SHOW_TREES)
> -		 || (ls_options & LS_TREE_ONLY)))
> -			 show_default(&data);
> +	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
> +	       find_unique_abbrev(data.oid, abbrev), size_text);
> +	show_tree_common_default_long(base, pathname, data.base->len);
> +	return 1;
> +}

This commit makes `git ls-tree -l ...` list files recursively, regardless of whether `-r` was provided or not. I believe it's due to show_tree_long() returning 1 instead of the value of recurse. Is there a reason why we unconditionally return 1 here?

Josh Steadmon· Apr 4, 2022, 22:42 UTC · re: Josh Steadmon · lore

[RFC PATCH] ls-tree: `-l` should not imply recursive listing

In 9c4d58ff2c (ls-tree: split up "fast path" callbacks, 2022-03-23), a refactoring of the various read_tree_at() callbacks caused us to unconditionally recurse into directories if `-l` (long format) was passed on the command line, regardless of whether or not we also pass the `-r` (recursive) flag.

Fix this by making show_tree_long() return the value of `recurse`, rather than always returning 1. This value is interpreted by read_tree_at() to be a signal on whether or not to recurse.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
I believe this is the correct fix for the change in `git ls-tree -l`
output. I would also like to add tests in a future fix, but I do not
have time to add them today.
 builtin/ls-tree.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
Show changes to builtin/ls-tree.c +1 −1
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5dac9ee5b9..e279be8bb6 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -255,7 +255,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base,
 	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
 	       find_unique_abbrev(data.oid, abbrev), size_text);
 	show_tree_common_default_long(base, pathname, data.base->len);
-	return 1;
+	return recurse;
 }
 
 static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,

base-commit: faa21c10d44184f616d391c158dcbb13b9c72ef3
-- 
2.35.1.1094.g7c7d902a7c-goog
Ævar Arnfjörð Bjarmason· Apr 4, 2022, 23:45 UTC · re: Josh Steadmon · lore

[PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

Fix a regression introduced in 9c4d58ff2c3 (ls-tree: split up "fast path" callbacks, 2022-03-23), and improve the tests added in the 1041d58b4d9 (Merge branch 'tl/ls-tree-oid-only', 2022-04-04) topic it was merged as part of to test the full expected output of various "ls-tree" options with and without -r.

Let's fix it, and also add tests not only for that blindspot, but also any other potential blindspots. To that end test the "modes" of -d, -r and -t (as well as "no mode") against all of the format options.

These tests all pass with that topic reverted (except those that would fail because they're testing the new --object-only feature introduced in that topic), which should give us confidence that there were no further regressions in this area.

Reported-By: Josh Steadmon <steadmon@google.com>
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
On Mon, Apr 04 2022, Josh Steadmon wrote:
> I believe this is the correct fix for the change in `git ls-tree -l`
> output. I would also like to add tests in a future fix, but I do not
> have time to add them today.
Indeed. I guess that makes this a proposed v2,

I refreshed my E-Mail when I was just about to submit this and spotted that you'd sent your fix in, but I came up with this (in retrospect a pretty obvious think-o) fix independently, sorry about the bug.

The tests took me a bit longer though...

Haing written them I guess we could do them post-release, since the important thing is to validate the changes. As noted in the commit message we're now testing all combinations of the "mode" and "format" options.

 builtin/ls-tree.c         |   2 +-
 t/t3104-ls-tree-format.sh | 126 +++++++++++++++++++++++++++++++++++---
 2 files changed, 119 insertions(+), 9 deletions(-)
Show changes to 2 files +119 −9

builtin/ls-tree.c, t/t3104-ls-tree-format.sh

diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 5dac9ee5b9d..e279be8bb63 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -255,7 +255,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base,
 	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
 	       find_unique_abbrev(data.oid, abbrev), size_text);
 	show_tree_common_default_long(base, pathname, data.base->len);
-	return 1;
+	return recurse;
 }
 
 static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 0769a933d69..520b5a95c08 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -23,6 +23,19 @@ test_ls_tree_format () {
 	fmtopts=$3 &&
 	shift 2 &&
 
+	cat >expect &&
+	cat <&6 >expect.-d &&
+	cat <&7 >expect.-r &&
+	cat <&8 >expect.-t &&
+
+	for opt in '' '-d' '-r' '-t'
+	do
+		test_expect_success "'ls-tree $opts${opt:+ $opt}' output" '
+			git ls-tree ${opt:+$opt }$opts $opt HEAD >actual &&
+			test_cmp expect${opt:+.$opt} actual
+		'
+	done
+
 	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
 		git ls-tree $opts -r HEAD >expect &&
 		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
@@ -39,38 +52,135 @@ test_ls_tree_format () {
 
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
-	""
+	"" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	OUT_D
+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_R
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
-	"--long"
+	"--long" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	040000 tree $(git rev-parse HEAD:dir)       -	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
+	OUT
+	040000 tree $(git rev-parse HEAD:dir)       -	dir
+	OUT_D
+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)      13	dir/sub-file.t
+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
+	OUT_R
+	040000 tree $(git rev-parse HEAD:dir)       -	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
+	OUT_T
 
 test_ls_tree_format \
 	"%(path)" \
-	"--name-only"
+	"--name-only" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	dir
+	top-file.t
+	OUT
+	dir
+	OUT_D
+	dir/sub-file.t
+	top-file.t
+	OUT_R
+	dir
+	top-file.t
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectname)" \
-	"--object-only"
+	"--object-only" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	$(git rev-parse HEAD:dir)
+	$(git rev-parse HEAD:top-file.t)
+	OUT
+	$(git rev-parse HEAD:dir)
+	OUT_D
+	$(git rev-parse HEAD:dir/sub-file.t)
+	$(git rev-parse HEAD:top-file.t)
+	OUT_R
+	$(git rev-parse HEAD:dir)
+	$(git rev-parse HEAD:top-file.t)
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectname)" \
 	"--object-only --abbrev" \
-	"--abbrev"
+	"--abbrev" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
+	$(git rev-parse HEAD:top-file.t| test_copy_bytes 7)
+	OUT
+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
+	OUT_D
+	$(git rev-parse HEAD:dir/sub-file.t | test_copy_bytes 7)
+	$(git rev-parse HEAD:top-file.t | test_copy_bytes 7)
+	OUT_R
+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
+	$(git rev-parse HEAD:top-file.t | test_copy_bytes 7)
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"-t" \
-	"-t"
+	"-t" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	OUT_D
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_R
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"--full-name" \
-	"--full-name"
+	"--full-name" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	OUT_D
+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_R
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_T
 
 test_ls_tree_format \
 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
 	"--full-tree" \
-	"--full-tree"
+	"--full-tree" \
+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	OUT_D
+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_R
+	040000 tree $(git rev-parse HEAD:dir)	dir
+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
+	OUT_T
 
 test_done
-- 
2.35.1.1604.g35dc6517170
Junio C Hamano· Apr 6, 2022, 17:56 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
Show 20 quoted lines
> Haing written them I guess we could do them post-release, since the
> important thing is to validate the changes. As noted in the commit
> message we're now testing all combinations of the "mode" and "format"
> options.
>
>  builtin/ls-tree.c         |   2 +-
>  t/t3104-ls-tree-format.sh | 126 +++++++++++++++++++++++++++++++++++---
>  2 files changed, 119 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 5dac9ee5b9d..e279be8bb63 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -255,7 +255,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base,
>  	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
>  	       find_unique_abbrev(data.oid, abbrev), size_text);
>  	show_tree_common_default_long(base, pathname, data.base->len);
> -	return 1;
> +	return recurse;
>  }
OK, two people by happenstance wrote the same fix is reassuring ;-)
> +	cat >expect &&
> +	cat <&6 >expect.-d &&
> +	cat <&7 >expect.-r &&
> +	cat <&8 >expect.-t &&

Let's not go too cute like this. This forces the caller to remember which among 6, 7, and 8 corresponds to which option. It is too ugly to live.

Show 16 quoted lines
>  test_ls_tree_format \
>  	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
> -	""
> +	"" \
> +	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
> +	040000 tree $(git rev-parse HEAD:dir)	dir
> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> +	OUT
> +	040000 tree $(git rev-parse HEAD:dir)	dir
> +	OUT_D
> +	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> +	OUT_R
> +	040000 tree $(git rev-parse HEAD:dir)	dir
> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> +	OUT_T
I do not actually mind if it were more like this:
	test_ls_tree_format \
		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" <<-EXPECT
	git ls-tree HEAD
	040000 tree $(git rev-parse HEAD:dir)	dir
	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
	git ls-tree -d HEAD
	040000 tree $(git rev-parse HEAD:dir)	dir
	git ls-tree -r HEAD
	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
	git ls-tree -t HEAD
	040000 tree $(git rev-parse HEAD:dir)	dir
	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
	EXPECT

that is, we only read from the standard input, each section begins with "git ls-tree" command invocation that gets tested, followed by its expected output, and ends at either the end of the input or the beginning of the next section.

Ævar Arnfjörð Bjarmason· Apr 6, 2022, 20:36 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

On Wed, Apr 06 2022, Junio C Hamano wrote:
Show 24 quoted lines
> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:
>
>> Haing written them I guess we could do them post-release, since the
>> important thing is to validate the changes. As noted in the commit
>> message we're now testing all combinations of the "mode" and "format"
>> options.
>>
>>  builtin/ls-tree.c         |   2 +-
>>  t/t3104-ls-tree-format.sh | 126 +++++++++++++++++++++++++++++++++++---
>>  2 files changed, 119 insertions(+), 9 deletions(-)
>>
>> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
>> index 5dac9ee5b9d..e279be8bb63 100644
>> --- a/builtin/ls-tree.c
>> +++ b/builtin/ls-tree.c
>> @@ -255,7 +255,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base,
>>  	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
>>  	       find_unique_abbrev(data.oid, abbrev), size_text);
>>  	show_tree_common_default_long(base, pathname, data.base->len);
>> -	return 1;
>> +	return recurse;
>>  }
>
> OK, two people by happenstance wrote the same fix is reassuring ;-)
*nod*
Show 8 quoted lines
>> +	cat >expect &&
>> +	cat <&6 >expect.-d &&
>> +	cat <&7 >expect.-r &&
>> +	cat <&8 >expect.-t &&
>
> Let's not go too cute like this.  This forces the caller to remember
> which among 6, 7, and 8 corresponds to which option.  It is too ugly
> to live.
I think it's rather elegant actually, but to be fair it would, per:
	git grep '<&[1-9]| [1-9]<<-'
Be the user with the most FD's using this sort of pattern.
Show 38 quoted lines
>>  test_ls_tree_format \
>>  	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
>> -	""
>> +	"" \
>> +	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
>> +	040000 tree $(git rev-parse HEAD:dir)	dir
>> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
>> +	OUT
>> +	040000 tree $(git rev-parse HEAD:dir)	dir
>> +	OUT_D
>> +	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
>> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
>> +	OUT_R
>> +	040000 tree $(git rev-parse HEAD:dir)	dir
>> +	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
>> +	OUT_T
>
> I do not actually mind if it were more like this:
>
> 	test_ls_tree_format \
> 		"%(objectmode) %(objecttype) %(objectname)%x09%(path)" <<-EXPECT
> 	git ls-tree HEAD
> 	040000 tree $(git rev-parse HEAD:dir)	dir
> 	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> 	git ls-tree -d HEAD
> 	040000 tree $(git rev-parse HEAD:dir)	dir
> 	git ls-tree -r HEAD
> 	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
> 	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> 	git ls-tree -t HEAD
> 	040000 tree $(git rev-parse HEAD:dir)	dir
> 	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
> 	EXPECT
>
> that is, we only read from the standard input, each section begins
> with "git ls-tree" command invocation that gets tested, followed by
> its expected output, and ends at either the end of the input or the
> beginning of the next section.

I don't see how needing to implementa while-loop parser for a custom format just to get to the end-goal of having those things split up for us is more elegant, when the shell can do that by splitting those up by stream.

But if you insist on this being rewritten we could just unroll the loop, and have each test_ls_tree_format specify the full option and one standard input, but doing it in the function was a bit less verbosity.

Anyway, I see you replied on Josh's that you'd queue it, so it's unclear to me if you'd like to pick this up with the version with the tests at all, so I won't hack up a "v3" until that's clear...

Junio C Hamano· Apr 6, 2022, 21:51 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 14 quoted lines
>>> +	cat >expect &&
>>> +	cat <&6 >expect.-d &&
>>> +	cat <&7 >expect.-r &&
>>> +	cat <&8 >expect.-t &&
>>
>> Let's not go too cute like this.  This forces the caller to remember
>> which among 6, 7, and 8 corresponds to which option.  It is too ugly
>> to live.
>
> I think it's rather elegant actually, but to be fair it would, per:
>
> 	git grep '<&[1-9]| [1-9]<<-'
>
> Be the user with the most FD's using this sort of pattern.

Please give a clear explanation why "-d" has to be 6, "-r" 7 and "-t" 8, that can be used by developers as a memory aid to help them write new tests using the helper.

Or justify why the developers have to memorize such a meaningless correspondence, if there is no any good reason.

Alternatively, you can stop abusing the word "elegant". It is not a synonym to "what I wrote" ;-).

Ævar Arnfjörð Bjarmason· Apr 7, 2022, 07:14 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

On Wed, Apr 06 2022, Junio C Hamano wrote:
Show 20 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>>>> +	cat >expect &&
>>>> +	cat <&6 >expect.-d &&
>>>> +	cat <&7 >expect.-r &&
>>>> +	cat <&8 >expect.-t &&
>>>
>>> Let's not go too cute like this.  This forces the caller to remember
>>> which among 6, 7, and 8 corresponds to which option.  It is too ugly
>>> to live.
>>
>> I think it's rather elegant actually, but to be fair it would, per:
>>
>> 	git grep '<&[1-9]| [1-9]<<-'
>>
>> Be the user with the most FD's using this sort of pattern.
>
> Please give a clear explanation why "-d" has to be 6, "-r" 7 and
> "-t" 8, that can be used by developers as a memory aid to help them
> write new tests using the helper.

It's documented when test-lib.sh does the redirection, since Fabian Stelzer's a6714088e0c (test-lib: make BAIL_OUT() work in tests and prereq, 2021-12-01).

I guess a bit of archane knowledge not documented there is that FD #5 is used for the test-lib.sh itself, e.g. BUG(), so if you want that to work properly you can't touch it.

But everything as of #6 is generally fair game, secondary helpers like "test_pause" won't work properly, but as far as the test framork is concerned 6-9 is good.

So I don't really have a better explanation the same on as why pick "int i" rather than "int x", it's just the prevailing pattern.

> Or justify why the developers have to memorize such a meaningless
> correspondence, if there is no any good reason.
Explained above, I think.
> Alternatively, you can stop abusing the word "elegant".  It is not a
> synonym to "what I wrote" ;-).

The "elegant" part is getting away with passing structured data into a shell function, which it's generally resistant to.

As much as I'd like to take credit from it I just picked it up from code Ilya wrote in 0445e6f0a12 (test-lib: '--run' to run only specific tests, 2014-04-30). I didn't know about it before then.

In any case, you merged down Josh's version without these tests for rc1, so I'm assuming that's a "no" to the upthread "if you'd like to pick this up with the version with the tests at all" in the context of the RC period.

So I think we can table this for now, or would you like a version of this without (a)use of these file descriptors to pass in arguments?

Junio C Hamano· Apr 7, 2022, 18:40 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 17 quoted lines
>>>> Let's not go too cute like this.  This forces the caller to remember
>>>> which among 6, 7, and 8 corresponds to which option.  It is too ugly
>>>> to live.
>>>
>>> I think it's rather elegant actually, but to be fair it would, per:
>>>
>>> 	git grep '<&[1-9]| [1-9]<<-'
>>>
>>> Be the user with the most FD's using this sort of pattern.
>>
>> Please give a clear explanation why "-d" has to be 6, "-r" 7 and
>> "-t" 8, that can be used by developers as a memory aid to help them
>> write new tests using the helper.
>
> It's documented when test-lib.sh does the redirection, since Fabian
> Stelzer's a6714088e0c (test-lib: make BAIL_OUT() work in tests and
> prereq, 2021-12-01).

Sorry, but that is not what I asked. I know what we use lower file descriptors for, and I didn't ask why we start from 6 (as opposed to 3).

The updated helper forces our developers to know that the expected result for "-d" has to go to #6 (not #7 or #8), and "-r" to #7 (not #6 or #8), etc., in order to write new tests using it, and in order to spot a mistake while reviewing such new tests.

It is an invitation to unmaintainable mess.  Don't encourage it.
Ævar Arnfjörð Bjarmason· May 31, 2022, 17:21 UTC · re: Junio C Hamano · lore

[PATCH] ls-tree: test for the regression in 9c4d58ff2c3

Add a test for the regression introduced in my 9c4d58ff2c3 (ls-tree: split up "fast path" callbacks, 2022-03-23) and fixed in 350296cc789 (ls-tree: `-l` should not imply recursive listing, 2022-04-04), and test for the test of ls-tree option/mode combinations to make sure we don't have other blind spots.

The setup for these tests can be shared with those added in the 1041d58b4d9 (Merge branch 'tl/ls-tree-oid-only', 2022-04-04) topic, so let's create a new t/lib-t3100.sh to help them share data.

The existing tests in "t3104-ls-tree-format.sh" didn't deal with a submodule, which they'll now encounter with as the setup_basic_ls_tree_data() sets one up.

This extensive testing should give us confidence that there were no further regressions in this area. The lack of testing was noted back in [1], but unfortunately we didn't cover that blind-spot before 9c4d58ff2c3.

1. https://lore.kernel.org/git/211115.86o86lqe3c.gmgdl@evledraar.gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
On Thu, Apr 07 2022, Junio C Hamano wrote:
Show 30 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>>>>> Let's not go too cute like this.  This forces the caller to remember
>>>>> which among 6, 7, and 8 corresponds to which option.  It is too ugly
>>>>> to live.
>>>>
>>>> I think it's rather elegant actually, but to be fair it would, per:
>>>>
>>>>    git grep '<&[1-9]| [1-9]<<-'
>>>>
>>>> Be the user with the most FD's using this sort of pattern.
>>>
>>> Please give a clear explanation why "-d" has to be 6, "-r" 7 and
>>> "-t" 8, that can be used by developers as a memory aid to help them
>>> write new tests using the helper.
>>
>> It's documented when test-lib.sh does the redirection, since Fabian
>> Stelzer's a6714088e0c (test-lib: make BAIL_OUT() work in tests and
>> prereq, 2021-12-01).
>
> Sorry, but that is not what I asked.  I know what we use lower file
> descriptors for, and I didn't ask why we start from 6 (as opposed to
> 3).
>
> The updated helper forces our developers to know that the expected
> result for "-d" has to go to #6 (not #7 or #8), and "-r" to #7 (not
> #6 or #8), etc., in order to write new tests using it, and in order
> to spot a mistake while reviewing such new tests.
>
> It is an invitation to unmaintainable mess.  Don't encourage it.

I came up with this at the time, but didn't submit it since we were in the rc period.

But since we're past that I think it makes sense to have a test for the regression we fixed, the below is a lot less magical, but larger by line count. I think this alternate test implementation should address the concerns you had.

Range-diff:
1:  ed83b3b74ab ! 1:  0fdfec624eb ls-tree: fix --long implying -r regression in 9c4d58ff2c3
    @@ Metadata
     Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
      ## Commit message ##
    -    ls-tree: fix --long implying -r regression in 9c4d58ff2c3
    +    ls-tree: test for the regression in 9c4d58ff2c3
     
    -    Fix a regression introduced in 9c4d58ff2c3 (ls-tree: split up "fast
    -    path" callbacks, 2022-03-23), and improve the tests added in the
    -    1041d58b4d9 (Merge branch 'tl/ls-tree-oid-only', 2022-04-04) topic it
    -    was merged as part of to test the full expected output of various
    -    "ls-tree" options with and without -r.
    +    Add a test for the regression introduced in my 9c4d58ff2c3 (ls-tree:
    +    split up "fast path" callbacks, 2022-03-23) and fixed in
    +    350296cc789 (ls-tree: `-l` should not imply recursive listing,
    +    2022-04-04), and test for the test of ls-tree option/mode combinations
    +    to make sure we don't have other blind spots.
     
    -    Let's fix it, and also add tests not only for that blindspot, but also
    -    any other potential blindspots. To that end test the "modes" of -d, -r
    -    and -t (as well as "no mode") against all of the format options.
    +    The setup for these tests can be shared with those added in the
    +    1041d58b4d9 (Merge branch 'tl/ls-tree-oid-only', 2022-04-04) topic, so
    +    let's create a new t/lib-t3100.sh to help them share data.
     
    -    These tests all pass with that topic reverted (except those that would
    -    fail because they're testing the new --object-only feature introduced
    -    in that topic), which should give us confidence that there were no
    -    further regressions in this area.
    +    The existing tests in "t3104-ls-tree-format.sh" didn't deal with a
    +    submodule, which they'll now encounter with as the
    +    setup_basic_ls_tree_data() sets one up.
    +
    +    This extensive testing should give us confidence that there were no
    +    further regressions in this area. The lack of testing was noted back
    +    in [1], but unfortunately we didn't cover that blind-spot before
    +    9c4d58ff2c3.
    +
    +    1. https://lore.kernel.org/git/211115.86o86lqe3c.gmgdl@evledraar.gmail.com/
     
    -    Reported-By: Josh Steadmon <steadmon@google.com>
         Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
     
    - ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static int show_tree_long(const struct object_id *oid, struct strbuf *base,
    - 	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
    - 	       find_unique_abbrev(data.oid, abbrev), size_text);
    - 	show_tree_common_default_long(base, pathname, data.base->len);
    --	return 1;
    -+	return recurse;
    - }
    - 
    - static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
    + ## t/lib-t3100.sh (new) ##
    +@@
    ++#!/bin/sh
    ++
    ++setup_basic_ls_tree_data () {
    ++	mkdir dir &&
    ++	test_commit dir/sub-file &&
    ++	test_commit top-file &&
    ++	git clone . submodule &&
    ++	git submodule add ./submodule &&
    ++	git commit -m"add submodule"
    ++}
     
      ## t/t3104-ls-tree-format.sh ##
    -@@ t/t3104-ls-tree-format.sh: test_ls_tree_format () {
    - 	fmtopts=$3 &&
    - 	shift 2 &&
    +@@ t/t3104-ls-tree-format.sh: test_description='ls-tree --format'
    + 
    + TEST_PASSES_SANITIZE_LEAK=true
    + . ./test-lib.sh
    ++. "$TEST_DIRECTORY"/lib-t3100.sh
    + 
    + test_expect_success 'ls-tree --format usage' '
    + 	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
    +@@ t/t3104-ls-tree-format.sh: test_expect_success 'ls-tree --format usage' '
    + '
    + 
    + test_expect_success 'setup' '
    +-	mkdir dir &&
    +-	test_commit dir/sub-file &&
    +-	test_commit top-file
    ++	setup_basic_ls_tree_data
    + '
      
    + test_ls_tree_format () {
    +
    + ## t/t3105-ls-tree-output.sh (new) ##
    +@@
    ++#!/bin/sh
    ++
    ++test_description='ls-tree output'
    ++
    ++TEST_PASSES_SANITIZE_LEAK=true
    ++. ./test-lib.sh
    ++. "$TEST_DIRECTORY"/lib-t3100.sh
    ++
    ++test_expect_success 'ls-tree --format usage' '
    ++	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
    ++	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
    ++	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
    ++'
    ++
    ++test_expect_success 'setup' '
    ++	setup_basic_ls_tree_data
    ++'
    ++
    ++test_ls_tree_format_mode_output () {
    ++	local opts=$1 &&
    ++	shift &&
     +	cat >expect &&
    -+	cat <&6 >expect.-d &&
    -+	cat <&7 >expect.-r &&
    -+	cat <&8 >expect.-t &&
     +
    -+	for opt in '' '-d' '-r' '-t'
    ++	while test $# -gt 0
     +	do
    -+		test_expect_success "'ls-tree $opts${opt:+ $opt}' output" '
    -+			git ls-tree ${opt:+$opt }$opts $opt HEAD >actual &&
    -+			test_cmp expect${opt:+.$opt} actual
    ++		local mode="$1" &&
    ++		shift &&
    ++
    ++		test_expect_success "'ls-tree $opts${mode:+ $mode}' output" '
    ++			git ls-tree ${mode:+$mode }$opts HEAD >actual &&
    ++			test_cmp expect actual
     +		'
    ++
    ++		case "$opts" in
    ++		--full-tree)
    ++			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir, fails)" '
    ++				test_must_fail git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../
    ++			'
    ++			;;
    ++		*)
    ++			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir)" '
    ++				git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../ >actual &&
    ++				test_cmp expect actual
    ++			'
    ++			;;
    ++		esac
     +	done
    ++}
     +
    - 	test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" '
    - 		git ls-tree $opts -r HEAD >expect &&
    - 		git ls-tree --format="$format" -r $fmtopts HEAD >actual &&
    -@@ t/t3104-ls-tree-format.sh: test_ls_tree_format () {
    - 
    - test_ls_tree_format \
    - 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    --	""
    -+	"" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	OUT_D
    -+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_R
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)" \
    --	"--long"
    -+	"--long" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	040000 tree $(git rev-parse HEAD:dir)       -	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
    -+	OUT
    -+	040000 tree $(git rev-parse HEAD:dir)       -	dir
    -+	OUT_D
    -+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)      13	dir/sub-file.t
    -+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
    -+	OUT_R
    -+	040000 tree $(git rev-parse HEAD:dir)       -	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)       9	top-file.t
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(path)" \
    --	"--name-only"
    -+	"--name-only" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    ++# test exact output of option (none, --long, ...) and mode (none and
    ++# -d, -r -t) and combinations
    ++test_expect_success 'setup: HEAD_* variables' '
    ++	HEAD_gitmodules=$(git rev-parse HEAD:.gitmodules) &&
    ++	HEAD_dir=$(git rev-parse HEAD:dir) &&
    ++	HEAD_top_file=$(git rev-parse HEAD:top-file.t) &&
    ++	HEAD_submodule=$(git rev-parse HEAD:submodule) &&
    ++	HEAD_dir_sub_file=$(git rev-parse HEAD:dir/sub-file.t)
    ++'
    ++## opt =
    ++test_ls_tree_format_mode_output "" "" "-t" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++test_ls_tree_format_mode_output "" "-d" <<-EOF
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "" "-r" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++## opt = --long
    ++test_ls_tree_format_mode_output "--long" "" "-t" <<-EOF
    ++	100644 blob $HEAD_gitmodules      61	.gitmodules
    ++	040000 tree $HEAD_dir       -	dir
    ++	160000 commit $HEAD_submodule       -	submodule
    ++	100644 blob $HEAD_top_file       9	top-file.t
    ++	EOF
    ++test_ls_tree_format_mode_output "--long" "-d" <<-EOF
    ++	040000 tree $HEAD_dir       -	dir
    ++	160000 commit $HEAD_submodule       -	submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--long" "-r" <<-EOF
    ++	100644 blob $HEAD_gitmodules      61	.gitmodules
    ++	100644 blob $HEAD_dir_sub_file      13	dir/sub-file.t
    ++	160000 commit $HEAD_submodule       -	submodule
    ++	100644 blob $HEAD_top_file       9	top-file.t
    ++	EOF
    ++## opt = --name-only
    ++test_ls_tree_format_mode_output "--name-only" "" "-t" <<-EOF
    ++	.gitmodules
     +	dir
    ++	submodule
     +	top-file.t
    -+	OUT
    ++	EOF
    ++test_ls_tree_format_mode_output "--name-only" "-d" <<-EOF
     +	dir
    -+	OUT_D
    ++	submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--name-only" "-r" <<-EOF
    ++	.gitmodules
     +	dir/sub-file.t
    ++	submodule
     +	top-file.t
    -+	OUT_R
    -+	dir
    -+	top-file.t
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectname)" \
    --	"--object-only"
    -+	"--object-only" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	$(git rev-parse HEAD:dir)
    -+	$(git rev-parse HEAD:top-file.t)
    -+	OUT
    -+	$(git rev-parse HEAD:dir)
    -+	OUT_D
    -+	$(git rev-parse HEAD:dir/sub-file.t)
    -+	$(git rev-parse HEAD:top-file.t)
    -+	OUT_R
    -+	$(git rev-parse HEAD:dir)
    -+	$(git rev-parse HEAD:top-file.t)
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectname)" \
    - 	"--object-only --abbrev" \
    --	"--abbrev"
    -+	"--abbrev" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
    -+	$(git rev-parse HEAD:top-file.t| test_copy_bytes 7)
    -+	OUT
    -+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
    -+	OUT_D
    -+	$(git rev-parse HEAD:dir/sub-file.t | test_copy_bytes 7)
    -+	$(git rev-parse HEAD:top-file.t | test_copy_bytes 7)
    -+	OUT_R
    -+	$(git rev-parse HEAD:dir | test_copy_bytes 7)
    -+	$(git rev-parse HEAD:top-file.t | test_copy_bytes 7)
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    - 	"-t" \
    --	"-t"
    -+	"-t" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	OUT_D
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_R
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    - 	"--full-name" \
    --	"--full-name"
    -+	"--full-name" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	OUT_D
    -+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_R
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_T
    - 
    - test_ls_tree_format \
    - 	"%(objectmode) %(objecttype) %(objectname)%x09%(path)" \
    - 	"--full-tree" \
    --	"--full-tree"
    -+	"--full-tree" \
    -+	<<-OUT 6<<-OUT_D 7<<-OUT_R 8<<-OUT_T
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	OUT_D
    -+	100644 blob $(git rev-parse HEAD:dir/sub-file.t)	dir/sub-file.t
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_R
    -+	040000 tree $(git rev-parse HEAD:dir)	dir
    -+	100644 blob $(git rev-parse HEAD:top-file.t)	top-file.t
    -+	OUT_T
    - 
    - test_done
    ++	EOF
    ++## opt = --object-only
    ++test_ls_tree_format_mode_output "--object-only" "" "-t" <<-EOF
    ++	$HEAD_gitmodules
    ++	$HEAD_dir
    ++	$HEAD_submodule
    ++	$HEAD_top_file
    ++	EOF
    ++test_ls_tree_format_mode_output "--object-only" "-d" <<-EOF
    ++	$HEAD_dir
    ++	$HEAD_submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--object-only" "-r" <<-EOF
    ++	$HEAD_gitmodules
    ++	$HEAD_dir_sub_file
    ++	$HEAD_submodule
    ++	$HEAD_top_file
    ++	EOF
    ++## opt = --object-only --abbrev
    ++test_expect_success 'setup: HEAD_short_* variables' '
    ++	HEAD_short_gitmodules=$(git rev-parse --short HEAD:.gitmodules) &&
    ++	HEAD_short_dir=$(git rev-parse --short HEAD:dir) &&
    ++	HEAD_short_top_file=$(git rev-parse --short HEAD:top-file.t) &&
    ++	HEAD_short_submodule=$(git rev-parse --short HEAD:submodule) &&
    ++	HEAD_short_dir_sub_file=$(git rev-parse --short HEAD:dir/sub-file.t)
    ++'
    ++test_ls_tree_format_mode_output "--object-only --abbrev" "" "-t" <<-EOF
    ++	$HEAD_short_gitmodules
    ++	$HEAD_short_dir
    ++	$HEAD_short_submodule
    ++	$HEAD_short_top_file
    ++	EOF
    ++test_ls_tree_format_mode_output "--object-only --abbrev" "-d" <<-EOF
    ++	$HEAD_short_dir
    ++	$HEAD_short_submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--object-only --abbrev" "-r" <<-EOF
    ++	$HEAD_short_gitmodules
    ++	$HEAD_short_dir_sub_file
    ++	$HEAD_short_submodule
    ++	$HEAD_short_top_file
    ++	EOF
    ++## opt = --full-name
    ++test_ls_tree_format_mode_output "--full-name" "" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++test_ls_tree_format_mode_output "--full-name" "-d" <<-EOF
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--full-name" "-r" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++test_ls_tree_format_mode_output "--full-name" "-t" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++## opt = --full-tree
    ++test_ls_tree_format_mode_output "--full-tree" "" "-t" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++test_ls_tree_format_mode_output "--full-tree" "-d" <<-EOF
    ++	040000 tree $HEAD_dir	dir
    ++	160000 commit $HEAD_submodule	submodule
    ++	EOF
    ++test_ls_tree_format_mode_output "--full-tree" "-r" <<-EOF
    ++	100644 blob $HEAD_gitmodules	.gitmodules
    ++	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
    ++	160000 commit $HEAD_submodule	submodule
    ++	100644 blob $HEAD_top_file	top-file.t
    ++	EOF
    ++
    ++test_done
 t/lib-t3100.sh            |  10 ++
 t/t3104-ls-tree-format.sh |   5 +-
 t/t3105-ls-tree-output.sh | 192 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 204 insertions(+), 3 deletions(-)
 create mode 100644 t/lib-t3100.sh
 create mode 100755 t/t3105-ls-tree-output.sh
Show changes to 3 files +204 −3

t/lib-t3100.sh, t/t3104-ls-tree-format.sh, t/t3105-ls-tree-output.sh

diff --git a/t/lib-t3100.sh b/t/lib-t3100.sh
new file mode 100644
index 00000000000..eabb5fd8034
--- /dev/null
+++ b/t/lib-t3100.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+setup_basic_ls_tree_data () {
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file &&
+	git clone . submodule &&
+	git submodule add ./submodule &&
+	git commit -m"add submodule"
+}
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 0769a933d69..383896667b6 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -4,6 +4,7 @@ test_description='ls-tree --format'
 
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-t3100.sh
 
 test_expect_success 'ls-tree --format usage' '
 	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
@@ -12,9 +13,7 @@ test_expect_success 'ls-tree --format usage' '
 '
 
 test_expect_success 'setup' '
-	mkdir dir &&
-	test_commit dir/sub-file &&
-	test_commit top-file
+	setup_basic_ls_tree_data
 '
 
 test_ls_tree_format () {
diff --git a/t/t3105-ls-tree-output.sh b/t/t3105-ls-tree-output.sh
new file mode 100755
index 00000000000..29511d9331b
--- /dev/null
+++ b/t/t3105-ls-tree-output.sh
@@ -0,0 +1,192 @@
+#!/bin/sh
+
+test_description='ls-tree output'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-t3100.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	setup_basic_ls_tree_data
+'
+
+test_ls_tree_format_mode_output () {
+	local opts=$1 &&
+	shift &&
+	cat >expect &&
+
+	while test $# -gt 0
+	do
+		local mode="$1" &&
+		shift &&
+
+		test_expect_success "'ls-tree $opts${mode:+ $mode}' output" '
+			git ls-tree ${mode:+$mode }$opts HEAD >actual &&
+			test_cmp expect actual
+		'
+
+		case "$opts" in
+		--full-tree)
+			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir, fails)" '
+				test_must_fail git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../
+			'
+			;;
+		*)
+			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir)" '
+				git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../ >actual &&
+				test_cmp expect actual
+			'
+			;;
+		esac
+	done
+}
+
+# test exact output of option (none, --long, ...) and mode (none and
+# -d, -r -t) and combinations
+test_expect_success 'setup: HEAD_* variables' '
+	HEAD_gitmodules=$(git rev-parse HEAD:.gitmodules) &&
+	HEAD_dir=$(git rev-parse HEAD:dir) &&
+	HEAD_top_file=$(git rev-parse HEAD:top-file.t) &&
+	HEAD_submodule=$(git rev-parse HEAD:submodule) &&
+	HEAD_dir_sub_file=$(git rev-parse HEAD:dir/sub-file.t)
+'
+## opt =
+test_ls_tree_format_mode_output "" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+## opt = --long
+test_ls_tree_format_mode_output "--long" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules      61	.gitmodules
+	040000 tree $HEAD_dir       -	dir
+	160000 commit $HEAD_submodule       -	submodule
+	100644 blob $HEAD_top_file       9	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--long" "-d" <<-EOF
+	040000 tree $HEAD_dir       -	dir
+	160000 commit $HEAD_submodule       -	submodule
+	EOF
+test_ls_tree_format_mode_output "--long" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules      61	.gitmodules
+	100644 blob $HEAD_dir_sub_file      13	dir/sub-file.t
+	160000 commit $HEAD_submodule       -	submodule
+	100644 blob $HEAD_top_file       9	top-file.t
+	EOF
+## opt = --name-only
+test_ls_tree_format_mode_output "--name-only" "" "-t" <<-EOF
+	.gitmodules
+	dir
+	submodule
+	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--name-only" "-d" <<-EOF
+	dir
+	submodule
+	EOF
+test_ls_tree_format_mode_output "--name-only" "-r" <<-EOF
+	.gitmodules
+	dir/sub-file.t
+	submodule
+	top-file.t
+	EOF
+## opt = --object-only
+test_ls_tree_format_mode_output "--object-only" "" "-t" <<-EOF
+	$HEAD_gitmodules
+	$HEAD_dir
+	$HEAD_submodule
+	$HEAD_top_file
+	EOF
+test_ls_tree_format_mode_output "--object-only" "-d" <<-EOF
+	$HEAD_dir
+	$HEAD_submodule
+	EOF
+test_ls_tree_format_mode_output "--object-only" "-r" <<-EOF
+	$HEAD_gitmodules
+	$HEAD_dir_sub_file
+	$HEAD_submodule
+	$HEAD_top_file
+	EOF
+## opt = --object-only --abbrev
+test_expect_success 'setup: HEAD_short_* variables' '
+	HEAD_short_gitmodules=$(git rev-parse --short HEAD:.gitmodules) &&
+	HEAD_short_dir=$(git rev-parse --short HEAD:dir) &&
+	HEAD_short_top_file=$(git rev-parse --short HEAD:top-file.t) &&
+	HEAD_short_submodule=$(git rev-parse --short HEAD:submodule) &&
+	HEAD_short_dir_sub_file=$(git rev-parse --short HEAD:dir/sub-file.t)
+'
+test_ls_tree_format_mode_output "--object-only --abbrev" "" "-t" <<-EOF
+	$HEAD_short_gitmodules
+	$HEAD_short_dir
+	$HEAD_short_submodule
+	$HEAD_short_top_file
+	EOF
+test_ls_tree_format_mode_output "--object-only --abbrev" "-d" <<-EOF
+	$HEAD_short_dir
+	$HEAD_short_submodule
+	EOF
+test_ls_tree_format_mode_output "--object-only --abbrev" "-r" <<-EOF
+	$HEAD_short_gitmodules
+	$HEAD_short_dir_sub_file
+	$HEAD_short_submodule
+	$HEAD_short_top_file
+	EOF
+## opt = --full-name
+test_ls_tree_format_mode_output "--full-name" "" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+## opt = --full-tree
+test_ls_tree_format_mode_output "--full-tree" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-tree" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "--full-tree" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+
+test_done
-- 
2.36.1.1100.g16130010d07
Johannes Schindelin· Jun 2, 2022, 15:18 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH] ls-tree: test for the regression in 9c4d58ff2c3

Hi Ævar,
On Tue, 31 May 2022, Ævar Arnfjörð Bjarmason wrote:
Show 5 quoted lines
> Add a test for the regression introduced in my 9c4d58ff2c3 (ls-tree:
> split up "fast path" callbacks, 2022-03-23) and fixed in
> 350296cc789 (ls-tree: `-l` should not imply recursive listing,
> 2022-04-04), and test for the test of ls-tree option/mode combinations
> to make sure we don't have other blind spots.
That's adding a lot of new test cases, which is not free of cost.
> Range-diff:
> [... 433 lines, essentially a rewrite...]
Pasting 443 lines of range-diff also is not free of cost.
Show 15 quoted lines
> diff --git a/t/t3105-ls-tree-output.sh b/t/t3105-ls-tree-output.sh
> new file mode 100755
> index 00000000000..29511d9331b
> --- /dev/null
> +++ b/t/t3105-ls-tree-output.sh
> @@ -0,0 +1,192 @@
> +#!/bin/sh
> +
> +test_description='ls-tree output'
> +
> [...]
> +'
> +
> +test_ls_tree_format_mode_output () {
> +	local opts=$1 &&
This line does not quote `$1`, but...
Show 39 quoted lines
> +	shift &&
> +	cat >expect &&
> +
> +	while test $# -gt 0
> +	do
> +		local mode="$1" &&
> +		shift &&
> +
> +		test_expect_success "'ls-tree $opts${mode:+ $mode}' output" '
> +			git ls-tree ${mode:+$mode }$opts HEAD >actual &&
> +			test_cmp expect actual
> +		'
> +
> +		case "$opts" in
> +		--full-tree)
> +			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir, fails)" '
> +				test_must_fail git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../
> +			'
> +			;;
> +		*)
> +			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir)" '
> +				git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../ >actual &&
> +				test_cmp expect actual
> +			'
> +			;;
> +		esac
> +	done
> +}
> +
> [...]
> +## opt = --object-only --abbrev
> +test_expect_success 'setup: HEAD_short_* variables' '
> +	HEAD_short_gitmodules=$(git rev-parse --short HEAD:.gitmodules) &&
> +	HEAD_short_dir=$(git rev-parse --short HEAD:dir) &&
> +	HEAD_short_top_file=$(git rev-parse --short HEAD:top-file.t) &&
> +	HEAD_short_submodule=$(git rev-parse --short HEAD:submodule) &&
> +	HEAD_short_dir_sub_file=$(git rev-parse --short HEAD:dir/sub-file.t)
> +'
> +test_ls_tree_format_mode_output "--object-only --abbrev" "" "-t" <<-EOF

... this line passes a first argument that contains spaces. Hence the tests fail in CI: https://github.com/git/git/runs/6703333447?check_suite_focus=true

Further, since this failure is outside of any `test_expect_success` or `test_expect_failure`, the error message about this is not even included in the weblogs (but of course it is included in the full logs that are included in the build artifacts). For the record, here is the error message:

	[...]
	ok 35 - 'ls-tree --object-only -r' output (via subdir)
	+ git rev-parse --short HEAD:.gitmodules
	+ HEAD_short_gitmodules=6da7993
	+ git rev-parse --short HEAD:dir
	+ HEAD_short_dir=aba57e0
	+ git rev-parse --short HEAD:top-file.t
	+ HEAD_short_top_file=02dad95
	+ git rev-parse --short HEAD:submodule
	+ HEAD_short_submodule=61a63ac
	+ git rev-parse --short HEAD:dir/sub-file.t
	+ HEAD_short_dir_sub_file=a150abd
	ok 36 - setup: HEAD_short_* variables
	t3105-ls-tree-output.sh: 20: local: --abbrev: bad variable name
	FATAL: Unexpected exit with code 2
Note: I am only pointing out why the CI/PR run fails, I have not formed
any opinion on the actual code other than noticing a lot of what might be
repetitive lines and suspecting that adding this many new test cases
increases the runtime of the test suite and thereby adds to developer
friction and the benefit (namely, to catch future regressions) might not
justify that. But again, I have not fully formed an opinion about this
patch.

Ciao, Johannes

Junio C Hamano· Jun 2, 2022, 17:48 UTC · re: Johannes Schindelin · lore

Re: [PATCH] ls-tree: test for the regression in 9c4d58ff2c3

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 8 quoted lines
>> +test_ls_tree_format_mode_output () {
>> +	local opts=$1 &&
>
> This line does not quote `$1`, but...
>
> ... this line passes a first argument that contains spaces. Hence the
> tests fail in CI:
> https://github.com/git/git/runs/6703333447?check_suite_focus=true
We had a portability discussion not so in distant past on "local".
  https://lore.kernel.org/git/xmqqsftc3nd6.fsf@gitster.g/
The conclusion from the thread IIRC is
 * Very safe
	local var; var=$1
 * Probably OK
	local var="$1"
 * Not portable
	local var=$1
Thanks.
Ævar Arnfjörð Bjarmason· Jun 3, 2022, 09:54 UTC · re: Johannes Schindelin · lore

js/ci-github-workflow-markup output regression (was: [PATCH] ls-tree: test for the regression in 9c4d58ff2c3)

On Thu, Jun 02 2022, Johannes Schindelin wrote:
Show 6 quoted lines
> On Tue, 31 May 2022, Ævar Arnfjörð Bjarmason wrote:
> [...]
>> +test_ls_tree_format_mode_output () {
>> +	local opts=$1 &&
>
> This line does not quote `$1`, but...

Well spotted, thanks, I don't know how I missed that, will fix it & re-roll, but per changed $subject...

Show 10 quoted lines
> [...]
> ... this line passes a first argument that contains spaces. Hence the
> tests fail in CI:
> https://github.com/git/git/runs/6703333447?check_suite_focus=true
>
> Further, since this failure is outside of any `test_expect_success` or
> `test_expect_failure`, the error message about this is not even included
> in the weblogs (but of course it is included in the full logs that are
> included in the build artifacts). For the record, here is the error
> message:

...this part of it though seems like a pretty bad regression in your merged-to-next js/ci-github-workflow-markup topic, which just happens to be unearthed by this CI failure.

On top of master this patch will get this CI failure:
https://github.com/avar/git/runs/6675920732?check_suite_focus=true#step:5:598;
Ending in ("[...]" edit is mine):
	
	 ok 35 - 'ls-tree --object-only -r' output (via subdir)
	+ git rev-parse --short HEAD:.gitmodules
	[...]
	+ HEAD_short_dir_sub_file=a150abd
	
	ok 36 - setup: HEAD_short_* variables
	t3105-ls-tree-output.sh: 20: local: --abbrev: bad variable name
	FATAL: Unexpected exit with code 2
So here we see that we got to test 36, and then got this error. All of
which is after the step we focused on to begin with would have shown:
	
	 Test Summary Report
	-------------------
	t3105-ls-tree-output.sh                          (Wstat: 256 Tests: 36 Failed: 0)
	  Non-zero exit status: 1
	  Parse errors: No plan found in TAP output

I.e. telling us we had a TAP parse error, so something like this was going on.

But now we'll instead get ("=>" edit is mine, it didn't copy/paste):
	=> Run tests
	=== Failed test: t3105-ls-tree-output ===
	The full logs are in the artifacts attached to this run.
	Error: Process completed with exit code 1.

Where expanding the "=>" in "Run tests" will get us the "Test summary report" from before, but now we have no "ci/print-test-failures.sh" step to emit the output we got from the test, and we just get a cryptic error about t3105 having failed *somewhere*.

As noted in the breadcrumb trail leading from "[2]" in [1] this is a fundamental limitation of the approach you picked for --github-workflow-markup. You're tasking the test-lib.sh to emit well-formed output, but as shown here we can die prematurely on "eval" failures.

But this does look easy to "solve" with a quicker fix, just bringing back the "ci/print-test-failures.sh" step so you can at least expand it, and not have to go to the "summary" and download the *.zip of the log itself. As that shows we still have the raw log there, it just didn't make it to the new GitHub Markdown formatting mechanism.

1. https://lore.kernel.org/git/220324.8635j7nyvw.gmgdl@evledraar.gmail.com/
Junio C Hamano· Jun 3, 2022, 19:27 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: js/ci-github-workflow-markup output regression

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 9 quoted lines
>> Further, since this failure is outside of any `test_expect_success` or
>> `test_expect_failure`, the error message about this is not even included
>> in the weblogs (but of course it is included in the full logs that are
>> included in the build artifacts). For the record, here is the error
>> message:
>
> ...this part of it though seems like a pretty bad regression in your
> merged-to-next js/ci-github-workflow-markup topic, which just happens to
> be unearthed by this CI failure.

Indeed it makes it impossible to figure it out things like this case. But ...

Show 5 quoted lines
> But this does look easy to "solve" with a quicker fix, just bringing
> back the "ci/print-test-failures.sh" step so you can at least expand it,
> and not have to go to the "summary" and download the *.zip of the log
> itself. As that shows we still have the raw log there, it just didn't
> make it to the new GitHub Markdown formatting mechanism.

... it seems a solution is possible? Care to send in a patch (or perhaps Dscho already has a counter-proposal)?

Thanks.
Ævar Arnfjörð Bjarmason· Jun 3, 2022, 23:13 UTC · re: Junio C Hamano · lore

Re: js/ci-github-workflow-markup output regression

On Fri, Jun 03 2022, Junio C Hamano wrote:
Show 23 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>>> Further, since this failure is outside of any `test_expect_success` or
>>> `test_expect_failure`, the error message about this is not even included
>>> in the weblogs (but of course it is included in the full logs that are
>>> included in the build artifacts). For the record, here is the error
>>> message:
>>
>> ...this part of it though seems like a pretty bad regression in your
>> merged-to-next js/ci-github-workflow-markup topic, which just happens to
>> be unearthed by this CI failure.
>
> Indeed it makes it impossible to figure it out things like this
> case.  But ...
>
>> But this does look easy to "solve" with a quicker fix, just bringing
>> back the "ci/print-test-failures.sh" step so you can at least expand it,
>> and not have to go to the "summary" and download the *.zip of the log
>> itself. As that shows we still have the raw log there, it just didn't
>> make it to the new GitHub Markdown formatting mechanism.
>
> ... it seems a solution is possible?  Care to send in a patch (or
> perhaps Dscho already has a counter-proposal)?
The only thing I have at the moment is:
    1. git revert -m 1 bd37e9e41f5
    2. merge: https://lore.kernel.org/git/cover-v6-00.29-00000000000-20220525T094123Z-avarab@gmail.com/
    3. merge: https://lore.kernel.org/git/cover-v6-00.14-00000000000-20220525T100743Z-avarab@gmail.com/

I.e. to pick this in the sequence I'd proposed doing & have tested thoroughly.

It also addresses other noted some other regressions in "next", but as noted e.g. in [A] there's other issues in "next", e.g. that even the "raw" trace logs are altered as a side-effect of running with --github-workflow-markup, and of course the major UX slowdowns.

So I think the better way forward would be to do that & leave out [B], i.e. make the new output format optional, and wait until outstanding issues are fixed until we flip the default.

But do I have something that neatly fixes the issue(s) on top of "next" without a revert & re-apply? No, sorry.

In case you want to go for that the resolution for [2] is [C], and a "git rm ci/run-build-and-tests.sh ci/run-static-analysis.sh".

A. https://lore.kernel.org/git/patch-v6-13.14-fbe0d99c6b3-20220525T100743Z-avarab@gmail.com/
B. https://lore.kernel.org/git/patch-v6-14.14-0b02b186c87-20220525T100743Z-avarab@gmail.com/
C. 
	diff --git a/Makefile b/Makefile
	index 19f3756f7eb..c2b0a728df5 100644
	--- a/Makefile
	+++ b/Makefile
	@@ -3618,3 +3618,4 @@ ci-static-analysis: ci-check-directional-formatting
	 ci-static-analysis: check-builtins
	 ci-static-analysis: check-coccicheck
	 ci-static-analysis: hdr-check
	+ci-static-analysis: check-pot
	diff --git a/ci/lib.sh b/ci/lib.sh
	index 80e89f89b7f..9c54c1330e6 100755
	--- a/ci/lib.sh
	+++ b/ci/lib.sh
	@@ -270,7 +270,7 @@ linux-TEST-vars)
	        setenv --test GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS 1
	        setenv --test GIT_TEST_MULTI_PACK_INDEX 1
	        setenv --test GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP 1
	-       setenv --test GIT_TEST_ADD_I_USE_BUILTIN 1
	+       setenv --test GIT_TEST_ADD_I_USE_BUILTIN 0
	        setenv --test GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME master
	        setenv --test GIT_TEST_WRITE_REV_INDEX 1
	        setenv --test GIT_TEST_CHECKOUT_WORKERS 2
	
Junio C Hamano· Jun 7, 2022, 18:25 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: js/ci-github-workflow-markup output regression

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
Show 22 quoted lines
> On Fri, Jun 03 2022, Junio C Hamano wrote:
>
>> Indeed it makes it impossible to figure it out things like this
>> case.  But ...
>>
>>> But this does look easy to "solve" with a quicker fix, just bringing
>>> back the "ci/print-test-failures.sh" step so you can at least expand it,
>>> and not have to go to the "summary" and download the *.zip of the log
>>> itself. As that shows we still have the raw log there, it just didn't
>>> make it to the new GitHub Markdown formatting mechanism.
>>
>> ... it seems a solution is possible?  Care to send in a patch (or
>> perhaps Dscho already has a counter-proposal)?
>
> The only thing I have at the moment is:
>
>     1. git revert -m 1 bd37e9e41f5
>     2. merge: https://lore.kernel.org/git/cover-v6-00.29-00000000000-20220525T094123Z-avarab@gmail.com/
>     3. merge: https://lore.kernel.org/git/cover-v6-00.14-00000000000-20220525T100743Z-avarab@gmail.com/
>
> I.e. to pick this in the sequence I'd proposed doing & have tested
> thoroughly.

I know you two have difference in opinions, but throwing away everything the other party did and forcing your stuff in is not a very effective way to work together.

> It also addresses other noted some other regressions in "next", but as
> noted e.g. in [A] there's other issues in "next", e.g. that even the
> "raw" trace logs are altered as a side-effect of running with
> --github-workflow-markup, and of course the major UX slowdowns.

Dscho? I know you do not care about the UX slowdown as much as others, but I am sure you do not consider what is in 'next' is perfect. It seems to need further work to go back to the feature parity with what it replaced.

Ævar Arnfjörð Bjarmason· Jun 7, 2022, 21:40 UTC · re: Junio C Hamano · lore

Re: js/ci-github-workflow-markup output regression

On Tue, Jun 07 2022, Junio C Hamano wrote:
Show 28 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> On Fri, Jun 03 2022, Junio C Hamano wrote:
>>
>>> Indeed it makes it impossible to figure it out things like this
>>> case.  But ...
>>>
>>>> But this does look easy to "solve" with a quicker fix, just bringing
>>>> back the "ci/print-test-failures.sh" step so you can at least expand it,
>>>> and not have to go to the "summary" and download the *.zip of the log
>>>> itself. As that shows we still have the raw log there, it just didn't
>>>> make it to the new GitHub Markdown formatting mechanism.
>>>
>>> ... it seems a solution is possible?  Care to send in a patch (or
>>> perhaps Dscho already has a counter-proposal)?
>>
>> The only thing I have at the moment is:
>>
>>     1. git revert -m 1 bd37e9e41f5
>>     2. merge: https://lore.kernel.org/git/cover-v6-00.29-00000000000-20220525T094123Z-avarab@gmail.com/
>>     3. merge: https://lore.kernel.org/git/cover-v6-00.14-00000000000-20220525T100743Z-avarab@gmail.com/
>>
>> I.e. to pick this in the sequence I'd proposed doing & have tested
>> thoroughly.
>
> I know you two have difference in opinions, but throwing away
> everything the other party did and forcing your stuff in is not a
> very effective way to work together.

I'm suggesting getting Johannes's patches in combined with the changes & bugfixes I'd proposed.

So no, not throwing that work away, it would (applied up to 14/14) give you functionally the same end result that's on "next" now as far as the new GitHub Markdown output is concerned. The [3] above has links to the relevant CI output.

I had tried to rebase the above [2] on top of "next" before this discussion started, I agree that would be ideal, but it's a much larger logical change that I don't have time to pursue now.

I.e. there's a reason I proposed doing it in that order, a logical rebasing of [2] on top of bd37e9e41f5 would involve a lot of backing out of the existing direction taken there. I.e. the whole part where the split by "steps" provides much of the ci/* specific code in bd37e9e41f5 for free.

Show 9 quoted lines
>> It also addresses other noted some other regressions in "next", but as
>> noted e.g. in [A] there's other issues in "next", e.g. that even the
>> "raw" trace logs are altered as a side-effect of running with
>> --github-workflow-markup, and of course the major UX slowdowns.
>
> Dscho?  I know you do not care about the UX slowdown as much as
> others, but I am sure you do not consider what is in 'next' is
> perfect. It seems to need further work to go back to the feature
> parity with what it replaced.

Just to be clear [3] up to 14/14 would still exhibit this particular bug, but with 13/14 it wouldn't from the links in [3] the relevant outputs are:

 "next" (well, similar): https://github.com/avar/git/runs/6571972194?check_suite_focus=true
 [3] with 14/14: https://github.com/avar/git/runs/6588407676?check_suite_focus=true
 [3] with 13/14: https://github.com/avar/git/runs/6588579493?check_suite_focus=true

I really would like to get is out of this long-running ci/ limbo, perhaps Johannes has some proposed patches, but I don't think fixing the outstanding bugs is going to be trivial or easy. Some of it's hard to tease apart, e.g. the altered *.out logs seem to require some tricky test-lib.sh and test-lib-functions.sh changes.

I don't see why wouldn't get all of that code in now though, just hidden behind a flag, that would take the pressure off dealing with the current regressions, [2] with 13/14 would do that. Then once those outstanding issues are fixed we'd just need the one-line 14/14 change to flip the default CI output.

But is it the smallest possible change on top of what's now in "next"? No, of course not.

But I don't have those hypothetical patches, just the above. That's all I meant in reply to the "care to send in a patch?" upthread.

Johannes Schindelin· Jun 8, 2022, 08:04 UTC · re: Junio C Hamano · lore

Re: js/ci-github-workflow-markup output regression

Hi Junio,
On Tue, 7 Jun 2022, Junio C Hamano wrote:
Show 11 quoted lines
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
> > On Fri, Jun 03 2022, Junio C Hamano wrote:
> >
> >> Indeed it makes it impossible to figure it out things like this case.
> >> But ...
> >>
> >>> But this does look easy to "solve" with a quicker fix, just bringing
> >>> back the "ci/print-test-failures.sh" step so you can at least expand
> >>> it, and not have to go to the "summary" and download the *.zip of
> >>> the log itself.

I agree that re-adding the `ci/print-test-failures.sh` step makes sense, given the recent experience.

Show 5 quoted lines
> >>> As that shows we still have the raw log there, it just didn't make
> >>> it to the new GitHub Markdown formatting mechanism.
> >>
> >> ... it seems a solution is possible?  Care to send in a patch (or
> >> perhaps Dscho already has a counter-proposal)?
I will work on this.
Show 12 quoted lines
> > The only thing I have at the moment is:
> >
> >     1. git revert -m 1 bd37e9e41f5
> >     2. merge: https://lore.kernel.org/git/cover-v6-00.29-00000000000-20220525T094123Z-avarab@gmail.com/
> >     3. merge: https://lore.kernel.org/git/cover-v6-00.14-00000000000-20220525T100743Z-avarab@gmail.com/
> >
> > I.e. to pick this in the sequence I'd proposed doing & have tested
> > thoroughly.
>
> I know you two have difference in opinions, but throwing away everything
> the other party did and forcing your stuff in is not a very effective
> way to work together.

I had already pointed out a rather terrible issue in that 29-strong patch series: Dropping Azure Pipelines support makes it unnecessarily harder to work on Git security issues. And it's not like we have an armada of people working on those. I, for one, am pretty worn out from the recent work.

It might not be the intention of that patch series to make my life harder, but it sure would be its impact. And intent does not excuse impact.

I therefore had to conclude that the patch series in this form cannot be merged.

I recall that other reviews reached the same consensus, that this 29-strong patch series is too unfocused on any particular goal. So maybe calling this "my opinion" is not exactly fair.

Show 9 quoted lines
> > It also addresses other noted some other regressions in "next", but as
> > noted e.g. in [A] there's other issues in "next", e.g. that even the
> > "raw" trace logs are altered as a side-effect of running with
> > --github-workflow-markup, and of course the major UX slowdowns.
>
> Dscho?  I know you do not care about the UX slowdown as much as
> others, but I am sure you do not consider what is in 'next' is
> perfect. It seems to need further work to go back to the feature
> parity with what it replaced.

Indeed, I do not consider it perfect. I even said so much in every iteration's cover letter ;-)

I will work on bringing back the `print-test-failures` step.

Ciao, Dscho

Ævar Arnfjörð Bjarmason· Jun 9, 2022, 19:43 UTC · re: Johannes Schindelin · lore

Re: js/ci-github-workflow-markup output regression

On Wed, Jun 08 2022, Johannes Schindelin wrote:
Show 50 quoted lines
> Hi Junio,
>
> On Tue, 7 Jun 2022, Junio C Hamano wrote:
>
>> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>>
>> > On Fri, Jun 03 2022, Junio C Hamano wrote:
>> >
>> >> Indeed it makes it impossible to figure it out things like this case.
>> >> But ...
>> >>
>> >>> But this does look easy to "solve" with a quicker fix, just bringing
>> >>> back the "ci/print-test-failures.sh" step so you can at least expand
>> >>> it, and not have to go to the "summary" and download the *.zip of
>> >>> the log itself.
>
> I agree that re-adding the `ci/print-test-failures.sh` step makes sense,
> given the recent experience.
>
>> >>> As that shows we still have the raw log there, it just didn't make
>> >>> it to the new GitHub Markdown formatting mechanism.
>> >>
>> >> ... it seems a solution is possible?  Care to send in a patch (or
>> >> perhaps Dscho already has a counter-proposal)?
>
> I will work on this.
>
>> > The only thing I have at the moment is:
>> >
>> >     1. git revert -m 1 bd37e9e41f5
>> >     2. merge: https://lore.kernel.org/git/cover-v6-00.29-00000000000-20220525T094123Z-avarab@gmail.com/
>> >     3. merge: https://lore.kernel.org/git/cover-v6-00.14-00000000000-20220525T100743Z-avarab@gmail.com/
>> >
>> > I.e. to pick this in the sequence I'd proposed doing & have tested
>> > thoroughly.
>>
>> I know you two have difference in opinions, but throwing away everything
>> the other party did and forcing your stuff in is not a very effective
>> way to work together.
>
> I had already pointed out a rather terrible issue in that 29-strong patch
> series: Dropping Azure Pipelines support makes it unnecessarily harder to
> work on Git security issues. And it's not like we have an armada of people
> working on those. I, for one, am pretty worn out from the recent work.
>
> It might not be the intention of that patch series to make my life harder,
> but it sure would be its impact. And intent does not excuse impact.
>
> I therefore had to conclude that the patch series in this form cannot be
> merged.

You raised the same concern in February, and I made some practical suggestions for how to proceed with that in:

    https://lore.kernel.org/git/220222.86y2236ndp.gmgdl@evledraar.gmail.com/
You didn't reply, and here was a reminder in late March:
    https://lore.kernel.org/git/cover-v2-00.25-00000000000-20220325T182534Z-avarab@gmail.com/

You then had a similar concern, and I replied again in early April saying I'd be happy to acomodate you, if you could reply to that original E-Mail and clarify what you'd like exactly:

    https://lore.kernel.org/git/220406.86bkxeecoi.gmgdl@evledraar.gmail.com/
And here's a mention of it again in late April:
    https://lore.kernel.org/git/220421.86fsm66zmz.gmgdl@evledraar.gmail.com/

Then a few weeks ago you brought up the same point, and I replied again you to please reply to that E-mail from back in February:

    https://lore.kernel.org/git/220524.86y1yrwaw0.gmgdl@evledraar.gmail.com/
So really Johannes, I'm completely fine with accommodating you.

But when you suggest that some ad-hoc combination of out-of-tree code and not-used-in-tree code you have must not be touched *and* you're seemingly unwilling to figure out some way forward you're not being very helpful.

Instead you just keep repeating that something must not be changed, and when it's pointed out to you that that would block someone else's patches, but would you be OK with X, Y or Z you seemingly just blackhole the replies you get.

So can you reply to that this time? Thanks.
> I recall that other reviews reached the same consensus, that this
> 29-strong patch series is too unfocused on any particular goal. So maybe
> calling this "my opinion" is not exactly fair.

Yes, that's something others brought up, but it's unrelated to the Azure issue you're raising here. That change was contained within the first 6 or so patches of that series.

Ævar Arnfjörð Bjarmason· Jun 3, 2022, 10:23 UTC · re: Ævar Arnfjörð Bjarmason · lore

[PATCH v2] ls-tree: test for the regression in 9c4d58ff2c3

Add a test for the regression introduced in my 9c4d58ff2c3 (ls-tree: split up "fast path" callbacks, 2022-03-23) and fixed in 350296cc789 (ls-tree: `-l` should not imply recursive listing, 2022-04-04), and test for the test of ls-tree option/mode combinations to make sure we don't have other blind spots.

The setup for these tests can be shared with those added in the 1041d58b4d9 (Merge branch 'tl/ls-tree-oid-only', 2022-04-04) topic, so let's create a new t/lib-t3100.sh to help them share data.

The existing tests in "t3104-ls-tree-format.sh" didn't deal with a submodule, which they'll now encounter with as the setup_basic_ls_tree_data() sets one up.

This extensive testing should give us confidence that there were no further regressions in this area. The lack of testing was noted back in [1], but unfortunately we didn't cover that blind-spot before 9c4d58ff2c3.

1. https://lore.kernel.org/git/211115.86o86lqe3c.gmgdl@evledraar.gmail.com/
Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---

Fix a quoting issue in v1 that made CI fail, now passes: https://github.com/avar/git/tree/avar/ls-tree-rewrite-regression-test-2

I usually check the CI before submitting, but I don't know how that slipped through in this case, sorry, and thanks for the quick review/diagnosis.

Range-diff against v1:
1:  0fdfec624eb ! 1:  f2beb02dd29 ls-tree: test for the regression in 9c4d58ff2c3
    @@ t/t3105-ls-tree-output.sh (new)
     +'
     +
     +test_ls_tree_format_mode_output () {
    -+	local opts=$1 &&
    ++	local opts="$1" &&
     +	shift &&
     +	cat >expect &&
     +
 t/lib-t3100.sh            |  10 ++
 t/t3104-ls-tree-format.sh |   5 +-
 t/t3105-ls-tree-output.sh | 192 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 204 insertions(+), 3 deletions(-)
 create mode 100644 t/lib-t3100.sh
 create mode 100755 t/t3105-ls-tree-output.sh
Show changes to 3 files +204 −3

t/lib-t3100.sh, t/t3104-ls-tree-format.sh, t/t3105-ls-tree-output.sh

diff --git a/t/lib-t3100.sh b/t/lib-t3100.sh
new file mode 100644
index 00000000000..eabb5fd8034
--- /dev/null
+++ b/t/lib-t3100.sh
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+setup_basic_ls_tree_data () {
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file &&
+	git clone . submodule &&
+	git submodule add ./submodule &&
+	git commit -m"add submodule"
+}
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 0769a933d69..383896667b6 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -4,6 +4,7 @@ test_description='ls-tree --format'
 
 TEST_PASSES_SANITIZE_LEAK=true
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-t3100.sh
 
 test_expect_success 'ls-tree --format usage' '
 	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
@@ -12,9 +13,7 @@ test_expect_success 'ls-tree --format usage' '
 '
 
 test_expect_success 'setup' '
-	mkdir dir &&
-	test_commit dir/sub-file &&
-	test_commit top-file
+	setup_basic_ls_tree_data
 '
 
 test_ls_tree_format () {
diff --git a/t/t3105-ls-tree-output.sh b/t/t3105-ls-tree-output.sh
new file mode 100755
index 00000000000..ce2391e28be
--- /dev/null
+++ b/t/t3105-ls-tree-output.sh
@@ -0,0 +1,192 @@
+#!/bin/sh
+
+test_description='ls-tree output'
+
+TEST_PASSES_SANITIZE_LEAK=true
+. ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-t3100.sh
+
+test_expect_success 'ls-tree --format usage' '
+	test_expect_code 129 git ls-tree --format=fmt -l HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&
+	test_expect_code 129 git ls-tree --format=fmt --name-status HEAD
+'
+
+test_expect_success 'setup' '
+	setup_basic_ls_tree_data
+'
+
+test_ls_tree_format_mode_output () {
+	local opts="$1" &&
+	shift &&
+	cat >expect &&
+
+	while test $# -gt 0
+	do
+		local mode="$1" &&
+		shift &&
+
+		test_expect_success "'ls-tree $opts${mode:+ $mode}' output" '
+			git ls-tree ${mode:+$mode }$opts HEAD >actual &&
+			test_cmp expect actual
+		'
+
+		case "$opts" in
+		--full-tree)
+			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir, fails)" '
+				test_must_fail git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../
+			'
+			;;
+		*)
+			test_expect_success "'ls-tree $opts${mode:+ $mode}' output (via subdir)" '
+				git -C dir ls-tree --full-name ${mode:+$mode }$opts HEAD -- ../ >actual &&
+				test_cmp expect actual
+			'
+			;;
+		esac
+	done
+}
+
+# test exact output of option (none, --long, ...) and mode (none and
+# -d, -r -t) and combinations
+test_expect_success 'setup: HEAD_* variables' '
+	HEAD_gitmodules=$(git rev-parse HEAD:.gitmodules) &&
+	HEAD_dir=$(git rev-parse HEAD:dir) &&
+	HEAD_top_file=$(git rev-parse HEAD:top-file.t) &&
+	HEAD_submodule=$(git rev-parse HEAD:submodule) &&
+	HEAD_dir_sub_file=$(git rev-parse HEAD:dir/sub-file.t)
+'
+## opt =
+test_ls_tree_format_mode_output "" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+## opt = --long
+test_ls_tree_format_mode_output "--long" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules      61	.gitmodules
+	040000 tree $HEAD_dir       -	dir
+	160000 commit $HEAD_submodule       -	submodule
+	100644 blob $HEAD_top_file       9	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--long" "-d" <<-EOF
+	040000 tree $HEAD_dir       -	dir
+	160000 commit $HEAD_submodule       -	submodule
+	EOF
+test_ls_tree_format_mode_output "--long" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules      61	.gitmodules
+	100644 blob $HEAD_dir_sub_file      13	dir/sub-file.t
+	160000 commit $HEAD_submodule       -	submodule
+	100644 blob $HEAD_top_file       9	top-file.t
+	EOF
+## opt = --name-only
+test_ls_tree_format_mode_output "--name-only" "" "-t" <<-EOF
+	.gitmodules
+	dir
+	submodule
+	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--name-only" "-d" <<-EOF
+	dir
+	submodule
+	EOF
+test_ls_tree_format_mode_output "--name-only" "-r" <<-EOF
+	.gitmodules
+	dir/sub-file.t
+	submodule
+	top-file.t
+	EOF
+## opt = --object-only
+test_ls_tree_format_mode_output "--object-only" "" "-t" <<-EOF
+	$HEAD_gitmodules
+	$HEAD_dir
+	$HEAD_submodule
+	$HEAD_top_file
+	EOF
+test_ls_tree_format_mode_output "--object-only" "-d" <<-EOF
+	$HEAD_dir
+	$HEAD_submodule
+	EOF
+test_ls_tree_format_mode_output "--object-only" "-r" <<-EOF
+	$HEAD_gitmodules
+	$HEAD_dir_sub_file
+	$HEAD_submodule
+	$HEAD_top_file
+	EOF
+## opt = --object-only --abbrev
+test_expect_success 'setup: HEAD_short_* variables' '
+	HEAD_short_gitmodules=$(git rev-parse --short HEAD:.gitmodules) &&
+	HEAD_short_dir=$(git rev-parse --short HEAD:dir) &&
+	HEAD_short_top_file=$(git rev-parse --short HEAD:top-file.t) &&
+	HEAD_short_submodule=$(git rev-parse --short HEAD:submodule) &&
+	HEAD_short_dir_sub_file=$(git rev-parse --short HEAD:dir/sub-file.t)
+'
+test_ls_tree_format_mode_output "--object-only --abbrev" "" "-t" <<-EOF
+	$HEAD_short_gitmodules
+	$HEAD_short_dir
+	$HEAD_short_submodule
+	$HEAD_short_top_file
+	EOF
+test_ls_tree_format_mode_output "--object-only --abbrev" "-d" <<-EOF
+	$HEAD_short_dir
+	$HEAD_short_submodule
+	EOF
+test_ls_tree_format_mode_output "--object-only --abbrev" "-r" <<-EOF
+	$HEAD_short_gitmodules
+	$HEAD_short_dir_sub_file
+	$HEAD_short_submodule
+	$HEAD_short_top_file
+	EOF
+## opt = --full-name
+test_ls_tree_format_mode_output "--full-name" "" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-name" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+## opt = --full-tree
+test_ls_tree_format_mode_output "--full-tree" "" "-t" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+test_ls_tree_format_mode_output "--full-tree" "-d" <<-EOF
+	040000 tree $HEAD_dir	dir
+	160000 commit $HEAD_submodule	submodule
+	EOF
+test_ls_tree_format_mode_output "--full-tree" "-r" <<-EOF
+	100644 blob $HEAD_gitmodules	.gitmodules
+	100644 blob $HEAD_dir_sub_file	dir/sub-file.t
+	160000 commit $HEAD_submodule	submodule
+	100644 blob $HEAD_top_file	top-file.t
+	EOF
+
+test_done
-- 
2.36.1.1119.g5a713b43bd4
Johannes Schindelin· Jun 8, 2022, 21:55 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v2] ls-tree: test for the regression in 9c4d58ff2c3

Hi Ævar,
On Fri, 3 Jun 2022, Ævar Arnfjörð Bjarmason wrote:
Show 8 quoted lines
> Range-diff against v1:
> 1:  0fdfec624eb ! 1:  f2beb02dd29 ls-tree: test for the regression in 9c4d58ff2c3
>     @@ t/t3105-ls-tree-output.sh (new)
>      +'
>      +
>      +test_ls_tree_format_mode_output () {
>     -+	local opts=$1 &&
>     ++	local opts="$1" &&

This is a minimal fix for the CI failure I reported, but the hefty addition of a dozen or so test cases (with the corresponding time penalty for everybody who wishes to run the test suite) is still there, which seems quite excessive.

Ciao, Johannes

Teng Long· Apr 7, 2022, 09:29 UTC · re: Ævar Arnfjörð Bjarmason · lore

Re: [PATCH v2] ls-tree: fix --long implying -r regression in 9c4d58ff2c3

On Tue,  5 Apr 2022 01:45:30 +0200, Ævar Arnfjörð Bjarmason wrote:
Show 12 quoted lines
> Indeed. I guess that makes this a proposed v2,
> 
> I refreshed my E-Mail when I was just about to submit this and spotted
> that you'd sent your fix in, but I came up with this (in retrospect a
> pretty obvious think-o) fix independently, sorry about the bug.
> 
> The tests took me a bit longer though...
> 
> Haing written them I guess we could do them post-release, since the
> important thing is to validate the changes. As noted in the commit
> message we're now testing all combinations of the "mode" and "format"
> options.
Thanks for the quickly fix.
> +    	       git ls-tree ${opt:+$opt }$opts $opt HEAD >actual &&

I think maybe the "$opt" here should be removed, because "${opt:+$opt }" will not append it if "$opt" is null or unset, or will append "$opt ", like:

Show changes to t/t3104-ls-tree-format.sh +1 −2
diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh
index 520b5a95c0..9d3ad0295e 100755
--- a/t/t3104-ls-tree-format.sh
+++ b/t/t3104-ls-tree-format.sh
@@ -31,7 +31,7 @@ test_ls_tree_format () {
        for opt in '' '-d' '-r' '-t'
        do
                test_expect_success "'ls-tree $opts${opt:+ $opt}' output" '
-                       git ls-tree ${opt:+$opt }$opts $opt HEAD >actual &&
+                       git ls-tree ${opt:+$opt }$opts HEAD >actual &&
                        test_cmp expect${opt:+.$opt} actual
                '
        done
--

But I'm not sure it's intentional or inadvertently design or maybe I misunderstood
it here.

Thanks.
Junio C Hamano· Apr 6, 2022, 15:41 UTC · re: Josh Steadmon · lore

Re: [RFC PATCH] ls-tree: `-l` should not imply recursive listing

Josh Steadmon <steadmon@google.com> writes:
Show 15 quoted lines
> In 9c4d58ff2c (ls-tree: split up "fast path" callbacks, 2022-03-23), a
> refactoring of the various read_tree_at() callbacks caused us to
> unconditionally recurse into directories if `-l` (long format) was
> passed on the command line, regardless of whether or not we also pass
> the `-r` (recursive) flag.
>
> Fix this by making show_tree_long() return the value of `recurse`,
> rather than always returning 1. This value is interpreted by
> read_tree_at() to be a signal on whether or not to recurse.
>
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
> I believe this is the correct fix for the change in `git ls-tree -l`
> output. I would also like to add tests in a future fix, but I do not
> have time to add them today.

It's rather an obvious bug and it is sad that existing tests did not find (and no, I do not think lower-level unit testing would have helped us in any way, either).

Will queue.
Thanks for a quick fix.
Show 18 quoted lines
>  builtin/ls-tree.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 5dac9ee5b9..e279be8bb6 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -255,7 +255,7 @@ static int show_tree_long(const struct object_id *oid, struct strbuf *base,
>  	printf("%06o %s %s %7s\t", data.mode, type_name(data.type),
>  	       find_unique_abbrev(data.oid, abbrev), size_text);
>  	show_tree_common_default_long(base, pathname, data.base->len);
> -	return 1;
> +	return recurse;
>  }
>  
>  static int show_tree_name_only(const struct object_id *oid, struct strbuf *base,
>
> base-commit: faa21c10d44184f616d391c158dcbb13b9c72ef3
Junio C Hamano· Mar 23, 2022, 19:54 UTC · re: Teng Long · lore

Re: [PATCH v14 00/15] ls-tree: "--object-only" and "--format" opts

Teng Long <dyroneteng@gmail.com> writes:
Show 10 quoted lines
>  8:  55f1e10d7e !  9:  f4fc377dfa ls-tree: slightly refactor `show_tree()`
>     @@ Metadata
>       ## Commit message ##
>          ls-tree: slightly refactor `show_tree()`
>      
>     -    This is a non-functional change, we use a new int "shown_fields" to mark
>     -    which columns to output, and `parse_shown_fields()` to calculate the
>     -    value of "shown_fields".
>     +    This is a non-functional change, we introduce an enum "ls_tree_cmdmode"
>     +    then use it to mark which columns to output.

I think readers will read "non-functional change" as "we changed it and made it not function anymore", but you meant something else ;-)

I'll let it pass, though, for the sake of expediency. The series somehow ended up to be unnecessarily longer and more complicated, becoming closer to a complete rewrite of the implementation, compared to the earlier rounds which were more like incremental enhancement.

Thanks, will queue.
>          This has the advantage of making the show_tree logic simpler and more
>          readable, as well as making it easier to extend new options (for example,
>          if we want to add a "--object-only" option, we just need to add a similar
>     +    "short-circuit logic in "show_tree()").
Teng Long· Mar 24, 2022, 03:00 UTC · re: Junio C Hamano · lore

Re: [PATCH v14 00/15] ls-tree: "--object-only" and "--format" opts

> I think readers will read "non-functional change" as "we changed it
> and made it not function anymore", but you meant something else ;-)

I tried to translate by Google and the translation is OK, but indeed it will bring some little murmur on "function", I will try to avoid this like ambiguity words for more improved description.

Thanks.
Ævar Arnfjörð Bjarmason· Feb 28, 2022, 16:18 UTC · lore

Re: [PATCH v11 08/13] ls-tree: slightly refactor `show_tree()`

On Mon, Feb 28 2022, Teng Long wrote:

[Since this had HTML parts it didn't make it to the git ML, quoting it here in full & replying on-list].

Show 107 quoted lines
> On Sat, Feb 19, 2022 at 2:04 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:
>
>  > I think this and the 09/13 really don't make sense in combination.
>  > 
>  > Now, I clearly prefer to put options for the command into its own little
>  > struct to pass around, I think it makes for easier reading than the
>  > globals you end up with.
>  > 
>  > But tastes differ, and some built-ins use one, and some the other
>  > pattern.
>  > 
>  > But this is really the worst of both worlds, let's just pick one or the
>  > other, not effectively some some ptions in that struct in 09/13, and
>  > some in globals here...
>
> I'm not 100 percent sure about it, but I agree with we can just pick one or
> the other.
>
> So, how about: 
>      1. add "unsigned shown_fields"  in "struct show_tree_data"
>      2.  move global "show_fields" to "struct show_tree_data"
>      3. move "parse_show_fields()" to "show_tree()"
>
> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
> index 293b8f9dfb..92add01ecc 100644
> --- a/builtin/ls-tree.c
> +++ b/builtin/ls-tree.c
> @@ -25,7 +25,6 @@ static int ls_options;
>  static struct pathspec pathspec;
>  static int chomp_prefix;
>  static const char *ls_tree_prefix;
> -static unsigned int shown_fields;
>  #define FIELD_PATH_NAME 1
>  #define FIELD_SIZE (1 << 1)
>  #define FIELD_OBJECT_NAME (1 << 2)
> @@ -40,6 +39,7 @@ struct show_tree_data {
>         const struct object_id *oid;
>         const char *pathname;
>         struct strbuf *base;
> +       unsigned int shown_fields;
>  };
>  
>  static const  char * const ls_tree_usage[] = {
> @@ -55,19 +55,19 @@ enum {
>  
>  static int cmdmode = MODE_UNSPECIFIED;
>  
> -static int parse_shown_fields(void)
> +static int parse_shown_fields(unsigned int *shown_fields)
>  {
>         if (cmdmode == MODE_NAME_ONLY) {
> -               shown_fields = FIELD_PATH_NAME;
> +               *shown_fields = FIELD_PATH_NAME;
>                 return 0;
>         }
>  
>         if (!ls_options || (ls_options & LS_RECURSIVE)
>             || (ls_options & LS_SHOW_TREES)
>             || (ls_options & LS_TREE_ONLY))
> -               shown_fields = FIELD_DEFAULT;
> +               *shown_fields = FIELD_DEFAULT;
>         if (cmdmode == MODE_LONG)
> -               shown_fields = FIELD_LONG_DEFAULT;
> +               *shown_fields = FIELD_LONG_DEFAULT;
>         return 1;
>  }
>  
> @@ -105,7 +105,7 @@ static int show_default(struct show_tree_data *data)
>  {
>         size_t baselen = data->base->len;
>  
> -       if (shown_fields & FIELD_SIZE) {
> +       if (data->shown_fields & FIELD_SIZE) {
>                 char size_text[24];
>                 if (data->type == OBJ_BLOB) {
>                         unsigned long size;
> @@ -137,15 +137,19 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
>  {
>         int recurse = 0;
>         size_t baselen;
> +       unsigned int shown_fields = 0;
>         enum object_type type = object_type(mode);
> -       struct show_tree_data data = {
> +               struct show_tree_data data = {
>                 .mode = mode,
>                 .type = type,
>                 .oid = oid,
>                 .pathname = pathname,
>                 .base = base,
> +               .shown_fields = shown_fields,
>         };
>  
> +       parse_shown_fields(&shown_fields);
> +
>         if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))
>                 recurse = READ_TREE_RECURSIVE;
>         if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))
> @@ -219,8 +223,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)
>         if (get_oid(argv[0], &oid))
>                 die("Not a valid object name %s", argv[0]);
>  
> -       parse_shown_fields();
> -
>         /*
>          * show_recursive() rolls its own matching code and is
>          * generally ignorant of 'struct pathspec'. The magic mask
>

Yes, this looks good, i.e. these are (I think, I didn't look in much detail) now all store one place instead of two.

Show 11 quoted lines
>  > >+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */
>
>  >Why do we need some FIELD_DEFAULT here as opposed to just having it by
>  >an enum field with a valu of 0?
>
>  
> You mean to use "cmdmode" or set "FIELD_DEFAULT" to 0, if the former  I think is a
> similar situation to your last replied paragraph so I will reply to that. Or if the 
> latter, we want them as a bitmask not only a flag,  so  I think "0" here means no fields
> will be shown and "29" is the default bitmask value (also some context metioned in
> https://public-inbox.org/git/xmqqmtlu7bb0.fsf@gitster.g/).

I meant (elaborated on below) that it seemed like this was conflating "default mode" with "wants to emit size" in the state machine.

I.e. I didn't try rewriting it (or re-visited it now), but it seemed at the time with some minor changes it could be eliminated, but maybe not...

Show 5 quoted lines
>  > let's name this enum type and use it, see e.g. builtin/help.c's "static
>  > enum help_action" for an example.
>
> Totally agree with that. 
> I want to name it "mutx_option", I'm not sure whether we need a better one. 

Sure, I don't think the name matters much since it's now, so whatever you think is OK.

I just wanted to point out that some existing code makes the same pattern simpler by relying on the 0-init and having fields starting at 0.

Show 19 quoted lines
>  > I still don't really get why we can't just use the one MODE_*
>  > here. E.g. doesn't MODE_LONG map to FIELD_LONG_DEFAULT, MODE_NAME_ONLY
>  > to FIELD_PATH_NAME etc?
>
>  
> For current, the answer is YES I think.
>
>  > Is this all so we can do "shown_fields & FIELD_SIZE" in show_default()
>  > as opposed to e.g. checking "default format or long format?" ?
>
> Actually, in v3 patch I use "cmdmode" to determine the output fields and the next patches Junio
> suggest using bitmasks for the work (https://public-inbox.org/git/xmqqmtlu7bb0.fsf@gitster.g/).
>
> I didn't understand it at first,  and I thought it might make sense. The cmd_mode is used here to indicate that options are mutually exclusive, which may have the same effect in certain
> cases, such as some short-circuited options (-- name-only).
>
> However, cmd_mode is not a complete representation of what fields we want to output, because some options may not be mutually exclusive but can also be used to change the output (for
> example, we want to add -format or others). If there is a bitmask associated with the output field, we can quickly and explicitly know what to output in the show_tree() phase, without
> thinking about the relationship to cmd_mode or doing a translation of meaning, and at the same time  this will make it easier to adapt to change I think.
*nod*

I'm not quite sure I get what you're aiming for, but I'll look at it again in any future re-roll & see if I can submit a patch-on-top next time if I have any comments (if at all worth it). Thanks!

← back to recent threads