[PATCH 0/3] support `--oid-only` in `ls-tree`

STALE1594d

Revision v1 of 3 in this series.

194 messages, 11 authors, 2022-04-07 · page 2 of 3 · open the first message on its own page

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-10 18:05:54

On Mon, Jan 10 2022, Johannes Schindelin wrote:
Hi Teng,

On Mon, 10 Jan 2022, Teng Long wrote:
quoted
[...] about the using "strbuf_addf(line, "%7s" , "-");" or
"strbuf_addstr(line, "      -");". [...]

Why I prefer more of the former that is because, for the single line,
it's more readable I think.
I strongly disagree. Using a format requires the reader to interpret a
`printf()` format, to remember (if they ever knew) the rules about padding
with `%<number>s` formats, and then to satisfy themselves that the result
is correct.

That's quite the cognitive load you put on the reader for something as
trivial as "      -".

Not a fan,
Johannes
I think you can argue that, but saying that this series must change that
existing "%7s" format just because it happened to trip over an existin
coccinelle rule as code was changed from printf() to strbuf_addf() is
going overboard. 

Also, the ls-tree output has existing alignment issues, and the
documentation says:

    "right-justified with minimum width of 7 characters"

So I'd think we'd want to keep the %7s, and in some future change change
that format to be dynamic so we'd align things properly if some fields
were longer than 7 characters.

Re: [PATCH v9 8/9] ls-tree.c: introduce "--format" option

From: Martin Ågren <hidden>
Date: 2022-01-10 19:41:47

Hi Teng,

On Fri, 7 Jan 2022 at 06:34, Teng Long [off-list ref] wrote:
+--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
Above, there is a missing space just before "are". That causes the
manpage to render a little bit funny.
+       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`.
+
+Customized format:
+
+It's support to print customized format by `%(fieldname)` with `--format` option.
I had to re-read this to understand. How about the following?

    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.

Just a suggestion. Feel free to tweak or ignore. :-)
+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>
+
+The output format changes to:
+
+        {"object":"<object>", "file":"<file>"}
+
Nice!

Martin

Re: [PATCH v9 8/9] ls-tree.c: introduce "--format" option

From: Teng Long <hidden>
Date: 2022-01-11 09:35:08

On Tue, Jan 11, 2022 at 3:41 AM Martin Ågren [off-list ref] wrote:
Above, there is a missing space just before "are". That causes the
manpage to render a little bit funny.
quoted
+       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`.
+
Thanks for reviewing.  will be fixed in the next patch.
quoted
+Customized format:
+
+It's support to print customized format by `%(fieldname)` with `--format` option.
I had to re-read this to understand. How about the following?

    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.

Just a suggestion. Feel free to tweak or ignore. :-)
Yours reads more smoothly than mine, it will be applied in next patch.

Thanks.

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Teng Long <hidden>
Date: 2022-01-11 10:37:41

On Tue, Jan 11, 2022 at 2:02 AM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
quoted
The original rule was introduced by commit:

    https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac
Let's refer to commits like this:

    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
OK, I will.
That file currently has:

     18:format F =~ "s";
     26:format F =~ "s";
     47:format F =~ "s";

If we're fixing these let's fix the other logic errors as well.
Thanks for the reminding, they'll be applied in the next patch.

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Taylor Blau <hidden>
Date: 2022-01-11 16:42:07

On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:
On Thu, Jan 06 2022, Teng Long wrote:
quoted
The original rule was introduced by commit:

    https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac
Let's refer to commits like this:

    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
I find it helpful to have an alias like:

    $ git config alias.ll
    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short

in my $HOME/.gitconfig so that I can easily format commits in the
standard way.

I think that this alias came from Peff, but I can't remember.

Thanks,
Taylor

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: René Scharfe <hidden>
Date: 2022-01-11 19:06:38

Am 11.01.22 um 17:42 schrieb Taylor Blau:
On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:
quoted
On Thu, Jan 06 2022, Teng Long wrote:
quoted
The original rule was introduced by commit:

    https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac
Let's refer to commits like this:

    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
I find it helpful to have an alias like:

    $ git config alias.ll
    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short

in my $HOME/.gitconfig so that I can easily format commits in the
standard way.
You can shorten "--pretty='tformat:%h (%s, %ad)' --date=short" to
"--pretty=reference" or "--format=reference".  For me that's easy enough
to remember that I don't need an alias.

Silly question, going further off-topic: What's "git always" doing?

René

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Taylor Blau <hidden>
Date: 2022-01-11 20:11:22

On Tue, Jan 11, 2022 at 08:06:00PM +0100, René Scharfe wrote:
Am 11.01.22 um 17:42 schrieb Taylor Blau:
quoted
I find it helpful to have an alias like:

    $ git config alias.ll
    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short

in my $HOME/.gitconfig so that I can easily format commits in the
standard way.
You can shorten "--pretty='tformat:%h (%s, %ad)' --date=short" to
"--pretty=reference" or "--format=reference".  For me that's easy enough
to remember that I don't need an alias.
Ah, of course. Peff's copy likely predates `--pretty=reference`, and I
inherited the cruft from him. Your suggestion has the nice benefit of
colorizing the output when going to the terminal.
Silly question, going further off-topic: What's "git always" doing?
Oops, I should have mentioned. It's another alias to ensure that the
following command is always run in a Git repository (either the current
one or a hand-picked default):

    $ git config alias.always
    !git rev-parse 2>/dev/null || cd ~/src/git; git

I often read mail out of my home directory, and the above works with my
`:Git` command in Vim (which passes its arguments to `git always` and
inserts the result back into my buffer). That way I don't have to first
`:cd ~/src/git` and then `:Git ll xyz`, I can just `:Git ll xyz` and it
does what I meant most of the time.

Thanks,
Taylor

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-11 20:40:14

On Tue, Jan 11 2022, Taylor Blau wrote:
On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:
quoted
On Thu, Jan 06 2022, Teng Long wrote:
quoted
The original rule was introduced by commit:

    https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac
Let's refer to commits like this:

    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)
I find it helpful to have an alias like:

    $ git config alias.ll
    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short

in my $HOME/.gitconfig so that I can easily format commits in the
standard way.

I think that this alias came from Peff, but I can't remember.
Nowadays you can do this as:

    git show -s --pretty=reference

See Documentation/SubmittingPatches

I use:

    $ git help reference
    'reference' is aliased to '!git --no-pager log --pretty=reference -1'

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Teng Long <hidden>
Date: 2022-01-13 03:29:12

On Wed, Jan 12, 2022 at 12:42 AM Taylor Blau [off-list ref] wrote:
I find it helpful to have an alias like:

    $ git config alias.ll
    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short

in my $HOME/.gitconfig so that I can easily format commits in the
standard way.

I think that this alias came from Peff, but I can't remember.
Wow. That's cool and efficient.

Thanks.

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Teng Long <hidden>
Date: 2022-01-13 03:34:24

On Wed, Jan 12, 2022 at 4:11 AM Taylor Blau [off-list ref] wrote:
quoted
Silly question, going further off-topic: What's "git always" doing?
Oops, I should have mentioned. It's another alias to ensure that the
following command is always run in a Git repository (either the current
one or a hand-picked default):

    $ git config alias.always
    !git rev-parse 2>/dev/null || cd ~/src/git; git

I often read mail out of my home directory, and the above works with my
`:Git` command in Vim (which passes its arguments to `git always` and
inserts the result back into my buffer). That way I don't have to first
`:cd ~/src/git` and then `:Git ll xyz`, I can just `:Git ll xyz` and it
does what I meant most of the time.

