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

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

From
Teng Long <dyroneteng@gmail.com>
Date
Jan 1, 2022, 13:50 UTC
Message-ID
<cb881183cb7179e3e7e983b7c9ffe115f845115c.1641043500.git.dyroneteng@gmail.com>
In-Reply-To
<cover.1641043500.git.dyroneteng@gmail.com>

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 <dyroneteng@gmail.com>
---
 Documentation/git-ls-tree.txt |   7 +-
 builtin/ls-tree.c             | 140 +++++++++++++++++++++++++---------
 t/t3104-ls-tree-oid.sh        |  51 +++++++++++++
 3 files changed, 159 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 df8312408d..85ca7358ba 100644
--- a/builtin/ls-tree.c
+++ b/builtin/ls-tree.c
@@ -16,22 +16,59 @@
 
 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_bits;
+#define SHOW_FILE_NAME 1
+#define SHOW_SIZE (1 << 1)
+#define SHOW_OBJECT_NAME (1 << 2)
+#define SHOW_TYPE (1 << 3)
+#define SHOW_MODE (1 << 4)
+#define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */
 
 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_bits = SHOW_FILE_NAME;
+		return 0;
+	}
+	if (cmdmode == MODE_OBJECT_ONLY) {
+		shown_bits = SHOW_OBJECT_NAME;
+		return 0;
+	}
+	if (!ls_options || (ls_options & LS_RECURSIVE)
+	    || (ls_options & LS_SHOW_TREES)
+	    || (ls_options & LS_TREE_ONLY))
+		shown_bits = SHOW_DEFAULT;
+	if (cmdmode == MODE_LONG)
+		shown_bits = SHOW_DEFAULT | SHOW_SIZE;
+	return 1;
+}
+
+static int show_recursive(const char *base, size_t baselen,
+			  const char *pathname)
 {
 	int i;
 
@@ -61,6 +98,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_bits & 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));
+	}
+	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_init(enum object_type *type, struct strbuf *base,
 			  const char *pathname, unsigned mode, int *retval)
 {
@@ -89,34 +159,24 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,
 	if (show_tree_init(&type, base, pathname, mode, &retval))
 		return retval;
 
-	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_bits ^ SHOW_OBJECT_NAME)) {
+		printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination);
+		return retval;
 	}
-	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_bits ^ SHOW_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);
+	}
+
+	if (!(shown_bits ^ SHOW_DEFAULT) ||
+	    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))
+		show_default(oid, type, pathname, mode, base);
+
 	return retval;
 }
 