The same question is clear now。
Thanks for the explanations from Taylor Blau and René Scharfe.

Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`

From: Teng Long <hidden>
Date: 2022-01-13 03:35:28

On Wed, Jan 12, 2022 at 4:40 AM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
Nowadays you can do this as:

    git show -s --pretty=reference

See Documentation/SubmittingPatches

I use:

    $ git help reference
    'reference' is aliased to '!git --no-pager log --pretty=reference -1'
Make sense.
Thanks.

[PATCH v10 0/9] ls-tree: "--object-only" and "--format" opts

From: Teng Long <hidden>
Date: 2022-01-13 03:42:22

Major diff from v9:

    1. Exchange the order of "cocci: allow padding with `strbuf_addf()`"
and "introduce "--format" option".
       
       (Advice from Johannes Schindelin) 
       Let coccillene's changes take effect before the commit with the rule
       check failure. 

    2. ls-tree.c: support --object-only option for "git-ls-tree"
       
       (Advice from Junio C Hamano)
       Rename the return value name from "recursive" to "recurse".
       Rename "init_type()" to "get_type()".
       Remove "init_recursive()"       

    3. ls-tree.c: introduce "--format" option
       
       (Advice from Martin Ågren)
       Fix some document errors and wording.
       Bug fixing in "t3105" (Always return 129 without specifying an argument).

    4. cocci: allow padding with `strbuf_addf()`  

       (Advice from Ævar Arnfjörð Bjarmason)
       Fix the other logic errors as well.

Thanks.

Teng Long (5):
  ls-tree: optimize naming and handling of "return" in show_tree()
  ls-tree.c: support --object-only option for "git-ls-tree"
  ls-tree.c: introduce struct "show_tree_data"
  cocci: allow padding with `strbuf_addf()`
  ls-tree.c: introduce "--format" option

Ævar Arnfjörð Bjarmason (4):
  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"

 Documentation/git-ls-tree.txt   |  56 +++++-
 builtin/ls-tree.c               | 328 +++++++++++++++++++++++++-------
 contrib/coccinelle/strbuf.cocci |   6 +-
 t/t3104-ls-tree-oid.sh          |  51 +++++
 t/t3105-ls-tree-format.sh       |  55 ++++++
 5 files changed, 426 insertions(+), 70 deletions(-)
 create mode 100755 t/t3104-ls-tree-oid.sh
 create mode 100755 t/t3105-ls-tree-format.sh

Range-diff against v9:
 1:  75503c41a7 <  -:  ---------- 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:  b04188c822 ls-tree: optimize naming and handling of "return" in show_tree()
 2:  e0274f079a !  6:  bcfbc935b8 ls-tree.c: support --object-only option for "git-ls-tree"
    @@ builtin/ls-tree.c
      {
      	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 enum object_type get_type(unsigned int mode)
    + 	        : OBJ_BLOB);
      }
      
     +static int show_default(const struct object_id *oid, enum object_type type,
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     +	return 1;
     +}
     +
    - static void init_type(unsigned mode, enum object_type *type)
    + static int show_tree(const struct object_id *oid, struct strbuf *base,
    + 		const char *pathname, unsigned mode, void *context)
      {
    - 	if (S_ISGITLINK(mode))
     @@ 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 !READ_TREE_RECURSIVE;
    + 		return 0;
      
     -	if (!(ls_options & LS_NAME_ONLY)) {
     -		if (ls_options & LS_SHOW_SIZE) {
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -		}
     +	if (shown_fields == FIELD_OBJECT_NAME) {
     +		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
    -+		return recursive;
    ++		return recurse;
      	}
     -	baselen = base->len;
     -	strbuf_addstr(base, pathname);
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     +					   chomp_prefix ? ls_tree_prefix : NULL,
     +					   stdout, line_termination);
     +		strbuf_setlen(base, baselen);
    -+		return recursive;
    ++		return recurse;
     +	}
     +
     +	if (shown_fields >= FIELD_DEFAULT)
     +		show_default(oid, type, pathname, mode, base);
     +
    - 	return recursive;
    + 	return recurse;
      }
      
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
 3:  725c4d0187 !  7:  3ddffa1027 ls-tree.c: 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 int show_recursive(const char *base, size_t baselen,
    - 	return 0;
    +@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
    + 	        : OBJ_BLOB);
      }
      
     -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 recursive = 0;
      	size_t baselen;
    --	enum object_type type = OBJ_BLOB;
    + 	enum object_type type = get_type(mode);
    + 
     +	struct show_tree_data data = {
     +		.mode = mode,
    -+		.type = OBJ_BLOB,
    ++		.type = type,
     +		.oid = oid,
     +		.pathname = pathname,
     +		.base = base,
     +	};
    - 
    --	init_type(mode, &type);
    -+	init_type(mode, &data.type);
    - 	init_recursive(base, pathname, &recursive);
    - 
    --	if (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))
    -+	if (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))
    - 		return recursive;
    --	if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    -+	if (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    - 		return !READ_TREE_RECURSIVE;
    - 
    - 	if (shown_fields == FIELD_OBJECT_NAME) {
    ++
    + 	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,
      	}
      
    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb
     -		show_default(oid, type, pathname, mode, base);
     +		show_default(&data);
      
    - 	return recursive;
    + 	return recurse;
      }
 -:  ---------- >  8:  4b58a707c2 cocci: allow padding with `strbuf_addf()`
 4:  7df58483a4 !  9:  db058bf670 ls-tree.c: introduce "--format" option
    @@ Documentation/git-ls-tree.txt: OPTIONS
     +--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
    ++	`%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
    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl
      
     +Customized format:
     +
    -+It's support to print customized format by `%(fieldname)` with `--format` option.
    ++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
     +
    @@ builtin/ls-tree.c: static int parse_shown_fields(void)
      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,
    - 	return 0;
    +@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)
    + 	        : OBJ_BLOB);
      }
      
    -+static void init_recursive(struct strbuf *base, const char *pathname,
    -+				int *recursive)
    -+{
    -+	if (show_recursive(base->buf, base->len, pathname))
    -+		*recursive = READ_TREE_RECURSIVE;
    -+}
    -+
    -+static void init_type(unsigned mode, enum object_type *type)
    -+{
    -+	if (S_ISGITLINK(mode))
    -+		*type = OBJ_COMMIT;
    -+	else if (S_ISDIR(mode))
    -+		*type = OBJ_TREE;
    -+}
    -+
     +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,
     +			 const char *pathname, unsigned mode, void *context)
     +{
     +	size_t baselen;
    -+	int recursive = 0;
    ++	int recurse = 0;
     +	struct strbuf line = STRBUF_INIT;
    ++	enum object_type type = get_type(mode);
    ++
     +	struct show_tree_data data = {
     +		.mode = mode,
    -+		.type = OBJ_BLOB,
    ++		.type = type,
     +		.oid = oid,
     +		.pathname = pathname,
     +		.base = base,
     +	};
     +
    -+	init_type(mode, &data.type);
    -+	init_recursive(base, pathname, &recursive);
    -+
    -+	if (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))
    -+		return recursive;
    -+	if (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))
    -+		return !READ_TREE_RECURSIVE;
    ++	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(&line, format, expand_show_tree, &data);
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,
     +	fwrite(line.buf, line.len, 1, stdout);
     +	strbuf_release(&line);
     +	strbuf_setlen(base, baselen);
    -+	return recursive;
    ++	return recurse;
     +}
     +
      static int show_default(struct show_tree_data *data)
      {
      	size_t baselen = data->base->len;
    -@@ builtin/ls-tree.c: static int show_default(struct show_tree_data *data)
    - 	return 1;
    - }
    - 
    --static void init_type(unsigned mode, enum object_type *type)
    --{
    --	if (S_ISGITLINK(mode))
    --		*type = OBJ_COMMIT;
    --	else if (S_ISDIR(mode))
    --		*type = OBJ_TREE;
    --}
    --
    --static void init_recursive(struct strbuf *base, const char *pathname,
    --				int *recursive)
    --{
    --	if (show_recursive(base->buf, base->len, pathname))
    --		*recursive = READ_TREE_RECURSIVE;
    --}
    --
    - static int show_tree(const struct object_id *oid, struct strbuf *base,
    - 		const char *pathname, unsigned mode, void *context)
    - {
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
      	struct object_id oid;
      	struct tree *tree;
    @@ t/t3105-ls-tree-format.sh (new)
     +. ./test-lib.sh
     +
     +test_expect_success 'ls-tree --format usage' '
    -+	test_expect_code 129 git ls-tree --format=fmt -l &&
    -+	test_expect_code 129 git ls-tree --format=fmt --name-only &&
    -+	test_expect_code 129 git ls-tree --format=fmt --name-status &&
    -+	test_expect_code 129 git ls-tree --format=fmt --object-only
    ++	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_success 'setup' '
 5:  8dafb2b377 <  -:  ---------- cocci: allow padding with `strbuf_addf()`
-- 
2.34.1.390.g2ae0a9cb82.dirty

[PATCH v10 1/9] ls-tree: remove commented-out code

From: Teng Long <hidden>
Date: 2022-01-13 03:42:25

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
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.390.g2ae0a9cb82.dirty

[PATCH v10 2/9] ls-tree: add missing braces to "else" arms

From: Teng Long <hidden>
Date: 2022-01-13 03:42:26

From: Ævar Arnfjörð Bjarmason <redacted>

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

Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
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.390.g2ae0a9cb82.dirty

[PATCH v10 3/9] ls-tree: use "enum object_type", not {blob,tree,commit}_type

From: Teng Long <hidden>
Date: 2022-01-13 03:42:31

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 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 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.390.g2ae0a9cb82.dirty

[PATCH v10 4/9] ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"

From: Teng Long <hidden>
Date: 2022-01-13 03:42:34

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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.390.g2ae0a9cb82.dirty

[PATCH v10 5/9] ls-tree: optimize naming and handling of "return" in show_tree()

From: Teng Long <hidden>
Date: 2022-01-13 03:42:36

The variable which "show_tree()" return is named "retval", a name that's
a little hard to understand. This commit tries to make the variable
and the related codes more clear in the context.

The commit firstly rename "retval" to "recurse" which is a more
meaningful name than before. Secondly, "get_type()" is introduced
to setup the "type" by "mode", this will remove some of the nested if.
After this, The codes here become a little bit clearer, so we do not
need to take a look at "read_tree_at()" in "tree.c" to make sure the
context of the return value.

Signed-off-by: Teng Long <redacted>
---
 builtin/ls-tree.c | 33 ++++++++++++++++++---------------
 1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..9729854a3d 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -61,24 +61,27 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static enum object_type get_type(unsigned int mode)
+{
+	return (S_ISGITLINK(mode)
+	        ? OBJ_COMMIT
+	        : S_ISDIR(mode)
+	        ? OBJ_TREE
+	        : OBJ_BLOB);
+}
+
 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;
-
-	if (S_ISGITLINK(mode)) {
-		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 = OBJ_TREE;
-	}
-	else if (ls_options & LS_TREE_ONLY)
+	enum object_type type = get_type(mode);
+
+	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)) {
@@ -109,7 +112,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.390.g2ae0a9cb82.dirty

[PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Teng Long <hidden>
Date: 2022-01-13 03:42:40

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).

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

Signed-off-by: Teng Long <redacted>
---
 Documentation/git-ls-tree.txt |   7 +-
 builtin/ls-tree.c             | 141 +++++++++++++++++++++++++---------
 t/t3104-ls-tree-oid.sh        |  51 ++++++++++++
 3 files changed, 160 insertions(+), 39 deletions(-)
 create mode 100755 t/t3104-ls-tree-oid.sh
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..729370f235 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] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]
 	    <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 9729854a3d..e1a2f8225b 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,22 +16,60 @@
 
 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)
+#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_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 int show_recursive(const char *base, size_t baselen, const char *pathname)
+enum {
+	MODE_UNSPECIFIED = 0,
+	MODE_NAME_ONLY,
+	MODE_OBJECT_ONLY,
+	MODE_LONG,
+};
+
+static int cmdmode = MODE_UNSPECIFIED;
+
+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;
+		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;
 
@@ -70,6 +108,39 @@ static enum object_type get_type(unsigned int mode)
 	        : OBJ_BLOB);
 }
 
+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)
 {
@@ -84,34 +155,24 @@ 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_OBJECT_NAME) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		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_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);
+
 	return recurse;
 }
 
@@ -129,12 +190,14 @@ 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", &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,
@@ -165,6 +228,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
diff --git a/t/t3104-ls-tree-oid.sh b/t/t3104-ls-tree-oid.sh
new file mode 100755
index 0000000000..6ce62bd769
--- /dev/null
+++ b/t/t3104-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.390.g2ae0a9cb82.dirty

[PATCH v10 7/9] ls-tree.c: introduce struct "show_tree_data"

From: Teng Long <hidden>
Date: 2022-01-13 03:42:47

"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: Teng Long <redacted>
---
 builtin/ls-tree.c | 44 +++++++++++++++++++++++++++++---------------
 1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index e1a2f8225b..56cc166adb 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -35,6 +35,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
@@ -108,17 +116,15 @@ static enum object_type get_type(unsigned int mode)
 	        : OBJ_BLOB);
 }
 
-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),
@@ -126,18 +132,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;
 }
 
@@ -148,6 +154,14 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	size_t baselen;
 	enum object_type type = get_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))
@@ -171,7 +185,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.390.g2ae0a9cb82.dirty

[PATCH v10 8/9] cocci: allow padding with `strbuf_addf()`

From: Teng Long <hidden>
Date: 2022-01-13 03:42:49

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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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.390.g2ae0a9cb82.dirty

[PATCH v10 9/9] ls-tree.c: introduce "--format" option

From: Teng Long <hidden>
Date: 2022-01-13 03:43:01

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: Teng Long <redacted>
---
 Documentation/git-ls-tree.txt |  51 +++++++++++++-
 builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-
 t/t3105-ls-tree-format.sh     |  55 +++++++++++++++
 3 files changed, 230 insertions(+), 5 deletions(-)
 create mode 100755 t/t3105-ls-tree-format.sh
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index 729370f235..ebdde6eae3 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,9 +10,9 @@ 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>...]
 DESCRIPTION
 -----------
 Lists the contents of a given tree object, like what "/bin/ls -a" does
@@ -79,6 +79,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
@@ -87,6 +97,9 @@ OPTIONS
 
 Output Format
 -------------
+
+Default format:
+
         <mode> SP <type> SP <object> TAB <file>
 
 This output format is compatible with what `--index-info --stdin` of
@@ -105,6 +118,38 @@ 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 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>
+
+The output format changes to:
+
+        {"object":"<object>", "file":"<file>"}
+
+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:
+
+mode::
+	The mode of the object.
+type::
+	The type of the object (`blob` or `tree`).
+object::
+	The name of the object.
+size[: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.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index 56cc166adb..e048a68ee0 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -57,6 +57,12 @@ 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) {
@@ -76,6 +82,72 @@ 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)
+{
+	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 *line, 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);
+
+	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, "(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)) {
+		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;
+		strbuf_addstr(data->base, data->pathname);
+		name = relative_path(data->base->buf, prefix, &sb);
+		quote_c_style(name, &quoted, NULL, 0);
+		strbuf_addbuf(line, &quoted);
+		strbuf_release(&sb);
+		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)
 {
@@ -116,6 +188,38 @@ static enum object_type get_type(unsigned int mode)
 	        : OBJ_BLOB);
 }
 
+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 line = STRBUF_INIT;
+	enum object_type type = get_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(&line, format, expand_show_tree, &data);
+	strbuf_addch(&line, line_termination);
+	fwrite(line.buf, line.len, 1, stdout);
+	strbuf_release(&line);
+	strbuf_setlen(base, baselen);
+	return recurse;
+}
+
 static int show_default(struct show_tree_data *data)
 {
 	size_t baselen = data->base->len;
@@ -195,6 +299,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),
@@ -217,6 +322,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()
 	};
@@ -237,6 +345,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))
@@ -260,6 +372,19 @@ 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) ||
+	     !strcmp(format, long_format) ||
+	     !strcmp(format, name_only_format) ||
+	     !strcmp(format, object_only_format)))
+		fn = show_tree;
+	else if (format)
+		fn = show_tree_fmt;
+
+	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
 }
diff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh
new file mode 100755
index 0000000000..ea0f51d866
--- /dev/null
+++ b/t/t3105-ls-tree-format.sh
@@ -0,0 +1,55 @@
+#!/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_code 129 git ls-tree --format=fmt --object-only HEAD
+'
+
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	shift 2 &&
+	git ls-tree $opts -r HEAD >expect.raw &&
+	sed "s/^/> /" >expect <expect.raw &&
+	git ls-tree --format="> $format" -r HEAD >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'ls-tree --format=<default-like>' '
+	test_ls_tree_format \
+		"%(mode) %(type) %(object)%x09%(file)" \
+		""
+'
+
+test_expect_success 'ls-tree --format=<long-like>' '
+	test_ls_tree_format \
+		"%(mode) %(type) %(object) %(size:padded)%x09%(file)" \
+		"--long"
+'
+
+test_expect_success 'ls-tree --format=<name-only-like>' '
+	test_ls_tree_format \
+		"%(file)" \
+		"--name-only"
+'
+
+test_expect_success 'ls-tree --format=<object-only-like>' '
+	test_ls_tree_format \
+		"%(object)" \
+		"--object-only"
+'
+
+test_done
-- 
2.34.1.390.g2ae0a9cb82.dirty

Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of "return" in show_tree()

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-13 06:55:58

On Thu, Jan 13 2022, Teng Long wrote:

Re the $subject: Is "optimize naming" here just referring to the
s/retval/recurse/g?

Personally I think just a s/retval/ret/g here would make more senes if
we're doing any change at all, and in either case having this variable
re-rename split up as its own commit would make the proposed control
flow changes clearer.
quoted hunk
The variable which "show_tree()" return is named "retval", a name that's
a little hard to understand. This commit tries to make the variable
and the related codes more clear in the context.

The commit firstly rename "retval" to "recurse" which is a more
meaningful name than before. Secondly, "get_type()" is introduced
to setup the "type" by "mode", this will remove some of the nested if.
After this, The codes here become a little bit clearer, so we do not
need to take a look at "read_tree_at()" in "tree.c" to make sure the
context of the return value.

Signed-off-by: Teng Long <redacted>
---
 builtin/ls-tree.c | 33 ++++++++++++++++++---------------
 1 file changed, 18 insertions(+), 15 deletions(-)
diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c
index eecc7482d5..9729854a3d 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -61,24 +61,27 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname
 	return 0;
 }
 
+static enum object_type get_type(unsigned int mode)
+{
+	return (S_ISGITLINK(mode)
+	        ? OBJ_COMMIT
+	        : S_ISDIR(mode)
+	        ? OBJ_TREE
+	        : OBJ_BLOB);
+}
This new function is a re-invention of the object_type() utility in
cache.h, and isn't needed. I.e....
 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;
-
-	if (S_ISGITLINK(mode)) {
-		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 = OBJ_TREE;
-	}
-	else if (ls_options & LS_TREE_ONLY)
+	enum object_type type = get_type(mode);
...just drop it and do this:

	-       enum object_type type = get_type(mode);
	+       enum object_type type = object_type(mode);

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-13 07:02:43

On Thu, Jan 13 2022, Teng Long wrote:
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 the RFC series I sent this was first implemented in terms of the
--format option, and I skipped the custom implementation you're adding
here:
https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/

I think in terms of patch series structure it would make sense to do
that, and then have this custom --object-only implementation in terms of
not-"--format " follow from that, and thus with the tests for the two
(we'd add the tests you're adding here first, just for a
--format="%(objectname)" or whatever) we'd see that the two are 1=1
equivalent in terms of functionality, but that this one is <X>% more
optimized.

Re: [PATCH v10 7/9] ls-tree.c: introduce struct "show_tree_data"

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-13 07:07:11

On Thu, Jan 13 2022, Teng Long wrote:
"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.
Is the only reason this is split off from 9/9 because you're injecting a
8/9 commit for the coccinelle rule change, and wanted to find some
logical cut-off between the two?
Signed-off-by: Teng Long <redacted>
For both this & 9/9 this seems to mostly/substantially be code I wrote
and submitted as part of
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com;

The convention we use in such cases is to retain the "Author" header and
just add your own Signed-off-by to patches you're modifying/splitting
up.

Re: [PATCH v10 9/9] ls-tree.c: introduce "--format" option

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-13 09:04:27

On Thu, Jan 13 2022, Teng Long wrote:
quoted hunk
Signed-off-by: Teng Long <redacted>
---
 Documentation/git-ls-tree.txt |  51 +++++++++++++-
 builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-
 t/t3105-ls-tree-format.sh     |  55 +++++++++++++++
 3 files changed, 230 insertions(+), 5 deletions(-)
 create mode 100755 t/t3105-ls-tree-format.sh
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index 729370f235..ebdde6eae3 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,9 +10,9 @@ 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>]] 
Let's split up this re-flow only change into its own commit? I.e. the
only non-whitespace change here is beginning with [--format].

If it was the right thing to do to re-flow this then we didn't need
[--format=<format>] to exist to do so...
+	    [--format=<format>] <tree-ish> [<path>...]
 DESCRIPTION
Removing this \n breaks the formatting in the file. See "make man && man
./Documentation/git-ls-tree.1". The ./Documentation/doc-diff utility is
also handy for sanity checking the documentation formatting.
quoted hunk
 -----------
 Lists the contents of a given tree object, like what "/bin/ls -a" does