@@ -134,12 +194,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,
@@ -170,6 +232,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.33.0.rc1.1802.gbb1c3936fb.dirty
Previous: Teng LongNext: Junio C Hamano
Message 9 of 74 in “ls-tree: "--object-only" and "--format" opts”
  1. 0/8 ls-tree: "--object-only" and "--format" optsTeng Long, Jan 1, 2022
  2. 1/8 ls-tree: remove commented-out codeTeng Long, Jan 1, 2022
  3. 2/8 ls-tree: add missing braces to "else" armsTeng Long, Jan 1, 2022
  4. 3/8 ls-tree: use "enum object_type", not {blob,tree,commit}_typeTeng Long, Jan 1, 2022
  5. 4/8 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"Teng Long, Jan 1, 2022
  6. 5/8 ls-tree: split up the "init" part of show_tree()Teng Long, Jan 1, 2022
  7. Junio C HamanoJan 4, 2022
  8. Teng LongJan 4, 2022
  9. 6/8 ls-tree.c: support --object-only option for "git-ls-tree"Teng Long, Jan 1, 2022
  10. Junio C HamanoJan 4, 2022
  11. Teng LongJan 4, 2022
  12. 7/8 ls-tree.c: introduce struct "shown_data"Teng Long, Jan 1, 2022
  13. Junio C HamanoJan 3, 2022
  14. Teng LongJan 4, 2022
  15. 8/8 ls-tree.c: introduce "--format" optionTeng Long, Jan 1, 2022
  16. Johannes SchindelinJan 4, 2022
  17. Johannes SchindelinJan 4, 2022
  18. Teng LongJan 5, 2022
  19. Teng LongJan 5, 2022
  20. Johannes SchindelinJan 5, 2022
  21. Teng LongJan 5, 2022
  22. 0/9 ls-tree.c: introduce "--format" optionTeng Long, Jan 6, 2022
  23. 1/9 ls-tree: remove commented-out codeTeng Long, Jan 6, 2022
  24. 2/9 ls-tree: add missing braces to "else" armsTeng Long, Jan 6, 2022
  25. 3/9 ls-tree: use "enum object_type", not {blob,tree,commit}_typeTeng Long, Jan 6, 2022
  26. 4/9 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"Teng Long, Jan 6, 2022
  27. 5/9 ls-tree: optimize naming and handling of "return" in show_tree()Teng Long, Jan 6, 2022
  28. Junio C HamanoJan 6, 2022
  29. Teng LongJan 11, 2022
  30. 6/9 ls-tree.c: support --object-only option for "git-ls-tree"Teng Long, Jan 6, 2022
  31. 7/9 ls-tree.c: introduce struct "show_tree_data"Teng Long, Jan 6, 2022
  32. 8/9 ls-tree.c: introduce "--format" optionTeng Long, Jan 6, 2022
  33. Martin ÅgrenJan 10, 2022
  34. Teng LongJan 11, 2022
  35. 9/9 cocci: allow padding with `strbuf_addf()`Teng Long, Jan 6, 2022
  36. Johannes SchindelinJan 7, 2022
  37. Teng LongJan 10, 2022
  38. Johannes SchindelinJan 10, 2022
  39. Teng LongJan 10, 2022
  40. Junio C HamanoJan 10, 2022
  41. Ævar Arnfjörð BjarmasonJan 10, 2022
  42. Junio C HamanoJan 10, 2022
  43. Ævar Arnfjörð BjarmasonJan 10, 2022
  44. Teng LongJan 11, 2022
  45. Taylor BlauJan 11, 2022
  46. René ScharfeJan 11, 2022
  47. Taylor BlauJan 11, 2022
  48. Teng LongJan 13, 2022
  49. Ævar Arnfjörð BjarmasonJan 11, 2022
  50. Teng LongJan 13, 2022
  51. Teng LongJan 13, 2022
  52. 0/9 ls-tree: "--object-only" and "--format" optsTeng Long, Jan 13, 2022
  53. 1/9 ls-tree: remove commented-out codeTeng Long, Jan 13, 2022
  54. 2/9 ls-tree: add missing braces to "else" armsTeng Long, Jan 13, 2022
  55. 3/9 ls-tree: use "enum object_type", not {blob,tree,commit}_typeTeng Long, Jan 13, 2022
  56. 4/9 ls-tree: use "size_t", not "int" for "struct strbuf"'s "len"Teng Long, Jan 13, 2022
  57. 5/9 ls-tree: optimize naming and handling of "return" in show_tree()Teng Long, Jan 13, 2022
  58. Ævar Arnfjörð BjarmasonJan 13, 2022
  59. Teng LongJan 14, 2022
  60. Ævar Arnfjörð BjarmasonJan 14, 2022
  61. 6/9 ls-tree.c: support --object-only option for "git-ls-tree"Teng Long, Jan 13, 2022
  62. Ævar Arnfjörð BjarmasonJan 13, 2022
  63. Teng LongJan 14, 2022
  64. Ævar Arnfjörð BjarmasonJan 14, 2022
  65. Teng LongJan 18, 2022
  66. Ævar Arnfjörð BjarmasonFeb 4, 2022
  67. Teng LongFeb 7, 2022
  68. 7/9 ls-tree.c: introduce struct "show_tree_data"Teng Long, Jan 13, 2022
  69. Ævar Arnfjörð BjarmasonJan 13, 2022
  70. Teng LongJan 14, 2022
  71. 8/9 cocci: allow padding with `strbuf_addf()`Teng Long, Jan 13, 2022
  72. 9/9 ls-tree.c: introduce "--format" optionTeng Long, Jan 13, 2022
  73. Ævar Arnfjörð BjarmasonJan 13, 2022
  74. Teng LongJan 18, 2022

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

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