@@ -79,6 +79,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`.
+
These new docs make sense & seem to cover all the basis, thanks!
+
+Default format:
+
         <mode> SP <type> SP <object> TAB <file>
Here because we've added --format discussing the previous pseudo-format
as a "default" format becomes confusing. Let's instead say:

        The output format of `ls-tree` is determined by either the `--format` option,
        or other format-altering options such as `--name-long` 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:
        ---
        %(mode) %(type) %(object)%x09%(file)
        ---

Or something like that. We could then discuss e.g. --name-long being
`%(mode) %(type) %(object) %(size:padded)%x09%(file)` when we discuss
that option.
quoted hunk
 This output format is compatible with what `--index-info --stdin` of
@@ -105,6 +118,38 @@ 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 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>
+
+The output format changes to:
+
+        {"object":"<object>", "file":"<file>"}
This one-liner is guaranteed to result in invalid JSON on some
repositories, both because JSON is inherently a bad fit for git's data
model (JSON needs to be in one Unicode encoding, Git's tree data might
me in a mixture of encodings), and because it'll break if the file
includes a '"'.

I think it's better to just replace this with some example involving -z,
or at least prominently note that this is broken in the general case,
but can be used ad-hoc to quickly check things with "jq" or whatever.
+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:
+
+mode::
+	The mode of the object.
+type::
+	The type of the object (`blob` or `tree`).
+object::
+	The name of the object.
+size[: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.
In
https://lore.kernel.org/git/cover.1641043500.git.dyroneteng@gmail.com/
you noted that you changed the field names of e.g. "objectname" to
"object" etc. You're right that I picked these as-is from the
git-for-each-ref formatting.

1/3 of your reasoning for doing so was to make it consistent with the
documentation examples of e.g.:

     <mode> SP <type> SP <object> TAB <file>

I think in any case (as noted above) we should change those to use the
--format), so that leaves just:

 - "I prefer to make the name more simple to memorize and type"
 - "I think the names with "object" prefix are [from git-for-each-ref
   and the object* prefixes aren't redundant there, but would be here]".

I think both of those still apply, but I think having these consistent
with git-for-each-ref outweighs the slight benefit of shorter names.

Right now only a handful of things support these sort of --format
directives, but we've already got RFC/WIP patches to add that to
git-cat-file, and are likely to add more in the future.

I'd also like us to eventually be able to combine what are now separate
built-ins with their own --format to expose more deeply some internal
APIs via IPC. E.g. now you can do this:

    git for-each-ref --format='%(refname) %(tree)'

But to list each of those trees you'd need to pipe that output into this
new 'git ls-tree --format. But imagine being able to do something like:

    git for-each-ref --format='%(refname) %(git-ls-tree --format %%(objectname) %(tree))'

Where we'd just invoke git-ls-tree for you without running a full
sub-process. I think both for that hypothetical and working with the two
--formats now having to use %(type) in some places but %(objecttype)
etc. in others is just needlessly confusing. Let's just consistently use
the same format names everywhere.

Specifically for your s/path/file/ name change, that's just inaccurate, consider:

    $ ./git ls-tree --format="%(mode) %(type) %(file)" -t HEAD -- t/README
    040000 tree t
    100644 blob t/README

And:

    $ $ (cd t && ../git ls-tree --format="%(mode) %(type) %(file)" -t -r HEAD -- README)
    040000 tree ./
    100644 blob README

I.e. we talk about <path> in the existing SYNOPSIS for a reason. That we
had a "<file>" in the existing format demo was a bug/shorthand that we
shouldn't be propagating further.
[...]
+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)";
+
One advantage of keeping the variable names I picked in
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
is that they align, so you can instantly see that the first two are
equivalent until the "%x09".

It also makes it easier to review to avoid such churn, to see what you
really changed I'm looking at a local version of a range-diff where I
renamed these, the struct you renamed etc. back just to see what you
/really/ changed. I.e. what are functional v.s. renaming changes.
quoted hunk
 static int parse_shown_fields(void)
 {
 	if (cmdmode == MODE_NAME_ONLY) {
@@ -76,6 +82,72 @@ 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)
+{
+	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);
Here you changed my '"%"PRIuMAX' to '"%" PRIuMAX'. The former is the
prevailing style in this codebase, and avoiding the formatting churn
makes the inter-diff easier to read.
+	} else if (padded) {
+		strbuf_addf(line, "%7s", "-");
+	} else {
+		strbuf_addstr(line, "-");
+	}
+}
Ditto some harder to review interdiff due to renaming
churn. I.e. s/line/sb/ in both this and expand_show_tree(). I really
wouldn't care at all except because of all the manual work in reviewing
the inter-diff between my original version & this derived version.

In the case of "line" that's not even an improvement. With a --format
we're not building a "line", the user is free to insert any arbitrary
directives including \n's, so we might be working on multiple lines.
+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
+'
This & several other changes v.s. my version are good, e.g. here I seem
to have repeated the logic error I noted for your version (i.e omitting
"HEAD"), oops!
+test_expect_success 'setup' '
+	mkdir dir &&
+	test_commit dir/sub-file &&
+	test_commit top-file
+'
+
+test_ls_tree_format () {
+	format=$1 &&
+	opts=$2 &&
+	shift 2 &&
+	git ls-tree $opts -r HEAD >expect.raw &&
+	sed "s/^/> /" >expect <expect.raw &&
+	git ls-tree --format="> $format" -r HEAD >actual &&
+	test_cmp expect actual
+}
+
+test_expect_success 'ls-tree --format=<default-like>' '
+	test_ls_tree_format \
+		"%(mode) %(type) %(object)%x09%(file)" \
+		""
+'
+
+test_expect_success 'ls-tree --format=<long-like>' '
+	test_ls_tree_format \
+		"%(mode) %(type) %(object) %(size:padded)%x09%(file)" \
+		"--long"
+'
+
+test_expect_success 'ls-tree --format=<name-only-like>' '
+	test_ls_tree_format \
+		"%(file)" \
+		"--name-only"
+'
+
+test_expect_success 'ls-tree --format=<object-only-like>' '
+	test_ls_tree_format \
+		"%(object)" \
+		"--object-only"
+'
+
+test_done
As I noted in my RFC CL (https://lore.kernel.org/git/RFC-cover-0.7-00000000000-20211217T131635Z-avarab@gmail.com/):

	"the tests for ls-tree are really
	lacking. E.g. I seem to have a rather obvious bug in how -t and the
	--format interact here, but no test catches it."

So first, in my version of adding --format I was careful to make
--name-only etc. imply a given --format, and then only at the last
minute would we take the "fast path":
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/

You rewrote that in
https://lore.kernel.org/git/e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com/
and qremoved the limited "GIT_TEST_LS_TREE_FORMAT_BACKEND" testing I
added, so now the internal --format machinery can't be run through the
existing tests we do have.

Even with that re-added I really wouldn't trust that this code is doing
the right thing (and as noted, I don't trust my own RFC version
either). I think e.g. our "coverage" Makefile targets would be a good
start as a first approximation, i.e. running the /ls-tree/ tests and
seeing if we have full coverage.
Signed-off-by: Teng Long <redacted>
As I noted in 7/9 I think this patch is 9/9 still mostly something I
wrote, so that the "author" and Signed-off-by should be preserved. The
below is a range-diff of an amended version I've been looking at in
trying to review this. It undoes several (but not all) of your
formatting/renaming-only changes, just so that I could see what the
non-formatting changes were:

1:  6c96dff15c5 ! 1:  917bb168d45 ls-tree: add a --format=<fmt> option
    @@
      ## Metadata ##
    -Author: Ævar Arnfjörð Bjarmason [off-list ref]
    +Author: Teng Long [off-list ref]
     
      ## Commit message ##
    -    ls-tree: add a --format=<fmt> option
    +    ls-tree.c: 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
    @@ Commit message
     
         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_format() in if
    +    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.
     
    -    "Slower" here can bee seen via the the following "hyperfine"
    -    command. This uses GIT_TEST_LS_TREE_FORMAT_BACKEND=<bool> to force the
    -    use of the new backend:
    -
    -        $ hyperfine -L env false,true -L f "-r,-r -l,-r --name-only,-r --format='%(objectname)'" 'GIT_TEST_LS_TREE_FORMAT_BACKEND={env} ./git -C ~/g/linux ls-tree {f} HEAD' -r 10
    -        Benchmark 1: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r HEAD
    -          Time (mean ± σ):      86.1 ms ±   0.6 ms    [User: 65.2 ms, System: 20.9 ms]
    -          Range (min … max):    85.2 ms …  87.5 ms    10 runs
    +    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.
     
    -        Benchmark 2: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r HEAD
    -          Time (mean ± σ):     122.5 ms ±   0.6 ms    [User: 101.3 ms, System: 21.1 ms]
    -          Range (min … max):   121.8 ms … 123.4 ms    10 runs
    +    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].
     
    -        Benchmark 3: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r -l HEAD
    -          Time (mean ± σ):     277.7 ms ±   1.3 ms    [User: 234.6 ms, System: 43.0 ms]
    -          Range (min … max):   275.9 ms … 279.7 ms    10 runs
    +    Here is the statistics about performance tests:
     
    -        Benchmark 4: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r -l HEAD
    -          Time (mean ± σ):     332.8 ms ±   2.6 ms    [User: 282.0 ms, System: 50.7 ms]
    -          Range (min … max):   329.6 ms … 338.2 ms    10 runs
    +    1. Default format (hitten the builtin formats):
     
    -        Benchmark 5: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --name-only HEAD
    -          Time (mean ± σ):      71.8 ms ±   0.4 ms    [User: 54.1 ms, System: 17.6 ms]
    -          Range (min … max):    71.2 ms …  72.5 ms    10 runs
    +        "git ls-tree <tree-ish>" vs "--format='%(mode) %(type) %(object)%x09%(file)'"
     
    -        Benchmark 6: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --name-only HEAD
    -          Time (mean ± σ):      86.6 ms ±   0.5 ms    [User: 65.7 ms, System: 20.7 ms]
    -          Range (min … max):    85.9 ms …  87.4 ms    10 runs
    +        $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
     
    -        Benchmark 7: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD
    -          Time (mean ± σ):      85.8 ms ±   0.6 ms    [User: 66.2 ms, System: 19.5 ms]
    -          Range (min … max):    85.0 ms …  86.9 ms    10 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
     
    -        Benchmark 8: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD
    -          Time (mean ± σ):      85.3 ms ±   0.2 ms    [User: 66.6 ms, System: 18.7 ms]
    -          Range (min … max):    85.0 ms …  85.7 ms    10 runs
    +    2. Default format includes object size (hitten the builtin formats):
     
    -        Summary
    -          'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --name-only HEAD' ran
    -            1.19 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD'
    -            1.19 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD'
    -            1.20 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r HEAD'
    -            1.21 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --name-only HEAD'
    -            1.71 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r HEAD'
    -            3.87 ± 0.03 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r -l HEAD'
    -            4.64 ± 0.05 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r -l HEAD'
    +        "git ls-tree -l <tree-ish>" vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'"
     
    -    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.
    +        $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
     
    -    But even a --format='%(objectname)' is fast with the new backend, so
    -    this is viable as a replacement for adding new formats, and we'll pay
    -    for this added complexity as a one-off, and not again every time a new
    -    format needs to be added. See [1] for an example of what it would
    -    otherwise take to add an --object-name flag.
    +        $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
     
    -    1. https://lore.kernel.org/git/2e449d1c792ff81da5f22c8bf65ed33c393d62f8.1639721750.git.dyroneteng@gmail.com/
    +    Links:
    +            [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
     
    -    Signed-off-by: Ævar Arnfjörð Bjarmason [off-list ref]
    +    Signed-off-by: Teng Long [off-list ref]
     
    - ## builtin/ls-tree.c ##
    -@@ builtin/ls-tree.c: static struct pathspec pathspec;
    - static int chomp_prefix;
    - static const char *ls_tree_prefix;
    + ## 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>...]
      
    -+/*
    -+ * The format equivalents that show_tree() is prepared to handle.
    -+ */
    -+static const char *ls_tree_format_d = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
    -+static const char *ls_tree_format_l = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    -+static const char *ls_tree_format_n = "%(path)";
    + DESCRIPTION
    + -----------
    +@@ Documentation/git-ls-tree.txt: 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`.
     +
    - static const  char * const ls_tree_usage[] = {
    - 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
    - 	NULL
    - };
    + [<path>...]::
    + 	When paths are given, show them (note that this isn't really raw
    + 	pathnames, but rather a list of patterns to match).  Otherwise
    +@@ Documentation/git-ls-tree.txt: OPTIONS
      
    -+struct read_tree_ls_tree_data {
    -+	const char *format;
    -+	struct strbuf sb_scratch;
    -+	struct strbuf sb_tmp;
    -+};
    + Output Format
    + -------------
    ++
    ++Default format:
    ++
    +         <mode> SP <type> SP <object> TAB <file>
    + 
    + This output format is compatible with what `--index-info --stdin` of
    +@@ 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.
    + 
    ++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 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>
    ++
    ++The output format changes to:
    ++
    ++        {"object":"<object>", "file":"<file>"}
    ++
    ++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:
    ++
    ++mode::
    ++	The mode of the object.
    ++type::
    ++	The type of the object (`blob` or `tree`).
    ++object::
    ++	The name of the object.
    ++size[: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.
    ++
    + GIT
    + ---
    + Part of the linkgit:git[1] suite
    +
    + ## builtin/ls-tree.c ##
    +@@ 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 expand_ls_tree_data {
     +	unsigned mode;
     +	enum object_type type;
     +	const struct object_id *oid;
     +	const char *pathname;
    -+	const char *basebuf;
    -+	struct strbuf *sb_scratch;
    -+	struct strbuf *sb_tmp;
    ++	struct strbuf *base;
     +};
     +
    - static int show_recursive(const char *base, size_t baselen, const char *pathname)
    + static const  char * const ls_tree_usage[] = {
    + 	N_("git ls-tree [<options>] <tree-ish> [<path>...]"),
    + 	NULL
    +@@ builtin/ls-tree.c: enum {
    + 
    + static int cmdmode = MODE_UNSPECIFIED;
    + 
    ++static const char *format;
    ++static const char *ls_tree_format_d = "%(objectmode) %(objecttype) %(objectname)%x09%(path)";
    ++static const char *ls_tree_format_l = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)";
    ++static const char *ls_tree_format_n = "%(path)";
    ++static const char *ls_tree_format_o = "%(objectname)";
    ++
    + static int parse_shown_fields(void)
      {
    - 	int i;
    -@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname
    - 	return 0;
    + 	if (cmdmode == MODE_NAME_ONLY) {
    +@@ builtin/ls-tree.c: static int parse_shown_fields(void)
    + 	return 1;
      }
      
    -+static void expand_objectsize(struct strbuf *sb,
    -+			      const struct object_id *oid,
    -+			      const enum object_type type,
    -+			      unsigned int padded)
    ++static void expand_objectsize(struct strbuf *sb, 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));
    ++			die(_("could not get object info about '%s'"),
    ++			    oid_to_hex(oid));
     +		if (padded)
     +			strbuf_addf(sb, "%7"PRIuMAX, (uintmax_t)size);
     +		else
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     +	}
     +}
     +
    -+static size_t expand_show_tree(struct strbuf *sb,
    -+			       const char *start,
    ++static size_t expand_show_tree(struct strbuf *sb, const char *start,
     +			       void *context)
     +{
     +	struct expand_ls_tree_data *data = context;
     +	const char *end;
     +	const char *p;
    -+	size_t len;
    ++	unsigned int errlen;
    ++	size_t len = strbuf_expand_literal_cb(sb, start, NULL);
     +
    -+	len = strbuf_expand_literal_cb(sb, start, NULL);
     +	if (len)
     +		return len;
    -+
     +	if (*start != '(')
    -+		die(_("bad format as of '%s'"), start);
    ++		die(_("bad ls-tree format: as '%s'"), start);
    ++
     +	end = strchr(start + 1, ')');
     +	if (!end)
    -+		die(_("ls-tree format element '%s' does not end in ')'"),
    -+		    start);
    -+	len = end - start + 1;
    ++		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)) {
    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c
     +	} else if (skip_prefix(start, "(objectsize)", &p)) {
     +		expand_objectsize(sb, data->oid, data->type, 0);
     +	} else if (skip_prefix(start, "(objectname)", &p)) {
    -+		strbuf_addstr(sb, find_unique_abbrev(data->oid, abbrev));
    ++		strbuf_add_unique_abbrev(sb, data->oid, abbrev);
     +	} else if (skip_prefix(start, "(path)", &p)) {
    -+		const char *name = data->basebuf;
    ++		const char *name = data->base->buf;
     +		const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;
    -+
    -+		if (prefix)
    -+			name = relative_path(name, prefix, data->sb_scratch);
    -+		quote_c_style(name, data->sb_tmp, NULL, 0);
    -+		strbuf_add(sb, data->sb_tmp->buf, data->sb_tmp->len);
    -+
    -+		strbuf_reset(data->sb_tmp);
    -+		/* The relative_path() function resets "scratch" */
    ++		struct strbuf quoted = STRBUF_INIT;
    ++		struct strbuf s = STRBUF_INIT;
    ++		strbuf_addstr(data->base, data->pathname);
    ++		name = relative_path(data->base->buf, prefix, &s);
    ++		quote_c_style(name, &quoted, NULL, 0);
    ++		strbuf_addbuf(sb, &quoted);
    ++		strbuf_release(&s);
    ++		strbuf_release(&quoted);
     +	} else {
    -+		unsigned int errlen = (unsigned long)len;
    -+		die(_("bad ls-tree format specifiec %%%.*s"), errlen, start);
    ++		errlen = (unsigned long)len;
    ++		die(_("bad ls-tree format: %%%.*s"), errlen, start);
     +	}
    -+
     +	return len;
     +}
     +
    - static int show_tree_init(enum object_type *type, struct strbuf *base,
    - 			  const char *pathname, unsigned mode, int *retval)
    + static int show_recursive(const char *base, size_t baselen,
    + 			  const char *pathname)
      {
    -@@ builtin/ls-tree.c: static int show_tree_init(enum object_type *type, struct strbuf *base,
    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,
      	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_tree_fmt(const struct object_id *oid, struct strbuf *base,
     +			 const char *pathname, unsigned mode, void *context)
    -+{
    -+	struct read_tree_ls_tree_data *data = context;
    -+	struct expand_ls_tree_data my_data = {
    + {
    +-	size_t baselen = base->len;
    ++	size_t baselen;
    ++	int recurse = 0;
    ++	struct strbuf line = STRBUF_INIT;
    ++	enum object_type type = object_type(mode);
    ++
    ++	struct expand_ls_tree_data data = {
     +		.mode = mode,
    -+		.type = OBJ_BLOB,
    ++		.type = type,
     +		.oid = oid,
     +		.pathname = pathname,
    -+		.sb_scratch = &data->sb_scratch,
    -+		.sb_tmp = &data->sb_tmp,
    ++		.base = base,
     +	};
    -+	struct strbuf sb = STRBUF_INIT;
    -+	int retval = 0;
    -+	size_t baselen;
     +
    -+	if (show_tree_init(&my_data.type, base, pathname, mode, &retval))
    -+		return retval;
    ++	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_addstr(base, pathname);
    -+	strbuf_reset(&sb);
    -+	my_data.basebuf = base->buf;
    -+
    -+	strbuf_expand(&sb, data->format, expand_show_tree, &my_data);
    -+	strbuf_addch(&sb, line_termination);
    -+	fwrite(sb.buf, sb.len, 1, stdout);
    ++	strbuf_expand(&line, format, expand_show_tree, &data);
    ++	strbuf_addch(&line, line_termination);
    ++	fwrite(line.buf, line.len, 1, stdout);
    ++	strbuf_release(&line);
     +	strbuf_setlen(base, baselen);
    -+
    -+	return retval;
    ++	return recurse;
     +}
     +
    - static int show_tree(const struct object_id *oid, struct strbuf *base,
    - 		const char *pathname, unsigned mode, void *context)
    - {
    ++static int show_default(struct expand_ls_tree_data *data)
    ++{
    ++	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),
    +@@ builtin/ls-tree.c: 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;
    + }
    + 
    +@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,
    + 	size_t baselen;
    + 	enum object_type type = object_type(mode);
    + 
    ++	struct expand_ls_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))
    +@@ builtin/ls-tree.c: 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;
    + }
     @@ 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;
    -+	const char *implicit_format = NULL;
    -+	const char *format = NULL;
    -+	struct read_tree_ls_tree_data read_tree_cb_data = {
    -+		.sb_scratch = STRBUF_INIT,
    -+		.sb_tmp = STRBUF_INIT,
    -+	};
    ++	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),
    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref
      		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_STRING_F(0, "format", &format, N_("format"),
    ++			     N_("format to use for the output"),
    ++			     PARSE_OPT_NONEG),
      		OPT__ABBREV(&abbrev),
      		OPT_END()
      	};
    -+	read_tree_fn_t fn = show_tree;
    - 
    - 	git_config(git_default_config, NULL);
    - 	ls_tree_prefix = prefix;
     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)
    - 	if ( (LS_TREE_ONLY|LS_RECURSIVE) ==
      	    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))
      		ls_options |= LS_SHOW_TREES;
    -+	if (ls_options & LS_NAME_ONLY)
    -+		implicit_format = ls_tree_format_n;
    -+	if (ls_options & LS_SHOW_SIZE)
    -+		implicit_format = ls_tree_format_l;
    -+
    -+	if (format && implicit_format)
    -+		usage_msg_opt(_("providing --format cannot be combined with other format-altering options"),
    -+			      ls_tree_usage, ls_tree_options);
    -+	if (implicit_format)
    -+		format = implicit_format;
    -+	if (!format)
    -+		format = ls_tree_format_d;
      
    ++	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))
     @@ 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);
     +
     +	/*
     +	 * The generic show_tree_fmt() is slower than show_tree(), so
     +	 * take the fast path if possible.
     +	 */
    -+	if (format && (!strcmp(format, ls_tree_format_d) ||
    -+		       !strcmp(format, ls_tree_format_l) ||
    -+		       !strcmp(format, ls_tree_format_n)))
    ++	if (format &&
    ++	    (!strcmp(format, ls_tree_format_d) ||
    ++	     !strcmp(format, ls_tree_format_l) ||
    ++	     !strcmp(format, ls_tree_format_n) ||
    ++	     !strcmp(format, ls_tree_format_o)))
     +		fn = show_tree;
     +	else if (format)
     +		fn = show_tree_fmt;
    -+	/*
    -+	 * Allow forcing the show_tree_fmt(), to test that it can
    -+	 * handle the test suite.
    -+	 */
    -+	if (git_env_bool("GIT_TEST_LS_TREE_FORMAT_BACKEND", 0))
    -+		fn = show_tree_fmt;
     +
    -+	read_tree_cb_data.format = format;
    - 	return !!read_tree(the_repository, tree,
    --			   &pathspec, show_tree, NULL);
    -+			   &pathspec, fn, &read_tree_cb_data);
    ++	return !!read_tree(the_repository, tree, &pathspec, fn, NULL);
      }
     
      ## t/t3105-ls-tree-format.sh (new) ##
    @@ t/t3105-ls-tree-format.sh (new)
     +. ./test-lib.sh
     +
     +test_expect_success 'ls-tree --format usage' '
    -+	test_expect_code 129 git ls-tree --format=fmt -l &&
    -+	test_expect_code 129 git ls-tree --format=fmt --name-only &&
    -+	test_expect_code 129 git ls-tree --format=fmt --name-status
    ++	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_success 'setup' '
    @@ t/t3105-ls-tree-format.sh (new)
     +	test_ls_tree_format \
     +		"%(path)" \
     +		"--name-only"
    ++'
     +
    ++test_expect_success 'ls-tree --format=<object-only-like>' '
    ++	test_ls_tree_format \
    ++		"%(objectname)" \
    ++		"--object-only"
     +'
     +
     +test_done

Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of "return" in show_tree()

From: Teng Long <hidden>
Date: 2022-01-14 07:59:14

On Thu, Jan 13, 2022 at 2:55 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
Re the $subject: Is "optimize naming" here just referring to the
s/retval/recurse/g?
Yes.
Personally I think just a s/retval/ret/g here would make more senes if
we're doing any change at all, and in either case having this variable
re-rename split up as its own commit would make the proposed control
flow changes clearer.
Do you mean that I can split the current one into two commits,  one does
the renaming work and another one does the left work?

If so, I will do this in the next patch.
This new function is a re-invention of the object_type() utility in
cache.h, and isn't needed. I.e....

...just drop it and do this:

        -       enum object_type type = get_type(mode);
        +       enum object_type type = object_type(mode);
You are absolutely correct.
I will replace get_type() to object_type() in the next patch.

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Teng Long <hidden>
Date: 2022-01-14 08:18:47

On Thu, Jan 13, 2022 at 3:02 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
In the RFC series I sent this was first implemented in terms of the
--format option, and I skipped the custom implementation you're adding
here:
https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/

I think in terms of patch series structure it would make sense to do
that, and then have this custom --object-only implementation in terms of
not-"--format " follow from that, and thus with the tests for the two
Sorry, the "not-"--format" means?
(we'd add the tests you're adding here first, just for a
--format="%(objectname)" or whatever) we'd see that the two are 1=1
equivalent in terms of functionality, but that this one is <X>% more
optimized.
Please allow me to understand your advice,  if we put the commit of
introducing "--format" before the commit of introducing "--object-only", will
be better because it's possible to supply more optimized performance
(if we have) information in the commit message.

Re: [PATCH v10 7/9] ls-tree.c: introduce struct "show_tree_data"

From: Teng Long <hidden>
Date: 2022-01-14 09:13:02

On Thu, Jan 13, 2022 at 3:07 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
quoted
"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.
Is the only reason this is split off from 9/9 because you're injecting a
8/9 commit for the coccinelle rule change, and wanted to find some
logical cut-off between the two?
I hope "show_tree()" and "the show_tree_format()" to share this structure,
so I made this a pre-prepared and non-functional commit.

After that, in the 9/9, the structure can be used directly and focus on the
functionality changes. If we merge this commit with 9/9, 9/9 will contain a part
of the changes that let "show_tree()" use the new structure, which has nothing
to do with "show_tree_format()" actually, because we designed them to go
through different execution logic. So, personally, I prefer not to mix them
together.

So, the commit of "show_tree_data()" originally was not for "coccinelle".
The only thing that is certain is that coccinelle also should go before 9/9
I think. With regard to 8/9 and 7/9, I think the current order is OK because
they're not related.
For both this & 9/9 this seems to mostly/substantially be code I wrote
and submitted as part of
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com;

The convention we use in such cases is to retain the "Author" header and
just add your own Signed-off-by to patches you're modifying/splitting
up.
Oops. Sorry for that, I misunderstood it before and I'll be fixed in
the next path.

Thanks.

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-14 11:59:50

On Fri, Jan 14 2022, Teng Long wrote:
On Thu, Jan 13, 2022 at 3:02 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
quoted
In the RFC series I sent this was first implemented in terms of the
--format option, and I skipped the custom implementation you're adding
here:
https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/

I think in terms of patch series structure it would make sense to do
that, and then have this custom --object-only implementation in terms of
not-"--format " follow from that, and thus with the tests for the two
Sorry, the "not-"--format" means?
Sorry about not being clear. I mean there's two potential
implementations. One that't is terms of --format='%(objectname)', and
the other with your custom (faster) code to implement it.
quoted
(we'd add the tests you're adding here first, just for a
--format="%(objectname)" or whatever) we'd see that the two are 1=1
equivalent in terms of functionality, but that this one is <X>% more
optimized.
Please allow me to understand your advice,  if we put the commit of
introducing "--format" before the commit of introducing "--object-only", will
be better because it's possible to supply more optimized performance
(if we have) information in the commit message.
Yes, you get the functionality you need with a simple alias of
--format='%(objectname)' to --object-name (or whatever), so the only
reason to carry the extra code is for optimization.

I wonder if the extra difference in performance is still something you
care about, or if just the --format implementation would be OK.

But in any case, starting with a simpler implementation and testing it
makes the progression easier to reason about.

Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of "return" in show_tree()

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-01-14 12:00:23

On Fri, Jan 14 2022, Teng Long wrote:
On Thu, Jan 13, 2022 at 2:55 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
quoted
Re the $subject: Is "optimize naming" here just referring to the
s/retval/recurse/g?
Yes.
quoted
Personally I think just a s/retval/ret/g here would make more senes if
we're doing any change at all, and in either case having this variable
re-rename split up as its own commit would make the proposed control
flow changes clearer.
Do you mean that I can split the current one into two commits,  one does
the renaming work and another one does the left work?

If so, I will do this in the next patch.
Yes, at least I would find it easier to read :)
quoted
This new function is a re-invention of the object_type() utility in
cache.h, and isn't needed. I.e....

...just drop it and do this:

        -       enum object_type type = get_type(mode);
        +       enum object_type type = object_type(mode);
You are absolutely correct.
I will replace get_type() to object_type() in the next patch.
Thanks!

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Teng Long <hidden>
Date: 2022-01-18 09:55:36

On Fri, Jan 14, 2022 at 7:59 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
Yes, you get the functionality you need with a simple alias of
--format='%(objectname)' to --object-name (or whatever), so the only
reason to carry the extra code is for optimization.

I wonder if the extra difference in performance is still something you
care about, or if just the --format implementation would be OK.

But in any case, starting with a simpler implementation and testing it
makes the progression easier to reason about.
Actually, at first, I wanted to achieve this in a simple way, as the
"--object-only" implementation.

With the discussion in the community, I think both of them can achieve
this purpose. "--object-only" is more intuitive, while "--format "is
more flexible.
For example, if the terminal supports automatic completion, the function of
this option can be clearly known with typing TAB and lower costs of use and
understanding. "--format"  also works, but maybe have to check the help
document to see if there are fields that support the same purpose.

Because the community had a different opinion about it. Junio, might prefer
an "--object-only" approach, if I understand the context correctly.

So I have some inclination to support both. However, I can accept that only
"--format" is supported.

So in the next patch, I hope to do some refactoring of the commit to support
"--object-only" as the top commit. If in the end, we decide that "--format" is
enough, we can discard the top "--object-only" commit.

I know you guys currently are busy on the new 2.35 release, so a later reply
is OK.

Thanks.

Re: [PATCH v10 9/9] ls-tree.c: introduce "--format" option

From: Teng Long <hidden>
Date: 2022-01-18 12:59:55

On Thu, Jan 13, 2022 at 5:04 PM Ævar Arnfjörð Bjarmason [off-list ref] wrote:
quoted
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index 729370f235..ebdde6eae3 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,9 +10,9 @@ 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>]]
Let's split up this re-flow only change into its own commit? I.e. the
only non-whitespace change here is beginning with [--format].

If it was the right thing to do to re-flow this then we didn't need
[--format=<format>] to exist to do so...
Agree, especially if "--format" comes earlier as you mentioned in
another reply.

The doc change should only include the new "--format" here, so
we just:
diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt
index db02d6d79a..b02f028aca 100644
--- a/Documentation/git-ls-tree.txt
+++ b/Documentation/git-ls-tree.txt
@@ -10,5 +10,5 @@ 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>]] [--format=<format>]
            <tree-ish> [<path>...]
is OK.
quoted
+         [--format=<format>] <tree-ish> [<path>...]
 DESCRIPTION
Removing this \n breaks the formatting in the file. See "make man && man
./Documentation/git-ls-tree.1". The ./Documentation/doc-diff utility is
also handy for sanity checking the documentation formatting.
This is my mistake and will be corrected in the next patch.
quoted
 -----------
 Lists the contents of a given tree object, like what "/bin/ls -a" does
@@ -79,6 +79,16 @@ OPTION
      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`.
+
These new docs make sense & seem to cover all the basis, thanks!
Actually, the content is not from me, I borrowed it from other
documents, but I'm glad if it's described and placed here correctly.
Here because we've added --format discussing the previous pseudo-format
as a "default" format becomes confusing. Let's instead say:

        The output format of `ls-tree` is determined by either the `--format` option,
        or other format-altering options such as `--name-long` 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:
        ---
        %(mode) %(type) %(object)%x09%(file)
        ---
Make sense.

I will use this paragraph instead in next patch except a tiny
nit (s/--name-long/--name-only/).
quoted
+The output format changes to:
+
+        {"object":"<object>", "file":"<file>"}
This one-liner is guaranteed to result in invalid JSON on some
repositories, both because JSON is inherently a bad fit for git's data
model (JSON needs to be in one Unicode encoding, Git's tree data might
me in a mixture of encodings), and because it'll break if the file
includes a '"'.
Correct and especially we use a "quote_c" style behind.
I think it's better to just replace this with some example involving -z,
or at least prominently note that this is broken in the general case,
but can be used ad-hoc to quickly check things with "jq" or whatever.
Your suggestion is great, but personally I don't want to introduce more
complexity and other tools in here, and try to describe it in a simple way.
I think the below maybe is enough:
@@ -117,14 +127,10 @@ 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 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>
-
-The output format changes to:
+For example, if you only care about the <object> and <file> fields, you can
+execute with a specific "--format" like
 
-        {"object":"<object>", "file":"<file>"}
+        git ls-tree --format="%(object) %(file)" <tree-ish>
 
 FIELD NAMES
 -----------
quoted
+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:
+
+mode::
+     The mode of the object.
+type::
+     The type of the object (`blob` or `tree`).
+object::
+     The name of the object.
+size[: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.
In
https://lore.kernel.org/git/cover.1641043500.git.dyroneteng@gmail.com/
you noted that you changed the field names of e.g. "objectname" to
"object" etc. You're right that I picked these as-is from the
git-for-each-ref formatting.

1/3 of your reasoning for doing so was to make it consistent with the
documentation examples of e.g.:

     <mode> SP <type> SP <object> TAB <file>

I think in any case (as noted above) we should change those to use the
--format), so that leaves just:

 - "I prefer to make the name more simple to memorize and type"
 - "I think the names with "object" prefix are [from git-for-each-ref
   and the object* prefixes aren't redundant there, but would be here]".

I think both of those still apply, but I think having these consistent
with git-for-each-ref outweighs the slight benefit of shorter names.

Right now only a handful of things support these sort of --format
directives, but we've already got RFC/WIP patches to add that to
git-cat-file, and are likely to add more in the future.
New and important input for me on this.
I'd also like us to eventually be able to combine what are now separate
built-ins with their own --format to expose more deeply some internal
APIs via IPC. E.g. now you can do this:

    git for-each-ref --format='%(refname) %(tree)'

But to list each of those trees you'd need to pipe that output into this
new 'git ls-tree --format. But imagine being able to do something like:

    git for-each-ref --format='%(refname) %(git-ls-tree --format %%(objectname) %(tree))'
Make sense.
Where we'd just invoke git-ls-tree for you without running a full
sub-process. I think both for that hypothetical and working with the two
--formats now having to use %(type) in some places but %(objecttype)
etc. in others is just needlessly confusing. Let's just consistently use
the same format names everywhere.

Specifically for your s/path/file/ name change, that's just inaccurate, consider:

    $ ./git ls-tree --format="%(mode) %(type) %(file)" -t HEAD -- t/README
    040000 tree t
    100644 blob t/README

And:

    $ $ (cd t && ../git ls-tree --format="%(mode) %(type) %(file)" -t -r HEAD -- README)
    040000 tree ./
    100644 blob README

I.e. we talk about <path> in the existing SYNOPSIS for a reason. That we
had a "<file>" in the existing format demo was a bug/shorthand that we
shouldn't be propagating further.
Should use "path" instead of "file" in here.

Make sense.
quoted
[...]
+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)";
+
One advantage of keeping the variable names I picked in
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
is that they align, so you can instantly see that the first two are
equivalent until the "%x09".
Ha. Thanks.I think they not align in here is because my variables' names not align :)  

Actually, I was hesitating to use like "object" or follow the "objectname" like
rules. I would like git to have unified naming style on this, but I didn't have
that much input (other usage already on the way) at the time, so I chose to use
a shorter and probably more memorable name based on the document.

But now, I agree with you, to use the same naming conventions because on the whole,
especially multiple commands have the same appeal on format naming, uniformity is
more important than memorability of a single command, and I also think maybe we
might need to describe and maintain these rules of <fieldname> in a document
somewhere.
It also makes it easier to review to avoid such churn, to see what you
really changed I'm looking at a local version of a range-diff where I
renamed these, the struct you renamed etc. back just to see what you
/really/ changed. I.e. what are functional v.s. renaming changes.

Here you changed my '"%"PRIuMAX' to '"%" PRIuMAX'. The former is the
prevailing style in this codebase, and avoiding the formatting churn
makes the inter-diff easier to read.
Will fix it in next patch.
Ditto some harder to review interdiff due to renaming
churn. I.e. s/line/sb/ in both this and expand_show_tree(). I really
wouldn't care at all except because of all the manual work in reviewing
the inter-diff between my original version & this derived version.

In the case of "line" that's not even an improvement. With a --format
we're not building a "line", the user is free to insert any arbitrary
directives including \n's, so we might be working on multiple lines.
Make sense.

Will optimize in next patch.
As I noted in my RFC CL (https://lore.kernel.org/git/RFC-cover-0.7-00000000000-20211217T131635Z-avarab@gmail.com/):

        "the tests for ls-tree are really
        lacking. E.g. I seem to have a rather obvious bug in how -t and the
        --format interact here, but no test catches it."

So first, in my version of adding --format I was careful to make
--name-only etc. imply a given --format, and then only at the last
minute would we take the "fast path":
https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/
I'm not sure I understand all the meanings above. I think I have forgot about the content here.
and I have to add some tests for other options like "-t" combined with "--format".

Please correct me if I misunderstood.
You rewrote that in
https://lore.kernel.org/git/e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com/
and qremoved the limited "GIT_TEST_LS_TREE_FORMAT_BACKEND" testing I
added, so now the internal --format machinery can't be run through the
existing tests we do have.
I thought the "GIT_TEST_LS_TREE_FORMAT_BACKEND" is only used in your RFC for testing conveniently,
and should be removed in the end (non-RFC). So I removed it...

So should we add it back?
Even with that re-added I really wouldn't trust that this code is doing
the right thing (and as noted, I don't trust my own RFC version
either). I think e.g. our "coverage" Makefile targets would be a good
start as a first approximation, i.e. running the /ls-tree/ tests and
seeing if we have full coverage.
Yeah. I haven't tried it yet, but I will try to run coverage detection when
the next patch is completed
As I noted in 7/9 I think this patch is 9/9 still mostly something I
wrote, so that the "author" and Signed-off-by should be preserved. The
below is a range-diff of an amended version I've been looking at in
trying to review this. It undoes several (but not all) of your
formatting/renaming-only changes, just so that I could see what the
non-formatting changes were:
Sorry for that. I misunderstand something here.
I will fix in the next patch.

Thanks.

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-04 13:04:57

On Tue, Jan 18 2022, Teng Long wrote:
On Fri, Jan 14, 2022 at 7:59 PM Ævar Arnfjörð Bjarmason
[off-list ref] wrote:
quoted
Yes, you get the functionality you need with a simple alias of
--format='%(objectname)' to --object-name (or whatever), so the only
reason to carry the extra code is for optimization.

I wonder if the extra difference in performance is still something you
care about, or if just the --format implementation would be OK.

But in any case, starting with a simpler implementation and testing it
makes the progression easier to reason about.
Actually, at first, I wanted to achieve this in a simple way, as the
"--object-only" implementation.

With the discussion in the community, I think both of them can achieve
this purpose. "--object-only" is more intuitive, while "--format "is
more flexible.
For example, if the terminal supports automatic completion, the function of
this option can be clearly known with typing TAB and lower costs of use and
understanding. "--format"  also works, but maybe have to check the help
document to see if there are fields that support the same purpose.

Because the community had a different opinion about it. Junio, might prefer
an "--object-only" approach, if I understand the context correctly.

So I have some inclination to support both. However, I can accept that only
"--format" is supported.
I'm only talking about how it's implemented internally, not whether we
have an --object-only option in the UI. I think it's good to have the
option for completion etc.

I.e. in my RFC implementation of it here it's just a trivial wrapper
around specifying a --format:
https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/;
Implementing it is 6 lines of trivial C code boilerplate.

But when you picked that up & ran with it you ended up carrying your
original implementation:
https://lore.kernel.org/git/e0274f079a7d381b9a936bfcd53bad64167c18b8.1641440700.git.dyroneteng@gmail.com/

I'm not saying we shouldn't have that, but that in any case a sequence of:

 1. Add a --format option
 2. Add a --object-only alias for a --format (what my RFC 7/7 does)
 3. Add a custom more optimized --object-only implementation

Would make the patch progression much easier to read, and we'd consider
the correctness of --object-only (1 and 2) separate from the
optimization question (3).

But maybe we won't need (3) at all in the end, i.e. is (1 and 2) fast
enough for it not to matter (I think probably "yes", but I don't have a
strong opinion on that).
So in the next patch, I hope to do some refactoring of the commit to support
"--object-only" as the top commit. If in the end, we decide that "--format" is
enough, we can discard the top "--object-only" commit.
*nod*, now that I read ahead I think you pretty much agree with that plan :)
I know you guys currently are busy on the new 2.35 release, so a later reply
is OK.
Now would be a good time :)

I was reminded of this because Junio's proposed it for next at
https://lore.kernel.org/git/xmqqr18jnr2t.fsf@gitster.g/

I think per the above & other replies of mine (including not matters of
code arrangement opinion, but e.g. the doc formatting bug) we'll need at
least one more re-roll of this. Thanks for sticking with this & working
on this!

I'll indicate that in a reply to that "What's Cooking" report.

Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for "git-ls-tree"

From: Teng Long <hidden>
Date: 2022-02-07 02:22:39

On Fri, Feb 4, 2022 at 9:04 PM Ævar Arnfjörð Bjarmason [off-list ref] wrote:
I'm not saying we shouldn't have that, but that in any case a sequence of:

 1. Add a --format option
 2. Add a --object-only alias for a --format (what my RFC 7/7 does)
 3. Add a custom more optimized --object-only implementation

Would make the patch progression much easier to read, and we'd consider
the correctness of --object-only (1 and 2) separate from the
optimization question (3).

But maybe we won't need (3) at all in the end, i.e. is (1 and 2) fast
enough for it not to matter (I think probably "yes", but I don't have a
strong opinion on that).
Sorry for the late reply, I had a vacation in the last two weeks (Chinese
New Year).

I have to say it's a very valuable recommendation and at the same time
I recognise that
spending more time on organizing commits ahead is important and make
small steps(or commits) sufficiently.
Now would be a good time :)

I was reminded of this because Junio's proposed it for next at
https://lore.kernel.org/git/xmqqr18jnr2t.fsf@gitster.g/

I think per the above & other replies of mine (including not matters of
code arrangement opinion, but e.g. the doc formatting bug) we'll need at
least one more re-roll of this. Thanks for sticking with this & working
on this!

I'll indicate that in a reply to that "What's Cooking" report.
Thanks for mentioning that. I will back work on it this week.

Thanks.

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

From: Teng Long <hidden>
Date: 2022-02-08 13:14:55

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:15

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 [off-list ref]
     
      ## 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 [off-list ref]
     
    - ## 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 [off-list ref]
    +Author: Ævar Arnfjörð Bjarmason [off-list ref]
     
      ## 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 [off-list ref]
         Signed-off-by: Teng Long [off-list ref]
     
      ## 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 [off-list ref]
    +Author: Johannes Schindelin [off-list ref]
     
      ## Commit message ##
         cocci: allow padding with `strbuf_addf()`
 5:  db058bf670 ! 11:  5936004f13 ls-tree.c: introduce "--format" option
    @@
      ## Metadata ##
    -Author: Teng Long [off-list ref]
    +Author: Ævar Arnfjörð Bjarmason [off-list ref]
     
      ## 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 [off-list ref]
         Signed-off-by: Teng Long [off-list ref]
     
      ## 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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:15

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 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 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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:15

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 <redacted>
---
 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;
+}
+
 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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:15

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 <redacted>
---
 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`.
 
 --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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

From: Ævar Arnfjörð Bjarmason <redacted>

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

Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

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 <redacted>
---
 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)) {
-- 
2.34.1.403.gb35f2687cf.dirty

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

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 <redacted>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

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 <redacted>
---
 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,
-- 
2.34.1.403.gb35f2687cf.dirty

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:16

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 <redacted>
---
 builtin/ls-tree.c | 126 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 89 insertions(+), 37 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:17

From: Johannes Schindelin <redacted>

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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:17

From: Ævar Arnfjörð Bjarmason <redacted>

"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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 builtin/ls-tree.c | 43 ++++++++++++++++++++++++++++---------------
 1 file changed, 28 insertions(+), 15 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-02-08 13:15:17

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 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
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 05:31:20

On Tue, Feb 08 2022, Teng Long wrote:
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:
    $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.
quoted hunk
Signed-off-by: Teng Long <redacted>
---
 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).
    
quoted hunk
 
 --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.
quoted hunk
+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.
quoted hunk
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.

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 05:47:33

On Tue, Feb 08 2022, Teng Long wrote:
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.
quoted hunk
Signed-off-by: Teng Long <redacted>
---
 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.
quoted hunk
 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:
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.

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 05:52:51

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.
quoted hunk
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!)
+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.

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 06:04:30

On Tue, Feb 08 2022, Teng Long wrote:
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...
+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?
+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.
+
+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?" ?

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 06:05:15

On Tue, Feb 08 2022, Teng Long wrote:
quoted hunk
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 <redacted>
---
 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.

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-02-19 06:25:17

On Tue, Feb 08 2022, Teng Long wrote:
quoted hunk
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 <redacted>
---
 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:
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...

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

From: Teng Long <hidden>
Date: 2022-03-04 10:42:59

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 [off-list ref]
     
    @@ 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 [off-list ref]
         Signed-off-by: Teng Long [off-list ref]
     
      ## 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 [off-list ref]
     
      ## 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 [off-list ref]
     
      ## 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 [off-list ref]
     
    @@ 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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:09

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 9 ---------
 1 file changed, 9 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:10

From: Ævar Arnfjörð Bjarmason <redacted>

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

Signed-off-by: Ævar Arnfjörð Bjarmason <redacted>
---
 builtin/ls-tree.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:12

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 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 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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:17

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
---
 builtin/ls-tree.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:26

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 <redacted>
---
 builtin/ls-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:28

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 <redacted>
---
 builtin/ls-tree.c | 21 +++++++++------------
 1 file changed, 9 insertions(+), 12 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:32

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 <redacted>
---
 builtin/ls-tree.c       | 12 ++++++------
 t/t3103-ls-tree-misc.sh |  9 +++++++++
 2 files changed, 15 insertions(+), 6 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:39

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 <redacted>
---
 builtin/ls-tree.c | 124 ++++++++++++++++++++++++++++++++--------------
 1 file changed, 87 insertions(+), 37 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:42

From: Ævar Arnfjörð Bjarmason <redacted>

"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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 builtin/ls-tree.c | 69 +++++++++++++++++++++++++++++------------------
 1 file changed, 43 insertions(+), 26 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:53

From: Johannes Schindelin <redacted>

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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 contrib/coccinelle/strbuf.cocci | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:57

From: Ævar Arnfjörð Bjarmason <redacted>

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 <redacted>
Signed-off-by: Teng Long <redacted>
---
 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
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

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

From: Teng Long <hidden>
Date: 2022-03-04 10:43:59

'--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 <redacted>
---
 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(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:12

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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:12

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 <redacted>
---
 t/t3101-ls-tree-dirname.sh | 55 ++++++++++++++++++++------------------
 1 file changed, 29 insertions(+), 26 deletions(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:18

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 <redacted>
---
 t/t3104-ls-tree-format.sh | 89 +++++++++++++++------------------------
 1 file changed, 33 insertions(+), 56 deletions(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:20

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 <redacted>
---
 builtin/ls-tree.c       | 11 ++++++++++-
 t/t3103-ls-tree-misc.sh | 15 +++++++++------
 2 files changed, 19 insertions(+), 7 deletions(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:21

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 <redacted>
---
 builtin/ls-tree.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:23

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 <redacted>
---
 builtin/ls-tree.c | 3 ---
 1 file changed, 3 deletions(-)
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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:24

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 <redacted>
---
 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 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

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

From: Ævar Arnfjörð Bjarmason <hidden>
Date: 2022-03-10 13:57:25

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 <redacted>
---
 builtin/ls-tree.c | 198 +++++++++++++++++++++++++++++-----------------
 1 file changed, 124 insertions(+), 74 deletions(-)
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
Next 44 of 44 remaining

Previous page

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help