{"thread":{"id":"57172","subject":"[PATCH v8 1/8] ls-tree: remove commented-out code","startedAt":"2022-01-01T13:51:00Z","lastAt":"2022-02-07T02:22:39Z","messageCount":74,"participants":["Teng Long","Junio C Hamano","Johannes Schindelin","Ævar Arnfjörð Bjarmason","Martin Ågren","Taylor Blau","René Scharfe"],"isPatch":true,"patchVersion":8,"patchTotal":8},"messages":[{"id":"445301","messageId":"2fcff7e0d40255e0be9496647787352dd0aeb784.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 1/8] ls-tree: remove commented-out code","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:24Z","receivedAt":"2022-01-01T13:51:00Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nRemove code added in f35a6d3bce7 (Teach core object handling functions\nabout gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output\nflushing before fork(), 2008-08-04), and then finally ending up in its\ncurrent form in d3bee161fef (tree.c: allow read_tree_recursive() to\ntraverse gitlink entries, 2009-01-25). All while being commented-out!\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3a442631c7..5f7c84950c 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tconst char *type = blob_type;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\t/*\n-\t\t * Maybe we want to have some recursive version here?\n-\t\t *\n-\t\t * Something similar to this incomplete example:\n-\t\t *\n-\t\tif (show_subprojects(base, baselen, pathname))\n-\t\t\tretval = READ_TREE_RECURSIVE;\n-\t\t *\n-\t\t */\n \t\ttype = commit_type;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445302","messageId":"cover.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1639721750.git.dyroneteng@gmail.com","subject":"[PATCH v8 0/8] ls-tree: \"--object-only\" and \"--format\" opts","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:23Z","receivedAt":"2022-01-01T13:51:00Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Diff from v6 (Origin) and v7 (RFC by Ævar):\n\n1. [v6] Performance Regression\n\nIn v6, Ævar pointed out that there's a nearly 10% performance\nregression under linux [1]. This is cause by in V6, I chose to\nuse a bitwisp operation to check whether the specified field to\nbe printed, this will separate the original to many \"printf\" to\ncombined the final output format. But some \"checks\" are unnecessary,\nlike we will check whether to print the \"mode\" and the \"type\", but\nwe do not really need to do that because only print them are\nmeaningless.\n\nSo in commit cb881183cb if this patch, I kept some parts of\nbitwise logic in \"show_tree\" because it's more intuitive than before\nI think. Then, move the original logic to function \"show_default\", now\nin \"show_tree\" the procedure is clearer, first \"show_tree_init\", then \ncheck whether it's asked only to print objectname or filename, or to\nprint a default format. After this, the performance regression problem\nwas solved, here is the performance test result based on linux in my env: \n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]\n    Range (min … max):   101.5 ms … 111.3 ms    28 runs\n    \n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]\n    Range (min … max):    99.3 ms … 109.5 ms    27 runs\n    \n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]\n    Range (min … max):   323.0 ms … 355.0 ms    10 runs\n    \n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]\n    Range (min … max):   330.4 ms … 349.9 ms    10 runs\n\n  2. [v6] Bugs in \"t3104\"\n\n  Ævar found that[2] I forgot to supply a <tree-ish> in tests, that's obviously a\n  bug need to fix and already done in this patch.\n\n  3. [RFC v7 by Ævar] the pre-works commits\n\n  Ævar helped to do some pre-works commits in V7, they are 2fcff7e0d4, 6fd1dd9383,\n  208654b5e2, 2637464fd8 and d77c895a4b, I think these are all reasonable, so I\n  just cherry-pick to this patch and continue the work base on them.\n\n  First, is to support `--object-only`, had been mentioned above. The second commit is\n  to create a \"shown_data\" struct to prepare the next \"--format\" commit for reusing the\n  struct. The last one (ls-tree.c: introduce \"--format\" option) is to support the\n  \"--format\" option.\n  \n  Ævar posted a commit for mainly supporting \"--format\" in RFC v7[3] and give some design\n  and performance test context. My commit based on Ævar's (I'm not sure I have to mark\n  something about Ævar in commit message, because I only made some modifications but the\n  idea is from Ævar) but exists some changes:\n\n      1).  Changed the format field names, the original's and the current's are:\n\n          objectmode -> mode\n          objecttype -> type\n          objectname -> object\n          path -> file\n\n          The original's are ok, just I prefer to make the name more simple to memorize and\n          type, in addition, the current Documentation/git-ls-tree.txt, at \"Output Format\"\n          section use \"<mode> SP <type> SP <object> TAB <file>\" to describe the format.\n\n          I think the names with \"object\" prefix are from Documentation/git-for-each-ref.txt,\n          use a \"objectname\" is not a redundant expression because there are also \"authorname\"\n          and \"refname\" to be distingushed in `git-for-each-ref`, but in \"git-ls-tree\",\n          currently, seems like no necessary, but I'm not so much sure about the naming rules\n          if I was missing something.\n      \n      2).  OPT_CMDMODE and OPT_BIT:\n\n          I noticed Ævar uses \"OPT_BIT\" in his patch but I use \"OPT_CMDMODE\" (actually\n          OPT_CMDMODE also is Ævar teached me) and they seems like both supporting to\n          make a mutual exclusive betweem options. I didn't change them to \"OPT_BIT\"\n          because they looked like working well, plz told me if I misunderstood. \n\n2. Performance comparation between \"master\" and v8\n\n      1). Default format( \"git ls-tree -r\" vs \"hitten builtin formats\" vs \"miss builtin formats\")\n\n        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n        Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n        Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]\n        Range (min … max):    99.2 ms … 113.2 ms    28 runs\n    \n        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\"\n        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\n        Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]\n        Range (min … max):   100.2 ms … 110.5 ms    29 runs\n\n        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='> %(mode) %(type) %(object)%x09%(file)'  HEAD\"\n        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='> %(mode) %(type) %(object)%x09%(file)'  HEAD\n        Time (mean ± σ):     145.3 ms ±   3.9 ms    [User: 119.0 ms, System: 26.2 ms]\n        Range (min … max):   139.7 ms … 150.8 ms    20 runs\n\n      2). Default format that including object size (( \"git ls-tree -r -l\" vs \"hitten builtin formats\" vs \"miss builtin formats\"))\n\n        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n        Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n        Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]\n        Range (min … max):   327.5 ms … 348.4 ms    10 runs\n    \n        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n        Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]\n        Range (min … max):   328.8 ms … 349.4 ms    10 runs\n\n        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='> %(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='> %(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n        Time (mean ± σ):     396.9 ms ±   8.9 ms    [User: 364.2 ms, System: 32.7 ms]\n        Range (min … max):   379.6 ms … 408.6 ms    10 runs\n\nThanks.\n\n[1] https://public-inbox.org/git/RFC-cover-0.7-00000000000-20211217T131635Z-avarab@gmail.com/\n[2] https://public-inbox.org/git/211217.86o85f8jey.gmgdl@evledraar.gmail.com/\n[3] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nTeng Long (3):\n  ls-tree.c: support --object-only option for \"git-ls-tree\"\n  ls-tree.c: introduce struct \"shown_data\"\n  ls-tree.c: introduce \"--format\" option\n\nÆvar Arnfjörð Bjarmason (5):\n  ls-tree: remove commented-out code\n  ls-tree: add missing braces to \"else\" arms\n  ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n  ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n  ls-tree: split up the \"init\" part of show_tree()\n\n Documentation/git-ls-tree.txt |  55 +++++-\n builtin/ls-tree.c             | 315 +++++++++++++++++++++++++++-------\n t/t3104-ls-tree-oid.sh        |  51 ++++++\n t/t3105-ls-tree-format.sh     |  55 ++++++\n 4 files changed, 415 insertions(+), 61 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n create mode 100755 t/t3105-ls-tree-format.sh\n\nRange-diff against v7:\n-:  ---------- > 1:  2fcff7e0d4 ls-tree: remove commented-out code\n-:  ---------- > 2:  6fd1dd9383 ls-tree: add missing braces to \"else\" arms\n-:  ---------- > 3:  208654b5e2 ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n-:  ---------- > 4:  2637464fd8 ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n-:  ---------- > 5:  d77c895a4b ls-tree: split up the \"init\" part of show_tree()\n1:  2e449d1c79 ! 6:  cb881183cb ls-tree.c: support `--object-only` option for \"git-ls-tree\"\n    @@ Metadata\n     Author: Teng Long <dyroneteng@gmail.com>\n     \n      ## Commit message ##\n    -    ls-tree.c: support `--object-only` option for \"git-ls-tree\"\n    +    ls-tree.c: support --object-only option for \"git-ls-tree\"\n     \n         We usually pipe the output from `git ls-trees` to tools like\n         `sed` or `cut` when we only want to extract some fields.\n    @@ Commit message\n     \n         Teach the \"--object-only\" option to the command to only show the\n         object name. This option cannot be used together with\n    -    \"--name-only\" or \"--long\" (mutually exclusive).\n    +    \"--name-only\" or \"--long\" , they are mutually exclusive (actually\n    +    \"--name-only\" and \"--long\" can be combined together before, this\n    +    commit by the way fix this bug).\n    +\n    +    A simple refactoring was done to the \"show_tree\" function, intead by\n    +    using bitwise operations to recognize the format for printing to\n    +    stdout. The reason for doing this is that we don't want to increase\n    +    the readability difficulty with the addition of \"-object-only\",\n    +    making this part of the logic easier to read and expand.\n    +\n    +    In terms of performance, there is no loss comparing to the\n    +    \"master\" (2ae0a9cb8298185a94e5998086f380a355dd8907), here are the\n    +    results of the performance tests in my environment based on linux\n    +    repository:\n    +\n    +        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    +        Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    +        Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]\n    +        Range (min … max):   101.5 ms … 111.3 ms    28 runs\n    +\n    +        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\"\n    +        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\n    +        Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]\n    +        Range (min … max):    99.3 ms … 109.5 ms    27 runs\n    +\n    +        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    +        Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    +        Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]\n    +        Range (min … max):   323.0 ms … 355.0 ms    10 runs\n    +\n    +        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\"\n    +        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\n    +        Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]\n    +        Range (min … max):   330.4 ms … 349.9 ms    10 runs\n     \n         Signed-off-by: Teng Long <dyroneteng@gmail.com>\n     \n    @@ builtin/ls-tree.c\n      \tNULL\n      };\n      \n    +-static int show_recursive(const char *base, size_t baselen, const char *pathname)\n     +enum {\n     +\tMODE_UNSPECIFIED = 0,\n     +\tMODE_NAME_ONLY,\n    @@ builtin/ls-tree.c\n     +\n     +static int cmdmode = MODE_UNSPECIFIED;\n     +\n    - static int show_recursive(const char *base, int baselen, const char *pathname)\n    ++static int parse_shown_fields(void)\n    ++{\n    ++\tif (cmdmode == MODE_NAME_ONLY) {\n    ++\t\tshown_bits = SHOW_FILE_NAME;\n    ++\t\treturn 0;\n    ++\t}\n    ++\tif (cmdmode == MODE_OBJECT_ONLY) {\n    ++\t\tshown_bits = SHOW_OBJECT_NAME;\n    ++\t\treturn 0;\n    ++\t}\n    ++\tif (!ls_options || (ls_options & LS_RECURSIVE)\n    ++\t    || (ls_options & LS_SHOW_TREES)\n    ++\t    || (ls_options & LS_TREE_ONLY))\n    ++\t\tshown_bits = SHOW_DEFAULT;\n    ++\tif (cmdmode == MODE_LONG)\n    ++\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n    ++\treturn 1;\n    ++}\n    ++\n    ++static int show_recursive(const char *base, size_t baselen,\n    ++\t\t\t  const char *pathname)\n      {\n      \tint i;\n    -@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - {\n    - \tint retval = 0;\n    - \tint baselen;\n    -+\tint interspace = 0;\n    - \tconst char *type = blob_type;\n      \n    - \tif (S_ISGITLINK(mode)) {\n    -@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - \t\t *\n    - \t\t * Something similar to this incomplete example:\n    - \t\t *\n    --\t\tif (show_subprojects(base, baselen, pathname))\n    --\t\t\tretval = READ_TREE_RECURSIVE;\n    -+\t\t * if (show_subprojects(base, baselen, pathname))\n    -+\t\t *\tretval = READ_TREE_RECURSIVE;\n    - \t\t *\n    - \t\t */\n    - \t\ttype = commit_type;\n    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname\n    + \treturn 0;\n    + }\n    + \n    ++static int show_default(const struct object_id *oid, enum object_type type,\n    ++\t\t\tconst char *pathname, unsigned mode,\n    ++\t\t\tstruct strbuf *base)\n    ++{\n    ++\tsize_t baselen = base->len;\n    ++\n    ++\tif (shown_bits & SHOW_SIZE) {\n    ++\t\tchar size_text[24];\n    ++\t\tif (type == OBJ_BLOB) {\n    ++\t\t\tunsigned long size;\n    ++\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n    ++\t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n    ++\t\t\telse\n    ++\t\t\t\txsnprintf(size_text, sizeof(size_text),\n    ++\t\t\t\t\t  \"%\" PRIuMAX, (uintmax_t)size);\n    ++\t\t} else {\n    ++\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n    ++\t\t}\n    ++\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n    ++\t\tfind_unique_abbrev(oid, abbrev), size_text);\n    ++\t} else {\n    ++\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n    ++\t\tfind_unique_abbrev(oid, abbrev));\n    ++\t}\n    ++\tbaselen = base->len;\n    ++\tstrbuf_addstr(base, pathname);\n    ++\twrite_name_quoted_relative(base->buf,\n    ++\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n    ++\t\t\t\t   line_termination);\n    ++\tstrbuf_setlen(base, baselen);\n    ++\treturn 1;\n    ++}\n    ++\n    + static int show_tree_init(enum object_type *type, struct strbuf *base,\n    + \t\t\t  const char *pathname, unsigned mode, int *retval)\n    + {\n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - \telse if (ls_options & LS_TREE_ONLY)\n    - \t\treturn 0;\n    + \tif (show_tree_init(&type, base, pathname, mode, &retval))\n    + \t\treturn retval;\n      \n     -\tif (!(ls_options & LS_NAME_ONLY)) {\n     -\t\tif (ls_options & LS_SHOW_SIZE) {\n     -\t\t\tchar size_text[24];\n    --\t\t\tif (!strcmp(type, blob_type)) {\n    +-\t\t\tif (type == OBJ_BLOB) {\n     -\t\t\t\tunsigned long size;\n     -\t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n     -\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\t\t\telse\n     -\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n     -\t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n    --\t\t\t} else\n    +-\t\t\t} else {\n     -\t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n    --\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n    +-\t\t\t}\n    +-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n     -\t\t\t       find_unique_abbrev(oid, abbrev),\n     -\t\t\t       size_text);\n    -+\tif (shown_bits & SHOW_MODE) {\n    -+\t\tprintf(\"%06o\", mode);\n    -+\t\tinterspace = 1;\n    -+\t}\n    -+\tif (shown_bits & SHOW_TYPE) {\n    -+\t\tprintf(\"%s%s\", interspace ? \" \" : \"\", type);\n    -+\t\tinterspace = 1;\n    -+\t}\n    -+\tif (shown_bits & SHOW_OBJECT_NAME) {\n    -+\t\tprintf(\"%s%s\", interspace ? \" \" : \"\",\n    -+\t\t       find_unique_abbrev(oid, abbrev));\n    -+\t\tif (!(shown_bits ^ SHOW_OBJECT_NAME))\n    -+\t\t\tgoto LINE_FINISH;\n    -+\t\tinterspace = 1;\n    -+\t}\n    -+\tif (shown_bits & SHOW_SIZE) {\n    -+\t\tchar size_text[24];\n    -+\t\tif (!strcmp(type, blob_type)) {\n    -+\t\t\tunsigned long size;\n    -+\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n    -+\t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n    -+\t\t\telse\n    -+\t\t\t\txsnprintf(size_text, sizeof(size_text),\n    -+\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n    - \t\t} else\n    --\t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n    +-\t\t} else {\n    +-\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n     -\t\t\t       find_unique_abbrev(oid, abbrev));\n    -+\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n    -+\t\tprintf(\"%s%7s\", interspace ? \" \" : \"\", size_text);\n    -+\t\tinterspace = 1;\n    -+\t}\n    -+\tif (shown_bits & SHOW_FILE_NAME) {\n    -+\t\tif (interspace)\n    -+\t\t\tprintf(\"\\t\");\n    -+\t\tbaselen = base->len;\n    -+\t\tstrbuf_addstr(base, pathname);\n    -+\t\twrite_name_quoted_relative(base->buf,\n    -+\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n    -+\t\t\t\t\t   stdout,\n    -+\t\t\t\t\t   line_termination\n    -+\t\t\t\t\t   ? CQ_NO_TERMINATOR_C_QUOTED\n    -+\t\t\t\t\t   : CQ_NO_TERMINATOR_AS_IS);\n    -+\t\tstrbuf_setlen(base, baselen);\n    +-\t\t}\n    ++\tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n    ++\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n    ++\t\treturn retval;\n      \t}\n     -\tbaselen = base->len;\n     -\tstrbuf_addstr(base, pathname);\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\t\t\t   stdout, line_termination);\n     -\tstrbuf_setlen(base, baselen);\n     +\n    -+LINE_FINISH:\n    -+\tputchar(line_termination);\n    ++\tif (!(shown_bits ^ SHOW_FILE_NAME)) {\n    ++\t\tbaselen = base->len;\n    ++\t\tstrbuf_addstr(base, pathname);\n    ++\t\twrite_name_quoted_relative(base->buf,\n    ++\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n    ++\t\t\t\t\t   stdout, line_termination);\n    ++\t\tstrbuf_setlen(base, baselen);\n    ++\t}\n    ++\n    ++\tif (!(shown_bits ^ SHOW_DEFAULT) ||\n    ++\t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n    ++\t\tshow_default(oid, type, pathname, mode, base);\n    ++\n      \treturn retval;\n      }\n      \n    -+static int parse_shown_fields(void)\n    -+{\n    -+\tif (cmdmode == MODE_NAME_ONLY) {\n    -+\t\tshown_bits = SHOW_FILE_NAME;\n    -+\t\treturn 0;\n    -+\t}\n    -+\tif (cmdmode == MODE_OBJECT_ONLY) {\n    -+\t\tshown_bits = SHOW_OBJECT_NAME;\n    -+\t\treturn 0;\n    -+\t}\n    -+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n    -+\t    || (ls_options & LS_SHOW_TREES)\n    -+\t    || (ls_options & LS_TREE_ONLY))\n    -+\t\tshown_bits = SHOW_DEFAULT;\n    -+\tif (cmdmode == MODE_LONG)\n    -+\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n    -+\treturn 1;\n    -+}\n    -+\n    - int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n    - {\n    - \tstruct object_id oid;\n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n      \t\t\tLS_SHOW_TREES),\n      \t\tOPT_SET_INT('z', NULL, &line_termination,\n    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref\n      \t\tdie(\"Not a valid object name %s\", argv[0]);\n      \n     +\tparse_shown_fields();\n    ++\n      \t/*\n      \t * show_recursive() rolls its own matching code and is\n      \t * generally ignorant of 'struct pathspec'. The magic mask\n     \n    - ## quote.c ##\n    -@@ quote.c: void quote_two_c_style(struct strbuf *sb, const char *prefix, const char *path,\n    - \n    - void write_name_quoted(const char *name, FILE *fp, int terminator)\n    - {\n    --\tif (terminator) {\n    -+\tif (0 < terminator || terminator == CQ_NO_TERMINATOR_C_QUOTED)\n    - \t\tquote_c_style(name, NULL, fp, 0);\n    --\t} else {\n    -+\telse\n    - \t\tfputs(name, fp);\n    --\t}\n    --\tfputc(terminator, fp);\n    -+\tif (0 <= terminator)\n    -+\t\tfputc(terminator, fp);\n    - }\n    - \n    - void write_name_quoted_relative(const char *name, const char *prefix,\n    -\n    - ## quote.h ##\n    -@@ quote.h: int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);\n    - #define CQUOTE_NODQ 01\n    - size_t quote_c_style(const char *name, struct strbuf *, FILE *, unsigned);\n    - void quote_two_c_style(struct strbuf *, const char *, const char *, unsigned);\n    -+/*\n    -+ * Write a name, typically a filename, followed by a terminator that\n    -+ * separates it from what comes next.\n    -+ * When terminator is NUL, the name is given as-is.  Otherwise, the\n    -+ * name is c-quoted, suitable for text output.  HT and LF are typical\n    -+ * values used for the terminator, but other positive values are possible.\n    -+ *\n    -+ * In addition to non-negative values two special values in terminator\n    -+ * are possible.\n    -+ *\n    -+ * -1: show the name c-quoted, without adding any terminator.\n    -+ * -2: show the name as-is, without adding any terminator.\n    -+ */\n    -+#define CQ_NO_TERMINATOR_C_QUOTED\t(-1)\n    -+#define CQ_NO_TERMINATOR_AS_IS\t\t(-2)\n    - \n    - void write_name_quoted(const char *name, FILE *, int terminator);\n    -+/*\n    -+ * Similar to the above, but the name is first made relative to the prefix\n    -+ * before being shown.\n    -+ */\n    - void write_name_quoted_relative(const char *name, const char *prefix,\n    - \t\t\t\tFILE *fp, int terminator);\n    - \n    -\n    - ## t/t3103-ls-tree-misc.sh ##\n    -@@ t/t3103-ls-tree-misc.sh: test_expect_success 'ls-tree fails with non-zero exit code on broken tree' '\n    - \ttest_must_fail git ls-tree -r HEAD\n    - '\n    - \n    -+test_expect_success 'usage: incompatible options: --name-status with --long' '\n    -+\ttest_expect_code 129 git ls-tree --long --name-status\n    -+'\n    -+\n    -+test_expect_success 'usage: incompatible options: --name-only with --long' '\n    -+\ttest_expect_code 129 git ls-tree --long --name-only\n    -+'\n    -+\n    - test_done\n    -\n      ## t/t3104-ls-tree-oid.sh (new) ##\n     @@\n     +#!/bin/sh\n    @@ t/t3104-ls-tree-oid.sh (new)\n     +'\n     +\n     +test_expect_success 'usage: incompatible options: --name-only with --object-only' '\n    -+\ttest_expect_code 129 git ls-tree --object-only --name-only\n    ++\ttest_expect_code 129 git ls-tree --object-only --name-only $tree\n     +'\n     +\n     +test_expect_success 'usage: incompatible options: --name-status with --object-only' '\n    -+\ttest_expect_code 129 git ls-tree --object-only --name-status\n    ++\ttest_expect_code 129 git ls-tree --object-only --name-status $tree\n     +'\n     +\n     +test_expect_success 'usage: incompatible options: --long with --object-only' '\n    -+\ttest_expect_code 129 git ls-tree --object-only --long\n    ++\ttest_expect_code 129 git ls-tree --object-only --long $tree\n     +'\n     +\n     +test_done\n-:  ---------- > 7:  296ebacafe ls-tree.c: introduce struct \"shown_data\"\n-:  ---------- > 8:  e0add802fb ls-tree.c: introduce \"--format\" option\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445303","messageId":"6fd1dd938303192d5b0a4cdc2914b2ff72cf5a3d.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 2/8] ls-tree: add missing braces to \"else\" arms","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:25Z","receivedAt":"2022-01-01T13:51:06Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd missing {} to the \"else\" arms in show_tree() per the\nCodingGuidelines.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 5f7c84950c..0a28f32ccb 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t\telse\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n \t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else\n+\t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t\t}\n \t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n-\t\t} else\n+\t\t} else {\n \t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev));\n+\t\t}\n \t}\n \tbaselen = base->len;\n \tstrbuf_addstr(base, pathname);\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445304","messageId":"208654b5e2aebbe988b5a8f478869253a0166e9b.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 3/8] ls-tree: use \"enum object_type\", not {blob,tree,commit}_type","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:26Z","receivedAt":"2022-01-01T13:51:09Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nChange the ls-tree.c code to use type_name() on the enum instead of\nusing the string constants. This doesn't matter either way for\nperformance, but makes this a bit easier to read as we'll no longer\nneed a strcmp() here.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 0a28f32ccb..3f0225b097 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n {\n \tint retval = 0;\n \tint baselen;\n-\tconst char *type = blob_type;\n+\tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\ttype = commit_type;\n+\t\ttype = OBJ_COMMIT;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n \t\t\tretval = READ_TREE_RECURSIVE;\n \t\t\tif (!(ls_options & LS_SHOW_TREES))\n \t\t\t\treturn retval;\n \t\t}\n-\t\ttype = tree_type;\n+\t\ttype = OBJ_TREE;\n \t}\n \telse if (ls_options & LS_TREE_ONLY)\n \t\treturn 0;\n@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (!(ls_options & LS_NAME_ONLY)) {\n \t\tif (ls_options & LS_SHOW_SIZE) {\n \t\t\tchar size_text[24];\n-\t\t\tif (!strcmp(type, blob_type)) {\n+\t\t\tif (type == OBJ_BLOB) {\n \t\t\t\tunsigned long size;\n \t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n \t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev));\n \t\t}\n \t}\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445305","messageId":"2637464fd8f6b8bae6c681f20c10c9fc76dc6d6c.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 4/8] ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:27Z","receivedAt":"2022-01-01T13:51:12Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nThe \"struct strbuf\"'s \"len\" member is a \"size_t\", not an \"int\", so\nlet's change our corresponding types accordingly. This also changes\nthe \"len\" and \"speclen\" variables, which are likewise used to store\nthe return value of strlen(), which returns \"size_t\", not \"int\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3f0225b097..eecc7482d5 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {\n \tNULL\n };\n \n-static int show_recursive(const char *base, int baselen, const char *pathname)\n+static int show_recursive(const char *base, size_t baselen, const char *pathname)\n {\n \tint i;\n \n@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)\n \n \tfor (i = 0; i < pathspec.nr; i++) {\n \t\tconst char *spec = pathspec.items[i].match;\n-\t\tint len, speclen;\n+\t\tsize_t len, speclen;\n \n \t\tif (strncmp(base, spec, baselen))\n \t\t\tcontinue;\n@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n \tint retval = 0;\n-\tint baselen;\n+\tsize_t baselen;\n \tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445306","messageId":"d77c895a4b9d5cb86443279c0156196254895ef4.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 5/8] ls-tree: split up the \"init\" part of show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:28Z","receivedAt":"2022-01-01T13:51:19Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nSplit up the \"init\" part of the show_tree() function where we decide\nwhat the \"type\" is, and whether we'll return early. This makes things\na bit less readable for now, but we'll soon re-use this in a sibling\nfunction, and avoiding the duplication will be worth it.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 30 +++++++++++++++++++-----------\n 1 file changed, 19 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex eecc7482d5..df8312408d 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -61,25 +61,33 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n \treturn 0;\n }\n \n-static int show_tree(const struct object_id *oid, struct strbuf *base,\n-\t\tconst char *pathname, unsigned mode, void *context)\n+static int show_tree_init(enum object_type *type, struct strbuf *base,\n+\t\t\t  const char *pathname, unsigned mode, int *retval)\n {\n-\tint retval = 0;\n-\tsize_t baselen;\n-\tenum object_type type = OBJ_BLOB;\n-\n \tif (S_ISGITLINK(mode)) {\n-\t\ttype = OBJ_COMMIT;\n+\t\t*type = OBJ_COMMIT;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n-\t\t\tretval = READ_TREE_RECURSIVE;\n+\t\t\t*retval = READ_TREE_RECURSIVE;\n \t\t\tif (!(ls_options & LS_SHOW_TREES))\n-\t\t\t\treturn retval;\n+\t\t\t\treturn 1;\n \t\t}\n-\t\ttype = OBJ_TREE;\n+\t\t*type = OBJ_TREE;\n \t}\n \telse if (ls_options & LS_TREE_ONLY)\n-\t\treturn 0;\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int show_tree(const struct object_id *oid, struct strbuf *base,\n+\t\tconst char *pathname, unsigned mode, void *context)\n+{\n+\tint retval = 0;\n+\tsize_t baselen;\n+\tenum object_type type = OBJ_BLOB;\n+\n+\tif (show_tree_init(&type, base, pathname, mode, &retval))\n+\t\treturn retval;\n \n \tif (!(ls_options & LS_NAME_ONLY)) {\n \t\tif (ls_options & LS_SHOW_SIZE) {\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445307","messageId":"cb881183cb7179e3e7e983b7c9ffe115f845115c.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 6/8] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:29Z","receivedAt":"2022-01-01T13:51:22Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"We usually pipe the output from `git ls-trees` to tools like\n`sed` or `cut` when we only want to extract some fields.\n\nWhen we want only the pathname component, we can pass\n`--name-only` option to omit such a pipeline, but there are no\noptions for extracting other fields.\n\nTeach the \"--object-only\" option to the command to only show the\nobject name. This option cannot be used together with\n\"--name-only\" or \"--long\" , they are mutually exclusive (actually\n\"--name-only\" and \"--long\" can be combined together before, this\ncommit by the way fix this bug).\n\nA simple refactoring was done to the \"show_tree\" function, intead by\nusing bitwise operations to recognize the format for printing to\nstdout. The reason for doing this is that we don't want to increase\nthe readability difficulty with the addition of \"-object-only\",\nmaking this part of the logic easier to read and expand.\n\nIn terms of performance, there is no loss comparing to the\n\"master\" (2ae0a9cb8298185a94e5998086f380a355dd8907), here are the\nresults of the performance tests in my environment based on linux\nrepository:\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]\n    Range (min … max):   101.5 ms … 111.3 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]\n    Range (min … max):    99.3 ms … 109.5 ms    27 runs\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]\n    Range (min … max):   323.0 ms … 355.0 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]\n    Range (min … max):   330.4 ms … 349.9 ms    10 runs\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |   7 +-\n builtin/ls-tree.c             | 140 +++++++++++++++++++++++++---------\n t/t3104-ls-tree-oid.sh        |  51 +++++++++++++\n 3 files changed, 159 insertions(+), 39 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex db02d6d79a..729370f235 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n \t    <tree-ish> [<path>...]\n \n DESCRIPTION\n@@ -59,6 +59,11 @@ OPTIONS\n --name-only::\n --name-status::\n \tList only filenames (instead of the \"long\" output), one per line.\n+\tCannot be combined with `--object-only`.\n+\n+--object-only::\n+\tList only names of the objects, one per line. Cannot be combined\n+\twith `--name-only` or `--name-status`.\n \n --abbrev[=<n>]::\n \tInstead of showing the full 40-byte hexadecimal object\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex df8312408d..85ca7358ba 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -16,22 +16,59 @@\n \n static int line_termination = '\\n';\n #define LS_RECURSIVE 1\n-#define LS_TREE_ONLY 2\n-#define LS_SHOW_TREES 4\n-#define LS_NAME_ONLY 8\n-#define LS_SHOW_SIZE 16\n+#define LS_TREE_ONLY (1 << 1)\n+#define LS_SHOW_TREES (1 << 2)\n+#define LS_NAME_ONLY (1 << 3)\n+#define LS_SHOW_SIZE (1 << 4)\n+#define LS_OBJECT_ONLY (1 << 5)\n static int abbrev;\n static int ls_options;\n static struct pathspec pathspec;\n static int chomp_prefix;\n static const char *ls_tree_prefix;\n+static unsigned int shown_bits;\n+#define SHOW_FILE_NAME 1\n+#define SHOW_SIZE (1 << 1)\n+#define SHOW_OBJECT_NAME (1 << 2)\n+#define SHOW_TYPE (1 << 3)\n+#define SHOW_MODE (1 << 4)\n+#define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n \n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n };\n \n-static int show_recursive(const char *base, size_t baselen, const char *pathname)\n+enum {\n+\tMODE_UNSPECIFIED = 0,\n+\tMODE_NAME_ONLY,\n+\tMODE_OBJECT_ONLY,\n+\tMODE_LONG,\n+};\n+\n+static int cmdmode = MODE_UNSPECIFIED;\n+\n+static int parse_shown_fields(void)\n+{\n+\tif (cmdmode == MODE_NAME_ONLY) {\n+\t\tshown_bits = SHOW_FILE_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (cmdmode == MODE_OBJECT_ONLY) {\n+\t\tshown_bits = SHOW_OBJECT_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n+\t    || (ls_options & LS_SHOW_TREES)\n+\t    || (ls_options & LS_TREE_ONLY))\n+\t\tshown_bits = SHOW_DEFAULT;\n+\tif (cmdmode == MODE_LONG)\n+\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n+\treturn 1;\n+}\n+\n+static int show_recursive(const char *base, size_t baselen,\n+\t\t\t  const char *pathname)\n {\n \tint i;\n \n@@ -61,6 +98,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n \treturn 0;\n }\n \n+static int show_default(const struct object_id *oid, enum object_type type,\n+\t\t\tconst char *pathname, unsigned mode,\n+\t\t\tstruct strbuf *base)\n+{\n+\tsize_t baselen = base->len;\n+\n+\tif (shown_bits & SHOW_SIZE) {\n+\t\tchar size_text[24];\n+\t\tif (type == OBJ_BLOB) {\n+\t\t\tunsigned long size;\n+\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n+\t\t\telse\n+\t\t\t\txsnprintf(size_text, sizeof(size_text),\n+\t\t\t\t\t  \"%\" PRIuMAX, (uintmax_t)size);\n+\t\t} else {\n+\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t}\n+\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t} else {\n+\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev));\n+\t}\n+\tbaselen = base->len;\n+\tstrbuf_addstr(base, pathname);\n+\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n+\t\t\t\t   line_termination);\n+\tstrbuf_setlen(base, baselen);\n+\treturn 1;\n+}\n+\n static int show_tree_init(enum object_type *type, struct strbuf *base,\n \t\t\t  const char *pathname, unsigned mode, int *retval)\n {\n@@ -89,34 +159,24 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (show_tree_init(&type, base, pathname, mode, &retval))\n \t\treturn retval;\n \n-\tif (!(ls_options & LS_NAME_ONLY)) {\n-\t\tif (ls_options & LS_SHOW_SIZE) {\n-\t\t\tchar size_text[24];\n-\t\t\tif (type == OBJ_BLOB) {\n-\t\t\t\tunsigned long size;\n-\t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"BAD\");\n-\t\t\t\telse\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else {\n-\t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n-\t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev),\n-\t\t\t       size_text);\n-\t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev));\n-\t\t}\n+\tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n+\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n+\t\treturn retval;\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n-\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n-\t\t\t\t   stdout, line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\n+\tif (!(shown_bits ^ SHOW_FILE_NAME)) {\n+\t\tbaselen = base->len;\n+\t\tstrbuf_addstr(base, pathname);\n+\t\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n+\t\t\t\t\t   stdout, line_termination);\n+\t\tstrbuf_setlen(base, baselen);\n+\t}\n+\n+\tif (!(shown_bits ^ SHOW_DEFAULT) ||\n+\t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n+\t\tshow_default(oid, type, pathname, mode, base);\n+\n \treturn retval;\n }\n \n@@ -134,12 +194,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\t\tLS_SHOW_TREES),\n \t\tOPT_SET_INT('z', NULL, &line_termination,\n \t\t\t    N_(\"terminate entries with NUL byte\"), 0),\n-\t\tOPT_BIT('l', \"long\", &ls_options, N_(\"include object size\"),\n-\t\t\tLS_SHOW_SIZE),\n-\t\tOPT_BIT(0, \"name-only\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n-\t\tOPT_BIT(0, \"name-status\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n+\t\tOPT_CMDMODE('l', \"long\", &cmdmode, N_(\"include object size\"),\n+\t\t\t    MODE_LONG),\n+\t\tOPT_CMDMODE(0, \"name-only\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"name-status\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"object-only\", &cmdmode, N_(\"list only objects\"),\n+\t\t\t    MODE_OBJECT_ONLY),\n \t\tOPT_SET_INT(0, \"full-name\", &chomp_prefix,\n \t\t\t    N_(\"use full path names\"), 0),\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n@@ -170,6 +232,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tif (get_oid(argv[0], &oid))\n \t\tdie(\"Not a valid object name %s\", argv[0]);\n \n+\tparse_shown_fields();\n+\n \t/*\n \t * show_recursive() rolls its own matching code and is\n \t * generally ignorant of 'struct pathspec'. The magic mask\ndiff --git a/t/t3104-ls-tree-oid.sh b/t/t3104-ls-tree-oid.sh\nnew file mode 100755\nindex 0000000000..6ce62bd769\n--- /dev/null\n+++ b/t/t3104-ls-tree-oid.sh\n@@ -0,0 +1,51 @@\n+#!/bin/sh\n+\n+test_description='git ls-tree objects handling.'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tmkdir -p C &&\n+\ttest_commit C/D.txt &&\n+\tfind *.txt path* \\( -type f -o -type l \\) -print |\n+\txargs git update-index --add &&\n+\ttree=$(git write-tree) &&\n+\techo $tree\n+'\n+\n+test_expect_success 'usage: --object-only' '\n+\tgit ls-tree --object-only $tree >current &&\n+\tgit ls-tree $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with -r' '\n+\tgit ls-tree --object-only -r $tree >current &&\n+\tgit ls-tree -r $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with --abbrev' '\n+\tgit ls-tree --object-only --abbrev=6 $tree >current &&\n+\tgit ls-tree --abbrev=6 $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-only with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-only $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-status with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-status $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --long with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --long $tree\n+'\n+\n+test_done\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445308","messageId":"296ebacafe4d40ffc5282aebb9fee972866c4ccc.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 7/8] ls-tree.c: introduce struct \"shown_data\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:30Z","receivedAt":"2022-01-01T13:51:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"show_data\" is a struct that packages the necessary fields for\nreusing. This commit is a front-loaded commit for support\n\"--format\" argument and does not affect any existing functionality.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/ls-tree.c | 47 +++++++++++++++++++++++++++++------------------\n 1 file changed, 29 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 85ca7358ba..009ffeb15d 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -34,6 +34,14 @@ static unsigned int shown_bits;\n #define SHOW_MODE (1 << 4)\n #define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n \n+struct shown_data {\n+\tunsigned mode;\n+\tenum object_type type;\n+\tconst struct object_id *oid;\n+\tconst char *pathname;\n+\tstruct strbuf *base;\n+};\n+\n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n@@ -98,17 +106,15 @@ static int show_recursive(const char *base, size_t baselen,\n \treturn 0;\n }\n \n-static int show_default(const struct object_id *oid, enum object_type type,\n-\t\t\tconst char *pathname, unsigned mode,\n-\t\t\tstruct strbuf *base)\n+static int show_default(struct shown_data *data)\n {\n-\tsize_t baselen = base->len;\n+\tsize_t baselen = data->base->len;\n \n \tif (shown_bits & SHOW_SIZE) {\n \t\tchar size_text[24];\n-\t\tif (type == OBJ_BLOB) {\n+\t\tif (data->type == OBJ_BLOB) {\n \t\t\tunsigned long size;\n-\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\tif (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n \t\t\telse\n \t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -116,18 +122,18 @@ static int show_default(const struct object_id *oid, enum object_type type,\n \t\t} else {\n \t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t}\n-\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t\tprintf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev), size_text);\n \t} else {\n-\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev));\n+\t\tprintf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev));\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n+\tbaselen = data->base->len;\n+\tstrbuf_addstr(data->base, data->pathname);\n+\twrite_name_quoted_relative(data->base->buf,\n \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n \t\t\t\t   line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\tstrbuf_setlen(data->base, baselen);\n \treturn 1;\n }\n \n@@ -154,11 +160,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n {\n \tint retval = 0;\n \tsize_t baselen;\n-\tenum object_type type = OBJ_BLOB;\n+\tstruct shown_data data = {\n+\t\t.mode = mode,\n+\t\t.type = OBJ_BLOB,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n \n-\tif (show_tree_init(&type, base, pathname, mode, &retval))\n+\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n \t\treturn retval;\n-\n \tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n \t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n \t\treturn retval;\n@@ -175,7 +186,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \n \tif (!(shown_bits ^ SHOW_DEFAULT) ||\n \t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n-\t\tshow_default(oid, type, pathname, mode, base);\n+\t\tshow_default(&data);\n \n \treturn retval;\n }\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445309","messageId":"e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-01T13:50:31Z","receivedAt":"2022-01-01T13:51:33Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Add a --format option to ls-tree. It has an existing default output,\nand then --long and --name-only options to emit the default output\nalong with the objectsize and, or to only emit object paths.\n\nRather than add --type-only, --object-only etc. we can just support a\n--format using a strbuf_expand() similar to \"for-each-ref\n--format\". We might still add such options in the future for\nconvenience.\n\nThe --format implementation is slower than the existing code, but this\nchange does not cause any performance regressions. We'll leave the\nexisting show_tree() unchanged, and only run show_tree_fmt() in if\na --format different than the hardcoded built-in ones corresponding to\nthe existing modes is provided.\n\nI.e. something like the \"--long\" output would be much slower with\nthis, mainly due to how we need to allocate various things to do with\nquote.c instead of spewing the output directly to stdout.\n\nThe new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's\nidea and suggestion, this commit makes modifications in terms of the\noriginal discussion on community [1].\n\nHere is the statistics about performance tests:\n\n1. Default format (hitten the builtin formats):\n\n    \"git ls-tree <tree-ish>\" vs \"--format='%(mode) %(type) %(object)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]\n    Range (min … max):    99.2 ms … 113.2 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\n    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]\n    Range (min … max):   100.2 ms … 110.5 ms    29 runs\n\n2. Default format includes object size (hitten the builtin formats):\n\n    \"git ls-tree -l <tree-ish>\" vs \"--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]\n    Range (min … max):   327.5 ms … 348.4 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]\n    Range (min … max):   328.8 ms … 349.4 ms    10 runs\n\nLinks:\n[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |  50 ++++++++-\n builtin/ls-tree.c             | 191 ++++++++++++++++++++++++++++------\n t/t3105-ls-tree-format.sh     |  55 ++++++++++\n 3 files changed, 259 insertions(+), 37 deletions(-)\n create mode 100755 t/t3105-ls-tree-format.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex 729370f235..ef23c0fac1 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,9 +10,9 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n-\t    <tree-ish> [<path>...]\n-\n+\t    [--name-only] [--name-status] [--object-only]\n+\t    [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--format=<format>] <tree-ish> [<path>...]\n DESCRIPTION\n -----------\n Lists the contents of a given tree object, like what \"/bin/ls -a\" does\n@@ -79,6 +79,16 @@ OPTIONS\n \tDo not limit the listing to the current working directory.\n \tImplies --full-name.\n \n+--format=<format>::\n+\tA string that interpolates `%(fieldname)` from the result\n+\tbeing shown. It also interpolates `%%` to `%`, and\n+\t`%xx` where `xx`are hex digits interpolates to character\n+\twith hex code `xx`; for example `%00` interpolates to\n+\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n+\tWhen specified, `--format` cannot be combined with other\n+\tformat-altering options, including `--long`, `--name-only`\n+\tand `--object-only`.\n+\n [<path>...]::\n \tWhen paths are given, show them (note that this isn't really raw\n \tpathnames, but rather a list of patterns to match).  Otherwise\n@@ -87,6 +97,9 @@ OPTIONS\n \n Output Format\n -------------\n+\n+Default format:\n+\n         <mode> SP <type> SP <object> TAB <file>\n \n This output format is compatible with what `--index-info --stdin` of\n@@ -105,6 +118,37 @@ quoted as explained for the configuration variable `core.quotePath`\n (see linkgit:git-config[1]).  Using `-z` the filename is output\n verbatim and the line is terminated by a NUL byte.\n \n+Customized format:\n+\n+It's support to print customized format by `%(fieldname)` with `--format` option.\n+For example, if you want to only print the <object> and <file> fields with a\n+JSON style, executing with a specific \"--format\" like\n+\n+\t\tgit ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n+\n+The output format changes to:\n+\n+\t\t{\"object\":\"<object>\", \"file\":\"<file>\"}\n+\n+FIELD NAMES\n+-----------\n+\n+Various values from structured fields can be used to interpolate\n+into the resulting output. For each outputing line, the following\n+names can be used:\n+\n+mode::\n+\tThe mode of the object.\n+type::\n+\tThe type of the object (`blob` or `tree`).\n+object::\n+\tThe name of the object.\n+size[:padded]::\n+\tThe size of the object (\"-\" if it's a tree).\n+\tIt also supports a padded format of size with \"%(size:padded)\".\n+file::\n+\tThe filename of the object.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 009ffeb15d..6e3e5a4d06 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -56,23 +56,75 @@ enum {\n \n static int cmdmode = MODE_UNSPECIFIED;\n \n-static int parse_shown_fields(void)\n+static const char *format;\n+static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n+static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n+static const char *name_only_format = \"%(file)\";\n+static const char *object_only_format = \"%(object)\";\n+\n+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n+\t\t\t      const enum object_type type, unsigned int padded)\n {\n-\tif (cmdmode == MODE_NAME_ONLY) {\n-\t\tshown_bits = SHOW_FILE_NAME;\n-\t\treturn 0;\n+\tif (type == OBJ_BLOB) {\n+\t\tunsigned long size;\n+\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n+\t\t\tdie(_(\"could not get object info about '%s'\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tif (padded)\n+\t\t\tstrbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n+\t\telse\n+\t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n+\t} else if (padded) {\n+\t\tstrbuf_addf(line, \"%7s\", \"-\");\n+\t} else {\n+\t\tstrbuf_addstr(line, \"-\");\n \t}\n-\tif (cmdmode == MODE_OBJECT_ONLY) {\n-\t\tshown_bits = SHOW_OBJECT_NAME;\n-\t\treturn 0;\n+}\n+\n+static size_t expand_show_tree(struct strbuf *line, const char *start,\n+\t\t\t       void *context)\n+{\n+\tstruct shown_data *data = context;\n+\tconst char *end;\n+\tconst char *p;\n+\tunsigned int errlen;\n+\tsize_t len;\n+\tlen = strbuf_expand_literal_cb(line, start, NULL);\n+\tif (len)\n+\t\treturn len;\n+\n+\tif (*start != '(')\n+\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n+\n+\tend = strchr(start + 1, ')');\n+\tif (!end)\n+\t\tdie(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n+\n+\tlen = end - start + 1;\n+\tif (skip_prefix(start, \"(mode)\", &p)) {\n+\t\tstrbuf_addf(line, \"%06o\", data->mode);\n+\t} else if (skip_prefix(start, \"(type)\", &p)) {\n+\t\tstrbuf_addstr(line, type_name(data->type));\n+\t} else if (skip_prefix(start, \"(size:padded)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 1);\n+\t} else if (skip_prefix(start, \"(size)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 0);\n+\t} else if (skip_prefix(start, \"(object)\", &p)) {\n+\t\tstrbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n+\t} else if (skip_prefix(start, \"(file)\", &p)) {\n+\t\tconst char *name = data->base->buf;\n+\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n+\t\tstruct strbuf quoted = STRBUF_INIT;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(data->base, data->pathname);\n+\t\tname = relative_path(data->base->buf, prefix, &sb);\n+\t\tquote_c_style(name, &quoted, NULL, 0);\n+\t\tstrbuf_addstr(line, quoted.buf);\n+\t} else {\n+\t\terrlen = (unsigned long)len;\n+\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n \t}\n-\tif (!ls_options || (ls_options & LS_RECURSIVE)\n-\t    || (ls_options & LS_SHOW_TREES)\n-\t    || (ls_options & LS_TREE_ONLY))\n-\t\tshown_bits = SHOW_DEFAULT;\n-\tif (cmdmode == MODE_LONG)\n-\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n-\treturn 1;\n+\treturn len;\n }\n \n static int show_recursive(const char *base, size_t baselen,\n@@ -106,6 +158,75 @@ static int show_recursive(const char *base, size_t baselen,\n \treturn 0;\n }\n \n+static int show_tree_init(enum object_type *type, struct strbuf *base,\n+\t\t\t  const char *pathname, unsigned mode, int *retval)\n+{\n+\tif (S_ISGITLINK(mode)) {\n+\t\t*type = OBJ_COMMIT;\n+\t} else if (S_ISDIR(mode)) {\n+\t\tif (show_recursive(base->buf, base->len, pathname)) {\n+\t\t\t*retval = READ_TREE_RECURSIVE;\n+\t\t\tif (!(ls_options & LS_SHOW_TREES))\n+\t\t\t\treturn 1;\n+\t\t}\n+\t\t*type = OBJ_TREE;\n+\t}\n+\telse if (ls_options & LS_TREE_ONLY)\n+\t\treturn 1;\n+\treturn 0;\n+}\n+\n+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n+\t\t\t const char *pathname, unsigned mode, void *context)\n+{\n+\tsize_t baselen;\n+\tint retval = 0;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct shown_data data = {\n+\t\t.mode = mode,\n+\t\t.type = OBJ_BLOB,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n+\n+\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n+\t\treturn retval;\n+\n+\tbaselen = base->len;\n+\tstrbuf_expand(&line, format, expand_show_tree, &data);\n+\tstrbuf_addch(&line, line_termination);\n+\tfwrite(line.buf, line.len, 1, stdout);\n+\tstrbuf_setlen(base, baselen);\n+\treturn retval;\n+}\n+\n+static int parse_shown_fields(void)\n+{\n+\tif (cmdmode == MODE_NAME_ONLY ||\n+\t    (format && !strcmp(format, name_only_format))) {\n+\t\tshown_bits = SHOW_FILE_NAME;\n+\t\treturn 1;\n+\t}\n+\n+\tif (cmdmode == MODE_OBJECT_ONLY ||\n+\t    (format && !strcmp(format, object_only_format))) {\n+\t\tshown_bits = SHOW_OBJECT_NAME;\n+\t\treturn 1;\n+\t}\n+\n+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n+\t    || (ls_options & LS_SHOW_TREES)\n+\t    || (ls_options & LS_TREE_ONLY)\n+\t\t|| (format && !strcmp(format, default_format)))\n+\t\tshown_bits = SHOW_DEFAULT;\n+\n+\tif (cmdmode == MODE_LONG ||\n+\t\t(format && !strcmp(format, long_format)))\n+\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n+\treturn 1;\n+}\n+\n static int show_default(struct shown_data *data)\n {\n \tsize_t baselen = data->base->len;\n@@ -137,24 +258,6 @@ static int show_default(struct shown_data *data)\n \treturn 1;\n }\n \n-static int show_tree_init(enum object_type *type, struct strbuf *base,\n-\t\t\t  const char *pathname, unsigned mode, int *retval)\n-{\n-\tif (S_ISGITLINK(mode)) {\n-\t\t*type = OBJ_COMMIT;\n-\t} else if (S_ISDIR(mode)) {\n-\t\tif (show_recursive(base->buf, base->len, pathname)) {\n-\t\t\t*retval = READ_TREE_RECURSIVE;\n-\t\t\tif (!(ls_options & LS_SHOW_TREES))\n-\t\t\t\treturn 1;\n-\t\t}\n-\t\t*type = OBJ_TREE;\n-\t}\n-\telse if (ls_options & LS_TREE_ONLY)\n-\t\treturn 1;\n-\treturn 0;\n-}\n-\n static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n@@ -196,6 +299,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tstruct object_id oid;\n \tstruct tree *tree;\n \tint i, full_tree = 0;\n+\tread_tree_fn_t fn = show_tree;\n \tconst struct option ls_tree_options[] = {\n \t\tOPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n \t\t\tLS_TREE_ONLY),\n@@ -218,6 +322,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n \t\t\t N_(\"list entire tree; not just current directory \"\n \t\t\t    \"(implies --full-name)\")),\n+\t\tOPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n+\t\t\t     N_(\"format to use for the output\"),\n+\t\t\t     PARSE_OPT_NONEG),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_END()\n \t};\n@@ -238,6 +345,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n \t\tls_options |= LS_SHOW_TREES;\n \n+\tif (format && cmdmode)\n+\t\tusage_msg_opt(\n+\t\t\t_(\"--format can't be combined with other format-altering options\"),\n+\t\t\tls_tree_usage, ls_tree_options);\n \tif (argc < 1)\n \t\tusage_with_options(ls_tree_usage, ls_tree_options);\n \tif (get_oid(argv[0], &oid))\n@@ -261,6 +372,18 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \ttree = parse_tree_indirect(&oid);\n \tif (!tree)\n \t\tdie(\"not a tree object\");\n-\treturn !!read_tree(the_repository, tree,\n-\t\t\t   &pathspec, show_tree, NULL);\n+\n+\t/*\n+\t * The generic show_tree_fmt() is slower than show_tree(), so\n+\t * take the fast path if possible.\n+\t */\n+\tif (format && (!strcmp(format, default_format) ||\n+\t\t\t\t   !strcmp(format, long_format) ||\n+\t\t\t\t   !strcmp(format, name_only_format) ||\n+\t\t\t\t   !strcmp(format, object_only_format)))\n+\t\tfn = show_tree;\n+\telse if (format)\n+\t\tfn = show_tree_fmt;\n+\n+\treturn !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n }\ndiff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh\nnew file mode 100755\nindex 0000000000..92b4d240e8\n--- /dev/null\n+++ b/t/t3105-ls-tree-format.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='ls-tree --format'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'ls-tree --format usage' '\n+\ttest_expect_code 129 git ls-tree --format=fmt -l &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-only &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-status &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --object-only\n+'\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\ttest_commit dir/sub-file &&\n+\ttest_commit top-file\n+'\n+\n+test_ls_tree_format () {\n+\tformat=$1 &&\n+\topts=$2 &&\n+\tshift 2 &&\n+\tgit ls-tree $opts -r HEAD >expect.raw &&\n+\tsed \"s/^/> /\" >expect <expect.raw &&\n+\tgit ls-tree --format=\"> $format\" -r HEAD >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'ls-tree --format=<default-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object)%x09%(file)\" \\\n+\t\t\"\"\n+'\n+\n+test_expect_success 'ls-tree --format=<long-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n+\t\t\"--long\"\n+'\n+\n+test_expect_success 'ls-tree --format=<name-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(file)\" \\\n+\t\t\"--name-only\"\n+'\n+\n+test_expect_success 'ls-tree --format=<object-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(object)\" \\\n+\t\t\"--object-only\"\n+'\n+\n+test_done\n-- \n2.33.0.rc1.1802.gbb1c3936fb.dirty\n\n"},{"id":"445369","messageId":"xmqqilv0iewz.fsf@gitster.g","threadId":"57172","inReplyTo":"296ebacafe4d40ffc5282aebb9fee972866c4ccc.1641043500.git.dyroneteng@gmail.com","subject":"Re: [PATCH v8 7/8] ls-tree.c: introduce struct \"shown_data\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-03T23:21:32Z","receivedAt":"2022-01-03T23:21:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> \"show_data\" is a struct that packages the necessary fields for\n\nIs that shown_data?\n\n> reusing. This commit is a front-loaded commit for support\n> \"--format\" argument and does not affect any existing functionality.\n\nWhat's a front-loaded commit?  Is that some joke around a washing\nmachine that I do not quite get, or something?\n\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n> ---\n>  builtin/ls-tree.c | 47 +++++++++++++++++++++++++++++------------------\n>  1 file changed, 29 insertions(+), 18 deletions(-)\n>\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index 85ca7358ba..009ffeb15d 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -34,6 +34,14 @@ static unsigned int shown_bits;\n>  #define SHOW_MODE (1 << 4)\n>  #define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n>  \n> +struct shown_data {\n> +\tunsigned mode;\n> +\tenum object_type type;\n> +\tconst struct object_id *oid;\n> +\tconst char *pathname;\n> +\tstruct strbuf *base;\n> +};\n> +\n>  static const  char * const ls_tree_usage[] = {\n>  \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n>  \tNULL\n> @@ -98,17 +106,15 @@ static int show_recursive(const char *base, size_t baselen,\n>  \treturn 0;\n>  }\n>  \n> -static int show_default(const struct object_id *oid, enum object_type type,\n> -\t\t\tconst char *pathname, unsigned mode,\n> -\t\t\tstruct strbuf *base)\n> +static int show_default(struct shown_data *data)\n>  {\n> -\tsize_t baselen = base->len;\n> +\tsize_t baselen = data->base->len;\n>  \n>  \tif (shown_bits & SHOW_SIZE) {\n>  \t\tchar size_text[24];\n> -\t\tif (type == OBJ_BLOB) {\n> +\t\tif (data->type == OBJ_BLOB) {\n>  \t\t\tunsigned long size;\n> -\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n> +\t\t\tif (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n>  \t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n>  \t\t\telse\n>  \t\t\t\txsnprintf(size_text, sizeof(size_text),\n> @@ -116,18 +122,18 @@ static int show_default(const struct object_id *oid, enum object_type type,\n>  \t\t} else {\n>  \t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n>  \t\t}\n> -\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n> -\t\tfind_unique_abbrev(oid, abbrev), size_text);\n> +\t\tprintf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n> +\t\tfind_unique_abbrev(data->oid, abbrev), size_text);\n>  \t} else {\n> -\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n> -\t\tfind_unique_abbrev(oid, abbrev));\n> +\t\tprintf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n> +\t\tfind_unique_abbrev(data->oid, abbrev));\n>  \t}\n> -\tbaselen = base->len;\n> -\tstrbuf_addstr(base, pathname);\n> -\twrite_name_quoted_relative(base->buf,\n> +\tbaselen = data->base->len;\n> +\tstrbuf_addstr(data->base, data->pathname);\n> +\twrite_name_quoted_relative(data->base->buf,\n>  \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n>  \t\t\t\t   line_termination);\n> -\tstrbuf_setlen(base, baselen);\n> +\tstrbuf_setlen(data->base, baselen);\n>  \treturn 1;\n>  }\n>  \n> @@ -154,11 +160,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n>  {\n>  \tint retval = 0;\n>  \tsize_t baselen;\n> -\tenum object_type type = OBJ_BLOB;\n> +\tstruct shown_data data = {\n> +\t\t.mode = mode,\n> +\t\t.type = OBJ_BLOB,\n> +\t\t.oid = oid,\n> +\t\t.pathname = pathname,\n> +\t\t.base = base,\n> +\t};\n>  \n> -\tif (show_tree_init(&type, base, pathname, mode, &retval))\n> +\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n>  \t\treturn retval;\n> -\n>  \tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n>  \t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n>  \t\treturn retval;\n> @@ -175,7 +186,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n>  \n>  \tif (!(shown_bits ^ SHOW_DEFAULT) ||\n>  \t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n> -\t\tshow_default(oid, type, pathname, mode, base);\n> +\t\tshow_default(&data);\n>  \n>  \treturn retval;\n>  }\n"},{"id":"445374","messageId":"xmqqh7akgutd.fsf@gitster.g","threadId":"57172","inReplyTo":"cb881183cb7179e3e7e983b7c9ffe115f845115c.1641043500.git.dyroneteng@gmail.com","subject":"Re: [PATCH v8 6/8] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-04T01:21:02Z","receivedAt":"2022-01-04T01:21:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> A simple refactoring was done to the \"show_tree\" function, intead by\n> using bitwise operations to recognize the format for printing to\n> stdout. The reason for doing this is that we don't want to increase\n> the readability difficulty with the addition of \"-object-only\",\n> making this part of the logic easier to read and expand.\n\nThe resulting code looks unnecessarily complex and brittle; some\nSHOW_FOO mean SHOW_FOO_ONLY_AND_NOTHING_ELSE while other SHOW_BAR\nmeans SHOW_BAR_BUT_WE_MAY_SHOW_OTHER_THINGS_IN_LATER_PART, and the\ndistinction is not clear from their names (which means it is hard\nto later extend and enhance the behaviour of the code).\n\n> +\tif (!(shown_bits ^ SHOW_FILE_NAME)) {\n\nIs the use of XOR operator significant here?\n\nI.e. \"if (shown_bits & SHOW_FILE_NAME)\" would have been a much more\nnatural way to guard \"this is a block that shows the file name\",\nthan \"the result MUST BE all bits off if we flip SHOW_FILE_NAME bit\noff\".  If various SHOW_FOO bits are meant to be mutually exclusive,\nthen \"if ((shown_bits & SHOW_FILE_NAME) == SHOW_FILE_NAME)\" would\nalso make sense, but as I said upfront, it is unclear to me if\nshown_bits are meant to be a collection of \"this bit means this\nfield is shown (and it implies nothing else)\", so I dunno.\n"},{"id":"445377","messageId":"CADMgQSTBp9ykxtPUHgA1LCQQ1zCmfsUMLGa=ccyqjci9VXVsRg@mail.gmail.com","threadId":"57172","inReplyTo":"xmqqilv0iewz.fsf@gitster.g","subject":"Re: [PATCH v8 7/8] ls-tree.c: introduce struct \"shown_data\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-04T02:02:24Z","receivedAt":"2022-01-04T02:02:38Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":">> \"show_data\" is a struct that packages the necessary fields for\n\n>Is that shown_data?\n\nYes, sorry for that mislead.\n\n>> reusing. This commit is a front-loaded commit for support\n>> \"--format\" argument and does not affect any existing functionality.\n\n> What's a front-loaded commit?  Is that some joke around a washing\n> machine that I do not quite get, or something?\n\nI know this word recently (from an English training institute), feel\nsorry if it confuses you, I think it means pre-prepared.\n\nI found two demo sentences from:\nhttps://dictionary.cambridge.org/us/dictionary/english/front-load\n\nI'm not sure that I used it right or wrong, if it's the wrong way or\njust make others feel like in haze, I will use “pre-prepared”\npreferentially\nnext time.\n\nThanks.\n\nJunio C Hamano <gitster@pobox.com> 于2022年1月4日周二 07:21写道：\n>\n> Teng Long <dyroneteng@gmail.com> writes:\n>\n> > \"show_data\" is a struct that packages the necessary fields for\n>\n> Is that shown_data?\n>\n> > reusing. This commit is a front-loaded commit for support\n> > \"--format\" argument and does not affect any existing functionality.\n>\n> What's a front-loaded commit?  Is that some joke around a washing\n> machine that I do not quite get, or something?\n>\n> > Signed-off-by: Teng Long <dyroneteng@gmail.com>\n> > ---\n> >  builtin/ls-tree.c | 47 +++++++++++++++++++++++++++++------------------\n> >  1 file changed, 29 insertions(+), 18 deletions(-)\n> >\n> > diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> > index 85ca7358ba..009ffeb15d 100644\n> > --- a/builtin/ls-tree.c\n> > +++ b/builtin/ls-tree.c\n> > @@ -34,6 +34,14 @@ static unsigned int shown_bits;\n> >  #define SHOW_MODE (1 << 4)\n> >  #define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n> >\n> > +struct shown_data {\n> > +     unsigned mode;\n> > +     enum object_type type;\n> > +     const struct object_id *oid;\n> > +     const char *pathname;\n> > +     struct strbuf *base;\n> > +};\n> > +\n> >  static const  char * const ls_tree_usage[] = {\n> >       N_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n> >       NULL\n> > @@ -98,17 +106,15 @@ static int show_recursive(const char *base, size_t baselen,\n> >       return 0;\n> >  }\n> >\n> > -static int show_default(const struct object_id *oid, enum object_type type,\n> > -                     const char *pathname, unsigned mode,\n> > -                     struct strbuf *base)\n> > +static int show_default(struct shown_data *data)\n> >  {\n> > -     size_t baselen = base->len;\n> > +     size_t baselen = data->base->len;\n> >\n> >       if (shown_bits & SHOW_SIZE) {\n> >               char size_text[24];\n> > -             if (type == OBJ_BLOB) {\n> > +             if (data->type == OBJ_BLOB) {\n> >                       unsigned long size;\n> > -                     if (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n> > +                     if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n> >                               xsnprintf(size_text, sizeof(size_text), \"BAD\");\n> >                       else\n> >                               xsnprintf(size_text, sizeof(size_text),\n> > @@ -116,18 +122,18 @@ static int show_default(const struct object_id *oid, enum object_type type,\n> >               } else {\n> >                       xsnprintf(size_text, sizeof(size_text), \"-\");\n> >               }\n> > -             printf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n> > -             find_unique_abbrev(oid, abbrev), size_text);\n> > +             printf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n> > +             find_unique_abbrev(data->oid, abbrev), size_text);\n> >       } else {\n> > -             printf(\"%06o %s %s\\t\", mode, type_name(type),\n> > -             find_unique_abbrev(oid, abbrev));\n> > +             printf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n> > +             find_unique_abbrev(data->oid, abbrev));\n> >       }\n> > -     baselen = base->len;\n> > -     strbuf_addstr(base, pathname);\n> > -     write_name_quoted_relative(base->buf,\n> > +     baselen = data->base->len;\n> > +     strbuf_addstr(data->base, data->pathname);\n> > +     write_name_quoted_relative(data->base->buf,\n> >                                  chomp_prefix ? ls_tree_prefix : NULL, stdout,\n> >                                  line_termination);\n> > -     strbuf_setlen(base, baselen);\n> > +     strbuf_setlen(data->base, baselen);\n> >       return 1;\n> >  }\n> >\n> > @@ -154,11 +160,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n> >  {\n> >       int retval = 0;\n> >       size_t baselen;\n> > -     enum object_type type = OBJ_BLOB;\n> > +     struct shown_data data = {\n> > +             .mode = mode,\n> > +             .type = OBJ_BLOB,\n> > +             .oid = oid,\n> > +             .pathname = pathname,\n> > +             .base = base,\n> > +     };\n> >\n> > -     if (show_tree_init(&type, base, pathname, mode, &retval))\n> > +     if (show_tree_init(&data.type, base, pathname, mode, &retval))\n> >               return retval;\n> > -\n> >       if (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n> >               printf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n> >               return retval;\n> > @@ -175,7 +186,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n> >\n> >       if (!(shown_bits ^ SHOW_DEFAULT) ||\n> >           !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n> > -             show_default(oid, type, pathname, mode, base);\n> > +             show_default(&data);\n> >\n> >       return retval;\n> >  }\n"},{"id":"445378","messageId":"xmqqwnjgfe4t.fsf@gitster.g","threadId":"57172","inReplyTo":"d77c895a4b9d5cb86443279c0156196254895ef4.1641043500.git.dyroneteng@gmail.com","subject":"Re: [PATCH v8 5/8] ls-tree: split up the \"init\" part of show_tree()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-04T02:06:42Z","receivedAt":"2022-01-04T02:06:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n\n> -static int show_tree(const struct object_id *oid, struct strbuf *base,\n> -\t\tconst char *pathname, unsigned mode, void *context)\n> +static int show_tree_init(enum object_type *type, struct strbuf *base,\n> +\t\t\t  const char *pathname, unsigned mode, int *retval)\n\nDon't we need some comment that explains what the function does,\nwhat its return value means, etc.?\n\n>  {\n> -\tint retval = 0;\n> -\tsize_t baselen;\n> -\tenum object_type type = OBJ_BLOB;\n> -\n>  \tif (S_ISGITLINK(mode)) {\n> -\t\ttype = OBJ_COMMIT;\n> +\t\t*type = OBJ_COMMIT;\n>  \t} else if (S_ISDIR(mode)) {\n>  \t\tif (show_recursive(base->buf, base->len, pathname)) {\n> -\t\t\tretval = READ_TREE_RECURSIVE;\n> +\t\t\t*retval = READ_TREE_RECURSIVE;\n>  \t\t\tif (!(ls_options & LS_SHOW_TREES))\n> -\t\t\t\treturn retval;\n> +\t\t\t\treturn 1;\n>  \t\t}\n> -\t\ttype = OBJ_TREE;\n> +\t\t*type = OBJ_TREE;\n>  \t}\n>  \telse if (ls_options & LS_TREE_ONLY)\n> -\t\treturn 0;\n> +\t\treturn 1;\n> +\treturn 0;\n> +}\n\nIt seems that even from its returned value, the caller cannot tell\nif *retval was set by the function or not.  Perhaps it makes a much\ncleaner API to assign 0 to *retval at the beginning of this function,\njust like the original did so anyway? ...\n\n> +static int show_tree(const struct object_id *oid, struct strbuf *base,\n> +\t\tconst char *pathname, unsigned mode, void *context)\n> +{\n> +\tint retval = 0;\n\n... It would mean we can lose this initialization.\n\n> +\tsize_t baselen;\n> +\tenum object_type type = OBJ_BLOB;\n> +\n> +\tif (show_tree_init(&type, base, pathname, mode, &retval))\n> +\t\treturn retval;\n\n>  \n>  \tif (!(ls_options & LS_NAME_ONLY)) {\n>  \t\tif (ls_options & LS_SHOW_SIZE) {\n"},{"id":"445390","messageId":"20220104072951.10153-1-dyroneteng@gmail.com","threadId":"57172","inReplyTo":"xmqqh7akgutd.fsf@gitster.g","subject":"Re: [PATCH v8 6/8] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-04T07:29:51Z","receivedAt":"2022-01-04T07:30:12Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> The resulting code looks unnecessarily complex and brittle; some\n> SHOW_FOO mean SHOW_FOO_ONLY_AND_NOTHING_ELSE while other SHOW_BAR\n> means SHOW_BAR_BUT_WE_MAY_SHOW_OTHER_THINGS_IN_LATER_PART, and the\n> distinction is not clear from their names (which means it is hard\n> to later extend and enhance the behaviour of the code).\n\nI agree with you that the relevant code is not very clear, So I\nthink I will take these steps:\n\n1. Rename \"shown_bits\" -> \"shown_fields\"\n\n   essentially we want to show the fields but not bits to user that\n   may firstly solve the unclear nameing problem for \"shown_bits\"\n   itself.\n\n2. Rename related macro definitions of \"shown_fields by :\n\n\n\t   SHOW_FILE_NAME -> FILE_NAME_FIELD\n    \t   SHOW_SIZE -> SIZE_FIELD\n    \t   SHOW_OBJECT_NAME -> OBJECT_NAME_FIELD\n    \t   SHOW_TYPE -> TYPE_FIELD\n    \t   SHOW_DEFAULT -> DEFAULT_FIELDS\n\n    I think the confusion comes from \" SHOW_FOO_ONLY_AND_NOTHING_ELSE\"\n    and \" SHOW_BAR_BUT_WE_MAY_SHOW_OTHER_THINGS_IN_LATER_PART\" is\n    because some macros's is named by mixed the \"flags\" and \"the\n    operation of flags\" together. \n\n    So with renamings, we try to unify these definitions meaning,\n    they are just used for defining a \"field\" with a specified\n    non-repetitive bits.\n    \n    After that, you can show many \"fields\" by combining any of \"fields\",\n    such as what the builtin \"DEFAULT_FIELDS\" does, it shows all the\n    fields but except the \"size\" field.\n\n    By far, the \"field(s)\" only means the definition themselfs, and\n    no business with \"which one/ones\" or  \"how\" to shown.\n\n3. Decide \"which\" fields need to show\n\n   The definition of \"WHICH_FIELDS_TO_SHOWN\" is by \"shown_fields\",\n   it's used for parse from the options and compute it's value\n   by function \"parse_shown_fields()\".\n\n   Actually, the function already represent what work it do,\n   but the problem is I didn't notice to rename \"show_bits\"\n   to \"show_fields\" before. This may bring some confusion,\n   because we are not going to show the bits but the field(s).\n   So, I will do the <STEP.1>.\n\n4. Decide \"How\" fields to be shown\n\n    Now we have already know the field(s) we care about, next\n    step is to show the fields.\n\n> > +\tif (!(shown_bits ^ SHOW_FILE_NAME)) {\n\n> Is the use of XOR operator significant here?\n> \n> I.e. \"if (shown_bits & SHOW_FILE_NAME)\" would have been a much more\n> natural way to guard \"this is a block that shows the file name\",\n> than \"the result MUST BE all bits off if we flip SHOW_FILE_NAME bit\n> off\".  If various SHOW_FOO bits are meant to be mutually exclusive,\n> then \"if ((shown_bits & SHOW_FILE_NAME) == SHOW_FILE_NAME)\" would\n> also make sense, but as I said upfront, it is unclear to me if\n> shown_bits are meant to be a collection of \"this bit means this\n> field is shown (and it implies nothing else)\", so I dunno.\n\n    Not significant. Both work and It's all right for me. Your\n    readability is better, and now I know how to handle this\n    situation better.\n\n    E.g, if we only want to show a \"filename\" field\n    (with `--name-only`), we will use a way like\n    \"if ((shown_fields & FILE_NAME_FIELD) == FILE_NAME_FIELD)\"\n    to judge this situation.\n\n    And if we want to show fields in a builtin way\n    (as described in 'git-ls-tree.txt', default output format\n    is compatible with what `--index-info --stdin` of\n    'git update-index' expects.), we will use \"DEFAULT_FIELDS\"\n    instead.\n\n\nI am not sure if this solves the problem you are considering as I\nmay misunderstand, I can quickly finish this new patch and we can\nlook at it then。\n\nThanks.\n\n"},{"id":"445391","messageId":"CADMgQSRXJ0F0=egA_PcEz0NzXZMVo1nLv1N8dGyoRhdDsZPc9w@mail.gmail.com","threadId":"57172","inReplyTo":"xmqqwnjgfe4t.fsf@gitster.g","subject":"Re: [PATCH v8 5/8] ls-tree: split up the \"init\" part of show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-04T09:49:38Z","receivedAt":"2022-01-04T09:49:52Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Junio C Hamano writes:\n\n> Don't we need some comment that explains what the function does,\n> what its return value means, etc.?\n>\n> It seems that even from its returned value, the caller cannot tell\n> if *retval was set by the function or not.  Perhaps it makes a much\n> cleaner API to assign 0 to *retval at the beginning of this function,\n> just like the original did so anyway? ...\n\nOh, sorry for that, I did not notice the \"retval\" before because the\nnaming is unimpressive and the tests were passed, though...\n\nI just looked at it, actually, it's important, not as what it is named, it\naffects the result. The \"retval\" actually determine whether  to\nCONTINUE reading the current \"tree\" or BREAK into the next\none [1] .\n\nSo, I think this commit should be modified despite the tests are passed,\nfirstly, I want to rename \"retval\" to another name that makes sense,\nthen just make the relevant \"if\" and \"return\" logic more clearly with the\nnewname, finally, it'll be consistent with the definitions in \"read_tree_at()\"\nat \"tree.c\" [1].\n\n\n[1] https://github.com/dyrone/git/blob/master/tree.c#L40\n\nThanks.\n\nJunio C Hamano <gitster@pobox.com> 于2022年1月4日周二 10:06写道：\n>\n> Teng Long <dyroneteng@gmail.com> writes:\n>\n>\n> > -static int show_tree(const struct object_id *oid, struct strbuf *base,\n> > -             const char *pathname, unsigned mode, void *context)\n> > +static int show_tree_init(enum object_type *type, struct strbuf *base,\n> > +                       const char *pathname, unsigned mode, int *retval)\n>\n> Don't we need some comment that explains what the function does,\n> what its return value means, etc.?\n>\n> >  {\n> > -     int retval = 0;\n> > -     size_t baselen;\n> > -     enum object_type type = OBJ_BLOB;\n> > -\n> >       if (S_ISGITLINK(mode)) {\n> > -             type = OBJ_COMMIT;\n> > +             *type = OBJ_COMMIT;\n> >       } else if (S_ISDIR(mode)) {\n> >               if (show_recursive(base->buf, base->len, pathname)) {\n> > -                     retval = READ_TREE_RECURSIVE;\n> > +                     *retval = READ_TREE_RECURSIVE;\n> >                       if (!(ls_options & LS_SHOW_TREES))\n> > -                             return retval;\n> > +                             return 1;\n> >               }\n> > -             type = OBJ_TREE;\n> > +             *type = OBJ_TREE;\n> >       }\n> >       else if (ls_options & LS_TREE_ONLY)\n> > -             return 0;\n> > +             return 1;\n> > +     return 0;\n> > +}\n>\n> It seems that even from its returned value, the caller cannot tell\n> if *retval was set by the function or not.  Perhaps it makes a much\n> cleaner API to assign 0 to *retval at the beginning of this function,\n> just like the original did so anyway? ...\n>\n> > +static int show_tree(const struct object_id *oid, struct strbuf *base,\n> > +             const char *pathname, unsigned mode, void *context)\n> > +{\n> > +     int retval = 0;\n>\n> ... It would mean we can lose this initialization.\n>\n> > +     size_t baselen;\n> > +     enum object_type type = OBJ_BLOB;\n> > +\n> > +     if (show_tree_init(&type, base, pathname, mode, &retval))\n> > +             return retval;\n>\n> >\n> >       if (!(ls_options & LS_NAME_ONLY)) {\n> >               if (ls_options & LS_SHOW_SIZE) {\n"},{"id":"445417","messageId":"nycvar.QRO.7.76.6.2201041533540.7076@tvgsbejvaqbjf.bet","threadId":"57172","inReplyTo":"e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-04T14:38:01Z","receivedAt":"2022-01-04T14:38:37Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Teng,\n\nOn Sat, 1 Jan 2022, Teng Long wrote:\n\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index 009ffeb15d..6e3e5a4d06 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -56,23 +56,75 @@ enum {\n>\n>  static int cmdmode = MODE_UNSPECIFIED;\n>\n> -static int parse_shown_fields(void)\n> +static const char *format;\n> +static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n> +static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n> +static const char *name_only_format = \"%(file)\";\n> +static const char *object_only_format = \"%(object)\";\n> +\n> +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n> +\t\t\t      const enum object_type type, unsigned int padded)\n>  {\n> -\tif (cmdmode == MODE_NAME_ONLY) {\n> -\t\tshown_bits = SHOW_FILE_NAME;\n> -\t\treturn 0;\n> +\tif (type == OBJ_BLOB) {\n> +\t\tunsigned long size;\n> +\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n> +\t\t\tdie(_(\"could not get object info about '%s'\"),\n> +\t\t\t    oid_to_hex(oid));\n> +\t\tif (padded)\n> +\t\t\tstrbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n> +\t\telse\n> +\t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n> +\t} else if (padded) {\n> +\t\tstrbuf_addf(line, \"%7s\", \"-\");\n\nThis, along with two other similar instances, triggers the\n`static-analysis` job in the CI failure of `seen`. The suggested diff is:\n\n\n-- snip --\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 6e3e5a4d0634..8301d1a15f9a 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -75,7 +75,7 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n \t\telse\n \t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n \t} else if (padded) {\n-\t\tstrbuf_addf(line, \"%7s\", \"-\");\n+\t\tstrbuf_addstr(line, \"-\");\n \t} else {\n \t\tstrbuf_addstr(line, \"-\");\n \t}\n@@ -110,7 +110,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n \t} else if (skip_prefix(start, \"(size)\", &p)) {\n \t\texpand_objectsize(line, data->oid, data->type, 0);\n \t} else if (skip_prefix(start, \"(object)\", &p)) {\n-\t\tstrbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n+\t\tstrbuf_add_unique_abbrev(line, data->oid, abbrev);\n \t} else if (skip_prefix(start, \"(file)\", &p)) {\n \t\tconst char *name = data->base->buf;\n \t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n@@ -119,7 +119,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n \t\tstrbuf_addstr(data->base, data->pathname);\n \t\tname = relative_path(data->base->buf, prefix, &sb);\n \t\tquote_c_style(name, &quoted, NULL, 0);\n-\t\tstrbuf_addstr(line, quoted.buf);\n+\t\tstrbuf_addbuf(line, &quoted);\n \t} else {\n \t\terrlen = (unsigned long)len;\n \t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n-- snap --\n\nBut I think that the first hunk indicates a deeper issue, as `%7s`\nprobably meant to pad the dash to seven dashes (which that format won't\naccomplish, but `strbuf_addchars()` would)?\n\nCiao,\nDscho\n\n> +\t} else {\n> +\t\tstrbuf_addstr(line, \"-\");\n>  \t}\n> -\tif (cmdmode == MODE_OBJECT_ONLY) {\n> -\t\tshown_bits = SHOW_OBJECT_NAME;\n> -\t\treturn 0;\n> +}\n> +\n> +static size_t expand_show_tree(struct strbuf *line, const char *start,\n> +\t\t\t       void *context)\n> +{\n> +\tstruct shown_data *data = context;\n> +\tconst char *end;\n> +\tconst char *p;\n> +\tunsigned int errlen;\n> +\tsize_t len;\n> +\tlen = strbuf_expand_literal_cb(line, start, NULL);\n> +\tif (len)\n> +\t\treturn len;\n> +\n> +\tif (*start != '(')\n> +\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n> +\n> +\tend = strchr(start + 1, ')');\n> +\tif (!end)\n> +\t\tdie(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n> +\n> +\tlen = end - start + 1;\n> +\tif (skip_prefix(start, \"(mode)\", &p)) {\n> +\t\tstrbuf_addf(line, \"%06o\", data->mode);\n> +\t} else if (skip_prefix(start, \"(type)\", &p)) {\n> +\t\tstrbuf_addstr(line, type_name(data->type));\n> +\t} else if (skip_prefix(start, \"(size:padded)\", &p)) {\n> +\t\texpand_objectsize(line, data->oid, data->type, 1);\n> +\t} else if (skip_prefix(start, \"(size)\", &p)) {\n> +\t\texpand_objectsize(line, data->oid, data->type, 0);\n> +\t} else if (skip_prefix(start, \"(object)\", &p)) {\n> +\t\tstrbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n> +\t} else if (skip_prefix(start, \"(file)\", &p)) {\n> +\t\tconst char *name = data->base->buf;\n> +\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n> +\t\tstruct strbuf quoted = STRBUF_INIT;\n> +\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\tstrbuf_addstr(data->base, data->pathname);\n> +\t\tname = relative_path(data->base->buf, prefix, &sb);\n> +\t\tquote_c_style(name, &quoted, NULL, 0);\n> +\t\tstrbuf_addstr(line, quoted.buf);\n> +\t} else {\n> +\t\terrlen = (unsigned long)len;\n> +\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n>  \t}\n> -\tif (!ls_options || (ls_options & LS_RECURSIVE)\n> -\t    || (ls_options & LS_SHOW_TREES)\n> -\t    || (ls_options & LS_TREE_ONLY))\n> -\t\tshown_bits = SHOW_DEFAULT;\n> -\tif (cmdmode == MODE_LONG)\n> -\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n> -\treturn 1;\n> +\treturn len;\n>  }\n>\n>  static int show_recursive(const char *base, size_t baselen,\n> @@ -106,6 +158,75 @@ static int show_recursive(const char *base, size_t baselen,\n>  \treturn 0;\n>  }\n>\n> +static int show_tree_init(enum object_type *type, struct strbuf *base,\n> +\t\t\t  const char *pathname, unsigned mode, int *retval)\n> +{\n> +\tif (S_ISGITLINK(mode)) {\n> +\t\t*type = OBJ_COMMIT;\n> +\t} else if (S_ISDIR(mode)) {\n> +\t\tif (show_recursive(base->buf, base->len, pathname)) {\n> +\t\t\t*retval = READ_TREE_RECURSIVE;\n> +\t\t\tif (!(ls_options & LS_SHOW_TREES))\n> +\t\t\t\treturn 1;\n> +\t\t}\n> +\t\t*type = OBJ_TREE;\n> +\t}\n> +\telse if (ls_options & LS_TREE_ONLY)\n> +\t\treturn 1;\n> +\treturn 0;\n> +}\n> +\n> +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n> +\t\t\t const char *pathname, unsigned mode, void *context)\n> +{\n> +\tsize_t baselen;\n> +\tint retval = 0;\n> +\tstruct strbuf line = STRBUF_INIT;\n> +\tstruct shown_data data = {\n> +\t\t.mode = mode,\n> +\t\t.type = OBJ_BLOB,\n> +\t\t.oid = oid,\n> +\t\t.pathname = pathname,\n> +\t\t.base = base,\n> +\t};\n> +\n> +\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n> +\t\treturn retval;\n> +\n> +\tbaselen = base->len;\n> +\tstrbuf_expand(&line, format, expand_show_tree, &data);\n> +\tstrbuf_addch(&line, line_termination);\n> +\tfwrite(line.buf, line.len, 1, stdout);\n> +\tstrbuf_setlen(base, baselen);\n> +\treturn retval;\n> +}\n> +\n> +static int parse_shown_fields(void)\n> +{\n> +\tif (cmdmode == MODE_NAME_ONLY ||\n> +\t    (format && !strcmp(format, name_only_format))) {\n> +\t\tshown_bits = SHOW_FILE_NAME;\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (cmdmode == MODE_OBJECT_ONLY ||\n> +\t    (format && !strcmp(format, object_only_format))) {\n> +\t\tshown_bits = SHOW_OBJECT_NAME;\n> +\t\treturn 1;\n> +\t}\n> +\n> +\tif (!ls_options || (ls_options & LS_RECURSIVE)\n> +\t    || (ls_options & LS_SHOW_TREES)\n> +\t    || (ls_options & LS_TREE_ONLY)\n> +\t\t|| (format && !strcmp(format, default_format)))\n> +\t\tshown_bits = SHOW_DEFAULT;\n> +\n> +\tif (cmdmode == MODE_LONG ||\n> +\t\t(format && !strcmp(format, long_format)))\n> +\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n> +\treturn 1;\n> +}\n> +\n>  static int show_default(struct shown_data *data)\n>  {\n>  \tsize_t baselen = data->base->len;\n> @@ -137,24 +258,6 @@ static int show_default(struct shown_data *data)\n>  \treturn 1;\n>  }\n>\n> -static int show_tree_init(enum object_type *type, struct strbuf *base,\n> -\t\t\t  const char *pathname, unsigned mode, int *retval)\n> -{\n> -\tif (S_ISGITLINK(mode)) {\n> -\t\t*type = OBJ_COMMIT;\n> -\t} else if (S_ISDIR(mode)) {\n> -\t\tif (show_recursive(base->buf, base->len, pathname)) {\n> -\t\t\t*retval = READ_TREE_RECURSIVE;\n> -\t\t\tif (!(ls_options & LS_SHOW_TREES))\n> -\t\t\t\treturn 1;\n> -\t\t}\n> -\t\t*type = OBJ_TREE;\n> -\t}\n> -\telse if (ls_options & LS_TREE_ONLY)\n> -\t\treturn 1;\n> -\treturn 0;\n> -}\n> -\n>  static int show_tree(const struct object_id *oid, struct strbuf *base,\n>  \t\tconst char *pathname, unsigned mode, void *context)\n>  {\n> @@ -196,6 +299,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n>  \tstruct object_id oid;\n>  \tstruct tree *tree;\n>  \tint i, full_tree = 0;\n> +\tread_tree_fn_t fn = show_tree;\n>  \tconst struct option ls_tree_options[] = {\n>  \t\tOPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n>  \t\t\tLS_TREE_ONLY),\n> @@ -218,6 +322,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n>  \t\t\t N_(\"list entire tree; not just current directory \"\n>  \t\t\t    \"(implies --full-name)\")),\n> +\t\tOPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n> +\t\t\t     N_(\"format to use for the output\"),\n> +\t\t\t     PARSE_OPT_NONEG),\n>  \t\tOPT__ABBREV(&abbrev),\n>  \t\tOPT_END()\n>  \t};\n> @@ -238,6 +345,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n>  \t    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n>  \t\tls_options |= LS_SHOW_TREES;\n>\n> +\tif (format && cmdmode)\n> +\t\tusage_msg_opt(\n> +\t\t\t_(\"--format can't be combined with other format-altering options\"),\n> +\t\t\tls_tree_usage, ls_tree_options);\n>  \tif (argc < 1)\n>  \t\tusage_with_options(ls_tree_usage, ls_tree_options);\n>  \tif (get_oid(argv[0], &oid))\n> @@ -261,6 +372,18 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n>  \ttree = parse_tree_indirect(&oid);\n>  \tif (!tree)\n>  \t\tdie(\"not a tree object\");\n> -\treturn !!read_tree(the_repository, tree,\n> -\t\t\t   &pathspec, show_tree, NULL);\n> +\n> +\t/*\n> +\t * The generic show_tree_fmt() is slower than show_tree(), so\n> +\t * take the fast path if possible.\n> +\t */\n> +\tif (format && (!strcmp(format, default_format) ||\n> +\t\t\t\t   !strcmp(format, long_format) ||\n> +\t\t\t\t   !strcmp(format, name_only_format) ||\n> +\t\t\t\t   !strcmp(format, object_only_format)))\n> +\t\tfn = show_tree;\n> +\telse if (format)\n> +\t\tfn = show_tree_fmt;\n> +\n> +\treturn !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n>  }\n> diff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh\n> new file mode 100755\n> index 0000000000..92b4d240e8\n> --- /dev/null\n> +++ b/t/t3105-ls-tree-format.sh\n> @@ -0,0 +1,55 @@\n> +#!/bin/sh\n> +\n> +test_description='ls-tree --format'\n> +\n> +TEST_PASSES_SANITIZE_LEAK=true\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'ls-tree --format usage' '\n> +\ttest_expect_code 129 git ls-tree --format=fmt -l &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --name-only &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --name-status &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --object-only\n> +'\n> +\n> +test_expect_success 'setup' '\n> +\tmkdir dir &&\n> +\ttest_commit dir/sub-file &&\n> +\ttest_commit top-file\n> +'\n> +\n> +test_ls_tree_format () {\n> +\tformat=$1 &&\n> +\topts=$2 &&\n> +\tshift 2 &&\n> +\tgit ls-tree $opts -r HEAD >expect.raw &&\n> +\tsed \"s/^/> /\" >expect <expect.raw &&\n> +\tgit ls-tree --format=\"> $format\" -r HEAD >actual &&\n> +\ttest_cmp expect actual\n> +}\n> +\n> +test_expect_success 'ls-tree --format=<default-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(mode) %(type) %(object)%x09%(file)\" \\\n> +\t\t\"\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<long-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n> +\t\t\"--long\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<name-only-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(file)\" \\\n> +\t\t\"--name-only\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<object-only-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(object)\" \\\n> +\t\t\"--object-only\"\n> +'\n> +\n> +test_done\n> --\n> 2.33.0.rc1.1802.gbb1c3936fb.dirty\n>\n>\n>\n"},{"id":"445423","messageId":"nycvar.QRO.7.76.6.2201041615560.7076@tvgsbejvaqbjf.bet","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201041533540.7076@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-04T15:17:28Z","receivedAt":"2022-01-04T15:17:52Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Teng,\n\nOn Tue, 4 Jan 2022, Johannes Schindelin wrote:\n\n> -- snip --\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index 6e3e5a4d0634..8301d1a15f9a 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -75,7 +75,7 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n>  \t\telse\n>  \t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n>  \t} else if (padded) {\n> -\t\tstrbuf_addf(line, \"%7s\", \"-\");\n> +\t\tstrbuf_addstr(line, \"-\");\n>  \t} else {\n>  \t\tstrbuf_addstr(line, \"-\");\n>  \t}\n> @@ -110,7 +110,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n>  \t} else if (skip_prefix(start, \"(size)\", &p)) {\n>  \t\texpand_objectsize(line, data->oid, data->type, 0);\n>  \t} else if (skip_prefix(start, \"(object)\", &p)) {\n> -\t\tstrbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n> +\t\tstrbuf_add_unique_abbrev(line, data->oid, abbrev);\n>  \t} else if (skip_prefix(start, \"(file)\", &p)) {\n>  \t\tconst char *name = data->base->buf;\n>  \t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n> @@ -119,7 +119,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n>  \t\tstrbuf_addstr(data->base, data->pathname);\n>  \t\tname = relative_path(data->base->buf, prefix, &sb);\n>  \t\tquote_c_style(name, &quoted, NULL, 0);\n> -\t\tstrbuf_addstr(line, quoted.buf);\n> +\t\tstrbuf_addbuf(line, &quoted);\n>  \t} else {\n>  \t\terrlen = (unsigned long)len;\n>  \t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n> -- snap --\n\nIn addition to that, you need these to quiet down the `linux-leaks` job of\nour CI build, which is also failing with your patches:\n\n-- snipsnap --\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 8301d1a15f9a..0dc0327e4785 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -120,6 +120,8 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n \t\tname = relative_path(data->base->buf, prefix, &sb);\n \t\tquote_c_style(name, &quoted, NULL, 0);\n \t\tstrbuf_addbuf(line, &quoted);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&quoted);\n \t} else {\n \t\terrlen = (unsigned long)len;\n \t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n@@ -197,6 +199,7 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n \tstrbuf_expand(&line, format, expand_show_tree, &data);\n \tstrbuf_addch(&line, line_termination);\n \tfwrite(line.buf, line.len, 1, stdout);\n+\tstrbuf_release(&line);\n \tstrbuf_setlen(base, baselen);\n \treturn retval;\n }\n"},{"id":"445510","messageId":"CADMgQSQ8+2pMdabbyCqZo4F8SRp-VH6wjD6tEx83m6FQhiwUHw@mail.gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201041615560.7076@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-05T09:40:47Z","receivedAt":"2022-01-05T09:41:05Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> In addition to that, you need these to quiet down the `linux-leaks` job of\n> our CI build, which is also failing with your patches:\n\nThanks.\nI will fix it in next patch.\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> 于2022年1月4日周二 23:17写道：\n>\n> Hi Teng,\n>\n> On Tue, 4 Jan 2022, Johannes Schindelin wrote:\n>\n> > -- snip --\n> > diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> > index 6e3e5a4d0634..8301d1a15f9a 100644\n> > --- a/builtin/ls-tree.c\n> > +++ b/builtin/ls-tree.c\n> > @@ -75,7 +75,7 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n> >               else\n> >                       strbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n> >       } else if (padded) {\n> > -             strbuf_addf(line, \"%7s\", \"-\");\n> > +             strbuf_addstr(line, \"-\");\n> >       } else {\n> >               strbuf_addstr(line, \"-\");\n> >       }\n> > @@ -110,7 +110,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n> >       } else if (skip_prefix(start, \"(size)\", &p)) {\n> >               expand_objectsize(line, data->oid, data->type, 0);\n> >       } else if (skip_prefix(start, \"(object)\", &p)) {\n> > -             strbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n> > +             strbuf_add_unique_abbrev(line, data->oid, abbrev);\n> >       } else if (skip_prefix(start, \"(file)\", &p)) {\n> >               const char *name = data->base->buf;\n> >               const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n> > @@ -119,7 +119,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n> >               strbuf_addstr(data->base, data->pathname);\n> >               name = relative_path(data->base->buf, prefix, &sb);\n> >               quote_c_style(name, &quoted, NULL, 0);\n> > -             strbuf_addstr(line, quoted.buf);\n> > +             strbuf_addbuf(line, &quoted);\n> >       } else {\n> >               errlen = (unsigned long)len;\n> >               die(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n> > -- snap --\n>\n> In addition to that, you need these to quiet down the `linux-leaks` job of\n> our CI build, which is also failing with your patches:\n>\n> -- snipsnap --\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index 8301d1a15f9a..0dc0327e4785 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -120,6 +120,8 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n>                 name = relative_path(data->base->buf, prefix, &sb);\n>                 quote_c_style(name, &quoted, NULL, 0);\n>                 strbuf_addbuf(line, &quoted);\n> +               strbuf_release(&sb);\n> +               strbuf_release(&quoted);\n>         } else {\n>                 errlen = (unsigned long)len;\n>                 die(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n> @@ -197,6 +199,7 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n>         strbuf_expand(&line, format, expand_show_tree, &data);\n>         strbuf_addch(&line, line_termination);\n>         fwrite(line.buf, line.len, 1, stdout);\n> +       strbuf_release(&line);\n>         strbuf_setlen(base, baselen);\n>         return retval;\n>  }\n"},{"id":"445511","messageId":"CADMgQSSjoxqzBDyGXiNC4wHqYGK7z4O0SG0zai85D-DtDHem=w@mail.gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201041533540.7076@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-05T09:58:41Z","receivedAt":"2022-01-05T09:59:03Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> This, along with two other similar instances, triggers the\n> `static-analysis` job in the CI failure of `seen`. The suggested diff is:\n\nThe second and third I will optimize in the next patch.\n\nThe first one. Actually I am a little puzzled from this :\n\n> -               strbuf_addf(line, \"%7s\", \"-\");\n> +               strbuf_addstr(line, \"-\");\n\n> But I think that the first hunk indicates a deeper issue, as `%7s`\n> probably meant to pad the dash to seven dashes (which that format won't\n> accomplish, but `strbuf_addchars()` would)?\n\n\"strbuf_addf(line, \"%7s\", \"-\");\" here is used to align the columns\nwith a width of\nseven chars, not repeat one DASH to seven.\n\nA little weird about the fix recommendation of  \"strbuf_addstr(line, \"-\");\" ,\nbecause it will only add a single DASH here.\n\nIt's the identical result which compares to the \"master\"[1]  I think with the\ncurrent codes and I tested the \"strbuf_addf()\" simply and it seems to work\nfine.\n\n[1] https://github.com/git/git/blob/master/builtin/ls-tree.c#L106\n\nThanks.\n\nJohannes Schindelin <Johannes.Schindelin@gmx.de> 于2022年1月4日周二 22:38写道：\n>\n> Hi Teng,\n>\n> On Sat, 1 Jan 2022, Teng Long wrote:\n>\n> > diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> > index 009ffeb15d..6e3e5a4d06 100644\n> > --- a/builtin/ls-tree.c\n> > +++ b/builtin/ls-tree.c\n> > @@ -56,23 +56,75 @@ enum {\n> >\n> >  static int cmdmode = MODE_UNSPECIFIED;\n> >\n> > -static int parse_shown_fields(void)\n> > +static const char *format;\n> > +static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n> > +static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n> > +static const char *name_only_format = \"%(file)\";\n> > +static const char *object_only_format = \"%(object)\";\n> > +\n> > +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n> > +                           const enum object_type type, unsigned int padded)\n> >  {\n> > -     if (cmdmode == MODE_NAME_ONLY) {\n> > -             shown_bits = SHOW_FILE_NAME;\n> > -             return 0;\n> > +     if (type == OBJ_BLOB) {\n> > +             unsigned long size;\n> > +             if (oid_object_info(the_repository, oid, &size) < 0)\n> > +                     die(_(\"could not get object info about '%s'\"),\n> > +                         oid_to_hex(oid));\n> > +             if (padded)\n> > +                     strbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n> > +             else\n> > +                     strbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n> > +     } else if (padded) {\n> > +             strbuf_addf(line, \"%7s\", \"-\");\n>\n> This, along with two other similar instances, triggers the\n> `static-analysis` job in the CI failure of `seen`. The suggested diff is:\n>\n>\n> -- snip --\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index 6e3e5a4d0634..8301d1a15f9a 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -75,7 +75,7 @@ static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n>                 else\n>                         strbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n>         } else if (padded) {\n> -               strbuf_addf(line, \"%7s\", \"-\");\n> +               strbuf_addstr(line, \"-\");\n>         } else {\n>                 strbuf_addstr(line, \"-\");\n>         }\n> @@ -110,7 +110,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n>         } else if (skip_prefix(start, \"(size)\", &p)) {\n>                 expand_objectsize(line, data->oid, data->type, 0);\n>         } else if (skip_prefix(start, \"(object)\", &p)) {\n> -               strbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n> +               strbuf_add_unique_abbrev(line, data->oid, abbrev);\n>         } else if (skip_prefix(start, \"(file)\", &p)) {\n>                 const char *name = data->base->buf;\n>                 const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n> @@ -119,7 +119,7 @@ static size_t expand_show_tree(struct strbuf *line, const char *start,\n>                 strbuf_addstr(data->base, data->pathname);\n>                 name = relative_path(data->base->buf, prefix, &sb);\n>                 quote_c_style(name, &quoted, NULL, 0);\n> -               strbuf_addstr(line, quoted.buf);\n> +               strbuf_addbuf(line, &quoted);\n>         } else {\n>                 errlen = (unsigned long)len;\n>                 die(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n> -- snap --\n>\n> But I think that the first hunk indicates a deeper issue, as `%7s`\n> probably meant to pad the dash to seven dashes (which that format won't\n> accomplish, but `strbuf_addchars()` would)?\n>\n> Ciao,\n> Dscho\n>\n> > +     } else {\n> > +             strbuf_addstr(line, \"-\");\n> >       }\n> > -     if (cmdmode == MODE_OBJECT_ONLY) {\n> > -             shown_bits = SHOW_OBJECT_NAME;\n> > -             return 0;\n> > +}\n> > +\n> > +static size_t expand_show_tree(struct strbuf *line, const char *start,\n> > +                            void *context)\n> > +{\n> > +     struct shown_data *data = context;\n> > +     const char *end;\n> > +     const char *p;\n> > +     unsigned int errlen;\n> > +     size_t len;\n> > +     len = strbuf_expand_literal_cb(line, start, NULL);\n> > +     if (len)\n> > +             return len;\n> > +\n> > +     if (*start != '(')\n> > +             die(_(\"bad ls-tree format: as '%s'\"), start);\n> > +\n> > +     end = strchr(start + 1, ')');\n> > +     if (!end)\n> > +             die(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n> > +\n> > +     len = end - start + 1;\n> > +     if (skip_prefix(start, \"(mode)\", &p)) {\n> > +             strbuf_addf(line, \"%06o\", data->mode);\n> > +     } else if (skip_prefix(start, \"(type)\", &p)) {\n> > +             strbuf_addstr(line, type_name(data->type));\n> > +     } else if (skip_prefix(start, \"(size:padded)\", &p)) {\n> > +             expand_objectsize(line, data->oid, data->type, 1);\n> > +     } else if (skip_prefix(start, \"(size)\", &p)) {\n> > +             expand_objectsize(line, data->oid, data->type, 0);\n> > +     } else if (skip_prefix(start, \"(object)\", &p)) {\n> > +             strbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n> > +     } else if (skip_prefix(start, \"(file)\", &p)) {\n> > +             const char *name = data->base->buf;\n> > +             const char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n> > +             struct strbuf quoted = STRBUF_INIT;\n> > +             struct strbuf sb = STRBUF_INIT;\n> > +             strbuf_addstr(data->base, data->pathname);\n> > +             name = relative_path(data->base->buf, prefix, &sb);\n> > +             quote_c_style(name, &quoted, NULL, 0);\n> > +             strbuf_addstr(line, quoted.buf);\n> > +     } else {\n> > +             errlen = (unsigned long)len;\n> > +             die(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n> >       }\n> > -     if (!ls_options || (ls_options & LS_RECURSIVE)\n> > -         || (ls_options & LS_SHOW_TREES)\n> > -         || (ls_options & LS_TREE_ONLY))\n> > -             shown_bits = SHOW_DEFAULT;\n> > -     if (cmdmode == MODE_LONG)\n> > -             shown_bits = SHOW_DEFAULT | SHOW_SIZE;\n> > -     return 1;\n> > +     return len;\n> >  }\n> >\n> >  static int show_recursive(const char *base, size_t baselen,\n> > @@ -106,6 +158,75 @@ static int show_recursive(const char *base, size_t baselen,\n> >       return 0;\n> >  }\n> >\n> > +static int show_tree_init(enum object_type *type, struct strbuf *base,\n> > +                       const char *pathname, unsigned mode, int *retval)\n> > +{\n> > +     if (S_ISGITLINK(mode)) {\n> > +             *type = OBJ_COMMIT;\n> > +     } else if (S_ISDIR(mode)) {\n> > +             if (show_recursive(base->buf, base->len, pathname)) {\n> > +                     *retval = READ_TREE_RECURSIVE;\n> > +                     if (!(ls_options & LS_SHOW_TREES))\n> > +                             return 1;\n> > +             }\n> > +             *type = OBJ_TREE;\n> > +     }\n> > +     else if (ls_options & LS_TREE_ONLY)\n> > +             return 1;\n> > +     return 0;\n> > +}\n> > +\n> > +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n> > +                      const char *pathname, unsigned mode, void *context)\n> > +{\n> > +     size_t baselen;\n> > +     int retval = 0;\n> > +     struct strbuf line = STRBUF_INIT;\n> > +     struct shown_data data = {\n> > +             .mode = mode,\n> > +             .type = OBJ_BLOB,\n> > +             .oid = oid,\n> > +             .pathname = pathname,\n> > +             .base = base,\n> > +     };\n> > +\n> > +     if (show_tree_init(&data.type, base, pathname, mode, &retval))\n> > +             return retval;\n> > +\n> > +     baselen = base->len;\n> > +     strbuf_expand(&line, format, expand_show_tree, &data);\n> > +     strbuf_addch(&line, line_termination);\n> > +     fwrite(line.buf, line.len, 1, stdout);\n> > +     strbuf_setlen(base, baselen);\n> > +     return retval;\n> > +}\n> > +\n> > +static int parse_shown_fields(void)\n> > +{\n> > +     if (cmdmode == MODE_NAME_ONLY ||\n> > +         (format && !strcmp(format, name_only_format))) {\n> > +             shown_bits = SHOW_FILE_NAME;\n> > +             return 1;\n> > +     }\n> > +\n> > +     if (cmdmode == MODE_OBJECT_ONLY ||\n> > +         (format && !strcmp(format, object_only_format))) {\n> > +             shown_bits = SHOW_OBJECT_NAME;\n> > +             return 1;\n> > +     }\n> > +\n> > +     if (!ls_options || (ls_options & LS_RECURSIVE)\n> > +         || (ls_options & LS_SHOW_TREES)\n> > +         || (ls_options & LS_TREE_ONLY)\n> > +             || (format && !strcmp(format, default_format)))\n> > +             shown_bits = SHOW_DEFAULT;\n> > +\n> > +     if (cmdmode == MODE_LONG ||\n> > +             (format && !strcmp(format, long_format)))\n> > +             shown_bits = SHOW_DEFAULT | SHOW_SIZE;\n> > +     return 1;\n> > +}\n> > +\n> >  static int show_default(struct shown_data *data)\n> >  {\n> >       size_t baselen = data->base->len;\n> > @@ -137,24 +258,6 @@ static int show_default(struct shown_data *data)\n> >       return 1;\n> >  }\n> >\n> > -static int show_tree_init(enum object_type *type, struct strbuf *base,\n> > -                       const char *pathname, unsigned mode, int *retval)\n> > -{\n> > -     if (S_ISGITLINK(mode)) {\n> > -             *type = OBJ_COMMIT;\n> > -     } else if (S_ISDIR(mode)) {\n> > -             if (show_recursive(base->buf, base->len, pathname)) {\n> > -                     *retval = READ_TREE_RECURSIVE;\n> > -                     if (!(ls_options & LS_SHOW_TREES))\n> > -                             return 1;\n> > -             }\n> > -             *type = OBJ_TREE;\n> > -     }\n> > -     else if (ls_options & LS_TREE_ONLY)\n> > -             return 1;\n> > -     return 0;\n> > -}\n> > -\n> >  static int show_tree(const struct object_id *oid, struct strbuf *base,\n> >               const char *pathname, unsigned mode, void *context)\n> >  {\n> > @@ -196,6 +299,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n> >       struct object_id oid;\n> >       struct tree *tree;\n> >       int i, full_tree = 0;\n> > +     read_tree_fn_t fn = show_tree;\n> >       const struct option ls_tree_options[] = {\n> >               OPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n> >                       LS_TREE_ONLY),\n> > @@ -218,6 +322,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n> >               OPT_BOOL(0, \"full-tree\", &full_tree,\n> >                        N_(\"list entire tree; not just current directory \"\n> >                           \"(implies --full-name)\")),\n> > +             OPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n> > +                          N_(\"format to use for the output\"),\n> > +                          PARSE_OPT_NONEG),\n> >               OPT__ABBREV(&abbrev),\n> >               OPT_END()\n> >       };\n> > @@ -238,6 +345,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n> >           ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n> >               ls_options |= LS_SHOW_TREES;\n> >\n> > +     if (format && cmdmode)\n> > +             usage_msg_opt(\n> > +                     _(\"--format can't be combined with other format-altering options\"),\n> > +                     ls_tree_usage, ls_tree_options);\n> >       if (argc < 1)\n> >               usage_with_options(ls_tree_usage, ls_tree_options);\n> >       if (get_oid(argv[0], &oid))\n> > @@ -261,6 +372,18 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n> >       tree = parse_tree_indirect(&oid);\n> >       if (!tree)\n> >               die(\"not a tree object\");\n> > -     return !!read_tree(the_repository, tree,\n> > -                        &pathspec, show_tree, NULL);\n> > +\n> > +     /*\n> > +      * The generic show_tree_fmt() is slower than show_tree(), so\n> > +      * take the fast path if possible.\n> > +      */\n> > +     if (format && (!strcmp(format, default_format) ||\n> > +                                !strcmp(format, long_format) ||\n> > +                                !strcmp(format, name_only_format) ||\n> > +                                !strcmp(format, object_only_format)))\n> > +             fn = show_tree;\n> > +     else if (format)\n> > +             fn = show_tree_fmt;\n> > +\n> > +     return !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n> >  }\n> > diff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh\n> > new file mode 100755\n> > index 0000000000..92b4d240e8\n> > --- /dev/null\n> > +++ b/t/t3105-ls-tree-format.sh\n> > @@ -0,0 +1,55 @@\n> > +#!/bin/sh\n> > +\n> > +test_description='ls-tree --format'\n> > +\n> > +TEST_PASSES_SANITIZE_LEAK=true\n> > +. ./test-lib.sh\n> > +\n> > +test_expect_success 'ls-tree --format usage' '\n> > +     test_expect_code 129 git ls-tree --format=fmt -l &&\n> > +     test_expect_code 129 git ls-tree --format=fmt --name-only &&\n> > +     test_expect_code 129 git ls-tree --format=fmt --name-status &&\n> > +     test_expect_code 129 git ls-tree --format=fmt --object-only\n> > +'\n> > +\n> > +test_expect_success 'setup' '\n> > +     mkdir dir &&\n> > +     test_commit dir/sub-file &&\n> > +     test_commit top-file\n> > +'\n> > +\n> > +test_ls_tree_format () {\n> > +     format=$1 &&\n> > +     opts=$2 &&\n> > +     shift 2 &&\n> > +     git ls-tree $opts -r HEAD >expect.raw &&\n> > +     sed \"s/^/> /\" >expect <expect.raw &&\n> > +     git ls-tree --format=\"> $format\" -r HEAD >actual &&\n> > +     test_cmp expect actual\n> > +}\n> > +\n> > +test_expect_success 'ls-tree --format=<default-like>' '\n> > +     test_ls_tree_format \\\n> > +             \"%(mode) %(type) %(object)%x09%(file)\" \\\n> > +             \"\"\n> > +'\n> > +\n> > +test_expect_success 'ls-tree --format=<long-like>' '\n> > +     test_ls_tree_format \\\n> > +             \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n> > +             \"--long\"\n> > +'\n> > +\n> > +test_expect_success 'ls-tree --format=<name-only-like>' '\n> > +     test_ls_tree_format \\\n> > +             \"%(file)\" \\\n> > +             \"--name-only\"\n> > +'\n> > +\n> > +test_expect_success 'ls-tree --format=<object-only-like>' '\n> > +     test_ls_tree_format \\\n> > +             \"%(object)\" \\\n> > +             \"--object-only\"\n> > +'\n> > +\n> > +test_done\n> > --\n> > 2.33.0.rc1.1802.gbb1c3936fb.dirty\n> >\n> >\n> >\n"},{"id":"445515","messageId":"nycvar.QRO.7.76.6.2201051348050.7076@tvgsbejvaqbjf.bet","threadId":"57172","inReplyTo":"CADMgQSSjoxqzBDyGXiNC4wHqYGK7z4O0SG0zai85D-DtDHem=w@mail.gmail.com","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-05T13:09:03Z","receivedAt":"2022-01-05T13:09:31Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Teng,\n\nOn Wed, 5 Jan 2022, Teng Long wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n>\n> > This, along with two other similar instances, triggers the\n> > `static-analysis` job in the CI failure of `seen`. The suggested diff is:\n>\n> The second and third I will optimize in the next patch.\n>\n> The first one. Actually I am a little puzzled from this :\n>\n> > -               strbuf_addf(line, \"%7s\", \"-\");\n> > +               strbuf_addstr(line, \"-\");\n>\n> > But I think that the first hunk indicates a deeper issue, as `%7s`\n> > probably meant to pad the dash to seven dashes (which that format won't\n> > accomplish, but `strbuf_addchars()` would)?\n>\n> \"strbuf_addf(line, \"%7s\", \"-\");\" here is used to align the columns\n> with a width of seven chars, not repeat one DASH to seven.\n\nAh. I misremembered and thought that `\"% 7s\"` would do that, but you're\ncorrect. See below for more on this.\n\nBut first, I wonder why the test suite passes with the `strbuf_addstr()`\ncall... Is this line not covered by any test case?\n\nAbout the `%7s` thing: The most obvious resolution is to use `\"      -\"`\nwith `strbuf_addstr()`. And I would argue that this is the best\nresolution.\n\nIf you disagree (and want to spin up a full `sprintf()` every time, just\nto add those six space characters), feel free to integrate the following\ninto your patch series:\n\n-- snip --\nFrom a390fcf7eec261c7f0e341bda79f2b1f326d151e Mon Sep 17 00:00:00 2001\nFrom: Johannes Schindelin <johannes.schindelin@gmx.de>\nDate: Wed, 5 Jan 2022 14:02:19 +0100\nSubject: [PATCH] cocci: allow padding with `strbuf_addf()`\n\nA convenient way to pad strings is to use something like\n`strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n\nHowever, the Coccinelle rule that forbids a format `\"%s\"` with a\nconstant string argument cast too wide a net, and also forbade such\npadding.\n\nLet's be a bit stricter in that Coccinelle rule.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n contrib/coccinelle/strbuf.cocci | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\nindex d9ada69b432..2d6e0f58fc8 100644\n--- a/contrib/coccinelle/strbuf.cocci\n+++ b/contrib/coccinelle/strbuf.cocci\n@@ -44,7 +44,7 @@ struct strbuf *SBP;\n\n @@\n expression E1, E2;\n-format F =~ \"s\";\n+format F =~ \"^s$\";\n @@\n - strbuf_addf(E1, \"%@F@\", E2);\n + strbuf_addstr(E1, E2);\n--\n2.33.0.windows.2\n-- snap --\n\nCiao,\nDscho\n\n>\n> A little weird about the fix recommendation of  \"strbuf_addstr(line, \"-\");\" ,\n> because it will only add a single DASH here.\n>\n> It's the identical result which compares to the \"master\"[1]  I think with the\n> current codes and I tested the \"strbuf_addf()\" simply and it seems to work\n> fine.\n>\n> [1] https://github.com/git/git/blob/master/builtin/ls-tree.c#L106\n"},{"id":"445536","messageId":"CADMgQSRxko6nC0zfDiVVfL2ZkdQVbBq0s59Er+6Nmg9vz4uJKQ@mail.gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201051348050.7076@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v8 8/8] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-05T16:44:56Z","receivedAt":"2022-01-05T16:45:15Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Wed, Jan 5, 2022 at 9:09 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n\n> Ah. I misremembered and thought that `\"% 7s\"` would do that, but you're\n> correct. See below for more on this.\n>\n> But first, I wonder why the test suite passes with the `strbuf_addstr()`\n> call... Is this line not covered by any test case?\n\nDefinitely, me too.\n\n> About the `%7s` thing: The most obvious resolution is to use `\"      -\"`\n> with `strbuf_addstr()`. And I would argue that this is the best\n> resolution.\n\nI agree that's a quick fix in that way.\nCan you feed me more info about why you think it's the best\nresolution?\n\n> If you disagree (and want to spin up a full `sprintf()` every time, just\n> to add those six space characters), feel free to integrate the following\n> into your patch series:\n>\n> -- snip --\n> From a390fcf7eec261c7f0e341bda79f2b1f326d151e Mon Sep 17 00:00:00 2001\n> From: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Date: Wed, 5 Jan 2022 14:02:19 +0100\n> Subject: [PATCH] cocci: allow padding with `strbuf_addf()`\n>\n> A convenient way to pad strings is to use something like\n> `strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n>\n> However, the Coccinelle rule that forbids a format `\"%s\"` with a\n> constant string argument cast too wide a net, and also forbade such\n> padding.\n>\n> Let's be a bit stricter in that Coccinelle rule.\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  contrib/coccinelle/strbuf.cocci | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\n> index d9ada69b432..2d6e0f58fc8 100644\n> --- a/contrib/coccinelle/strbuf.cocci\n> +++ b/contrib/coccinelle/strbuf.cocci\n> @@ -44,7 +44,7 @@ struct strbuf *SBP;\n>\n>  @@\n>  expression E1, E2;\n> -format F =~ \"s\";\n> +format F =~ \"^s$\";\n>  @@\n>  - strbuf_addf(E1, \"%@F@\", E2);\n>  + strbuf_addstr(E1, E2);\n> --\n> 2.33.0.windows.2\n> -- snap --\n\nI appreciate the input of 'coccinelle' and the commit.\n\nThe current relevant rules of 'strbuf' was added in commit [1], the\npurpose of it\nseems like to forbid some inefficient use cases and chase the performance\nprofit as much as possible.\n\nI think \"<SP*6>-\" and \"%7s\", they both with the same result, the former\nbenefits in performance, the later benefits in readability. So let's do a simple\nperformance test under \"linux\", then think about which is better for this case:\n\n    Benchmark 1: /opt/git/ls-tree-oid-only-addf/bin/git ls-tree -r\n--format='> %(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    Time (mean ± σ):     387.7 ms ±   8.8 ms    [User: 357.6 ms,\nSystem: 30.0 ms]\n    Range (min … max):   377.5 ms … 399.5 ms    10 runs\n\n    Benchmark 1: /opt/git/ls-tree-oid-only-addstr/bin/git ls-tree -r\n--format='> %(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    Time (mean ± σ):     388.9 ms ±   9.0 ms    [User: 362.7 ms,\nSystem: 26.1 ms]\n    Range (min … max):   373.4 ms … 399.8 ms    10 runs\n\nIt's with a slight performance difference between the two.\n\nSo, I decided to integrate your patch as a new commit in the current\npatchset and\nis it ok for me to mention it's from your guidance in the commit message or\na \"helped-by\" something like this?\n\nThanks.\n\n[1] https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n"},{"id":"445607","messageId":"cover.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641043500.git.dyroneteng@gmail.com","subject":"[PATCH v9 0/9] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:23Z","receivedAt":"2022-01-06T04:31:43Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Diff from v8:\n\n1). [Delete] ls-tree: split up the \"init\" part of show_tree()\n2). [New] ls-tree: optimize naming and handling of \"return\" in show_tree()\n\nIn last patch v8, I use commit \"ls-tree: split up the \"init\" part of show_tree()\"\n(d77c895a4b) from RFC patch v7 [1] and I did not notice the handling of return\nvalue which was called \"retval\" in it. As Junio mentioned in his reply [2], I\ntook a look about the \"retval\" and found it needs to be handled with care.\n\nSo, instead I introduced this new commit (instead of the old commit) to take\nresponsiblies for renaming \"retval\" and try to let the relevant codes maybe\nmore easier to understand.\n\n3). [Append] ls-tree.c: support --object-only option for \"git-ls-tree\"\n\nJunio pointed out that some of these fields are ambiguous [2] and on the\nnecessary of using xOR. \n\nI modified the commit and it's basically according to the steps I replied\nin [3], except the issue of using xOR, Junio suggest me to use\n\"if ((shown_fields & FILE_NAME_FIELD) == FILE_NAME_FIELD)\" instead, but\nin here I think use \"if (shown_fields == FILE_NAME_FIELD)\" is enough.\n\n4). [Append] ls-tree.c: introduce struct \"show_tree_data\"\n\nI had a spelling mistake which was found by Junio (\"show_data\" should be \n\"shown_data\"), that reminded me the \"show_data\" is a little abtract, so I\nrename it to \"show_tree_data\" which is the data struct used in function\n\"show_tree\", I think it's a bit better than before.\n\n5). [Append] ls-tree.c: introduce \"--format\" option\n\nDoc format modifications. Fix strbuf leaks and some other non-functional\nmodifications in \"ls-tree.c\"\n\n6). [New] cocci: allow padding with `strbuf_addf()`\n\nFix the static-analysis issue[4] which found by Johannes Schindelin. \n\nThanks.\n\n[1] https://public-inbox.org/git/xmqqwnjgfe4t.fsf@gitster.g/\n[2] https://public-inbox.org/git/xmqqwnjgfe4t.fsf@gitster.g/\n[3] https://public-inbox.org/git/20220104072951.10153-1-dyroneteng@gmail.com/#t\n[4] https://public-inbox.org/git/nycvar.QRO.7.76.6.2201051348050.7076@tvgsbejvaqbjf.bet/\n\nTeng Long (5):\n  ls-tree: optimize naming and handling of \"return\" in show_tree()\n  ls-tree.c: support --object-only option for \"git-ls-tree\"\n  ls-tree.c: introduce struct \"show_tree_data\"\n  ls-tree.c: introduce \"--format\" option\n  cocci: allow padding with `strbuf_addf()`\n\nÆvar Arnfjörð Bjarmason (4):\n  ls-tree: remove commented-out code\n  ls-tree: add missing braces to \"else\" arms\n  ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n  ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n\n Documentation/git-ls-tree.txt   |  55 +++++-\n builtin/ls-tree.c               | 332 ++++++++++++++++++++++++++------\n contrib/coccinelle/strbuf.cocci |   2 +-\n t/t3104-ls-tree-oid.sh          |  51 +++++\n t/t3105-ls-tree-format.sh       |  55 ++++++\n 5 files changed, 427 insertions(+), 68 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n create mode 100755 t/t3105-ls-tree-format.sh\n\nRange-diff against v8:\n 1:  d77c895a4b <  -:  ---------- ls-tree: split up the \"init\" part of show_tree()\n -:  ---------- >  1:  2fcff7e0d4 ls-tree: remove commented-out code\n -:  ---------- >  2:  6fd1dd9383 ls-tree: add missing braces to \"else\" arms\n -:  ---------- >  3:  208654b5e2 ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n -:  ---------- >  4:  2637464fd8 ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n -:  ---------- >  5:  75503c41a7 ls-tree: optimize naming and handling of \"return\" in show_tree()\n 2:  cb881183cb !  6:  e0274f079a ls-tree.c: support --object-only option for \"git-ls-tree\"\n    @@ builtin/ls-tree.c\n      static struct pathspec pathspec;\n      static int chomp_prefix;\n      static const char *ls_tree_prefix;\n    -+static unsigned int shown_bits;\n    -+#define SHOW_FILE_NAME 1\n    -+#define SHOW_SIZE (1 << 1)\n    -+#define SHOW_OBJECT_NAME (1 << 2)\n    -+#define SHOW_TYPE (1 << 3)\n    -+#define SHOW_MODE (1 << 4)\n    -+#define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n    ++static unsigned int shown_fields;\n    ++#define FIELD_FILE_NAME 1\n    ++#define FIELD_SIZE (1 << 1)\n    ++#define FIELD_OBJECT_NAME (1 << 2)\n    ++#define FIELD_TYPE (1 << 3)\n    ++#define FIELD_MODE (1 << 4)\n    ++#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n    ++#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n      \n      static const  char * const ls_tree_usage[] = {\n      \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n    @@ builtin/ls-tree.c\n     +static int parse_shown_fields(void)\n     +{\n     +\tif (cmdmode == MODE_NAME_ONLY) {\n    -+\t\tshown_bits = SHOW_FILE_NAME;\n    ++\t\tshown_fields = FIELD_FILE_NAME;\n     +\t\treturn 0;\n     +\t}\n     +\tif (cmdmode == MODE_OBJECT_ONLY) {\n    -+\t\tshown_bits = SHOW_OBJECT_NAME;\n    ++\t\tshown_fields = FIELD_OBJECT_NAME;\n     +\t\treturn 0;\n     +\t}\n     +\tif (!ls_options || (ls_options & LS_RECURSIVE)\n     +\t    || (ls_options & LS_SHOW_TREES)\n     +\t    || (ls_options & LS_TREE_ONLY))\n    -+\t\tshown_bits = SHOW_DEFAULT;\n    ++\t\tshown_fields = FIELD_DEFAULT;\n     +\tif (cmdmode == MODE_LONG)\n    -+\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n    ++\t\tshown_fields = FIELD_LONG_DEFAULT;\n     +\treturn 1;\n     +}\n     +\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c\n     +{\n     +\tsize_t baselen = base->len;\n     +\n    -+\tif (shown_bits & SHOW_SIZE) {\n    ++\tif (shown_fields & FIELD_SIZE) {\n     +\t\tchar size_text[24];\n     +\t\tif (type == OBJ_BLOB) {\n     +\t\t\tunsigned long size;\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c\n     +\treturn 1;\n     +}\n     +\n    - static int show_tree_init(enum object_type *type, struct strbuf *base,\n    - \t\t\t  const char *pathname, unsigned mode, int *retval)\n    + static void init_type(unsigned mode, enum object_type *type)\n      {\n    + \tif (S_ISGITLINK(mode))\n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - \tif (show_tree_init(&type, base, pathname, mode, &retval))\n    - \t\treturn retval;\n    + \tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    + \t\treturn !READ_TREE_RECURSIVE;\n      \n     -\tif (!(ls_options & LS_NAME_ONLY)) {\n     -\t\tif (ls_options & LS_SHOW_SIZE) {\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n     -\t\t\t       find_unique_abbrev(oid, abbrev));\n     -\t\t}\n    -+\tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n    ++\tif (shown_fields == FIELD_OBJECT_NAME) {\n     +\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n    -+\t\treturn retval;\n    ++\t\treturn recursive;\n      \t}\n     -\tbaselen = base->len;\n     -\tstrbuf_addstr(base, pathname);\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\t\t\t   stdout, line_termination);\n     -\tstrbuf_setlen(base, baselen);\n     +\n    -+\tif (!(shown_bits ^ SHOW_FILE_NAME)) {\n    ++\tif (shown_fields == FIELD_FILE_NAME) {\n     +\t\tbaselen = base->len;\n     +\t\tstrbuf_addstr(base, pathname);\n     +\t\twrite_name_quoted_relative(base->buf,\n     +\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n     +\t\t\t\t\t   stdout, line_termination);\n     +\t\tstrbuf_setlen(base, baselen);\n    ++\t\treturn recursive;\n     +\t}\n     +\n    -+\tif (!(shown_bits ^ SHOW_DEFAULT) ||\n    -+\t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n    ++\tif (shown_fields >= FIELD_DEFAULT)\n     +\t\tshow_default(oid, type, pathname, mode, base);\n     +\n    - \treturn retval;\n    + \treturn recursive;\n      }\n      \n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n 3:  296ebacafe !  7:  725c4d0187 ls-tree.c: introduce struct \"shown_data\"\n    @@ Metadata\n     Author: Teng Long <dyroneteng@gmail.com>\n     \n      ## Commit message ##\n    -    ls-tree.c: introduce struct \"shown_data\"\n    +    ls-tree.c: introduce struct \"show_tree_data\"\n     \n    -    \"show_data\" is a struct that packages the necessary fields for\n    -    reusing. This commit is a front-loaded commit for support\n    -    \"--format\" argument and does not affect any existing functionality.\n    +    \"show_tree_data\" is a struct that packages the necessary fields for\n    +    \"show_tree()\". This commit is a pre-prepared commit for supporting\n    +    \"--format\" option and it does not affect any existing functionality.\n     \n         Signed-off-by: Teng Long <dyroneteng@gmail.com>\n     \n      ## builtin/ls-tree.c ##\n    -@@ builtin/ls-tree.c: static unsigned int shown_bits;\n    - #define SHOW_MODE (1 << 4)\n    - #define SHOW_DEFAULT 29 /* 11101 size is not shown to output by default */\n    +@@ builtin/ls-tree.c: static unsigned int shown_fields;\n    + #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n    + #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n      \n    -+struct shown_data {\n    ++struct show_tree_data {\n     +\tunsigned mode;\n     +\tenum object_type type;\n     +\tconst struct object_id *oid;\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n     -static int show_default(const struct object_id *oid, enum object_type type,\n     -\t\t\tconst char *pathname, unsigned mode,\n     -\t\t\tstruct strbuf *base)\n    -+static int show_default(struct shown_data *data)\n    ++static int show_default(struct show_tree_data *data)\n      {\n     -\tsize_t baselen = base->len;\n     +\tsize_t baselen = data->base->len;\n      \n    - \tif (shown_bits & SHOW_SIZE) {\n    + \tif (shown_fields & FIELD_SIZE) {\n      \t\tchar size_text[24];\n     -\t\tif (type == OBJ_BLOB) {\n     +\t\tif (data->type == OBJ_BLOB) {\n    @@ builtin/ls-tree.c: static int show_default(const struct object_id *oid, enum obj\n      \n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n      {\n    - \tint retval = 0;\n    + \tint recursive = 0;\n      \tsize_t baselen;\n     -\tenum object_type type = OBJ_BLOB;\n    -+\tstruct shown_data data = {\n    ++\tstruct show_tree_data data = {\n     +\t\t.mode = mode,\n     +\t\t.type = OBJ_BLOB,\n     +\t\t.oid = oid,\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     +\t\t.base = base,\n     +\t};\n      \n    --\tif (show_tree_init(&type, base, pathname, mode, &retval))\n    -+\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n    - \t\treturn retval;\n    --\n    - \tif (!(shown_bits ^ SHOW_OBJECT_NAME)) {\n    - \t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n    - \t\treturn retval;\n    +-\tinit_type(mode, &type);\n    ++\tinit_type(mode, &data.type);\n    + \tinit_recursive(base, pathname, &recursive);\n    + \n    +-\tif (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    ++\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    + \t\treturn recursive;\n    +-\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    ++\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    + \t\treturn !READ_TREE_RECURSIVE;\n    + \n    + \tif (shown_fields == FIELD_OBJECT_NAME) {\n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    + \t}\n      \n    - \tif (!(shown_bits ^ SHOW_DEFAULT) ||\n    - \t    !(shown_bits ^ (SHOW_DEFAULT | SHOW_SIZE)))\n    + \tif (shown_fields >= FIELD_DEFAULT)\n     -\t\tshow_default(oid, type, pathname, mode, base);\n     +\t\tshow_default(&data);\n      \n    - \treturn retval;\n    + \treturn recursive;\n      }\n 4:  e0add802fb !  8:  7df58483a4 ls-tree.c: introduce \"--format\" option\n    @@ Commit message\n             Range (min … max):   328.8 ms … 349.4 ms    10 runs\n     \n         Links:\n    -    [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n    +            [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n     \n         Signed-off-by: Teng Long <dyroneteng@gmail.com>\n     \n    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl\n     +For example, if you want to only print the <object> and <file> fields with a\n     +JSON style, executing with a specific \"--format\" like\n     +\n    -+\t\tgit ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n    ++        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n     +\n     +The output format changes to:\n     +\n    -+\t\t{\"object\":\"<object>\", \"file\":\"<file>\"}\n    ++        {\"object\":\"<object>\", \"file\":\"<file>\"}\n     +\n     +FIELD NAMES\n     +-----------\n    @@ builtin/ls-tree.c: enum {\n      \n      static int cmdmode = MODE_UNSPECIFIED;\n      \n    --static int parse_shown_fields(void)\n     +static const char *format;\n     +static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n     +static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n     +static const char *name_only_format = \"%(file)\";\n     +static const char *object_only_format = \"%(object)\";\n     +\n    + static int parse_shown_fields(void)\n    + {\n    + \tif (cmdmode == MODE_NAME_ONLY) {\n    +@@ builtin/ls-tree.c: static int parse_shown_fields(void)\n    + \treturn 1;\n    + }\n    + \n     +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n     +\t\t\t      const enum object_type type, unsigned int padded)\n    - {\n    --\tif (cmdmode == MODE_NAME_ONLY) {\n    --\t\tshown_bits = SHOW_FILE_NAME;\n    --\t\treturn 0;\n    ++{\n     +\tif (type == OBJ_BLOB) {\n     +\t\tunsigned long size;\n     +\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n    @@ builtin/ls-tree.c: enum {\n     +\t\tstrbuf_addf(line, \"%7s\", \"-\");\n     +\t} else {\n     +\t\tstrbuf_addstr(line, \"-\");\n    - \t}\n    --\tif (cmdmode == MODE_OBJECT_ONLY) {\n    --\t\tshown_bits = SHOW_OBJECT_NAME;\n    --\t\treturn 0;\n    ++\t}\n     +}\n     +\n     +static size_t expand_show_tree(struct strbuf *line, const char *start,\n     +\t\t\t       void *context)\n     +{\n    -+\tstruct shown_data *data = context;\n    ++\tstruct show_tree_data *data = context;\n     +\tconst char *end;\n     +\tconst char *p;\n     +\tunsigned int errlen;\n    -+\tsize_t len;\n    -+\tlen = strbuf_expand_literal_cb(line, start, NULL);\n    ++\tsize_t len = strbuf_expand_literal_cb(line, start, NULL);\n    ++\n     +\tif (len)\n     +\t\treturn len;\n    -+\n     +\tif (*start != '(')\n     +\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n     +\n    @@ builtin/ls-tree.c: enum {\n     +\t} else if (skip_prefix(start, \"(size)\", &p)) {\n     +\t\texpand_objectsize(line, data->oid, data->type, 0);\n     +\t} else if (skip_prefix(start, \"(object)\", &p)) {\n    -+\t\tstrbuf_addstr(line, find_unique_abbrev(data->oid, abbrev));\n    ++\t\tstrbuf_add_unique_abbrev(line, data->oid, abbrev);\n     +\t} else if (skip_prefix(start, \"(file)\", &p)) {\n     +\t\tconst char *name = data->base->buf;\n     +\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n    @@ builtin/ls-tree.c: enum {\n     +\t\tstrbuf_addstr(data->base, data->pathname);\n     +\t\tname = relative_path(data->base->buf, prefix, &sb);\n     +\t\tquote_c_style(name, &quoted, NULL, 0);\n    -+\t\tstrbuf_addstr(line, quoted.buf);\n    ++\t\tstrbuf_addbuf(line, &quoted);\n    ++\t\tstrbuf_release(&sb);\n    ++\t\tstrbuf_release(&quoted);\n     +\t} else {\n     +\t\terrlen = (unsigned long)len;\n     +\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n    - \t}\n    --\tif (!ls_options || (ls_options & LS_RECURSIVE)\n    --\t    || (ls_options & LS_SHOW_TREES)\n    --\t    || (ls_options & LS_TREE_ONLY))\n    --\t\tshown_bits = SHOW_DEFAULT;\n    --\tif (cmdmode == MODE_LONG)\n    --\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n    --\treturn 1;\n    ++\t}\n     +\treturn len;\n    - }\n    - \n    ++}\n    ++\n      static int show_recursive(const char *base, size_t baselen,\n    + \t\t\t  const char *pathname)\n    + {\n     @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n      \treturn 0;\n      }\n      \n    -+static int show_tree_init(enum object_type *type, struct strbuf *base,\n    -+\t\t\t  const char *pathname, unsigned mode, int *retval)\n    ++static void init_recursive(struct strbuf *base, const char *pathname,\n    ++\t\t\t\tint *recursive)\n     +{\n    -+\tif (S_ISGITLINK(mode)) {\n    ++\tif (show_recursive(base->buf, base->len, pathname))\n    ++\t\t*recursive = READ_TREE_RECURSIVE;\n    ++}\n    ++\n    ++static void init_type(unsigned mode, enum object_type *type)\n    ++{\n    ++\tif (S_ISGITLINK(mode))\n     +\t\t*type = OBJ_COMMIT;\n    -+\t} else if (S_ISDIR(mode)) {\n    -+\t\tif (show_recursive(base->buf, base->len, pathname)) {\n    -+\t\t\t*retval = READ_TREE_RECURSIVE;\n    -+\t\t\tif (!(ls_options & LS_SHOW_TREES))\n    -+\t\t\t\treturn 1;\n    -+\t\t}\n    ++\telse if (S_ISDIR(mode))\n     +\t\t*type = OBJ_TREE;\n    -+\t}\n    -+\telse if (ls_options & LS_TREE_ONLY)\n    -+\t\treturn 1;\n    -+\treturn 0;\n     +}\n     +\n     +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n     +\t\t\t const char *pathname, unsigned mode, void *context)\n     +{\n     +\tsize_t baselen;\n    -+\tint retval = 0;\n    ++\tint recursive = 0;\n     +\tstruct strbuf line = STRBUF_INIT;\n    -+\tstruct shown_data data = {\n    ++\tstruct show_tree_data data = {\n     +\t\t.mode = mode,\n     +\t\t.type = OBJ_BLOB,\n     +\t\t.oid = oid,\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n     +\t\t.base = base,\n     +\t};\n     +\n    -+\tif (show_tree_init(&data.type, base, pathname, mode, &retval))\n    -+\t\treturn retval;\n    ++\tinit_type(mode, &data.type);\n    ++\tinit_recursive(base, pathname, &recursive);\n    ++\n    ++\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    ++\t\treturn recursive;\n    ++\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    ++\t\treturn !READ_TREE_RECURSIVE;\n     +\n     +\tbaselen = base->len;\n     +\tstrbuf_expand(&line, format, expand_show_tree, &data);\n     +\tstrbuf_addch(&line, line_termination);\n     +\tfwrite(line.buf, line.len, 1, stdout);\n    ++\tstrbuf_release(&line);\n     +\tstrbuf_setlen(base, baselen);\n    -+\treturn retval;\n    ++\treturn recursive;\n     +}\n     +\n    -+static int parse_shown_fields(void)\n    -+{\n    -+\tif (cmdmode == MODE_NAME_ONLY ||\n    -+\t    (format && !strcmp(format, name_only_format))) {\n    -+\t\tshown_bits = SHOW_FILE_NAME;\n    -+\t\treturn 1;\n    -+\t}\n    -+\n    -+\tif (cmdmode == MODE_OBJECT_ONLY ||\n    -+\t    (format && !strcmp(format, object_only_format))) {\n    -+\t\tshown_bits = SHOW_OBJECT_NAME;\n    -+\t\treturn 1;\n    -+\t}\n    -+\n    -+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n    -+\t    || (ls_options & LS_SHOW_TREES)\n    -+\t    || (ls_options & LS_TREE_ONLY)\n    -+\t\t|| (format && !strcmp(format, default_format)))\n    -+\t\tshown_bits = SHOW_DEFAULT;\n    -+\n    -+\tif (cmdmode == MODE_LONG ||\n    -+\t\t(format && !strcmp(format, long_format)))\n    -+\t\tshown_bits = SHOW_DEFAULT | SHOW_SIZE;\n    -+\treturn 1;\n    -+}\n    -+\n    - static int show_default(struct shown_data *data)\n    + static int show_default(struct show_tree_data *data)\n      {\n      \tsize_t baselen = data->base->len;\n    -@@ builtin/ls-tree.c: static int show_default(struct shown_data *data)\n    +@@ builtin/ls-tree.c: static int show_default(struct show_tree_data *data)\n      \treturn 1;\n      }\n      \n    --static int show_tree_init(enum object_type *type, struct strbuf *base,\n    --\t\t\t  const char *pathname, unsigned mode, int *retval)\n    +-static void init_type(unsigned mode, enum object_type *type)\n     -{\n    --\tif (S_ISGITLINK(mode)) {\n    +-\tif (S_ISGITLINK(mode))\n     -\t\t*type = OBJ_COMMIT;\n    --\t} else if (S_ISDIR(mode)) {\n    --\t\tif (show_recursive(base->buf, base->len, pathname)) {\n    --\t\t\t*retval = READ_TREE_RECURSIVE;\n    --\t\t\tif (!(ls_options & LS_SHOW_TREES))\n    --\t\t\t\treturn 1;\n    --\t\t}\n    +-\telse if (S_ISDIR(mode))\n     -\t\t*type = OBJ_TREE;\n    --\t}\n    --\telse if (ls_options & LS_TREE_ONLY)\n    --\t\treturn 1;\n    --\treturn 0;\n    +-}\n    +-\n    +-static void init_recursive(struct strbuf *base, const char *pathname,\n    +-\t\t\t\tint *recursive)\n    +-{\n    +-\tif (show_recursive(base->buf, base->len, pathname))\n    +-\t\t*recursive = READ_TREE_RECURSIVE;\n     -}\n     -\n      static int show_tree(const struct object_id *oid, struct strbuf *base,\n    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref\n     +\t * The generic show_tree_fmt() is slower than show_tree(), so\n     +\t * take the fast path if possible.\n     +\t */\n    -+\tif (format && (!strcmp(format, default_format) ||\n    -+\t\t\t\t   !strcmp(format, long_format) ||\n    -+\t\t\t\t   !strcmp(format, name_only_format) ||\n    -+\t\t\t\t   !strcmp(format, object_only_format)))\n    ++\tif (format &&\n    ++\t    (!strcmp(format, default_format) ||\n    ++\t     !strcmp(format, long_format) ||\n    ++\t     !strcmp(format, name_only_format) ||\n    ++\t     !strcmp(format, object_only_format)))\n     +\t\tfn = show_tree;\n     +\telse if (format)\n     +\t\tfn = show_tree_fmt;\n -:  ---------- >  9:  8dafb2b377 cocci: allow padding with `strbuf_addf()`\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445608","messageId":"2fcff7e0d40255e0be9496647787352dd0aeb784.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 1/9] ls-tree: remove commented-out code","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:24Z","receivedAt":"2022-01-06T04:31:51Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nRemove code added in f35a6d3bce7 (Teach core object handling functions\nabout gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output\nflushing before fork(), 2008-08-04), and then finally ending up in its\ncurrent form in d3bee161fef (tree.c: allow read_tree_recursive() to\ntraverse gitlink entries, 2009-01-25). All while being commented-out!\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3a442631c7..5f7c84950c 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tconst char *type = blob_type;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\t/*\n-\t\t * Maybe we want to have some recursive version here?\n-\t\t *\n-\t\t * Something similar to this incomplete example:\n-\t\t *\n-\t\tif (show_subprojects(base, baselen, pathname))\n-\t\t\tretval = READ_TREE_RECURSIVE;\n-\t\t *\n-\t\t */\n \t\ttype = commit_type;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445609","messageId":"6fd1dd938303192d5b0a4cdc2914b2ff72cf5a3d.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 2/9] ls-tree: add missing braces to \"else\" arms","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:25Z","receivedAt":"2022-01-06T04:31:56Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd missing {} to the \"else\" arms in show_tree() per the\nCodingGuidelines.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 5f7c84950c..0a28f32ccb 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t\telse\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n \t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else\n+\t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t\t}\n \t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n-\t\t} else\n+\t\t} else {\n \t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev));\n+\t\t}\n \t}\n \tbaselen = base->len;\n \tstrbuf_addstr(base, pathname);\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445610","messageId":"208654b5e2aebbe988b5a8f478869253a0166e9b.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 3/9] ls-tree: use \"enum object_type\", not {blob,tree,commit}_type","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:26Z","receivedAt":"2022-01-06T04:32:00Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nChange the ls-tree.c code to use type_name() on the enum instead of\nusing the string constants. This doesn't matter either way for\nperformance, but makes this a bit easier to read as we'll no longer\nneed a strcmp() here.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 0a28f32ccb..3f0225b097 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n {\n \tint retval = 0;\n \tint baselen;\n-\tconst char *type = blob_type;\n+\tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\ttype = commit_type;\n+\t\ttype = OBJ_COMMIT;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n \t\t\tretval = READ_TREE_RECURSIVE;\n \t\t\tif (!(ls_options & LS_SHOW_TREES))\n \t\t\t\treturn retval;\n \t\t}\n-\t\ttype = tree_type;\n+\t\ttype = OBJ_TREE;\n \t}\n \telse if (ls_options & LS_TREE_ONLY)\n \t\treturn 0;\n@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (!(ls_options & LS_NAME_ONLY)) {\n \t\tif (ls_options & LS_SHOW_SIZE) {\n \t\t\tchar size_text[24];\n-\t\t\tif (!strcmp(type, blob_type)) {\n+\t\t\tif (type == OBJ_BLOB) {\n \t\t\t\tunsigned long size;\n \t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n \t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev));\n \t\t}\n \t}\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445611","messageId":"2637464fd8f6b8bae6c681f20c10c9fc76dc6d6c.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 4/9] ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:27Z","receivedAt":"2022-01-06T04:32:03Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nThe \"struct strbuf\"'s \"len\" member is a \"size_t\", not an \"int\", so\nlet's change our corresponding types accordingly. This also changes\nthe \"len\" and \"speclen\" variables, which are likewise used to store\nthe return value of strlen(), which returns \"size_t\", not \"int\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3f0225b097..eecc7482d5 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {\n \tNULL\n };\n \n-static int show_recursive(const char *base, int baselen, const char *pathname)\n+static int show_recursive(const char *base, size_t baselen, const char *pathname)\n {\n \tint i;\n \n@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)\n \n \tfor (i = 0; i < pathspec.nr; i++) {\n \t\tconst char *spec = pathspec.items[i].match;\n-\t\tint len, speclen;\n+\t\tsize_t len, speclen;\n \n \t\tif (strncmp(base, spec, baselen))\n \t\t\tcontinue;\n@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n \tint retval = 0;\n-\tint baselen;\n+\tsize_t baselen;\n \tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445612","messageId":"75503c41a7e2f3fdbb59ce3568853049b55a2d3b.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:28Z","receivedAt":"2022-01-06T04:32:09Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"The variable which \"show_tree()\" return is named \"retval\", a name that's\na little hard to understand. This commit tries to make the variable\nand the related codes more clear in the context.\n\nThe change is based on three steps. The first is to rename \"retval\" to\na more meaningful name.\n\nThe second is that there are different \"return\" cases in \"show_tree\",\nsome places use \"return retval;\", some just directly use \"return 0;\",\nthis maybe cause some confusion when reading these \"returns\". For this\n, we change all the \"return\" cases to the new uniform name.\n\nThe last is there are some nested \"if\" judgments surround the \"returns\",\nthis even make the codes here a little hard to understand. So we put\nsome logic in individual methods, \"init_type()\" and \"init_recursive()\".\n\nAfter the steps, let us look at \"show_tree()\" again. It has a uniform\nreturn variable name now, and first we init the \"type\" by \"mode\", then\ncall \"init_recursive\" to init the value of \"recursive\" which means\nwhether to go on reading recusively into the \"tree\". The codes here\nbecome a little bit clearer, so we do not need to take a look at\n\"read_tree_at()\" in \"tree.c\" to make sure the context of the return\nvalue.\n\nSigned-off-by: Teng Long <dyronetengb@gmail.com>\n---\n builtin/ls-tree.c | 38 ++++++++++++++++++++++++--------------\n 1 file changed, 24 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex eecc7482d5..7383dddf8c 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -61,25 +61,35 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n \treturn 0;\n }\n \n+static void init_type(unsigned mode, enum object_type *type)\n+{\n+\tif (S_ISGITLINK(mode))\n+\t\t*type = OBJ_COMMIT;\n+\telse if (S_ISDIR(mode))\n+\t\t*type = OBJ_TREE;\n+}\n+\n+static void init_recursive(struct strbuf *base, const char *pathname,\n+\t\t\t\tint *recursive)\n+{\n+\tif (show_recursive(base->buf, base->len, pathname))\n+\t\t*recursive = READ_TREE_RECURSIVE;\n+}\n+\n static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n-\tint retval = 0;\n+\tint recursive = 0;\n \tsize_t baselen;\n \tenum object_type type = OBJ_BLOB;\n \n-\tif (S_ISGITLINK(mode)) {\n-\t\ttype = OBJ_COMMIT;\n-\t} else if (S_ISDIR(mode)) {\n-\t\tif (show_recursive(base->buf, base->len, pathname)) {\n-\t\t\tretval = READ_TREE_RECURSIVE;\n-\t\t\tif (!(ls_options & LS_SHOW_TREES))\n-\t\t\t\treturn retval;\n-\t\t}\n-\t\ttype = OBJ_TREE;\n-\t}\n-\telse if (ls_options & LS_TREE_ONLY)\n-\t\treturn 0;\n+\tinit_type(mode, &type);\n+\tinit_recursive(base, pathname, &recursive);\n+\n+\tif (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n+\t\treturn recursive;\n+\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n+\t\treturn !READ_TREE_RECURSIVE;\n \n \tif (!(ls_options & LS_NAME_ONLY)) {\n \t\tif (ls_options & LS_SHOW_SIZE) {\n@@ -109,7 +119,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n \t\t\t\t   stdout, line_termination);\n \tstrbuf_setlen(base, baselen);\n-\treturn retval;\n+\treturn recursive;\n }\n \n int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445613","messageId":"e0274f079a7d381b9a936bfcd53bad64167c18b8.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:29Z","receivedAt":"2022-01-06T04:32:13Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"We usually pipe the output from `git ls-trees` to tools like\n`sed` or `cut` when we only want to extract some fields.\n\nWhen we want only the pathname component, we can pass\n`--name-only` option to omit such a pipeline, but there are no\noptions for extracting other fields.\n\nTeach the \"--object-only\" option to the command to only show the\nobject name. This option cannot be used together with\n\"--name-only\" or \"--long\" , they are mutually exclusive (actually\n\"--name-only\" and \"--long\" can be combined together before, this\ncommit by the way fix this bug).\n\nA simple refactoring was done to the \"show_tree\" function, intead by\nusing bitwise operations to recognize the format for printing to\nstdout. The reason for doing this is that we don't want to increase\nthe readability difficulty with the addition of \"-object-only\",\nmaking this part of the logic easier to read and expand.\n\nIn terms of performance, there is no loss comparing to the\n\"master\" (2ae0a9cb8298185a94e5998086f380a355dd8907), here are the\nresults of the performance tests in my environment based on linux\nrepository:\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]\n    Range (min … max):   101.5 ms … 111.3 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]\n    Range (min … max):    99.3 ms … 109.5 ms    27 runs\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]\n    Range (min … max):   323.0 ms … 355.0 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]\n    Range (min … max):   330.4 ms … 349.9 ms    10 runs\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |   7 +-\n builtin/ls-tree.c             | 141 +++++++++++++++++++++++++---------\n t/t3104-ls-tree-oid.sh        |  51 ++++++++++++\n 3 files changed, 160 insertions(+), 39 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex db02d6d79a..729370f235 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n \t    <tree-ish> [<path>...]\n \n DESCRIPTION\n@@ -59,6 +59,11 @@ OPTIONS\n --name-only::\n --name-status::\n \tList only filenames (instead of the \"long\" output), one per line.\n+\tCannot be combined with `--object-only`.\n+\n+--object-only::\n+\tList only names of the objects, one per line. Cannot be combined\n+\twith `--name-only` or `--name-status`.\n \n --abbrev[=<n>]::\n \tInstead of showing the full 40-byte hexadecimal object\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 7383dddf8c..6b5e3ab9dd 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -16,22 +16,60 @@\n \n static int line_termination = '\\n';\n #define LS_RECURSIVE 1\n-#define LS_TREE_ONLY 2\n-#define LS_SHOW_TREES 4\n-#define LS_NAME_ONLY 8\n-#define LS_SHOW_SIZE 16\n+#define LS_TREE_ONLY (1 << 1)\n+#define LS_SHOW_TREES (1 << 2)\n+#define LS_NAME_ONLY (1 << 3)\n+#define LS_SHOW_SIZE (1 << 4)\n+#define LS_OBJECT_ONLY (1 << 5)\n static int abbrev;\n static int ls_options;\n static struct pathspec pathspec;\n static int chomp_prefix;\n static const char *ls_tree_prefix;\n+static unsigned int shown_fields;\n+#define FIELD_FILE_NAME 1\n+#define FIELD_SIZE (1 << 1)\n+#define FIELD_OBJECT_NAME (1 << 2)\n+#define FIELD_TYPE (1 << 3)\n+#define FIELD_MODE (1 << 4)\n+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n \n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n };\n \n-static int show_recursive(const char *base, size_t baselen, const char *pathname)\n+enum {\n+\tMODE_UNSPECIFIED = 0,\n+\tMODE_NAME_ONLY,\n+\tMODE_OBJECT_ONLY,\n+\tMODE_LONG,\n+};\n+\n+static int cmdmode = MODE_UNSPECIFIED;\n+\n+static int parse_shown_fields(void)\n+{\n+\tif (cmdmode == MODE_NAME_ONLY) {\n+\t\tshown_fields = FIELD_FILE_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (cmdmode == MODE_OBJECT_ONLY) {\n+\t\tshown_fields = FIELD_OBJECT_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n+\t    || (ls_options & LS_SHOW_TREES)\n+\t    || (ls_options & LS_TREE_ONLY))\n+\t\tshown_fields = FIELD_DEFAULT;\n+\tif (cmdmode == MODE_LONG)\n+\t\tshown_fields = FIELD_LONG_DEFAULT;\n+\treturn 1;\n+}\n+\n+static int show_recursive(const char *base, size_t baselen,\n+\t\t\t  const char *pathname)\n {\n \tint i;\n \n@@ -61,6 +99,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n \treturn 0;\n }\n \n+static int show_default(const struct object_id *oid, enum object_type type,\n+\t\t\tconst char *pathname, unsigned mode,\n+\t\t\tstruct strbuf *base)\n+{\n+\tsize_t baselen = base->len;\n+\n+\tif (shown_fields & FIELD_SIZE) {\n+\t\tchar size_text[24];\n+\t\tif (type == OBJ_BLOB) {\n+\t\t\tunsigned long size;\n+\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n+\t\t\telse\n+\t\t\t\txsnprintf(size_text, sizeof(size_text),\n+\t\t\t\t\t  \"%\" PRIuMAX, (uintmax_t)size);\n+\t\t} else {\n+\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t}\n+\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t} else {\n+\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev));\n+\t}\n+\tbaselen = base->len;\n+\tstrbuf_addstr(base, pathname);\n+\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n+\t\t\t\t   line_termination);\n+\tstrbuf_setlen(base, baselen);\n+\treturn 1;\n+}\n+\n static void init_type(unsigned mode, enum object_type *type)\n {\n \tif (S_ISGITLINK(mode))\n@@ -91,34 +162,24 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n \t\treturn !READ_TREE_RECURSIVE;\n \n-\tif (!(ls_options & LS_NAME_ONLY)) {\n-\t\tif (ls_options & LS_SHOW_SIZE) {\n-\t\t\tchar size_text[24];\n-\t\t\tif (type == OBJ_BLOB) {\n-\t\t\t\tunsigned long size;\n-\t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"BAD\");\n-\t\t\t\telse\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else {\n-\t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n-\t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev),\n-\t\t\t       size_text);\n-\t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev));\n-\t\t}\n+\tif (shown_fields == FIELD_OBJECT_NAME) {\n+\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n+\t\treturn recursive;\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n-\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n-\t\t\t\t   stdout, line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\n+\tif (shown_fields == FIELD_FILE_NAME) {\n+\t\tbaselen = base->len;\n+\t\tstrbuf_addstr(base, pathname);\n+\t\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n+\t\t\t\t\t   stdout, line_termination);\n+\t\tstrbuf_setlen(base, baselen);\n+\t\treturn recursive;\n+\t}\n+\n+\tif (shown_fields >= FIELD_DEFAULT)\n+\t\tshow_default(oid, type, pathname, mode, base);\n+\n \treturn recursive;\n }\n \n@@ -136,12 +197,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\t\tLS_SHOW_TREES),\n \t\tOPT_SET_INT('z', NULL, &line_termination,\n \t\t\t    N_(\"terminate entries with NUL byte\"), 0),\n-\t\tOPT_BIT('l', \"long\", &ls_options, N_(\"include object size\"),\n-\t\t\tLS_SHOW_SIZE),\n-\t\tOPT_BIT(0, \"name-only\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n-\t\tOPT_BIT(0, \"name-status\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n+\t\tOPT_CMDMODE('l', \"long\", &cmdmode, N_(\"include object size\"),\n+\t\t\t    MODE_LONG),\n+\t\tOPT_CMDMODE(0, \"name-only\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"name-status\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"object-only\", &cmdmode, N_(\"list only objects\"),\n+\t\t\t    MODE_OBJECT_ONLY),\n \t\tOPT_SET_INT(0, \"full-name\", &chomp_prefix,\n \t\t\t    N_(\"use full path names\"), 0),\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n@@ -172,6 +235,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tif (get_oid(argv[0], &oid))\n \t\tdie(\"Not a valid object name %s\", argv[0]);\n \n+\tparse_shown_fields();\n+\n \t/*\n \t * show_recursive() rolls its own matching code and is\n \t * generally ignorant of 'struct pathspec'. The magic mask\ndiff --git a/t/t3104-ls-tree-oid.sh b/t/t3104-ls-tree-oid.sh\nnew file mode 100755\nindex 0000000000..6ce62bd769\n--- /dev/null\n+++ b/t/t3104-ls-tree-oid.sh\n@@ -0,0 +1,51 @@\n+#!/bin/sh\n+\n+test_description='git ls-tree objects handling.'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tmkdir -p C &&\n+\ttest_commit C/D.txt &&\n+\tfind *.txt path* \\( -type f -o -type l \\) -print |\n+\txargs git update-index --add &&\n+\ttree=$(git write-tree) &&\n+\techo $tree\n+'\n+\n+test_expect_success 'usage: --object-only' '\n+\tgit ls-tree --object-only $tree >current &&\n+\tgit ls-tree $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with -r' '\n+\tgit ls-tree --object-only -r $tree >current &&\n+\tgit ls-tree -r $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with --abbrev' '\n+\tgit ls-tree --object-only --abbrev=6 $tree >current &&\n+\tgit ls-tree --abbrev=6 $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-only with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-only $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-status with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-status $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --long with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --long $tree\n+'\n+\n+test_done\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445614","messageId":"725c4d01877fe4e03bbe27f50db0b33f07158d63.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 7/9] ls-tree.c: introduce struct \"show_tree_data\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:30Z","receivedAt":"2022-01-06T04:32:14Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"show_tree_data\" is a struct that packages the necessary fields for\n\"show_tree()\". This commit is a pre-prepared commit for supporting\n\"--format\" option and it does not affect any existing functionality.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/ls-tree.c | 50 +++++++++++++++++++++++++++++------------------\n 1 file changed, 31 insertions(+), 19 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 6b5e3ab9dd..12beb02423 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -35,6 +35,14 @@ static unsigned int shown_fields;\n #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n \n+struct show_tree_data {\n+\tunsigned mode;\n+\tenum object_type type;\n+\tconst struct object_id *oid;\n+\tconst char *pathname;\n+\tstruct strbuf *base;\n+};\n+\n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n@@ -99,17 +107,15 @@ static int show_recursive(const char *base, size_t baselen,\n \treturn 0;\n }\n \n-static int show_default(const struct object_id *oid, enum object_type type,\n-\t\t\tconst char *pathname, unsigned mode,\n-\t\t\tstruct strbuf *base)\n+static int show_default(struct show_tree_data *data)\n {\n-\tsize_t baselen = base->len;\n+\tsize_t baselen = data->base->len;\n \n \tif (shown_fields & FIELD_SIZE) {\n \t\tchar size_text[24];\n-\t\tif (type == OBJ_BLOB) {\n+\t\tif (data->type == OBJ_BLOB) {\n \t\t\tunsigned long size;\n-\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\tif (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n \t\t\telse\n \t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -117,18 +123,18 @@ static int show_default(const struct object_id *oid, enum object_type type,\n \t\t} else {\n \t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t}\n-\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t\tprintf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev), size_text);\n \t} else {\n-\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev));\n+\t\tprintf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev));\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n+\tbaselen = data->base->len;\n+\tstrbuf_addstr(data->base, data->pathname);\n+\twrite_name_quoted_relative(data->base->buf,\n \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n \t\t\t\t   line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\tstrbuf_setlen(data->base, baselen);\n \treturn 1;\n }\n \n@@ -152,14 +158,20 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n {\n \tint recursive = 0;\n \tsize_t baselen;\n-\tenum object_type type = OBJ_BLOB;\n+\tstruct show_tree_data data = {\n+\t\t.mode = mode,\n+\t\t.type = OBJ_BLOB,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n \n-\tinit_type(mode, &type);\n+\tinit_type(mode, &data.type);\n \tinit_recursive(base, pathname, &recursive);\n \n-\tif (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n+\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n \t\treturn recursive;\n-\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n+\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n \t\treturn !READ_TREE_RECURSIVE;\n \n \tif (shown_fields == FIELD_OBJECT_NAME) {\n@@ -178,7 +190,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t}\n \n \tif (shown_fields >= FIELD_DEFAULT)\n-\t\tshow_default(oid, type, pathname, mode, base);\n+\t\tshow_default(&data);\n \n \treturn recursive;\n }\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445615","messageId":"7df58483a4f665f9f54b92a97a6b57704f1e7a0d.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 8/9] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:31Z","receivedAt":"2022-01-06T04:32:22Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Add a --format option to ls-tree. It has an existing default output,\nand then --long and --name-only options to emit the default output\nalong with the objectsize and, or to only emit object paths.\n\nRather than add --type-only, --object-only etc. we can just support a\n--format using a strbuf_expand() similar to \"for-each-ref\n--format\". We might still add such options in the future for\nconvenience.\n\nThe --format implementation is slower than the existing code, but this\nchange does not cause any performance regressions. We'll leave the\nexisting show_tree() unchanged, and only run show_tree_fmt() in if\na --format different than the hardcoded built-in ones corresponding to\nthe existing modes is provided.\n\nI.e. something like the \"--long\" output would be much slower with\nthis, mainly due to how we need to allocate various things to do with\nquote.c instead of spewing the output directly to stdout.\n\nThe new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's\nidea and suggestion, this commit makes modifications in terms of the\noriginal discussion on community [1].\n\nHere is the statistics about performance tests:\n\n1. Default format (hitten the builtin formats):\n\n    \"git ls-tree <tree-ish>\" vs \"--format='%(mode) %(type) %(object)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]\n    Range (min … max):    99.2 ms … 113.2 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\n    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]\n    Range (min … max):   100.2 ms … 110.5 ms    29 runs\n\n2. Default format includes object size (hitten the builtin formats):\n\n    \"git ls-tree -l <tree-ish>\" vs \"--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]\n    Range (min … max):   327.5 ms … 348.4 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]\n    Range (min … max):   328.8 ms … 349.4 ms    10 runs\n\nLinks:\n\t[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |  50 ++++++++++-\n builtin/ls-tree.c             | 158 ++++++++++++++++++++++++++++++----\n t/t3105-ls-tree-format.sh     |  55 ++++++++++++\n 3 files changed, 243 insertions(+), 20 deletions(-)\n create mode 100755 t/t3105-ls-tree-format.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex 729370f235..2ca8667c5b 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,9 +10,9 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n-\t    <tree-ish> [<path>...]\n-\n+\t    [--name-only] [--name-status] [--object-only]\n+\t    [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--format=<format>] <tree-ish> [<path>...]\n DESCRIPTION\n -----------\n Lists the contents of a given tree object, like what \"/bin/ls -a\" does\n@@ -79,6 +79,16 @@ OPTIONS\n \tDo not limit the listing to the current working directory.\n \tImplies --full-name.\n \n+--format=<format>::\n+\tA string that interpolates `%(fieldname)` from the result\n+\tbeing shown. It also interpolates `%%` to `%`, and\n+\t`%xx` where `xx`are hex digits interpolates to character\n+\twith hex code `xx`; for example `%00` interpolates to\n+\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n+\tWhen specified, `--format` cannot be combined with other\n+\tformat-altering options, including `--long`, `--name-only`\n+\tand `--object-only`.\n+\n [<path>...]::\n \tWhen paths are given, show them (note that this isn't really raw\n \tpathnames, but rather a list of patterns to match).  Otherwise\n@@ -87,6 +97,9 @@ OPTIONS\n \n Output Format\n -------------\n+\n+Default format:\n+\n         <mode> SP <type> SP <object> TAB <file>\n \n This output format is compatible with what `--index-info --stdin` of\n@@ -105,6 +118,37 @@ quoted as explained for the configuration variable `core.quotePath`\n (see linkgit:git-config[1]).  Using `-z` the filename is output\n verbatim and the line is terminated by a NUL byte.\n \n+Customized format:\n+\n+It's support to print customized format by `%(fieldname)` with `--format` option.\n+For example, if you want to only print the <object> and <file> fields with a\n+JSON style, executing with a specific \"--format\" like\n+\n+        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n+\n+The output format changes to:\n+\n+        {\"object\":\"<object>\", \"file\":\"<file>\"}\n+\n+FIELD NAMES\n+-----------\n+\n+Various values from structured fields can be used to interpolate\n+into the resulting output. For each outputing line, the following\n+names can be used:\n+\n+mode::\n+\tThe mode of the object.\n+type::\n+\tThe type of the object (`blob` or `tree`).\n+object::\n+\tThe name of the object.\n+size[:padded]::\n+\tThe size of the object (\"-\" if it's a tree).\n+\tIt also supports a padded format of size with \"%(size:padded)\".\n+file::\n+\tThe filename of the object.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 12beb02423..d0ba7c4365 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -57,6 +57,12 @@ enum {\n \n static int cmdmode = MODE_UNSPECIFIED;\n \n+static const char *format;\n+static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n+static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n+static const char *name_only_format = \"%(file)\";\n+static const char *object_only_format = \"%(object)\";\n+\n static int parse_shown_fields(void)\n {\n \tif (cmdmode == MODE_NAME_ONLY) {\n@@ -76,6 +82,72 @@ static int parse_shown_fields(void)\n \treturn 1;\n }\n \n+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n+\t\t\t      const enum object_type type, unsigned int padded)\n+{\n+\tif (type == OBJ_BLOB) {\n+\t\tunsigned long size;\n+\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n+\t\t\tdie(_(\"could not get object info about '%s'\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tif (padded)\n+\t\t\tstrbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n+\t\telse\n+\t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n+\t} else if (padded) {\n+\t\tstrbuf_addf(line, \"%7s\", \"-\");\n+\t} else {\n+\t\tstrbuf_addstr(line, \"-\");\n+\t}\n+}\n+\n+static size_t expand_show_tree(struct strbuf *line, const char *start,\n+\t\t\t       void *context)\n+{\n+\tstruct show_tree_data *data = context;\n+\tconst char *end;\n+\tconst char *p;\n+\tunsigned int errlen;\n+\tsize_t len = strbuf_expand_literal_cb(line, start, NULL);\n+\n+\tif (len)\n+\t\treturn len;\n+\tif (*start != '(')\n+\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n+\n+\tend = strchr(start + 1, ')');\n+\tif (!end)\n+\t\tdie(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n+\n+\tlen = end - start + 1;\n+\tif (skip_prefix(start, \"(mode)\", &p)) {\n+\t\tstrbuf_addf(line, \"%06o\", data->mode);\n+\t} else if (skip_prefix(start, \"(type)\", &p)) {\n+\t\tstrbuf_addstr(line, type_name(data->type));\n+\t} else if (skip_prefix(start, \"(size:padded)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 1);\n+\t} else if (skip_prefix(start, \"(size)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 0);\n+\t} else if (skip_prefix(start, \"(object)\", &p)) {\n+\t\tstrbuf_add_unique_abbrev(line, data->oid, abbrev);\n+\t} else if (skip_prefix(start, \"(file)\", &p)) {\n+\t\tconst char *name = data->base->buf;\n+\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n+\t\tstruct strbuf quoted = STRBUF_INIT;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(data->base, data->pathname);\n+\t\tname = relative_path(data->base->buf, prefix, &sb);\n+\t\tquote_c_style(name, &quoted, NULL, 0);\n+\t\tstrbuf_addbuf(line, &quoted);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&quoted);\n+\t} else {\n+\t\terrlen = (unsigned long)len;\n+\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n+\t}\n+\treturn len;\n+}\n+\n static int show_recursive(const char *base, size_t baselen,\n \t\t\t  const char *pathname)\n {\n@@ -107,6 +179,52 @@ static int show_recursive(const char *base, size_t baselen,\n \treturn 0;\n }\n \n+static void init_recursive(struct strbuf *base, const char *pathname,\n+\t\t\t\tint *recursive)\n+{\n+\tif (show_recursive(base->buf, base->len, pathname))\n+\t\t*recursive = READ_TREE_RECURSIVE;\n+}\n+\n+static void init_type(unsigned mode, enum object_type *type)\n+{\n+\tif (S_ISGITLINK(mode))\n+\t\t*type = OBJ_COMMIT;\n+\telse if (S_ISDIR(mode))\n+\t\t*type = OBJ_TREE;\n+}\n+\n+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n+\t\t\t const char *pathname, unsigned mode, void *context)\n+{\n+\tsize_t baselen;\n+\tint recursive = 0;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tstruct show_tree_data data = {\n+\t\t.mode = mode,\n+\t\t.type = OBJ_BLOB,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n+\n+\tinit_type(mode, &data.type);\n+\tinit_recursive(base, pathname, &recursive);\n+\n+\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n+\t\treturn recursive;\n+\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n+\t\treturn !READ_TREE_RECURSIVE;\n+\n+\tbaselen = base->len;\n+\tstrbuf_expand(&line, format, expand_show_tree, &data);\n+\tstrbuf_addch(&line, line_termination);\n+\tfwrite(line.buf, line.len, 1, stdout);\n+\tstrbuf_release(&line);\n+\tstrbuf_setlen(base, baselen);\n+\treturn recursive;\n+}\n+\n static int show_default(struct show_tree_data *data)\n {\n \tsize_t baselen = data->base->len;\n@@ -138,21 +256,6 @@ static int show_default(struct show_tree_data *data)\n \treturn 1;\n }\n \n-static void init_type(unsigned mode, enum object_type *type)\n-{\n-\tif (S_ISGITLINK(mode))\n-\t\t*type = OBJ_COMMIT;\n-\telse if (S_ISDIR(mode))\n-\t\t*type = OBJ_TREE;\n-}\n-\n-static void init_recursive(struct strbuf *base, const char *pathname,\n-\t\t\t\tint *recursive)\n-{\n-\tif (show_recursive(base->buf, base->len, pathname))\n-\t\t*recursive = READ_TREE_RECURSIVE;\n-}\n-\n static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n@@ -200,6 +303,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tstruct object_id oid;\n \tstruct tree *tree;\n \tint i, full_tree = 0;\n+\tread_tree_fn_t fn = show_tree;\n \tconst struct option ls_tree_options[] = {\n \t\tOPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n \t\t\tLS_TREE_ONLY),\n@@ -222,6 +326,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n \t\t\t N_(\"list entire tree; not just current directory \"\n \t\t\t    \"(implies --full-name)\")),\n+\t\tOPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n+\t\t\t     N_(\"format to use for the output\"),\n+\t\t\t     PARSE_OPT_NONEG),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_END()\n \t};\n@@ -242,6 +349,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n \t\tls_options |= LS_SHOW_TREES;\n \n+\tif (format && cmdmode)\n+\t\tusage_msg_opt(\n+\t\t\t_(\"--format can't be combined with other format-altering options\"),\n+\t\t\tls_tree_usage, ls_tree_options);\n \tif (argc < 1)\n \t\tusage_with_options(ls_tree_usage, ls_tree_options);\n \tif (get_oid(argv[0], &oid))\n@@ -265,6 +376,19 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \ttree = parse_tree_indirect(&oid);\n \tif (!tree)\n \t\tdie(\"not a tree object\");\n-\treturn !!read_tree(the_repository, tree,\n-\t\t\t   &pathspec, show_tree, NULL);\n+\n+\t/*\n+\t * The generic show_tree_fmt() is slower than show_tree(), so\n+\t * take the fast path if possible.\n+\t */\n+\tif (format &&\n+\t    (!strcmp(format, default_format) ||\n+\t     !strcmp(format, long_format) ||\n+\t     !strcmp(format, name_only_format) ||\n+\t     !strcmp(format, object_only_format)))\n+\t\tfn = show_tree;\n+\telse if (format)\n+\t\tfn = show_tree_fmt;\n+\n+\treturn !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n }\ndiff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh\nnew file mode 100755\nindex 0000000000..92b4d240e8\n--- /dev/null\n+++ b/t/t3105-ls-tree-format.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='ls-tree --format'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'ls-tree --format usage' '\n+\ttest_expect_code 129 git ls-tree --format=fmt -l &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-only &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-status &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --object-only\n+'\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\ttest_commit dir/sub-file &&\n+\ttest_commit top-file\n+'\n+\n+test_ls_tree_format () {\n+\tformat=$1 &&\n+\topts=$2 &&\n+\tshift 2 &&\n+\tgit ls-tree $opts -r HEAD >expect.raw &&\n+\tsed \"s/^/> /\" >expect <expect.raw &&\n+\tgit ls-tree --format=\"> $format\" -r HEAD >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'ls-tree --format=<default-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object)%x09%(file)\" \\\n+\t\t\"\"\n+'\n+\n+test_expect_success 'ls-tree --format=<long-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n+\t\t\"--long\"\n+'\n+\n+test_expect_success 'ls-tree --format=<name-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(file)\" \\\n+\t\t\"--name-only\"\n+'\n+\n+test_expect_success 'ls-tree --format=<object-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(object)\" \\\n+\t\t\"--object-only\"\n+'\n+\n+test_done\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445616","messageId":"8dafb2b3779715aa277eb825a94af87b72f3e093.1641440700.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-06T04:31:32Z","receivedAt":"2022-01-06T04:32:24Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"A convenient way to pad strings is to use something like\n`strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n\nHowever, the Coccinelle rule that forbids a format `\"%s\"` with a\nconstant string argument cast too wide a net, and also forbade such\npadding.\n\nThe original rule was introduced by commit:\n\n    https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n contrib/coccinelle/strbuf.cocci | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\nindex d9ada69b43..2d6e0f58fc 100644\n--- a/contrib/coccinelle/strbuf.cocci\n+++ b/contrib/coccinelle/strbuf.cocci\n@@ -44,7 +44,7 @@ struct strbuf *SBP;\n \n @@\n expression E1, E2;\n-format F =~ \"s\";\n+format F =~ \"^s$\";\n @@\n - strbuf_addf(E1, \"%@F@\", E2);\n + strbuf_addstr(E1, E2);\n-- \n2.33.0.rc1.1794.g2ae0a9cb82\n\n"},{"id":"445651","messageId":"xmqq5yqwd266.fsf@gitster.g","threadId":"57172","inReplyTo":"75503c41a7e2f3fdbb59ce3568853049b55a2d3b.1641440700.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-06T20:44:49Z","receivedAt":"2022-01-06T20:44:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Teng Long <dyroneteng@gmail.com> writes:\n\n> +static void init_type(unsigned mode, enum object_type *type)\n> +{\n> +\tif (S_ISGITLINK(mode))\n> +\t\t*type = OBJ_COMMIT;\n> +\telse if (S_ISDIR(mode))\n> +\t\t*type = OBJ_TREE;\n> +}\n> +\n> +static void init_recursive(struct strbuf *base, const char *pathname,\n> +\t\t\t\tint *recursive)\n> +{\n> +\tif (show_recursive(base->buf, base->len, pathname))\n> +\t\t*recursive = READ_TREE_RECURSIVE;\n> +}\n> +\n>  static int show_tree(const struct object_id *oid, struct strbuf *base,\n>  \t\tconst char *pathname, unsigned mode, void *context)\n>  {\n> -\tint retval = 0;\n> +\tint recursive = 0;\n>  \tsize_t baselen;\n>  \tenum object_type type = OBJ_BLOB;\n>  \n> -\tif (S_ISGITLINK(mode)) {\n> -\t\ttype = OBJ_COMMIT;\n> -\t} else if (S_ISDIR(mode)) {\n> -\t\tif (show_recursive(base->buf, base->len, pathname)) {\n> -\t\t\tretval = READ_TREE_RECURSIVE;\n> -\t\t\tif (!(ls_options & LS_SHOW_TREES))\n> -\t\t\t\treturn retval;\n> -\t\t}\n> -\t\ttype = OBJ_TREE;\n> -\t}\n> -\telse if (ls_options & LS_TREE_ONLY)\n> -\t\treturn 0;\n> +\tinit_type(mode, &type);\n\nWhat this one is doing sounds more like setting the type variable\nbased on the mode bits, and doing only half a job at it.  The name\n\"init\" does not sound like a good match to what it does.\n\nIf we make it a separate function, we probably should add the \"else\"\nclause to set *type to OBJ_BLOB there, so that the caller does not\nsay \"we'd assume it is BLOB initially, but tweak it based on mode\nbits\".\n\nI.e.\n\n\ttype = get_type(mode);\n\nwhere\n\n\tstatic enum object_type get_type(unsigned int mode)\n\t{\n\t\treturn (S_ISGITLINK(mode) \n\t\t        ? OBJ_COMMIT\n\t\t\t: S_ISDIR(mode)\n\t\t\t? OBJ_TREE\n\t\t\t: OBJ_BLOB);\n\t}\n\nor something like that, perhaps?  But I think open-coding the whole\nthing, after losing the \"We assume BLOB\" initialization, would be\nmuch easier to follow, i.e.\n\n\tif (S_ISGITLINK(mode))\n\t\ttype = OBJ_COMMIT;\n\telse if (S_ISDIR(mode))\n\t\ttype = OBJ_TREE;\n\telse\n\t\ttype = OBJ_BLOB;\n\nwithout adding init_type() helper function.\n\n> +\tinit_recursive(base, pathname, &recursive);\n\nThis is even less readable.  In the original, it was clear that we\nonly call show_recursive() on a path that is a true directory; we\nnow seem to unconditionally make a call to it.  Is that intended?\n\n\tSide note.  show_recursive() has a confusing name; it does\n\tnot show anything---it only decides if we want to go\n\trecursive.\n\nAt least, losing the \"we assume recursive is 0\" upfront in the\nvariable declaration and writing\n\n\tif (type == OBJ_TREE && show_recursive(...))\n\t\trecursive = READ_TREE_RECURSIVE;\n\telse\n\t\trecursive = 0;\n\nhere, without introducing init_recursive(), would make it easier to\nfollow.  If we really want to add a new function, perhaps\n\n\trecursive = get_recursive(type, base, pathname);\n \nwhere\n\n\tstatic int get_recursive(enum object_type type,\n\t\t\t\t struct strbuf *base, const char *pathname)\n\t{\n\t\tif (type == OBJ_TREE && show_recursive(...))\n\t\t\treturn READ_TREE_RECURSIVE;\n\t\telse\n\t\t\treturn 0;\n\t}\n\nbut I fail to see the point of doing so; open-coded 4 lines here\nwould make the flow of thought much better to me.\n\nIn any case, I think your splitting the original into \"this is about\ntype\" and \"this is about the recursive bit\" is a good idea to help\nmaking the resulting code easier to follow.\n\n> +\tif (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n> +\t\treturn recursive;\n\nWe are looking at an entry that is a directory.  We are running in\nrecursive mode.  And we are not told to show the directory itself in\nthe output.  We skip the rest of the function, which is about to\nshow this single entry.  Makes sense.\n\n\n> +\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n> +\t\treturn !READ_TREE_RECURSIVE;\n\nNegation of a non-zero integer constant is 0, so it is the same as\nthe original that returned 0, but I am not sure if it is enhancing\nor hurting readability of the code.  The user of the value, in\ntree.c::read_tree_at(), knows that the possible and valid values are\n0 and READ_TREE_RECURSIVE, so returning 0 would probably be a better\nidea.  After all, the initializer in the original for the variable\ndefinition of \"retval\" used \"0\", not \"!READ_TREE_RECURSIVE\".\n\nThe name \"recursive\" is much more specific than the overly generic\n\"retval\".  Its value is to be consumed by read_tree_at(), i.e. our\ncaller, to decide if we want it to recurse into the contents of the\ndirectory.  I would have called it \"recurse\" (or even \"to_recurse\"),\nif I were doing this change, though.\n"},{"id":"445717","messageId":"nycvar.QRO.7.76.6.2201071401240.339@tvgsbejvaqbjf.bet","threadId":"57172","inReplyTo":"8dafb2b3779715aa277eb825a94af87b72f3e093.1641440700.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-07T13:03:37Z","receivedAt":"2022-01-07T13:04:03Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Teng,\n\nOn Thu, 6 Jan 2022, Teng Long wrote:\n\n> A convenient way to pad strings is to use something like\n> `strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n>\n> However, the Coccinelle rule that forbids a format `\"%s\"` with a\n> constant string argument cast too wide a net, and also forbade such\n> padding.\n>\n> The original rule was introduced by commit:\n>\n>     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n\nDoing this in 9/9 is too late, by this time you already introduced the\ncode site that requires this workaround.\n\nAt the same time, I wonder why you want to defend spinning up the\nfull-blown `printf()` machinery just to pad text that you can easily pad\nyourself. It sounds like a lot of trouble to me to introduce this patch\nand then use an uncommon method to pad a fixed string at runtime. Too much\ntrouble for my liking.\n\nCiao,\nDscho\n\n>\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n> ---\n>  contrib/coccinelle/strbuf.cocci | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\n> index d9ada69b43..2d6e0f58fc 100644\n> --- a/contrib/coccinelle/strbuf.cocci\n> +++ b/contrib/coccinelle/strbuf.cocci\n> @@ -44,7 +44,7 @@ struct strbuf *SBP;\n>\n>  @@\n>  expression E1, E2;\n> -format F =~ \"s\";\n> +format F =~ \"^s$\";\n>  @@\n>  - strbuf_addf(E1, \"%@F@\", E2);\n>  + strbuf_addstr(E1, E2);\n> --\n> 2.33.0.rc1.1794.g2ae0a9cb82\n>\n>\n"},{"id":"445817","messageId":"20220110082254.54400-1-dyroneteng@gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201071401240.339@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-10T08:22:54Z","receivedAt":"2022-01-10T08:23:01Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\nI am not sure whether I have sent the email repeatedly, because it is\nnot shown on mailist. If so, sorry to bother you.\n\nJohannes Schindelin writes:\n\n> Doing this in 9/9 is too late, by this time you already introduced the\n> code site that requires this workaround.\n\nYes, you are correct.\nWill fixed if the patch is still remained to next one. \n\n> At the same time, I wonder why you want to defend spinning up the\n> full-blown `printf()` machinery just to pad text that you can easily pad\n> yourself. It sounds like a lot of trouble to me to introduce this patch\n> and then use an uncommon method to pad a fixed string at runtime. Too much\n> trouble for my liking.\n\nI may not have explained it clearly in the cover. Sorry for that, I'm going\nto explain some more here, please correct me if there is something wrong or\nthe method is not recommended or is not best practice in community.\n\nFirstly, the patch needs to be introduced I think and it has nothing to do\nwith using \"      -\" or \"%7s\" here, because the fix recommandation is not\naccurate in terms of the \"static-analysis\" report if someone just uses the\n\"addf\" api:\n\n-               strbuf_addf(line, \"%7s\", \"-\");\n+               strbuf_addstr(line, \"-\");\n\nThey have different execution results and bring confusion to people. \n\nThen secondly, about the using \"strbuf_addf(line, \"%7s\" , \"-\");\" or\n\"strbuf_addstr(line, \"      -\");\". I think you prefer the later and I prefer\nthe former, right? (I'm not a native English speaker, so I just want to make\nsure I understand whole your meannings).\n\nIf I understand everything correctly so far, it's good :)\n\nAs I metioned in a previous reply [1], I think there is no performance\nissue here..\n\nWhy I prefer more of the former that is because, for the single line,\nit's more readable I think. Maybe it's not going to modify very often,\nbut If someone want to know what this is, might have to do a count. So\nI don't think this is any more readable than \"%7s\".\n\nHere's what I think and looking forward to your reply.\n\nThanks.\n\n\n[1] https://public-inbox.org/git/CADMgQSRxko6nC0zfDiVVfL2ZkdQVbBq0s59Er+6Nmg9vz4uJKQ@mail.gmail.com/\n"},{"id":"445823","messageId":"nycvar.QRO.7.76.6.2201101346090.339@tvgsbejvaqbjf.bet","threadId":"57172","inReplyTo":"20220110082254.54400-1-dyroneteng@gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2022-01-10T12:49:13Z","receivedAt":"2022-01-10T12:50:18Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Teng,\n\nOn Mon, 10 Jan 2022, Teng Long wrote:\n\n> [...] about the using \"strbuf_addf(line, \"%7s\" , \"-\");\" or\n> \"strbuf_addstr(line, \"      -\");\". [...]\n>\n> Why I prefer more of the former that is because, for the single line,\n> it's more readable I think.\n\nI strongly disagree. Using a format requires the reader to interpret a\n`printf()` format, to remember (if they ever knew) the rules about padding\nwith `%<number>s` formats, and then to satisfy themselves that the result\nis correct.\n\nThat's quite the cognitive load you put on the reader for something as\ntrivial as \"      -\".\n\nNot a fan,\nJohannes\n"},{"id":"445832","messageId":"CADMgQSQWZRG2tCQ6tfBXMdyaVEzmWi0-5qCM3N1+YeN5nVMoGw@mail.gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201101346090.339@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-10T14:40:40Z","receivedAt":"2022-01-10T14:40:56Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"> I strongly disagree. Using a format requires the reader to interpret a\n> `printf()` format, to remember (if they ever knew) the rules about padding\n> with `%<number>s` formats, and then to satisfy themselves that the result\n> is correct.\n>\n> That's quite the cognitive load you put on the reader for something as\n> trivial as \"      -\".\n\n> Not a fan,\n> Johannes\n\nOk. I will modify the next patch according to your opinion, I just\nhope to understand the problems and make better contributions in the\nfuture.\n\nThanks.\n\nOn Mon, Jan 10, 2022 at 8:49 PM Johannes Schindelin\n<Johannes.Schindelin@gmx.de> wrote:\n>\n> Hi Teng,\n>\n> On Mon, 10 Jan 2022, Teng Long wrote:\n>\n> > [...] about the using \"strbuf_addf(line, \"%7s\" , \"-\");\" or\n> > \"strbuf_addstr(line, \"      -\");\". [...]\n> >\n> > Why I prefer more of the former that is because, for the single line,\n> > it's more readable I think.\n>\n> I strongly disagree. Using a format requires the reader to interpret a\n> `printf()` format, to remember (if they ever knew) the rules about padding\n> with `%<number>s` formats, and then to satisfy themselves that the result\n> is correct.\n>\n> That's quite the cognitive load you put on the reader for something as\n> trivial as \"      -\".\n>\n> Not a fan,\n> Johannes\n"},{"id":"445845","messageId":"xmqqh7ab1nzz.fsf@gitster.g","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201101346090.339@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-10T17:47:44Z","receivedAt":"2022-01-10T17:47:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> [...] about the using \"strbuf_addf(line, \"%7s\" , \"-\");\" or\n>> \"strbuf_addstr(line, \"      -\");\". [...]\n>>\n>> Why I prefer more of the former that is because, for the single line,\n>> it's more readable I think.\n>\n> I strongly disagree. Using a format requires the reader to interpret a\n> `printf()` format, to remember (if they ever knew) the rules about padding\n> with `%<number>s` formats, and then to satisfy themselves that the result\n> is correct.\n\nBoth \"more readable\" and \"cognitive load\" are quite subjective.\n\nFWIW, I have a slight preference to the former because I do not have\nto count the whitespaces to figure out at which column the construct\nis trying to align to.  Most of the time, however, I may not deeply\ncare if the thing is aligned exactly, and \"     -\" might be easier\nto scan and getting alarmed by seeing \"%7s\" to wonder if there is\nsomething unusual going on.  When I am reading not for finding out\nthe precise output format but for general correctness, bunch of \nunknown number of spaces followed by a dash might be easier to see.\n\nBut once you know the language, \"%7s\" is not so alarming, and it\ndoes make it easier to see both for casual scanning and for counting\ncolumns.  It also is likely that those who know the language would\nmake more efficient developers to fix and/or enhance the code, so I\nprefer to optimize the code for them.\n\n"},{"id":"445851","messageId":"220110.86ilurzcxo.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"8dafb2b3779715aa277eb825a94af87b72f3e093.1641440700.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-10T18:00:59Z","receivedAt":"2022-01-10T18:02:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 06 2022, Teng Long wrote:\n\n> A convenient way to pad strings is to use something like\n> `strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n>\n> However, the Coccinelle rule that forbids a format `\"%s\"` with a\n> constant string argument cast too wide a net, and also forbade such\n> padding.\n>\n> The original rule was introduced by commit:\n>\n>     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n\nLet's refer to commits like this:\n\n    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n\n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n> ---\n>  contrib/coccinelle/strbuf.cocci | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\n> index d9ada69b43..2d6e0f58fc 100644\n> --- a/contrib/coccinelle/strbuf.cocci\n> +++ b/contrib/coccinelle/strbuf.cocci\n> @@ -44,7 +44,7 @@ struct strbuf *SBP;\n>  \n>  @@\n>  expression E1, E2;\n> -format F =~ \"s\";\n> +format F =~ \"^s$\";\n>  @@\n>  - strbuf_addf(E1, \"%@F@\", E2);\n>  + strbuf_addstr(E1, E2);\n\nThat file currently has:\n\n     18:format F =~ \"s\";\n     26:format F =~ \"s\";\n     47:format F =~ \"s\";\n\nIf we're fixing these let's fix the other logic errors as well.\n"},{"id":"445852","messageId":"220110.86ee5fzcsi.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201101346090.339@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-10T18:02:47Z","receivedAt":"2022-01-10T18:05:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jan 10 2022, Johannes Schindelin wrote:\n\n> Hi Teng,\n>\n> On Mon, 10 Jan 2022, Teng Long wrote:\n>\n>> [...] about the using \"strbuf_addf(line, \"%7s\" , \"-\");\" or\n>> \"strbuf_addstr(line, \"      -\");\". [...]\n>>\n>> Why I prefer more of the former that is because, for the single line,\n>> it's more readable I think.\n>\n> I strongly disagree. Using a format requires the reader to interpret a\n> `printf()` format, to remember (if they ever knew) the rules about padding\n> with `%<number>s` formats, and then to satisfy themselves that the result\n> is correct.\n>\n> That's quite the cognitive load you put on the reader for something as\n> trivial as \"      -\".\n>\n> Not a fan,\n> Johannes\n\nI think you can argue that, but saying that this series must change that\nexisting \"%7s\" format just because it happened to trip over an existin\ncoccinelle rule as code was changed from printf() to strbuf_addf() is\ngoing overboard. \n\nAlso, the ls-tree output has existing alignment issues, and the\ndocumentation says:\n\n    \"right-justified with minimum width of 7 characters\"\n\nSo I'd think we'd want to keep the %7s, and in some future change change\nthat format to be dynamic so we'd align things properly if some fields\nwere longer than 7 characters.\n"},{"id":"445854","messageId":"xmqqwnj7zbgt.fsf@gitster.g","threadId":"57172","inReplyTo":"nycvar.QRO.7.76.6.2201071401240.339@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-10T18:34:26Z","receivedAt":"2022-01-10T18:36:21Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n>> The original rule was introduced by commit:\n>>\n>>     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n>\n> Doing this in 9/9 is too late, by this time you already introduced the\n> code site that requires this workaround.\n\nGood to point this out.  Thanks.\n"},{"id":"445870","messageId":"CAN0heSoTRpymQh0w2pfJcy+16nTHa6MwY+BAqkvfAmQsnj2ihg@mail.gmail.com","threadId":"57172","inReplyTo":"7df58483a4f665f9f54b92a97a6b57704f1e7a0d.1641440700.git.dyroneteng@gmail.com","subject":"Re: [PATCH v9 8/9] ls-tree.c: introduce \"--format\" option","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2022-01-10T19:41:28Z","receivedAt":"2022-01-10T19:41:47Z","isPatch":true,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"Hi Teng,\n\nOn Fri, 7 Jan 2022 at 06:34, Teng Long <dyroneteng@gmail.com> wrote:\n\n> +--format=<format>::\n> +       A string that interpolates `%(fieldname)` from the result\n> +       being shown. It also interpolates `%%` to `%`, and\n> +       `%xx` where `xx`are hex digits interpolates to character\n\nAbove, there is a missing space just before \"are\". That causes the\nmanpage to render a little bit funny.\n\n> +       with hex code `xx`; for example `%00` interpolates to\n> +       `\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n> +       When specified, `--format` cannot be combined with other\n> +       format-altering options, including `--long`, `--name-only`\n> +       and `--object-only`.\n> +\n\n> +Customized format:\n> +\n> +It's support to print customized format by `%(fieldname)` with `--format` option.\n\nI had to re-read this to understand. How about the following?\n\n    It is possible to print in a custom format by using the `--format`\n    option, which is able to interpolate different fields using a\n    `%(fieldname)` notation.\n\nJust a suggestion. Feel free to tweak or ignore. :-)\n\n> +For example, if you want to only print the <object> and <file> fields with a\n> +JSON style, executing with a specific \"--format\" like\n> +\n> +        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n> +\n> +The output format changes to:\n> +\n> +        {\"object\":\"<object>\", \"file\":\"<file>\"}\n> +\n\nNice!\n\nMartin\n"},{"id":"445905","messageId":"CADMgQSQpX2Dn2q9_y7=pat3s-xUtLaO0iVgrOus3-rvUXQReDg@mail.gmail.com","threadId":"57172","inReplyTo":"xmqq5yqwd266.fsf@gitster.g","subject":"Re: [PATCH v9 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-11T09:14:23Z","receivedAt":"2022-01-11T09:14:44Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Fri, Jan 7, 2022 at 4:44 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Teng Long <dyroneteng@gmail.com> writes:\n>\n\n> What this one is doing sounds more like setting the type variable\n> based on the mode bits, and doing only half a job at it.  The name\n> \"init\" does not sound like a good match to what it does.\n>\n> If we make it a separate function, we probably should add the \"else\"\n> clause to set *type to OBJ_BLOB there, so that the caller does not\n> say \"we'd assume it is BLOB initially, but tweak it based on mode\n> bits\".\n>\n> I.e.\n>\n>         type = get_type(mode);\n>\n> where\n>\n>         static enum object_type get_type(unsigned int mode)\n>         {\n>                 return (S_ISGITLINK(mode)\n>                         ? OBJ_COMMIT\n>                         : S_ISDIR(mode)\n>                         ? OBJ_TREE\n>                         : OBJ_BLOB);\n>         }\n\n> or something like that, perhaps?  But I think open-coding the whole\n> thing, after losing the \"We assume BLOB\" initialization, would be\n> much easier to follow, i.e.\n>\n>         if (S_ISGITLINK(mode))\n>                 type = OBJ_COMMIT;\n>         else if (S_ISDIR(mode))\n>                 type = OBJ_TREE;\n>         else\n>                 type = OBJ_BLOB;\n>\n> without adding init_type() helper function.\n>\n> > +     init_recursive(base, pathname, &recursive);\n>\n> This is even less readable.  In the original, it was clear that we\n> only call show_recursive() on a path that is a true directory; we\n> now seem to unconditionally make a call to it.  Is that intended?\n>\n>         Side note.  show_recursive() has a confusing name; it does\n>         not show anything---it only decides if we want to go\n>         recursive.\n>\n> At least, losing the \"we assume recursive is 0\" upfront in the\n> variable declaration and writing\n>\n>         if (type == OBJ_TREE && show_recursive(...))\n>                 recursive = READ_TREE_RECURSIVE;\n>         else\n>                 recursive = 0;\n>\n> here, without introducing init_recursive(), would make it easier to\n> follow.  If we really want to add a new function, perhaps\n>\n>         recursive = get_recursive(type, base, pathname);\n>\n> where\n>\n>         static int get_recursive(enum object_type type,\n>                                  struct strbuf *base, const char *pathname)\n>         {\n>                 if (type == OBJ_TREE && show_recursive(...))\n>                         return READ_TREE_RECURSIVE;\n>                 else\n>                         return 0;\n>         }\n>\n> but I fail to see the point of doing so; open-coded 4 lines here\n> would make the flow of thought much better to me.\n>\n> In any case, I think your splitting the original into \"this is about\n> type\" and \"this is about the recursive bit\" is a good idea to help\n> making the resulting code easier to follow.\n>\n> > +     if (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n> > +             return recursive;\n>\n> We are looking at an entry that is a directory.  We are running in\n> recursive mode.  And we are not told to show the directory itself in\n> the output.  We skip the rest of the function, which is about to\n> show this single entry.  Makes sense.\n>\n>\n> > +     if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n> > +             return !READ_TREE_RECURSIVE;\n>\n> Negation of a non-zero integer constant is 0, so it is the same as\n> the original that returned 0, but I am not sure if it is enhancing\n> or hurting readability of the code.  The user of the value, in\n> tree.c::read_tree_at(), knows that the possible and valid values are\n> 0 and READ_TREE_RECURSIVE, so returning 0 would probably be a better\n> idea.  After all, the initializer in the original for the variable\n> definition of \"retval\" used \"0\", not \"!READ_TREE_RECURSIVE\".\n>\n> The name \"recursive\" is much more specific than the overly generic\n> \"retval\".  Its value is to be consumed by read_tree_at(), i.e. our\n> caller, to decide if we want it to recurse into the contents of the\n> directory.  I would have called it \"recurse\" (or even \"to_recurse\"),\n> if I were doing this change, though.\n\nThanks, will apply in the next patch.\n"},{"id":"445906","messageId":"CADMgQSSjSZALdbPhaOSSP15=FszKy4vLtnGBDLeOqPuzmHDgSA@mail.gmail.com","threadId":"57172","inReplyTo":"CAN0heSoTRpymQh0w2pfJcy+16nTHa6MwY+BAqkvfAmQsnj2ihg@mail.gmail.com","subject":"Re: [PATCH v9 8/9] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-11T09:34:53Z","receivedAt":"2022-01-11T09:35:08Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Tue, Jan 11, 2022 at 3:41 AM Martin Ågren <martin.agren@gmail.com> wrote:\n\n> Above, there is a missing space just before \"are\". That causes the\n> manpage to render a little bit funny.\n>\n> > +       with hex code `xx`; for example `%00` interpolates to\n> > +       `\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n> > +       When specified, `--format` cannot be combined with other\n> > +       format-altering options, including `--long`, `--name-only`\n> > +       and `--object-only`.\n> > +\n\nThanks for reviewing.  will be fixed in the next patch.\n\n> > +Customized format:\n> > +\n> > +It's support to print customized format by `%(fieldname)` with `--format` option.\n>\n> I had to re-read this to understand. How about the following?\n>\n>     It is possible to print in a custom format by using the `--format`\n>     option, which is able to interpolate different fields using a\n>     `%(fieldname)` notation.\n>\n> Just a suggestion. Feel free to tweak or ignore. :-)\n\nYours reads more smoothly than mine, it will be applied in next patch.\n\nThanks.\n"},{"id":"445909","messageId":"CADMgQSQtAB_rX7TPcqbpDoozjghnHRq=HghGYQrmFVzWbB23Aw@mail.gmail.com","threadId":"57172","inReplyTo":"220110.86ilurzcxo.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-11T10:37:27Z","receivedAt":"2022-01-11T10:37:41Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Tue, Jan 11, 2022 at 2:02 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> > The original rule was introduced by commit:\n> >\n> >     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n>\n> Let's refer to commits like this:\n>\n>     28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n\nOK, I will.\n\n> That file currently has:\n>\n>      18:format F =~ \"s\";\n>      26:format F =~ \"s\";\n>      47:format F =~ \"s\";\n>\n> If we're fixing these let's fix the other logic errors as well.\n\nThanks for the reminding， they'll be applied in the next patch.\n"},{"id":"445930","messageId":"Yd2zXOrf+4qX8VLD@nand.local","threadId":"57172","inReplyTo":"220110.86ilurzcxo.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-01-11T16:42:04Z","receivedAt":"2022-01-11T16:42:07Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> On Thu, Jan 06 2022, Teng Long wrote:\n> > The original rule was introduced by commit:\n> >\n> >     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n>\n> Let's refer to commits like this:\n>\n>     28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n\nI find it helpful to have an alias like:\n\n    $ git config alias.ll\n    !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short\n\nin my $HOME/.gitconfig so that I can easily format commits in the\nstandard way.\n\nI think that this alias came from Peff, but I can't remember.\n\nThanks,\nTaylor\n"},{"id":"445953","messageId":"78aa8c28-d78b-7dee-6616-7708c363a244@web.de","threadId":"57172","inReplyTo":"Yd2zXOrf+4qX8VLD@nand.local","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-11T19:06:00Z","receivedAt":"2022-01-11T19:06:38Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 11.01.22 um 17:42 schrieb Taylor Blau:\n> On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> On Thu, Jan 06 2022, Teng Long wrote:\n>>> The original rule was introduced by commit:\n>>>\n>>>     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n>>\n>> Let's refer to commits like this:\n>>\n>>     28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n>\n> I find it helpful to have an alias like:\n>\n>     $ git config alias.ll\n>     !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short\n>\n> in my $HOME/.gitconfig so that I can easily format commits in the\n> standard way.\n\nYou can shorten \"--pretty='tformat:%h (%s, %ad)' --date=short\" to\n\"--pretty=reference\" or \"--format=reference\".  For me that's easy enough\nto remember that I don't need an alias.\n\nSilly question, going further off-topic: What's \"git always\" doing?\n\nRené\n"},{"id":"445964","messageId":"Yd3kZv4Oym1h7zap@nand.local","threadId":"57172","inReplyTo":"78aa8c28-d78b-7dee-6616-7708c363a244@web.de","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Taylor Blau","fromEmail":"me@ttaylorr.com","sentAt":"2022-01-11T20:11:18Z","receivedAt":"2022-01-11T20:11:22Z","isPatch":true,"sender":{"key":"me@ttaylorr.com","avatar":"https://avatars.githubusercontent.com/u/301000140?v=4"},"body":"On Tue, Jan 11, 2022 at 08:06:00PM +0100, René Scharfe wrote:\n> Am 11.01.22 um 17:42 schrieb Taylor Blau:\n> > I find it helpful to have an alias like:\n> >\n> >     $ git config alias.ll\n> >     !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short\n> >\n> > in my $HOME/.gitconfig so that I can easily format commits in the\n> > standard way.\n>\n> You can shorten \"--pretty='tformat:%h (%s, %ad)' --date=short\" to\n> \"--pretty=reference\" or \"--format=reference\".  For me that's easy enough\n> to remember that I don't need an alias.\n\nAh, of course. Peff's copy likely predates `--pretty=reference`, and I\ninherited the cruft from him. Your suggestion has the nice benefit of\ncolorizing the output when going to the terminal.\n\n> Silly question, going further off-topic: What's \"git always\" doing?\n\nOops, I should have mentioned. It's another alias to ensure that the\nfollowing command is always run in a Git repository (either the current\none or a hand-picked default):\n\n    $ git config alias.always\n    !git rev-parse 2>/dev/null || cd ~/src/git; git\n\nI often read mail out of my home directory, and the above works with my\n`:Git` command in Vim (which passes its arguments to `git always` and\ninserts the result back into my buffer). That way I don't have to first\n`:cd ~/src/git` and then `:Git ll xyz`, I can just `:Git ll xyz` and it\ndoes what I meant most of the time.\n\nThanks,\nTaylor\n"},{"id":"445971","messageId":"220111.86iluqxaz9.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"Yd2zXOrf+4qX8VLD@nand.local","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-11T20:39:18Z","receivedAt":"2022-01-11T20:40:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 11 2022, Taylor Blau wrote:\n\n> On Mon, Jan 10, 2022 at 07:00:59PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> On Thu, Jan 06 2022, Teng Long wrote:\n>> > The original rule was introduced by commit:\n>> >\n>> >     https://github.com/git/git/commit/28c23cd4c3902449aff72cb9a4a703220be0d6ac\n>>\n>> Let's refer to commits like this:\n>>\n>>     28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n>\n> I find it helpful to have an alias like:\n>\n>     $ git config alias.ll\n>     !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short\n>\n> in my $HOME/.gitconfig so that I can easily format commits in the\n> standard way.\n>\n> I think that this alias came from Peff, but I can't remember.\n\nNowadays you can do this as:\n\n    git show -s --pretty=reference\n\nSee Documentation/SubmittingPatches\n\nI use:\n\n    $ git help reference\n    'reference' is aliased to '!git --no-pager log --pretty=reference -1'\n"},{"id":"446084","messageId":"CADMgQST=4mmJSMYVY5i_ka_ONMDFCEYnrA3tNvoHE+L3VT83PA@mail.gmail.com","threadId":"57172","inReplyTo":"Yd2zXOrf+4qX8VLD@nand.local","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:28:58Z","receivedAt":"2022-01-13T03:29:12Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Wed, Jan 12, 2022 at 12:42 AM Taylor Blau <me@ttaylorr.com> wrote:\n\n>\n> I find it helpful to have an alias like:\n>\n>     $ git config alias.ll\n>     !git always --no-pager log -1 --pretty='tformat:%h (%s, %ad)' --date=short\n>\n> in my $HOME/.gitconfig so that I can easily format commits in the\n> standard way.\n>\n> I think that this alias came from Peff, but I can't remember.\n\nWow. That's cool and efficient.\n\nThanks.\n"},{"id":"446085","messageId":"CADMgQSTxZ92t34=P0==a3y=7W+vvApZ8qN3mq6r2ot9YNP4WQA@mail.gmail.com","threadId":"57172","inReplyTo":"Yd3kZv4Oym1h7zap@nand.local","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:34:11Z","receivedAt":"2022-01-13T03:34:24Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Wed, Jan 12, 2022 at 4:11 AM Taylor Blau <me@ttaylorr.com> wrote:\n\n> > Silly question, going further off-topic: What's \"git always\" doing?\n>\n> Oops, I should have mentioned. It's another alias to ensure that the\n> following command is always run in a Git repository (either the current\n> one or a hand-picked default):\n>\n>     $ git config alias.always\n>     !git rev-parse 2>/dev/null || cd ~/src/git; git\n>\n> I often read mail out of my home directory, and the above works with my\n> `:Git` command in Vim (which passes its arguments to `git always` and\n> inserts the result back into my buffer). That way I don't have to first\n> `:cd ~/src/git` and then `:Git ll xyz`, I can just `:Git ll xyz` and it\n> does what I meant most of the time.\n\n\nThe same question is clear now。\nThanks for the explanations from Taylor Blau and René Scharfe.\n"},{"id":"446086","messageId":"CADMgQSTpEG6iANtMZm58W81rdVfEQXiN2kpcomXCY_-Mw+g8Vg@mail.gmail.com","threadId":"57172","inReplyTo":"220111.86iluqxaz9.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v9 9/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:35:15Z","receivedAt":"2022-01-13T03:35:28Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Wed, Jan 12, 2022 at 4:40 AM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> Nowadays you can do this as:\n>\n>     git show -s --pretty=reference\n>\n> See Documentation/SubmittingPatches\n>\n> I use:\n>\n>     $ git help reference\n>     'reference' is aliased to '!git --no-pager log --pretty=reference -1'\n\nMake sense.\nThanks.\n"},{"id":"446087","messageId":"cover.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641440700.git.dyroneteng@gmail.com","subject":"[PATCH v10 0/9] ls-tree: \"--object-only\" and \"--format\" opts","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:03Z","receivedAt":"2022-01-13T03:42:22Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Major diff from v9:\n\n    1. Exchange the order of \"cocci: allow padding with `strbuf_addf()`\"\nand \"introduce \"--format\" option\".\n       \n       (Advice from Johannes Schindelin) \n       Let coccillene's changes take effect before the commit with the rule\n       check failure. \n\n    2. ls-tree.c: support --object-only option for \"git-ls-tree\"\n       \n       (Advice from Junio C Hamano)\n       Rename the return value name from \"recursive\" to \"recurse\".\n       Rename \"init_type()\" to \"get_type()\".\n       Remove \"init_recursive()\"       \n\n    3. ls-tree.c: introduce \"--format\" option\n       \n       (Advice from Martin Ågren)\n       Fix some document errors and wording.\n       Bug fixing in \"t3105\" (Always return 129 without specifying an argument).\n\n    4. cocci: allow padding with `strbuf_addf()`  \n\n       (Advice from Ævar Arnfjörð Bjarmason)\n       Fix the other logic errors as well.\n\nThanks.\n\nTeng Long (5):\n  ls-tree: optimize naming and handling of \"return\" in show_tree()\n  ls-tree.c: support --object-only option for \"git-ls-tree\"\n  ls-tree.c: introduce struct \"show_tree_data\"\n  cocci: allow padding with `strbuf_addf()`\n  ls-tree.c: introduce \"--format\" option\n\nÆvar Arnfjörð Bjarmason (4):\n  ls-tree: remove commented-out code\n  ls-tree: add missing braces to \"else\" arms\n  ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n  ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n\n Documentation/git-ls-tree.txt   |  56 +++++-\n builtin/ls-tree.c               | 328 +++++++++++++++++++++++++-------\n contrib/coccinelle/strbuf.cocci |   6 +-\n t/t3104-ls-tree-oid.sh          |  51 +++++\n t/t3105-ls-tree-format.sh       |  55 ++++++\n 5 files changed, 426 insertions(+), 70 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n create mode 100755 t/t3105-ls-tree-format.sh\n\nRange-diff against v9:\n 1:  75503c41a7 <  -:  ---------- ls-tree: optimize naming and handling of \"return\" in show_tree()\n -:  ---------- >  1:  2fcff7e0d4 ls-tree: remove commented-out code\n -:  ---------- >  2:  6fd1dd9383 ls-tree: add missing braces to \"else\" arms\n -:  ---------- >  3:  208654b5e2 ls-tree: use \"enum object_type\", not {blob,tree,commit}_type\n -:  ---------- >  4:  2637464fd8 ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"\n -:  ---------- >  5:  b04188c822 ls-tree: optimize naming and handling of \"return\" in show_tree()\n 2:  e0274f079a !  6:  bcfbc935b8 ls-tree.c: support --object-only option for \"git-ls-tree\"\n    @@ builtin/ls-tree.c\n      {\n      \tint i;\n      \n    -@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname\n    - \treturn 0;\n    +@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)\n    + \t        : OBJ_BLOB);\n      }\n      \n     +static int show_default(const struct object_id *oid, enum object_type type,\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c\n     +\treturn 1;\n     +}\n     +\n    - static void init_type(unsigned mode, enum object_type *type)\n    + static int show_tree(const struct object_id *oid, struct strbuf *base,\n    + \t\tconst char *pathname, unsigned mode, void *context)\n      {\n    - \tif (S_ISGITLINK(mode))\n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n      \tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    - \t\treturn !READ_TREE_RECURSIVE;\n    + \t\treturn 0;\n      \n     -\tif (!(ls_options & LS_NAME_ONLY)) {\n     -\t\tif (ls_options & LS_SHOW_SIZE) {\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\t}\n     +\tif (shown_fields == FIELD_OBJECT_NAME) {\n     +\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n    -+\t\treturn recursive;\n    ++\t\treturn recurse;\n      \t}\n     -\tbaselen = base->len;\n     -\tstrbuf_addstr(base, pathname);\n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     +\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n     +\t\t\t\t\t   stdout, line_termination);\n     +\t\tstrbuf_setlen(base, baselen);\n    -+\t\treturn recursive;\n    ++\t\treturn recurse;\n     +\t}\n     +\n     +\tif (shown_fields >= FIELD_DEFAULT)\n     +\t\tshow_default(oid, type, pathname, mode, base);\n     +\n    - \treturn recursive;\n    + \treturn recurse;\n      }\n      \n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n 3:  725c4d0187 !  7:  3ddffa1027 ls-tree.c: introduce struct \"show_tree_data\"\n    @@ builtin/ls-tree.c: static unsigned int shown_fields;\n      static const  char * const ls_tree_usage[] = {\n      \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n      \tNULL\n    -@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n    - \treturn 0;\n    +@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)\n    + \t        : OBJ_BLOB);\n      }\n      \n     -static int show_default(const struct object_id *oid, enum object_type type,\n    @@ builtin/ls-tree.c: static int show_default(const struct object_id *oid, enum obj\n      }\n      \n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - {\n    - \tint recursive = 0;\n      \tsize_t baselen;\n    --\tenum object_type type = OBJ_BLOB;\n    + \tenum object_type type = get_type(mode);\n    + \n     +\tstruct show_tree_data data = {\n     +\t\t.mode = mode,\n    -+\t\t.type = OBJ_BLOB,\n    ++\t\t.type = type,\n     +\t\t.oid = oid,\n     +\t\t.pathname = pathname,\n     +\t\t.base = base,\n     +\t};\n    - \n    --\tinit_type(mode, &type);\n    -+\tinit_type(mode, &data.type);\n    - \tinit_recursive(base, pathname, &recursive);\n    - \n    --\tif (type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    -+\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    - \t\treturn recursive;\n    --\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    -+\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    - \t\treturn !READ_TREE_RECURSIVE;\n    - \n    - \tif (shown_fields == FIELD_OBJECT_NAME) {\n    ++\n    + \tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n    + \t\trecurse = READ_TREE_RECURSIVE;\n    + \tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n     @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n      \t}\n      \n    @@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strb\n     -\t\tshow_default(oid, type, pathname, mode, base);\n     +\t\tshow_default(&data);\n      \n    - \treturn recursive;\n    + \treturn recurse;\n      }\n -:  ---------- >  8:  4b58a707c2 cocci: allow padding with `strbuf_addf()`\n 4:  7df58483a4 !  9:  db058bf670 ls-tree.c: introduce \"--format\" option\n    @@ Documentation/git-ls-tree.txt: OPTIONS\n     +--format=<format>::\n     +\tA string that interpolates `%(fieldname)` from the result\n     +\tbeing shown. It also interpolates `%%` to `%`, and\n    -+\t`%xx` where `xx`are hex digits interpolates to character\n    ++\t`%xx` where `xx` are hex digits interpolates to character\n     +\twith hex code `xx`; for example `%00` interpolates to\n     +\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n     +\tWhen specified, `--format` cannot be combined with other\n    @@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variabl\n      \n     +Customized format:\n     +\n    -+It's support to print customized format by `%(fieldname)` with `--format` option.\n    ++It is possible to print in a custom format by using the `--format` option,\n    ++which is able to interpolate different fields using a `%(fieldname)` notation.\n     +For example, if you want to only print the <object> and <file> fields with a\n     +JSON style, executing with a specific \"--format\" like\n     +\n    @@ builtin/ls-tree.c: static int parse_shown_fields(void)\n      static int show_recursive(const char *base, size_t baselen,\n      \t\t\t  const char *pathname)\n      {\n    -@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n    - \treturn 0;\n    +@@ builtin/ls-tree.c: static enum object_type get_type(unsigned int mode)\n    + \t        : OBJ_BLOB);\n      }\n      \n    -+static void init_recursive(struct strbuf *base, const char *pathname,\n    -+\t\t\t\tint *recursive)\n    -+{\n    -+\tif (show_recursive(base->buf, base->len, pathname))\n    -+\t\t*recursive = READ_TREE_RECURSIVE;\n    -+}\n    -+\n    -+static void init_type(unsigned mode, enum object_type *type)\n    -+{\n    -+\tif (S_ISGITLINK(mode))\n    -+\t\t*type = OBJ_COMMIT;\n    -+\telse if (S_ISDIR(mode))\n    -+\t\t*type = OBJ_TREE;\n    -+}\n    -+\n     +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n     +\t\t\t const char *pathname, unsigned mode, void *context)\n     +{\n     +\tsize_t baselen;\n    -+\tint recursive = 0;\n    ++\tint recurse = 0;\n     +\tstruct strbuf line = STRBUF_INIT;\n    ++\tenum object_type type = get_type(mode);\n    ++\n     +\tstruct show_tree_data data = {\n     +\t\t.mode = mode,\n    -+\t\t.type = OBJ_BLOB,\n    ++\t\t.type = type,\n     +\t\t.oid = oid,\n     +\t\t.pathname = pathname,\n     +\t\t.base = base,\n     +\t};\n     +\n    -+\tinit_type(mode, &data.type);\n    -+\tinit_recursive(base, pathname, &recursive);\n    -+\n    -+\tif (data.type == OBJ_TREE && recursive && !(ls_options & LS_SHOW_TREES))\n    -+\t\treturn recursive;\n    -+\tif (data.type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    -+\t\treturn !READ_TREE_RECURSIVE;\n    ++\tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n    ++\t\trecurse = READ_TREE_RECURSIVE;\n    ++\tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n    ++\t\treturn recurse;\n    ++\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    ++\t\treturn 0;\n     +\n     +\tbaselen = base->len;\n     +\tstrbuf_expand(&line, format, expand_show_tree, &data);\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n     +\tfwrite(line.buf, line.len, 1, stdout);\n     +\tstrbuf_release(&line);\n     +\tstrbuf_setlen(base, baselen);\n    -+\treturn recursive;\n    ++\treturn recurse;\n     +}\n     +\n      static int show_default(struct show_tree_data *data)\n      {\n      \tsize_t baselen = data->base->len;\n    -@@ builtin/ls-tree.c: static int show_default(struct show_tree_data *data)\n    - \treturn 1;\n    - }\n    - \n    --static void init_type(unsigned mode, enum object_type *type)\n    --{\n    --\tif (S_ISGITLINK(mode))\n    --\t\t*type = OBJ_COMMIT;\n    --\telse if (S_ISDIR(mode))\n    --\t\t*type = OBJ_TREE;\n    --}\n    --\n    --static void init_recursive(struct strbuf *base, const char *pathname,\n    --\t\t\t\tint *recursive)\n    --{\n    --\tif (show_recursive(base->buf, base->len, pathname))\n    --\t\t*recursive = READ_TREE_RECURSIVE;\n    --}\n    --\n    - static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - \t\tconst char *pathname, unsigned mode, void *context)\n    - {\n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n      \tstruct object_id oid;\n      \tstruct tree *tree;\n    @@ t/t3105-ls-tree-format.sh (new)\n     +. ./test-lib.sh\n     +\n     +test_expect_success 'ls-tree --format usage' '\n    -+\ttest_expect_code 129 git ls-tree --format=fmt -l &&\n    -+\ttest_expect_code 129 git ls-tree --format=fmt --name-only &&\n    -+\ttest_expect_code 129 git ls-tree --format=fmt --name-status &&\n    -+\ttest_expect_code 129 git ls-tree --format=fmt --object-only\n    ++\ttest_expect_code 129 git ls-tree --format=fmt -l HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --name-status HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --object-only HEAD\n     +'\n     +\n     +test_expect_success 'setup' '\n 5:  8dafb2b377 <  -:  ---------- cocci: allow padding with `strbuf_addf()`\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446088","messageId":"2fcff7e0d40255e0be9496647787352dd0aeb784.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 1/9] ls-tree: remove commented-out code","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:04Z","receivedAt":"2022-01-13T03:42:25Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nRemove code added in f35a6d3bce7 (Teach core object handling functions\nabout gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output\nflushing before fork(), 2008-08-04), and then finally ending up in its\ncurrent form in d3bee161fef (tree.c: allow read_tree_recursive() to\ntraverse gitlink entries, 2009-01-25). All while being commented-out!\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 9 ---------\n 1 file changed, 9 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3a442631c7..5f7c84950c 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tconst char *type = blob_type;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\t/*\n-\t\t * Maybe we want to have some recursive version here?\n-\t\t *\n-\t\t * Something similar to this incomplete example:\n-\t\t *\n-\t\tif (show_subprojects(base, baselen, pathname))\n-\t\t\tretval = READ_TREE_RECURSIVE;\n-\t\t *\n-\t\t */\n \t\ttype = commit_type;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446089","messageId":"6fd1dd938303192d5b0a4cdc2914b2ff72cf5a3d.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 2/9] ls-tree: add missing braces to \"else\" arms","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:05Z","receivedAt":"2022-01-13T03:42:26Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd missing {} to the \"else\" arms in show_tree() per the\nCodingGuidelines.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 ++++--\n 1 file changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 5f7c84950c..0a28f32ccb 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t\telse\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n \t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else\n+\t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t\t}\n \t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n-\t\t} else\n+\t\t} else {\n \t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n \t\t\t       find_unique_abbrev(oid, abbrev));\n+\t\t}\n \t}\n \tbaselen = base->len;\n \tstrbuf_addstr(base, pathname);\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446090","messageId":"208654b5e2aebbe988b5a8f478869253a0166e9b.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 3/9] ls-tree: use \"enum object_type\", not {blob,tree,commit}_type","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:06Z","receivedAt":"2022-01-13T03:42:31Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nChange the ls-tree.c code to use type_name() on the enum instead of\nusing the string constants. This doesn't matter either way for\nperformance, but makes this a bit easier to read as we'll no longer\nneed a strcmp() here.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 12 ++++++------\n 1 file changed, 6 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 0a28f32ccb..3f0225b097 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n {\n \tint retval = 0;\n \tint baselen;\n-\tconst char *type = blob_type;\n+\tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-\t\ttype = commit_type;\n+\t\ttype = OBJ_COMMIT;\n \t} else if (S_ISDIR(mode)) {\n \t\tif (show_recursive(base->buf, base->len, pathname)) {\n \t\t\tretval = READ_TREE_RECURSIVE;\n \t\t\tif (!(ls_options & LS_SHOW_TREES))\n \t\t\t\treturn retval;\n \t\t}\n-\t\ttype = tree_type;\n+\t\ttype = OBJ_TREE;\n \t}\n \telse if (ls_options & LS_TREE_ONLY)\n \t\treturn 0;\n@@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (!(ls_options & LS_NAME_ONLY)) {\n \t\tif (ls_options & LS_SHOW_SIZE) {\n \t\t\tchar size_text[24];\n-\t\t\tif (!strcmp(type, blob_type)) {\n+\t\t\tif (type == OBJ_BLOB) {\n \t\t\t\tunsigned long size;\n \t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n \t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t} else {\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev),\n \t\t\t       size_text);\n \t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type,\n+\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n \t\t\t       find_unique_abbrev(oid, abbrev));\n \t\t}\n \t}\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446091","messageId":"2637464fd8f6b8bae6c681f20c10c9fc76dc6d6c.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 4/9] ls-tree: use \"size_t\", not \"int\" for \"struct strbuf\"'s \"len\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:07Z","receivedAt":"2022-01-13T03:42:34Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nThe \"struct strbuf\"'s \"len\" member is a \"size_t\", not an \"int\", so\nlet's change our corresponding types accordingly. This also changes\nthe \"len\" and \"speclen\" variables, which are likewise used to store\nthe return value of strlen(), which returns \"size_t\", not \"int\".\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/ls-tree.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 3f0225b097..eecc7482d5 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -31,7 +31,7 @@ static const  char * const ls_tree_usage[] = {\n \tNULL\n };\n \n-static int show_recursive(const char *base, int baselen, const char *pathname)\n+static int show_recursive(const char *base, size_t baselen, const char *pathname)\n {\n \tint i;\n \n@@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname)\n \n \tfor (i = 0; i < pathspec.nr; i++) {\n \t\tconst char *spec = pathspec.items[i].match;\n-\t\tint len, speclen;\n+\t\tsize_t len, speclen;\n \n \t\tif (strncmp(base, spec, baselen))\n \t\t\tcontinue;\n@@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n \tint retval = 0;\n-\tint baselen;\n+\tsize_t baselen;\n \tenum object_type type = OBJ_BLOB;\n \n \tif (S_ISGITLINK(mode)) {\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446092","messageId":"b04188c822c32aab6ef59099a0c9078aeda065c3.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:08Z","receivedAt":"2022-01-13T03:42:36Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"The variable which \"show_tree()\" return is named \"retval\", a name that's\na little hard to understand. This commit tries to make the variable\nand the related codes more clear in the context.\n\nThe commit firstly rename \"retval\" to \"recurse\" which is a more\nmeaningful name than before. Secondly, \"get_type()\" is introduced\nto setup the \"type\" by \"mode\", this will remove some of the nested if.\nAfter this, The codes here become a little bit clearer, so we do not\nneed to take a look at \"read_tree_at()\" in \"tree.c\" to make sure the\ncontext of the return value.\n\nSigned-off-by: Teng Long <dyronetengb@gmail.com>\n---\n builtin/ls-tree.c | 33 ++++++++++++++++++---------------\n 1 file changed, 18 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex eecc7482d5..9729854a3d 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -61,24 +61,27 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n \treturn 0;\n }\n \n+static enum object_type get_type(unsigned int mode)\n+{\n+\treturn (S_ISGITLINK(mode)\n+\t        ? OBJ_COMMIT\n+\t        : S_ISDIR(mode)\n+\t        ? OBJ_TREE\n+\t        : OBJ_BLOB);\n+}\n+\n static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n-\tint retval = 0;\n+\tint recurse = 0;\n \tsize_t baselen;\n-\tenum object_type type = OBJ_BLOB;\n-\n-\tif (S_ISGITLINK(mode)) {\n-\t\ttype = OBJ_COMMIT;\n-\t} else if (S_ISDIR(mode)) {\n-\t\tif (show_recursive(base->buf, base->len, pathname)) {\n-\t\t\tretval = READ_TREE_RECURSIVE;\n-\t\t\tif (!(ls_options & LS_SHOW_TREES))\n-\t\t\t\treturn retval;\n-\t\t}\n-\t\ttype = OBJ_TREE;\n-\t}\n-\telse if (ls_options & LS_TREE_ONLY)\n+\tenum object_type type = get_type(mode);\n+\n+\tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n+\t\trecurse = READ_TREE_RECURSIVE;\n+\tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n+\t\treturn recurse;\n+\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n \t\treturn 0;\n \n \tif (!(ls_options & LS_NAME_ONLY)) {\n@@ -109,7 +112,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n \t\t\t\t   stdout, line_termination);\n \tstrbuf_setlen(base, baselen);\n-\treturn retval;\n+\treturn recurse;\n }\n \n int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446093","messageId":"bcfbc935b80b889273e3e54fec2a896e44acd2b5.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:09Z","receivedAt":"2022-01-13T03:42:40Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"We usually pipe the output from `git ls-trees` to tools like\n`sed` or `cut` when we only want to extract some fields.\n\nWhen we want only the pathname component, we can pass\n`--name-only` option to omit such a pipeline, but there are no\noptions for extracting other fields.\n\nTeach the \"--object-only\" option to the command to only show the\nobject name. This option cannot be used together with\n\"--name-only\" or \"--long\" , they are mutually exclusive (actually\n\"--name-only\" and \"--long\" can be combined together before, this\ncommit by the way fix this bug).\n\nA simple refactoring was done to the \"show_tree\" function, intead by\nusing bitwise operations to recognize the format for printing to\nstdout. The reason for doing this is that we don't want to increase\nthe readability difficulty with the addition of \"-object-only\",\nmaking this part of the logic easier to read and expand.\n\nIn terms of performance, there is no loss comparing to the\n\"master\" (2ae0a9cb8298185a94e5998086f380a355dd8907), here are the\nresults of the performance tests in my environment based on linux\nrepository:\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.8 ms ±   2.7 ms    [User: 85.7 ms, System: 20.0 ms]\n    Range (min … max):   101.5 ms … 111.3 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.0 ms ±   3.0 ms    [User: 83.7 ms, System: 21.2 ms]\n    Range (min … max):    99.3 ms … 109.5 ms    27 runs\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.4 ms ±  10.9 ms    [User: 308.3 ms, System: 29.0 ms]\n    Range (min … max):   323.0 ms … 355.0 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     337.6 ms ±   6.2 ms    [User: 309.4 ms, System: 28.1 ms]\n    Range (min … max):   330.4 ms … 349.9 ms    10 runs\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |   7 +-\n builtin/ls-tree.c             | 141 +++++++++++++++++++++++++---------\n t/t3104-ls-tree-oid.sh        |  51 ++++++++++++\n 3 files changed, 160 insertions(+), 39 deletions(-)\n create mode 100755 t/t3104-ls-tree-oid.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex db02d6d79a..729370f235 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n \t    <tree-ish> [<path>...]\n \n DESCRIPTION\n@@ -59,6 +59,11 @@ OPTIONS\n --name-only::\n --name-status::\n \tList only filenames (instead of the \"long\" output), one per line.\n+\tCannot be combined with `--object-only`.\n+\n+--object-only::\n+\tList only names of the objects, one per line. Cannot be combined\n+\twith `--name-only` or `--name-status`.\n \n --abbrev[=<n>]::\n \tInstead of showing the full 40-byte hexadecimal object\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 9729854a3d..e1a2f8225b 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -16,22 +16,60 @@\n \n static int line_termination = '\\n';\n #define LS_RECURSIVE 1\n-#define LS_TREE_ONLY 2\n-#define LS_SHOW_TREES 4\n-#define LS_NAME_ONLY 8\n-#define LS_SHOW_SIZE 16\n+#define LS_TREE_ONLY (1 << 1)\n+#define LS_SHOW_TREES (1 << 2)\n+#define LS_NAME_ONLY (1 << 3)\n+#define LS_SHOW_SIZE (1 << 4)\n+#define LS_OBJECT_ONLY (1 << 5)\n static int abbrev;\n static int ls_options;\n static struct pathspec pathspec;\n static int chomp_prefix;\n static const char *ls_tree_prefix;\n+static unsigned int shown_fields;\n+#define FIELD_FILE_NAME 1\n+#define FIELD_SIZE (1 << 1)\n+#define FIELD_OBJECT_NAME (1 << 2)\n+#define FIELD_TYPE (1 << 3)\n+#define FIELD_MODE (1 << 4)\n+#define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n+#define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n \n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n };\n \n-static int show_recursive(const char *base, size_t baselen, const char *pathname)\n+enum {\n+\tMODE_UNSPECIFIED = 0,\n+\tMODE_NAME_ONLY,\n+\tMODE_OBJECT_ONLY,\n+\tMODE_LONG,\n+};\n+\n+static int cmdmode = MODE_UNSPECIFIED;\n+\n+static int parse_shown_fields(void)\n+{\n+\tif (cmdmode == MODE_NAME_ONLY) {\n+\t\tshown_fields = FIELD_FILE_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (cmdmode == MODE_OBJECT_ONLY) {\n+\t\tshown_fields = FIELD_OBJECT_NAME;\n+\t\treturn 0;\n+\t}\n+\tif (!ls_options || (ls_options & LS_RECURSIVE)\n+\t    || (ls_options & LS_SHOW_TREES)\n+\t    || (ls_options & LS_TREE_ONLY))\n+\t\tshown_fields = FIELD_DEFAULT;\n+\tif (cmdmode == MODE_LONG)\n+\t\tshown_fields = FIELD_LONG_DEFAULT;\n+\treturn 1;\n+}\n+\n+static int show_recursive(const char *base, size_t baselen,\n+\t\t\t  const char *pathname)\n {\n \tint i;\n \n@@ -70,6 +108,39 @@ static enum object_type get_type(unsigned int mode)\n \t        : OBJ_BLOB);\n }\n \n+static int show_default(const struct object_id *oid, enum object_type type,\n+\t\t\tconst char *pathname, unsigned mode,\n+\t\t\tstruct strbuf *base)\n+{\n+\tsize_t baselen = base->len;\n+\n+\tif (shown_fields & FIELD_SIZE) {\n+\t\tchar size_text[24];\n+\t\tif (type == OBJ_BLOB) {\n+\t\t\tunsigned long size;\n+\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n+\t\t\telse\n+\t\t\t\txsnprintf(size_text, sizeof(size_text),\n+\t\t\t\t\t  \"%\" PRIuMAX, (uintmax_t)size);\n+\t\t} else {\n+\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n+\t\t}\n+\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t} else {\n+\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n+\t\tfind_unique_abbrev(oid, abbrev));\n+\t}\n+\tbaselen = base->len;\n+\tstrbuf_addstr(base, pathname);\n+\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n+\t\t\t\t   line_termination);\n+\tstrbuf_setlen(base, baselen);\n+\treturn 1;\n+}\n+\n static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t\tconst char *pathname, unsigned mode, void *context)\n {\n@@ -84,34 +155,24 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n \t\treturn 0;\n \n-\tif (!(ls_options & LS_NAME_ONLY)) {\n-\t\tif (ls_options & LS_SHOW_SIZE) {\n-\t\t\tchar size_text[24];\n-\t\t\tif (type == OBJ_BLOB) {\n-\t\t\t\tunsigned long size;\n-\t\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"BAD\");\n-\t\t\t\telse\n-\t\t\t\t\txsnprintf(size_text, sizeof(size_text),\n-\t\t\t\t\t\t  \"%\"PRIuMAX, (uintmax_t)size);\n-\t\t\t} else {\n-\t\t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n-\t\t\t}\n-\t\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev),\n-\t\t\t       size_text);\n-\t\t} else {\n-\t\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\t\t       find_unique_abbrev(oid, abbrev));\n-\t\t}\n+\tif (shown_fields == FIELD_OBJECT_NAME) {\n+\t\tprintf(\"%s%c\", find_unique_abbrev(oid, abbrev), line_termination);\n+\t\treturn recurse;\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n-\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n-\t\t\t\t   stdout, line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\n+\tif (shown_fields == FIELD_FILE_NAME) {\n+\t\tbaselen = base->len;\n+\t\tstrbuf_addstr(base, pathname);\n+\t\twrite_name_quoted_relative(base->buf,\n+\t\t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL,\n+\t\t\t\t\t   stdout, line_termination);\n+\t\tstrbuf_setlen(base, baselen);\n+\t\treturn recurse;\n+\t}\n+\n+\tif (shown_fields >= FIELD_DEFAULT)\n+\t\tshow_default(oid, type, pathname, mode, base);\n+\n \treturn recurse;\n }\n \n@@ -129,12 +190,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\t\tLS_SHOW_TREES),\n \t\tOPT_SET_INT('z', NULL, &line_termination,\n \t\t\t    N_(\"terminate entries with NUL byte\"), 0),\n-\t\tOPT_BIT('l', \"long\", &ls_options, N_(\"include object size\"),\n-\t\t\tLS_SHOW_SIZE),\n-\t\tOPT_BIT(0, \"name-only\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n-\t\tOPT_BIT(0, \"name-status\", &ls_options, N_(\"list only filenames\"),\n-\t\t\tLS_NAME_ONLY),\n+\t\tOPT_CMDMODE('l', \"long\", &cmdmode, N_(\"include object size\"),\n+\t\t\t    MODE_LONG),\n+\t\tOPT_CMDMODE(0, \"name-only\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"name-status\", &cmdmode, N_(\"list only filenames\"),\n+\t\t\t    MODE_NAME_ONLY),\n+\t\tOPT_CMDMODE(0, \"object-only\", &cmdmode, N_(\"list only objects\"),\n+\t\t\t    MODE_OBJECT_ONLY),\n \t\tOPT_SET_INT(0, \"full-name\", &chomp_prefix,\n \t\t\t    N_(\"use full path names\"), 0),\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n@@ -165,6 +228,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tif (get_oid(argv[0], &oid))\n \t\tdie(\"Not a valid object name %s\", argv[0]);\n \n+\tparse_shown_fields();\n+\n \t/*\n \t * show_recursive() rolls its own matching code and is\n \t * generally ignorant of 'struct pathspec'. The magic mask\ndiff --git a/t/t3104-ls-tree-oid.sh b/t/t3104-ls-tree-oid.sh\nnew file mode 100755\nindex 0000000000..6ce62bd769\n--- /dev/null\n+++ b/t/t3104-ls-tree-oid.sh\n@@ -0,0 +1,51 @@\n+#!/bin/sh\n+\n+test_description='git ls-tree objects handling.'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\tmkdir -p C &&\n+\ttest_commit C/D.txt &&\n+\tfind *.txt path* \\( -type f -o -type l \\) -print |\n+\txargs git update-index --add &&\n+\ttree=$(git write-tree) &&\n+\techo $tree\n+'\n+\n+test_expect_success 'usage: --object-only' '\n+\tgit ls-tree --object-only $tree >current &&\n+\tgit ls-tree $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with -r' '\n+\tgit ls-tree --object-only -r $tree >current &&\n+\tgit ls-tree -r $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: --object-only with --abbrev' '\n+\tgit ls-tree --object-only --abbrev=6 $tree >current &&\n+\tgit ls-tree --abbrev=6 $tree >result &&\n+\tcut -f1 result | cut -d \" \" -f3 >expected &&\n+\ttest_cmp current expected\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-only with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-only $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --name-status with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --name-status $tree\n+'\n+\n+test_expect_success 'usage: incompatible options: --long with --object-only' '\n+\ttest_expect_code 129 git ls-tree --object-only --long $tree\n+'\n+\n+test_done\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446094","messageId":"3ddffa10278b5814123a5bcd2fd3a531def3ecdc.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 7/9] ls-tree.c: introduce struct \"show_tree_data\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:10Z","receivedAt":"2022-01-13T03:42:47Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"\"show_tree_data\" is a struct that packages the necessary fields for\n\"show_tree()\". This commit is a pre-prepared commit for supporting\n\"--format\" option and it does not affect any existing functionality.\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n builtin/ls-tree.c | 44 +++++++++++++++++++++++++++++---------------\n 1 file changed, 29 insertions(+), 15 deletions(-)\n\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex e1a2f8225b..56cc166adb 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -35,6 +35,14 @@ static unsigned int shown_fields;\n #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n \n+struct show_tree_data {\n+\tunsigned mode;\n+\tenum object_type type;\n+\tconst struct object_id *oid;\n+\tconst char *pathname;\n+\tstruct strbuf *base;\n+};\n+\n static const  char * const ls_tree_usage[] = {\n \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n \tNULL\n@@ -108,17 +116,15 @@ static enum object_type get_type(unsigned int mode)\n \t        : OBJ_BLOB);\n }\n \n-static int show_default(const struct object_id *oid, enum object_type type,\n-\t\t\tconst char *pathname, unsigned mode,\n-\t\t\tstruct strbuf *base)\n+static int show_default(struct show_tree_data *data)\n {\n-\tsize_t baselen = base->len;\n+\tsize_t baselen = data->base->len;\n \n \tif (shown_fields & FIELD_SIZE) {\n \t\tchar size_text[24];\n-\t\tif (type == OBJ_BLOB) {\n+\t\tif (data->type == OBJ_BLOB) {\n \t\t\tunsigned long size;\n-\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n+\t\t\tif (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n \t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n \t\t\telse\n \t\t\t\txsnprintf(size_text, sizeof(size_text),\n@@ -126,18 +132,18 @@ static int show_default(const struct object_id *oid, enum object_type type,\n \t\t} else {\n \t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n \t\t}\n-\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev), size_text);\n+\t\tprintf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev), size_text);\n \t} else {\n-\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n-\t\tfind_unique_abbrev(oid, abbrev));\n+\t\tprintf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n+\t\tfind_unique_abbrev(data->oid, abbrev));\n \t}\n-\tbaselen = base->len;\n-\tstrbuf_addstr(base, pathname);\n-\twrite_name_quoted_relative(base->buf,\n+\tbaselen = data->base->len;\n+\tstrbuf_addstr(data->base, data->pathname);\n+\twrite_name_quoted_relative(data->base->buf,\n \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n \t\t\t\t   line_termination);\n-\tstrbuf_setlen(base, baselen);\n+\tstrbuf_setlen(data->base, baselen);\n \treturn 1;\n }\n \n@@ -148,6 +154,14 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \tsize_t baselen;\n \tenum object_type type = get_type(mode);\n \n+\tstruct show_tree_data data = {\n+\t\t.mode = mode,\n+\t\t.type = type,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n+\n \tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n \t\trecurse = READ_TREE_RECURSIVE;\n \tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n@@ -171,7 +185,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base,\n \t}\n \n \tif (shown_fields >= FIELD_DEFAULT)\n-\t\tshow_default(oid, type, pathname, mode, base);\n+\t\tshow_default(&data);\n \n \treturn recurse;\n }\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446095","messageId":"4b58a707c2913da3e2c39c1ffc09b08dfd98737e.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 8/9] cocci: allow padding with `strbuf_addf()`","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:11Z","receivedAt":"2022-01-13T03:42:49Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"A convenient way to pad strings is to use something like\n`strbuf_addf(&buf, \"%20s\", \"Hello, world!\")`.\n\nHowever, the Coccinelle rule that forbids a format `\"%s\"` with a\nconstant string argument cast too wide a net, and also forbade such\npadding.\n\nThe original rule was introduced by commit:\n\n    28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25)\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n contrib/coccinelle/strbuf.cocci | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci\nindex d9ada69b43..0970d98ad7 100644\n--- a/contrib/coccinelle/strbuf.cocci\n+++ b/contrib/coccinelle/strbuf.cocci\n@@ -15,7 +15,7 @@ constant fmt !~ \"%\";\n @@\n expression E;\n struct strbuf SB;\n-format F =~ \"s\";\n+format F =~ \"^s$\";\n @@\n - strbuf_addf(E, \"%@F@\", SB.buf);\n + strbuf_addbuf(E, &SB);\n@@ -23,7 +23,7 @@ format F =~ \"s\";\n @@\n expression E;\n struct strbuf *SBP;\n-format F =~ \"s\";\n+format F =~ \"^s$\";\n @@\n - strbuf_addf(E, \"%@F@\", SBP->buf);\n + strbuf_addbuf(E, SBP);\n@@ -44,7 +44,7 @@ struct strbuf *SBP;\n \n @@\n expression E1, E2;\n-format F =~ \"s\";\n+format F =~ \"^s$\";\n @@\n - strbuf_addf(E1, \"%@F@\", E2);\n + strbuf_addstr(E1, E2);\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446096","messageId":"db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com","threadId":"57172","inReplyTo":"cover.1641978175.git.dyroneteng@gmail.com","subject":"[PATCH v10 9/9] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-13T03:42:12Z","receivedAt":"2022-01-13T03:43:01Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"Add a --format option to ls-tree. It has an existing default output,\nand then --long and --name-only options to emit the default output\nalong with the objectsize and, or to only emit object paths.\n\nRather than add --type-only, --object-only etc. we can just support a\n--format using a strbuf_expand() similar to \"for-each-ref\n--format\". We might still add such options in the future for\nconvenience.\n\nThe --format implementation is slower than the existing code, but this\nchange does not cause any performance regressions. We'll leave the\nexisting show_tree() unchanged, and only run show_tree_fmt() in if\na --format different than the hardcoded built-in ones corresponding to\nthe existing modes is provided.\n\nI.e. something like the \"--long\" output would be much slower with\nthis, mainly due to how we need to allocate various things to do with\nquote.c instead of spewing the output directly to stdout.\n\nThe new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's\nidea and suggestion, this commit makes modifications in terms of the\noriginal discussion on community [1].\n\nHere is the statistics about performance tests:\n\n1. Default format (hitten the builtin formats):\n\n    \"git ls-tree <tree-ish>\" vs \"--format='%(mode) %(type) %(object)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]\n    Range (min … max):    99.2 ms … 113.2 ms    28 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\n    Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]\n    Range (min … max):   100.2 ms … 110.5 ms    29 runs\n\n2. Default format includes object size (hitten the builtin formats):\n\n    \"git ls-tree -l <tree-ish>\" vs \"--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'\"\n\n    $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]\n    Range (min … max):   327.5 ms … 348.4 ms    10 runs\n\n    $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n    Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]\n    Range (min … max):   328.8 ms … 349.4 ms    10 runs\n\nLinks:\n\t[1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nSigned-off-by: Teng Long <dyroneteng@gmail.com>\n---\n Documentation/git-ls-tree.txt |  51 +++++++++++++-\n builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-\n t/t3105-ls-tree-format.sh     |  55 +++++++++++++++\n 3 files changed, 230 insertions(+), 5 deletions(-)\n create mode 100755 t/t3105-ls-tree-format.sh\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex 729370f235..ebdde6eae3 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,9 +10,9 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n-\t    <tree-ish> [<path>...]\n-\n+\t    [--name-only] [--name-status] [--object-only]\n+\t    [--full-name] [--full-tree] [--abbrev[=<n>]]\n+\t    [--format=<format>] <tree-ish> [<path>...]\n DESCRIPTION\n -----------\n Lists the contents of a given tree object, like what \"/bin/ls -a\" does\n@@ -79,6 +79,16 @@ OPTIONS\n \tDo not limit the listing to the current working directory.\n \tImplies --full-name.\n \n+--format=<format>::\n+\tA string that interpolates `%(fieldname)` from the result\n+\tbeing shown. It also interpolates `%%` to `%`, and\n+\t`%xx` where `xx` are hex digits interpolates to character\n+\twith hex code `xx`; for example `%00` interpolates to\n+\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n+\tWhen specified, `--format` cannot be combined with other\n+\tformat-altering options, including `--long`, `--name-only`\n+\tand `--object-only`.\n+\n [<path>...]::\n \tWhen paths are given, show them (note that this isn't really raw\n \tpathnames, but rather a list of patterns to match).  Otherwise\n@@ -87,6 +97,9 @@ OPTIONS\n \n Output Format\n -------------\n+\n+Default format:\n+\n         <mode> SP <type> SP <object> TAB <file>\n \n This output format is compatible with what `--index-info --stdin` of\n@@ -105,6 +118,38 @@ quoted as explained for the configuration variable `core.quotePath`\n (see linkgit:git-config[1]).  Using `-z` the filename is output\n verbatim and the line is terminated by a NUL byte.\n \n+Customized format:\n+\n+It is possible to print in a custom format by using the `--format` option,\n+which is able to interpolate different fields using a `%(fieldname)` notation.\n+For example, if you want to only print the <object> and <file> fields with a\n+JSON style, executing with a specific \"--format\" like\n+\n+        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n+\n+The output format changes to:\n+\n+        {\"object\":\"<object>\", \"file\":\"<file>\"}\n+\n+FIELD NAMES\n+-----------\n+\n+Various values from structured fields can be used to interpolate\n+into the resulting output. For each outputing line, the following\n+names can be used:\n+\n+mode::\n+\tThe mode of the object.\n+type::\n+\tThe type of the object (`blob` or `tree`).\n+object::\n+\tThe name of the object.\n+size[:padded]::\n+\tThe size of the object (\"-\" if it's a tree).\n+\tIt also supports a padded format of size with \"%(size:padded)\".\n+file::\n+\tThe filename of the object.\n+\n GIT\n ---\n Part of the linkgit:git[1] suite\ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 56cc166adb..e048a68ee0 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -57,6 +57,12 @@ enum {\n \n static int cmdmode = MODE_UNSPECIFIED;\n \n+static const char *format;\n+static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n+static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n+static const char *name_only_format = \"%(file)\";\n+static const char *object_only_format = \"%(object)\";\n+\n static int parse_shown_fields(void)\n {\n \tif (cmdmode == MODE_NAME_ONLY) {\n@@ -76,6 +82,72 @@ static int parse_shown_fields(void)\n \treturn 1;\n }\n \n+static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n+\t\t\t      const enum object_type type, unsigned int padded)\n+{\n+\tif (type == OBJ_BLOB) {\n+\t\tunsigned long size;\n+\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n+\t\t\tdie(_(\"could not get object info about '%s'\"),\n+\t\t\t    oid_to_hex(oid));\n+\t\tif (padded)\n+\t\t\tstrbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n+\t\telse\n+\t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n+\t} else if (padded) {\n+\t\tstrbuf_addf(line, \"%7s\", \"-\");\n+\t} else {\n+\t\tstrbuf_addstr(line, \"-\");\n+\t}\n+}\n+\n+static size_t expand_show_tree(struct strbuf *line, const char *start,\n+\t\t\t       void *context)\n+{\n+\tstruct show_tree_data *data = context;\n+\tconst char *end;\n+\tconst char *p;\n+\tunsigned int errlen;\n+\tsize_t len = strbuf_expand_literal_cb(line, start, NULL);\n+\n+\tif (len)\n+\t\treturn len;\n+\tif (*start != '(')\n+\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n+\n+\tend = strchr(start + 1, ')');\n+\tif (!end)\n+\t\tdie(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n+\n+\tlen = end - start + 1;\n+\tif (skip_prefix(start, \"(mode)\", &p)) {\n+\t\tstrbuf_addf(line, \"%06o\", data->mode);\n+\t} else if (skip_prefix(start, \"(type)\", &p)) {\n+\t\tstrbuf_addstr(line, type_name(data->type));\n+\t} else if (skip_prefix(start, \"(size:padded)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 1);\n+\t} else if (skip_prefix(start, \"(size)\", &p)) {\n+\t\texpand_objectsize(line, data->oid, data->type, 0);\n+\t} else if (skip_prefix(start, \"(object)\", &p)) {\n+\t\tstrbuf_add_unique_abbrev(line, data->oid, abbrev);\n+\t} else if (skip_prefix(start, \"(file)\", &p)) {\n+\t\tconst char *name = data->base->buf;\n+\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n+\t\tstruct strbuf quoted = STRBUF_INIT;\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\tstrbuf_addstr(data->base, data->pathname);\n+\t\tname = relative_path(data->base->buf, prefix, &sb);\n+\t\tquote_c_style(name, &quoted, NULL, 0);\n+\t\tstrbuf_addbuf(line, &quoted);\n+\t\tstrbuf_release(&sb);\n+\t\tstrbuf_release(&quoted);\n+\t} else {\n+\t\terrlen = (unsigned long)len;\n+\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n+\t}\n+\treturn len;\n+}\n+\n static int show_recursive(const char *base, size_t baselen,\n \t\t\t  const char *pathname)\n {\n@@ -116,6 +188,38 @@ static enum object_type get_type(unsigned int mode)\n \t        : OBJ_BLOB);\n }\n \n+static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n+\t\t\t const char *pathname, unsigned mode, void *context)\n+{\n+\tsize_t baselen;\n+\tint recurse = 0;\n+\tstruct strbuf line = STRBUF_INIT;\n+\tenum object_type type = get_type(mode);\n+\n+\tstruct show_tree_data data = {\n+\t\t.mode = mode,\n+\t\t.type = type,\n+\t\t.oid = oid,\n+\t\t.pathname = pathname,\n+\t\t.base = base,\n+\t};\n+\n+\tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n+\t\trecurse = READ_TREE_RECURSIVE;\n+\tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n+\t\treturn recurse;\n+\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n+\t\treturn 0;\n+\n+\tbaselen = base->len;\n+\tstrbuf_expand(&line, format, expand_show_tree, &data);\n+\tstrbuf_addch(&line, line_termination);\n+\tfwrite(line.buf, line.len, 1, stdout);\n+\tstrbuf_release(&line);\n+\tstrbuf_setlen(base, baselen);\n+\treturn recurse;\n+}\n+\n static int show_default(struct show_tree_data *data)\n {\n \tsize_t baselen = data->base->len;\n@@ -195,6 +299,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \tstruct object_id oid;\n \tstruct tree *tree;\n \tint i, full_tree = 0;\n+\tread_tree_fn_t fn = show_tree;\n \tconst struct option ls_tree_options[] = {\n \t\tOPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n \t\t\tLS_TREE_ONLY),\n@@ -217,6 +322,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n \t\t\t N_(\"list entire tree; not just current directory \"\n \t\t\t    \"(implies --full-name)\")),\n+\t\tOPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n+\t\t\t     N_(\"format to use for the output\"),\n+\t\t\t     PARSE_OPT_NONEG),\n \t\tOPT__ABBREV(&abbrev),\n \t\tOPT_END()\n \t};\n@@ -237,6 +345,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n \t\tls_options |= LS_SHOW_TREES;\n \n+\tif (format && cmdmode)\n+\t\tusage_msg_opt(\n+\t\t\t_(\"--format can't be combined with other format-altering options\"),\n+\t\t\tls_tree_usage, ls_tree_options);\n \tif (argc < 1)\n \t\tusage_with_options(ls_tree_usage, ls_tree_options);\n \tif (get_oid(argv[0], &oid))\n@@ -260,6 +372,19 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \ttree = parse_tree_indirect(&oid);\n \tif (!tree)\n \t\tdie(\"not a tree object\");\n-\treturn !!read_tree(the_repository, tree,\n-\t\t\t   &pathspec, show_tree, NULL);\n+\n+\t/*\n+\t * The generic show_tree_fmt() is slower than show_tree(), so\n+\t * take the fast path if possible.\n+\t */\n+\tif (format &&\n+\t    (!strcmp(format, default_format) ||\n+\t     !strcmp(format, long_format) ||\n+\t     !strcmp(format, name_only_format) ||\n+\t     !strcmp(format, object_only_format)))\n+\t\tfn = show_tree;\n+\telse if (format)\n+\t\tfn = show_tree_fmt;\n+\n+\treturn !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n }\ndiff --git a/t/t3105-ls-tree-format.sh b/t/t3105-ls-tree-format.sh\nnew file mode 100755\nindex 0000000000..ea0f51d866\n--- /dev/null\n+++ b/t/t3105-ls-tree-format.sh\n@@ -0,0 +1,55 @@\n+#!/bin/sh\n+\n+test_description='ls-tree --format'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n+. ./test-lib.sh\n+\n+test_expect_success 'ls-tree --format usage' '\n+\ttest_expect_code 129 git ls-tree --format=fmt -l HEAD &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --name-status HEAD &&\n+\ttest_expect_code 129 git ls-tree --format=fmt --object-only HEAD\n+'\n+\n+test_expect_success 'setup' '\n+\tmkdir dir &&\n+\ttest_commit dir/sub-file &&\n+\ttest_commit top-file\n+'\n+\n+test_ls_tree_format () {\n+\tformat=$1 &&\n+\topts=$2 &&\n+\tshift 2 &&\n+\tgit ls-tree $opts -r HEAD >expect.raw &&\n+\tsed \"s/^/> /\" >expect <expect.raw &&\n+\tgit ls-tree --format=\"> $format\" -r HEAD >actual &&\n+\ttest_cmp expect actual\n+}\n+\n+test_expect_success 'ls-tree --format=<default-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object)%x09%(file)\" \\\n+\t\t\"\"\n+'\n+\n+test_expect_success 'ls-tree --format=<long-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n+\t\t\"--long\"\n+'\n+\n+test_expect_success 'ls-tree --format=<name-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(file)\" \\\n+\t\t\"--name-only\"\n+'\n+\n+test_expect_success 'ls-tree --format=<object-only-like>' '\n+\ttest_ls_tree_format \\\n+\t\t\"%(object)\" \\\n+\t\t\"--object-only\"\n+'\n+\n+test_done\n-- \n2.34.1.390.g2ae0a9cb82.dirty\n\n"},{"id":"446106","messageId":"220113.861r1cxgxx.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"b04188c822c32aab6ef59099a0c9078aeda065c3.1641978175.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T06:49:45Z","receivedAt":"2022-01-13T06:55:58Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 13 2022, Teng Long wrote:\n\nRe the $subject: Is \"optimize naming\" here just referring to the\ns/retval/recurse/g?\n\nPersonally I think just a s/retval/ret/g here would make more senes if\nwe're doing any change at all, and in either case having this variable\nre-rename split up as its own commit would make the proposed control\nflow changes clearer.\n\n> The variable which \"show_tree()\" return is named \"retval\", a name that's\n> a little hard to understand. This commit tries to make the variable\n> and the related codes more clear in the context.\n>\n> The commit firstly rename \"retval\" to \"recurse\" which is a more\n> meaningful name than before. Secondly, \"get_type()\" is introduced\n> to setup the \"type\" by \"mode\", this will remove some of the nested if.\n> After this, The codes here become a little bit clearer, so we do not\n> need to take a look at \"read_tree_at()\" in \"tree.c\" to make sure the\n> context of the return value.\n>\n> Signed-off-by: Teng Long <dyronetengb@gmail.com>\n> ---\n>  builtin/ls-tree.c | 33 ++++++++++++++++++---------------\n>  1 file changed, 18 insertions(+), 15 deletions(-)\n>\n> diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\n> index eecc7482d5..9729854a3d 100644\n> --- a/builtin/ls-tree.c\n> +++ b/builtin/ls-tree.c\n> @@ -61,24 +61,27 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname\n>  \treturn 0;\n>  }\n>  \n> +static enum object_type get_type(unsigned int mode)\n> +{\n> +\treturn (S_ISGITLINK(mode)\n> +\t        ? OBJ_COMMIT\n> +\t        : S_ISDIR(mode)\n> +\t        ? OBJ_TREE\n> +\t        : OBJ_BLOB);\n> +}\n\nThis new function is a re-invention of the object_type() utility in\ncache.h, and isn't needed. I.e....\n\n>  static int show_tree(const struct object_id *oid, struct strbuf *base,\n>  \t\tconst char *pathname, unsigned mode, void *context)\n>  {\n> -\tint retval = 0;\n> +\tint recurse = 0;\n>  \tsize_t baselen;\n> -\tenum object_type type = OBJ_BLOB;\n> -\n> -\tif (S_ISGITLINK(mode)) {\n> -\t\ttype = OBJ_COMMIT;\n> -\t} else if (S_ISDIR(mode)) {\n> -\t\tif (show_recursive(base->buf, base->len, pathname)) {\n> -\t\t\tretval = READ_TREE_RECURSIVE;\n> -\t\t\tif (!(ls_options & LS_SHOW_TREES))\n> -\t\t\t\treturn retval;\n> -\t\t}\n> -\t\ttype = OBJ_TREE;\n> -\t}\n> -\telse if (ls_options & LS_TREE_ONLY)\n> +\tenum object_type type = get_type(mode);\n\n...just drop it and do this:\n\n\t-       enum object_type type = get_type(mode);\n\t+       enum object_type type = object_type(mode);\n"},{"id":"446107","messageId":"220113.86wnj4w228.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"bcfbc935b80b889273e3e54fec2a896e44acd2b5.1641978175.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T06:59:35Z","receivedAt":"2022-01-13T07:02:43Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 13 2022, Teng Long wrote:\n\n> We usually pipe the output from `git ls-trees` to tools like\n> `sed` or `cut` when we only want to extract some fields.\n>\n> When we want only the pathname component, we can pass\n> `--name-only` option to omit such a pipeline, but there are no\n> options for extracting other fields.\n>\n> Teach the \"--object-only\" option to the command to only show the\n> object name. This option cannot be used together with\n> \"--name-only\" or \"--long\" , they are mutually exclusive (actually\n> \"--name-only\" and \"--long\" can be combined together before, this\n> commit by the way fix this bug).\n\nIn the RFC series I sent this was first implemented in terms of the\n--format option, and I skipped the custom implementation you're adding\nhere:\nhttps://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/\n\nI think in terms of patch series structure it would make sense to do\nthat, and then have this custom --object-only implementation in terms of\nnot-\"--format \" follow from that, and thus with the tests for the two\n(we'd add the tests you're adding here first, just for a\n--format=\"%(objectname)\" or whatever) we'd see that the two are 1=1\nequivalent in terms of functionality, but that this one is <X>% more\noptimized.\n"},{"id":"446108","messageId":"220113.86sftsw1ut.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"3ddffa10278b5814123a5bcd2fd3a531def3ecdc.1641978175.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 7/9] ls-tree.c: introduce struct \"show_tree_data\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T07:03:02Z","receivedAt":"2022-01-13T07:07:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 13 2022, Teng Long wrote:\n\n> \"show_tree_data\" is a struct that packages the necessary fields for\n> \"show_tree()\". This commit is a pre-prepared commit for supporting\n> \"--format\" option and it does not affect any existing functionality.\n\nIs the only reason this is split off from 9/9 because you're injecting a\n8/9 commit for the coccinelle rule change, and wanted to find some\nlogical cut-off between the two?\n\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n\nFor both this & 9/9 this seems to mostly/substantially be code I wrote\nand submitted as part of\nhttps://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com;\n\nThe convention we use in such cases is to retain the \"Author\" header and\njust add your own Signed-off-by to patches you're modifying/splitting\nup.\n"},{"id":"446110","messageId":"220113.86o84gvwfd.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com","subject":"Re: [PATCH v10 9/9] ls-tree.c: introduce \"--format\" option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-13T07:16:52Z","receivedAt":"2022-01-13T09:04:27Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 13 2022, Teng Long wrote:\n\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n> ---\n>  Documentation/git-ls-tree.txt |  51 +++++++++++++-\n>  builtin/ls-tree.c             | 129 +++++++++++++++++++++++++++++++++-\n>  t/t3105-ls-tree-format.sh     |  55 +++++++++++++++\n>  3 files changed, 230 insertions(+), 5 deletions(-)\n>  create mode 100755 t/t3105-ls-tree-format.sh\n>\n> diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\n> index 729370f235..ebdde6eae3 100644\n> --- a/Documentation/git-ls-tree.txt\n> +++ b/Documentation/git-ls-tree.txt\n> @@ -10,9 +10,9 @@ SYNOPSIS\n>  --------\n>  [verse]\n>  'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n> -\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n> -\t    <tree-ish> [<path>...]\n> -\n> +\t    [--name-only] [--name-status] [--object-only]\n> +\t    [--full-name] [--full-tree] [--abbrev[=<n>]] \n\nLet's split up this re-flow only change into its own commit? I.e. the\nonly non-whitespace change here is beginning with [--format].\n\nIf it was the right thing to do to re-flow this then we didn't need\n[--format=<format>] to exist to do so...\n\n> +\t    [--format=<format>] <tree-ish> [<path>...]\n>  DESCRIPTION\n\nRemoving this \\n breaks the formatting in the file. See \"make man && man\n./Documentation/git-ls-tree.1\". The ./Documentation/doc-diff utility is\nalso handy for sanity checking the documentation formatting.\n\n>  -----------\n>  Lists the contents of a given tree object, like what \"/bin/ls -a\" does\n> @@ -79,6 +79,16 @@ OPTIONS\n>  \tDo not limit the listing to the current working directory.\n>  \tImplies --full-name.\n>  \n> +--format=<format>::\n> +\tA string that interpolates `%(fieldname)` from the result\n> +\tbeing shown. It also interpolates `%%` to `%`, and\n> +\t`%xx` where `xx` are hex digits interpolates to character\n> +\twith hex code `xx`; for example `%00` interpolates to\n> +\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n> +\tWhen specified, `--format` cannot be combined with other\n> +\tformat-altering options, including `--long`, `--name-only`\n> +\tand `--object-only`.\n> +\n\nThese new docs make sense & seem to cover all the basis, thanks!\n\n> +\n> +Default format:\n> +\n>          <mode> SP <type> SP <object> TAB <file>\n\nHere because we've added --format discussing the previous pseudo-format\nas a \"default\" format becomes confusing. Let's instead say:\n\n        The output format of `ls-tree` is determined by either the `--format` option,\n        or other format-altering options such as `--name-long` etc. (see `--format` above).\n\n        The use of certain `--format` directives is equivalent to using those options,\n        but invoking the full formatting machinery can be slower than using an appropriate\n        formatting option.\n\n        In cases where the `--format` would exactly map to an existing option `ls-tree` will\n        use the appropriate faster path. Thus the default format is equivalent to:\n        ---\n        %(mode) %(type) %(object)%x09%(file)\n        ---\n\nOr something like that. We could then discuss e.g. --name-long being\n`%(mode) %(type) %(object) %(size:padded)%x09%(file)` when we discuss\nthat option.\n\n>  This output format is compatible with what `--index-info --stdin` of\n> @@ -105,6 +118,38 @@ quoted as explained for the configuration variable `core.quotePath`\n>  (see linkgit:git-config[1]).  Using `-z` the filename is output\n>  verbatim and the line is terminated by a NUL byte.\n>  \n> +Customized format:\n> +\n> +It is possible to print in a custom format by using the `--format` option,\n> +which is able to interpolate different fields using a `%(fieldname)` notation.\n> +For example, if you want to only print the <object> and <file> fields with a\n> +JSON style, executing with a specific \"--format\" like\n> +\n> +        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n> +\n> +The output format changes to:\n> +\n> +        {\"object\":\"<object>\", \"file\":\"<file>\"}\n\nThis one-liner is guaranteed to result in invalid JSON on some\nrepositories, both because JSON is inherently a bad fit for git's data\nmodel (JSON needs to be in one Unicode encoding, Git's tree data might\nme in a mixture of encodings), and because it'll break if the file\nincludes a '\"'.\n\nI think it's better to just replace this with some example involving -z,\nor at least prominently note that this is broken in the general case,\nbut can be used ad-hoc to quickly check things with \"jq\" or whatever.\n\n> +FIELD NAMES\n> +-----------\n> +\n> +Various values from structured fields can be used to interpolate\n> +into the resulting output. For each outputing line, the following\n> +names can be used:\n> +\n> +mode::\n> +\tThe mode of the object.\n> +type::\n> +\tThe type of the object (`blob` or `tree`).\n> +object::\n> +\tThe name of the object.\n> +size[:padded]::\n> +\tThe size of the object (\"-\" if it's a tree).\n> +\tIt also supports a padded format of size with \"%(size:padded)\".\n> +file::\n> +\tThe filename of the object.\n\nIn\nhttps://lore.kernel.org/git/cover.1641043500.git.dyroneteng@gmail.com/\nyou noted that you changed the field names of e.g. \"objectname\" to\n\"object\" etc. You're right that I picked these as-is from the\ngit-for-each-ref formatting.\n\n1/3 of your reasoning for doing so was to make it consistent with the\ndocumentation examples of e.g.:\n\n     <mode> SP <type> SP <object> TAB <file>\n\nI think in any case (as noted above) we should change those to use the\n--format), so that leaves just:\n\n - \"I prefer to make the name more simple to memorize and type\"\n - \"I think the names with \"object\" prefix are [from git-for-each-ref\n   and the object* prefixes aren't redundant there, but would be here]\".\n\nI think both of those still apply, but I think having these consistent\nwith git-for-each-ref outweighs the slight benefit of shorter names.\n\nRight now only a handful of things support these sort of --format\ndirectives, but we've already got RFC/WIP patches to add that to\ngit-cat-file, and are likely to add more in the future.\n\nI'd also like us to eventually be able to combine what are now separate\nbuilt-ins with their own --format to expose more deeply some internal\nAPIs via IPC. E.g. now you can do this:\n\n    git for-each-ref --format='%(refname) %(tree)'\n\nBut to list each of those trees you'd need to pipe that output into this\nnew 'git ls-tree --format. But imagine being able to do something like:\n\n    git for-each-ref --format='%(refname) %(git-ls-tree --format %%(objectname) %(tree))'\n\nWhere we'd just invoke git-ls-tree for you without running a full\nsub-process. I think both for that hypothetical and working with the two\n--formats now having to use %(type) in some places but %(objecttype)\netc. in others is just needlessly confusing. Let's just consistently use\nthe same format names everywhere.\n\nSpecifically for your s/path/file/ name change, that's just inaccurate, consider:\n\n    $ ./git ls-tree --format=\"%(mode) %(type) %(file)\" -t HEAD -- t/README\n    040000 tree t\n    100644 blob t/README\n\nAnd:\n\n    $ $ (cd t && ../git ls-tree --format=\"%(mode) %(type) %(file)\" -t -r HEAD -- README)\n    040000 tree ./\n    100644 blob README\n\nI.e. we talk about <path> in the existing SYNOPSIS for a reason. That we\nhad a \"<file>\" in the existing format demo was a bug/shorthand that we\nshouldn't be propagating further.\n\n> [...]\n> +static const char *format;\n> +static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n> +static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n> +static const char *name_only_format = \"%(file)\";\n> +static const char *object_only_format = \"%(object)\";\n> +\n\nOne advantage of keeping the variable names I picked in\nhttps://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\nis that they align, so you can instantly see that the first two are\nequivalent until the \"%x09\".\n\nIt also makes it easier to review to avoid such churn, to see what you\nreally changed I'm looking at a local version of a range-diff where I\nrenamed these, the struct you renamed etc. back just to see what you\n/really/ changed. I.e. what are functional v.s. renaming changes.\n\n>  static int parse_shown_fields(void)\n>  {\n>  \tif (cmdmode == MODE_NAME_ONLY) {\n> @@ -76,6 +82,72 @@ static int parse_shown_fields(void)\n>  \treturn 1;\n>  }\n>  \n> +static void expand_objectsize(struct strbuf *line, const struct object_id *oid,\n> +\t\t\t      const enum object_type type, unsigned int padded)\n> +{\n> +\tif (type == OBJ_BLOB) {\n> +\t\tunsigned long size;\n> +\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n> +\t\t\tdie(_(\"could not get object info about '%s'\"),\n> +\t\t\t    oid_to_hex(oid));\n> +\t\tif (padded)\n> +\t\t\tstrbuf_addf(line, \"%7\" PRIuMAX, (uintmax_t)size);\n> +\t\telse\n> +\t\t\tstrbuf_addf(line, \"%\" PRIuMAX, (uintmax_t)size);\n\nHere you changed my '\"%\"PRIuMAX' to '\"%\" PRIuMAX'. The former is the\nprevailing style in this codebase, and avoiding the formatting churn\nmakes the inter-diff easier to read.\n\n> +\t} else if (padded) {\n> +\t\tstrbuf_addf(line, \"%7s\", \"-\");\n> +\t} else {\n> +\t\tstrbuf_addstr(line, \"-\");\n> +\t}\n> +}\n\nDitto some harder to review interdiff due to renaming\nchurn. I.e. s/line/sb/ in both this and expand_show_tree(). I really\nwouldn't care at all except because of all the manual work in reviewing\nthe inter-diff between my original version & this derived version.\n\nIn the case of \"line\" that's not even an improvement. With a --format\nwe're not building a \"line\", the user is free to insert any arbitrary\ndirectives including \\n's, so we might be working on multiple lines.\n\n> +test_expect_success 'ls-tree --format usage' '\n> +\ttest_expect_code 129 git ls-tree --format=fmt -l HEAD &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --name-status HEAD &&\n> +\ttest_expect_code 129 git ls-tree --format=fmt --object-only HEAD\n> +'\n\nThis & several other changes v.s. my version are good, e.g. here I seem\nto have repeated the logic error I noted for your version (i.e omitting\n\"HEAD\"), oops!\n\n> +test_expect_success 'setup' '\n> +\tmkdir dir &&\n> +\ttest_commit dir/sub-file &&\n> +\ttest_commit top-file\n> +'\n> +\n> +test_ls_tree_format () {\n> +\tformat=$1 &&\n> +\topts=$2 &&\n> +\tshift 2 &&\n> +\tgit ls-tree $opts -r HEAD >expect.raw &&\n> +\tsed \"s/^/> /\" >expect <expect.raw &&\n> +\tgit ls-tree --format=\"> $format\" -r HEAD >actual &&\n> +\ttest_cmp expect actual\n> +}\n> +\n> +test_expect_success 'ls-tree --format=<default-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(mode) %(type) %(object)%x09%(file)\" \\\n> +\t\t\"\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<long-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(mode) %(type) %(object) %(size:padded)%x09%(file)\" \\\n> +\t\t\"--long\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<name-only-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(file)\" \\\n> +\t\t\"--name-only\"\n> +'\n> +\n> +test_expect_success 'ls-tree --format=<object-only-like>' '\n> +\ttest_ls_tree_format \\\n> +\t\t\"%(object)\" \\\n> +\t\t\"--object-only\"\n> +'\n> +\n> +test_done\n\nAs I noted in my RFC CL (https://lore.kernel.org/git/RFC-cover-0.7-00000000000-20211217T131635Z-avarab@gmail.com/):\n\n\t\"the tests for ls-tree are really\n\tlacking. E.g. I seem to have a rather obvious bug in how -t and the\n\t--format interact here, but no test catches it.\"\n\nSo first, in my version of adding --format I was careful to make\n--name-only etc. imply a given --format, and then only at the last\nminute would we take the \"fast path\":\nhttps://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nYou rewrote that in\nhttps://lore.kernel.org/git/e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com/\nand qremoved the limited \"GIT_TEST_LS_TREE_FORMAT_BACKEND\" testing I\nadded, so now the internal --format machinery can't be run through the\nexisting tests we do have.\n\nEven with that re-added I really wouldn't trust that this code is doing\nthe right thing (and as noted, I don't trust my own RFC version\neither). I think e.g. our \"coverage\" Makefile targets would be a good\nstart as a first approximation, i.e. running the /ls-tree/ tests and\nseeing if we have full coverage.\n\n> Signed-off-by: Teng Long <dyroneteng@gmail.com>\n\nAs I noted in 7/9 I think this patch is 9/9 still mostly something I\nwrote, so that the \"author\" and Signed-off-by should be preserved. The\nbelow is a range-diff of an amended version I've been looking at in\ntrying to review this. It undoes several (but not all) of your\nformatting/renaming-only changes, just so that I could see what the\nnon-formatting changes were:\n\n1:  6c96dff15c5 ! 1:  917bb168d45 ls-tree: add a --format=<fmt> option\n    @@\n      ## Metadata ##\n    -Author: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    +Author: Teng Long <dyroneteng@gmail.com>\n     \n      ## Commit message ##\n    -    ls-tree: add a --format=<fmt> option\n    +    ls-tree.c: introduce \"--format\" option\n     \n         Add a --format option to ls-tree. It has an existing default output,\n         and then --long and --name-only options to emit the default output\n    @@ Commit message\n     \n         The --format implementation is slower than the existing code, but this\n         change does not cause any performance regressions. We'll leave the\n    -    existing show_tree() unchanged, and only run show_tree_format() in if\n    +    existing show_tree() unchanged, and only run show_tree_fmt() in if\n         a --format different than the hardcoded built-in ones corresponding to\n         the existing modes is provided.\n     \n    -    \"Slower\" here can bee seen via the the following \"hyperfine\"\n    -    command. This uses GIT_TEST_LS_TREE_FORMAT_BACKEND=<bool> to force the\n    -    use of the new backend:\n    -\n    -        $ 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\n    -        Benchmark 1: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r HEAD\n    -          Time (mean ± σ):      86.1 ms ±   0.6 ms    [User: 65.2 ms, System: 20.9 ms]\n    -          Range (min … max):    85.2 ms …  87.5 ms    10 runs\n    +    I.e. something like the \"--long\" output would be much slower with\n    +    this, mainly due to how we need to allocate various things to do with\n    +    quote.c instead of spewing the output directly to stdout.\n     \n    -        Benchmark 2: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r HEAD\n    -          Time (mean ± σ):     122.5 ms ±   0.6 ms    [User: 101.3 ms, System: 21.1 ms]\n    -          Range (min … max):   121.8 ms … 123.4 ms    10 runs\n    +    The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's\n    +    idea and suggestion, this commit makes modifications in terms of the\n    +    original discussion on community [1].\n     \n    -        Benchmark 3: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r -l HEAD\n    -          Time (mean ± σ):     277.7 ms ±   1.3 ms    [User: 234.6 ms, System: 43.0 ms]\n    -          Range (min … max):   275.9 ms … 279.7 ms    10 runs\n    +    Here is the statistics about performance tests:\n     \n    -        Benchmark 4: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r -l HEAD\n    -          Time (mean ± σ):     332.8 ms ±   2.6 ms    [User: 282.0 ms, System: 50.7 ms]\n    -          Range (min … max):   329.6 ms … 338.2 ms    10 runs\n    +    1. Default format (hitten the builtin formats):\n     \n    -        Benchmark 5: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --name-only HEAD\n    -          Time (mean ± σ):      71.8 ms ±   0.4 ms    [User: 54.1 ms, System: 17.6 ms]\n    -          Range (min … max):    71.2 ms …  72.5 ms    10 runs\n    +        \"git ls-tree <tree-ish>\" vs \"--format='%(mode) %(type) %(object)%x09%(file)'\"\n     \n    -        Benchmark 6: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --name-only HEAD\n    -          Time (mean ± σ):      86.6 ms ±   0.5 ms    [User: 65.7 ms, System: 20.7 ms]\n    -          Range (min … max):    85.9 ms …  87.4 ms    10 runs\n    +        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r HEAD\"\n    +        Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD\n    +        Time (mean ± σ):     105.2 ms ±   3.3 ms    [User: 84.3 ms, System: 20.8 ms]\n    +        Range (min … max):    99.2 ms … 113.2 ms    28 runs\n     \n    -        Benchmark 7: GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD\n    -          Time (mean ± σ):      85.8 ms ±   0.6 ms    [User: 66.2 ms, System: 19.5 ms]\n    -          Range (min … max):    85.0 ms …  86.9 ms    10 runs\n    +        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\"\n    +        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)'  HEAD\n    +        Time (mean ± σ):     106.4 ms ±   2.7 ms    [User: 86.1 ms, System: 20.2 ms]\n    +        Range (min … max):   100.2 ms … 110.5 ms    29 runs\n     \n    -        Benchmark 8: GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD\n    -          Time (mean ± σ):      85.3 ms ±   0.2 ms    [User: 66.6 ms, System: 18.7 ms]\n    -          Range (min … max):    85.0 ms …  85.7 ms    10 runs\n    +    2. Default format includes object size (hitten the builtin formats):\n     \n    -        Summary\n    -          'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --name-only HEAD' ran\n    -            1.19 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD'\n    -            1.19 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r --format='%(objectname)' HEAD'\n    -            1.20 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r HEAD'\n    -            1.21 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r --name-only HEAD'\n    -            1.71 ± 0.01 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r HEAD'\n    -            3.87 ± 0.03 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=false ./git -C ~/g/linux ls-tree -r -l HEAD'\n    -            4.64 ± 0.05 times faster than 'GIT_TEST_LS_TREE_FORMAT_BACKEND=true ./git -C ~/g/linux ls-tree -r -l HEAD'\n    +        \"git ls-tree -l <tree-ish>\" vs \"--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'\"\n     \n    -    I.e. something like the \"--long\" output would be much slower with\n    -    this, mainly due to how we need to allocate various things to do with\n    -    quote.c instead of spewing the output directly to stdout.\n    +        $hyperfine --warmup=10 \"/opt/git/master/bin/git ls-tree -r -l HEAD\"\n    +        Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD\n    +        Time (mean ± σ):     335.1 ms ±   6.5 ms    [User: 304.6 ms, System: 30.4 ms]\n    +        Range (min … max):   327.5 ms … 348.4 ms    10 runs\n     \n    -    But even a --format='%(objectname)' is fast with the new backend, so\n    -    this is viable as a replacement for adding new formats, and we'll pay\n    -    for this added complexity as a one-off, and not again every time a new\n    -    format needs to be added. See [1] for an example of what it would\n    -    otherwise take to add an --object-name flag.\n    +        $hyperfine --warmup=10 \"/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\"\n    +        Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'  HEAD\n    +        Time (mean ± σ):     337.2 ms ±   8.2 ms    [User: 309.2 ms, System: 27.9 ms]\n    +        Range (min … max):   328.8 ms … 349.4 ms    10 runs\n     \n    -    1. https://lore.kernel.org/git/2e449d1c792ff81da5f22c8bf65ed33c393d62f8.1639721750.git.dyroneteng@gmail.com/\n    +    Links:\n    +            [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n     \n    -    Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n    +    Signed-off-by: Teng Long <dyroneteng@gmail.com>\n     \n    - ## builtin/ls-tree.c ##\n    -@@ builtin/ls-tree.c: static struct pathspec pathspec;\n    - static int chomp_prefix;\n    - static const char *ls_tree_prefix;\n    + ## Documentation/git-ls-tree.txt ##\n    +@@ Documentation/git-ls-tree.txt: SYNOPSIS\n    + --------\n    + [verse]\n    + 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n    +-\t    [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n    +-\t    <tree-ish> [<path>...]\n    ++\t    [--name-only] [--name-status] [--object-only]\n    ++\t    [--full-name] [--full-tree] [--abbrev[=<n>]]\n    ++\t    [--format=<format>] <tree-ish> [<path>...]\n      \n    -+/*\n    -+ * The format equivalents that show_tree() is prepared to handle.\n    -+ */\n    -+static const char *ls_tree_format_d = \"%(objectmode) %(objecttype) %(objectname)%x09%(path)\";\n    -+static const char *ls_tree_format_l = \"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)\";\n    -+static const char *ls_tree_format_n = \"%(path)\";\n    + DESCRIPTION\n    + -----------\n    +@@ Documentation/git-ls-tree.txt: OPTIONS\n    + \tDo not limit the listing to the current working directory.\n    + \tImplies --full-name.\n    + \n    ++--format=<format>::\n    ++\tA string that interpolates `%(fieldname)` from the result\n    ++\tbeing shown. It also interpolates `%%` to `%`, and\n    ++\t`%xx` where `xx` are hex digits interpolates to character\n    ++\twith hex code `xx`; for example `%00` interpolates to\n    ++\t`\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n    ++\tWhen specified, `--format` cannot be combined with other\n    ++\tformat-altering options, including `--long`, `--name-only`\n    ++\tand `--object-only`.\n     +\n    - static const  char * const ls_tree_usage[] = {\n    - \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n    - \tNULL\n    - };\n    + [<path>...]::\n    + \tWhen paths are given, show them (note that this isn't really raw\n    + \tpathnames, but rather a list of patterns to match).  Otherwise\n    +@@ Documentation/git-ls-tree.txt: OPTIONS\n      \n    -+struct read_tree_ls_tree_data {\n    -+\tconst char *format;\n    -+\tstruct strbuf sb_scratch;\n    -+\tstruct strbuf sb_tmp;\n    -+};\n    + Output Format\n    + -------------\n    ++\n    ++Default format:\n    ++\n    +         <mode> SP <type> SP <object> TAB <file>\n    + \n    + This output format is compatible with what `--index-info --stdin` of\n    +@@ Documentation/git-ls-tree.txt: quoted as explained for the configuration variable `core.quotePath`\n    + (see linkgit:git-config[1]).  Using `-z` the filename is output\n    + verbatim and the line is terminated by a NUL byte.\n    + \n    ++Customized format:\n     +\n    ++It is possible to print in a custom format by using the `--format` option,\n    ++which is able to interpolate different fields using a `%(fieldname)` notation.\n    ++For example, if you want to only print the <object> and <file> fields with a\n    ++JSON style, executing with a specific \"--format\" like\n    ++\n    ++        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n    ++\n    ++The output format changes to:\n    ++\n    ++        {\"object\":\"<object>\", \"file\":\"<file>\"}\n    ++\n    ++FIELD NAMES\n    ++-----------\n    ++\n    ++Various values from structured fields can be used to interpolate\n    ++into the resulting output. For each outputing line, the following\n    ++names can be used:\n    ++\n    ++mode::\n    ++\tThe mode of the object.\n    ++type::\n    ++\tThe type of the object (`blob` or `tree`).\n    ++object::\n    ++\tThe name of the object.\n    ++size[:padded]::\n    ++\tThe size of the object (\"-\" if it's a tree).\n    ++\tIt also supports a padded format of size with \"%(size:padded)\".\n    ++file::\n    ++\tThe filename of the object.\n    ++\n    + GIT\n    + ---\n    + Part of the linkgit:git[1] suite\n    +\n    + ## builtin/ls-tree.c ##\n    +@@ builtin/ls-tree.c: static unsigned int shown_fields;\n    + #define FIELD_DEFAULT 29 /* 11101 size is not shown to output by default */\n    + #define FIELD_LONG_DEFAULT  (FIELD_DEFAULT | FIELD_SIZE)\n    + \n     +struct expand_ls_tree_data {\n     +\tunsigned mode;\n     +\tenum object_type type;\n     +\tconst struct object_id *oid;\n     +\tconst char *pathname;\n    -+\tconst char *basebuf;\n    -+\tstruct strbuf *sb_scratch;\n    -+\tstruct strbuf *sb_tmp;\n    ++\tstruct strbuf *base;\n     +};\n     +\n    - static int show_recursive(const char *base, size_t baselen, const char *pathname)\n    + static const  char * const ls_tree_usage[] = {\n    + \tN_(\"git ls-tree [<options>] <tree-ish> [<path>...]\"),\n    + \tNULL\n    +@@ builtin/ls-tree.c: enum {\n    + \n    + static int cmdmode = MODE_UNSPECIFIED;\n    + \n    ++static const char *format;\n    ++static const char *ls_tree_format_d = \"%(objectmode) %(objecttype) %(objectname)%x09%(path)\";\n    ++static const char *ls_tree_format_l = \"%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)\";\n    ++static const char *ls_tree_format_n = \"%(path)\";\n    ++static const char *ls_tree_format_o = \"%(objectname)\";\n    ++\n    + static int parse_shown_fields(void)\n      {\n    - \tint i;\n    -@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, const char *pathname\n    - \treturn 0;\n    + \tif (cmdmode == MODE_NAME_ONLY) {\n    +@@ builtin/ls-tree.c: static int parse_shown_fields(void)\n    + \treturn 1;\n      }\n      \n    -+static void expand_objectsize(struct strbuf *sb,\n    -+\t\t\t      const struct object_id *oid,\n    -+\t\t\t      const enum object_type type,\n    -+\t\t\t      unsigned int padded)\n    ++static void expand_objectsize(struct strbuf *sb, const struct object_id *oid,\n    ++\t\t\t      const enum object_type type, unsigned int padded)\n     +{\n     +\tif (type == OBJ_BLOB) {\n     +\t\tunsigned long size;\n     +\t\tif (oid_object_info(the_repository, oid, &size) < 0)\n    -+\t\t\tdie(_(\"could not get object info about '%s'\"), oid_to_hex(oid));\n    ++\t\t\tdie(_(\"could not get object info about '%s'\"),\n    ++\t\t\t    oid_to_hex(oid));\n     +\t\tif (padded)\n     +\t\t\tstrbuf_addf(sb, \"%7\"PRIuMAX, (uintmax_t)size);\n     +\t\telse\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c\n     +\t}\n     +}\n     +\n    -+static size_t expand_show_tree(struct strbuf *sb,\n    -+\t\t\t       const char *start,\n    ++static size_t expand_show_tree(struct strbuf *sb, const char *start,\n     +\t\t\t       void *context)\n     +{\n     +\tstruct expand_ls_tree_data *data = context;\n     +\tconst char *end;\n     +\tconst char *p;\n    -+\tsize_t len;\n    ++\tunsigned int errlen;\n    ++\tsize_t len = strbuf_expand_literal_cb(sb, start, NULL);\n     +\n    -+\tlen = strbuf_expand_literal_cb(sb, start, NULL);\n     +\tif (len)\n     +\t\treturn len;\n    -+\n     +\tif (*start != '(')\n    -+\t\tdie(_(\"bad format as of '%s'\"), start);\n    ++\t\tdie(_(\"bad ls-tree format: as '%s'\"), start);\n    ++\n     +\tend = strchr(start + 1, ')');\n     +\tif (!end)\n    -+\t\tdie(_(\"ls-tree format element '%s' does not end in ')'\"),\n    -+\t\t    start);\n    -+\tlen = end - start + 1;\n    ++\t\tdie(_(\"bad ls-tree format: element '%s' does not end in ')'\"), start);\n     +\n    ++\tlen = end - start + 1;\n     +\tif (skip_prefix(start, \"(objectmode)\", &p)) {\n     +\t\tstrbuf_addf(sb, \"%06o\", data->mode);\n     +\t} else if (skip_prefix(start, \"(objecttype)\", &p)) {\n    @@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen, c\n     +\t} else if (skip_prefix(start, \"(objectsize)\", &p)) {\n     +\t\texpand_objectsize(sb, data->oid, data->type, 0);\n     +\t} else if (skip_prefix(start, \"(objectname)\", &p)) {\n    -+\t\tstrbuf_addstr(sb, find_unique_abbrev(data->oid, abbrev));\n    ++\t\tstrbuf_add_unique_abbrev(sb, data->oid, abbrev);\n     +\t} else if (skip_prefix(start, \"(path)\", &p)) {\n    -+\t\tconst char *name = data->basebuf;\n    ++\t\tconst char *name = data->base->buf;\n     +\t\tconst char *prefix = chomp_prefix ? ls_tree_prefix : NULL;\n    -+\n    -+\t\tif (prefix)\n    -+\t\t\tname = relative_path(name, prefix, data->sb_scratch);\n    -+\t\tquote_c_style(name, data->sb_tmp, NULL, 0);\n    -+\t\tstrbuf_add(sb, data->sb_tmp->buf, data->sb_tmp->len);\n    -+\n    -+\t\tstrbuf_reset(data->sb_tmp);\n    -+\t\t/* The relative_path() function resets \"scratch\" */\n    ++\t\tstruct strbuf quoted = STRBUF_INIT;\n    ++\t\tstruct strbuf s = STRBUF_INIT;\n    ++\t\tstrbuf_addstr(data->base, data->pathname);\n    ++\t\tname = relative_path(data->base->buf, prefix, &s);\n    ++\t\tquote_c_style(name, &quoted, NULL, 0);\n    ++\t\tstrbuf_addbuf(sb, &quoted);\n    ++\t\tstrbuf_release(&s);\n    ++\t\tstrbuf_release(&quoted);\n     +\t} else {\n    -+\t\tunsigned int errlen = (unsigned long)len;\n    -+\t\tdie(_(\"bad ls-tree format specifiec %%%.*s\"), errlen, start);\n    ++\t\terrlen = (unsigned long)len;\n    ++\t\tdie(_(\"bad ls-tree format: %%%.*s\"), errlen, start);\n     +\t}\n    -+\n     +\treturn len;\n     +}\n     +\n    - static int show_tree_init(enum object_type *type, struct strbuf *base,\n    - \t\t\t  const char *pathname, unsigned mode, int *retval)\n    + static int show_recursive(const char *base, size_t baselen,\n    + \t\t\t  const char *pathname)\n      {\n    -@@ builtin/ls-tree.c: static int show_tree_init(enum object_type *type, struct strbuf *base,\n    +@@ builtin/ls-tree.c: static int show_recursive(const char *base, size_t baselen,\n      \treturn 0;\n      }\n      \n    +-static int show_default(const struct object_id *oid, enum object_type type,\n    +-\t\t\tconst char *pathname, unsigned mode,\n    +-\t\t\tstruct strbuf *base)\n     +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base,\n     +\t\t\t const char *pathname, unsigned mode, void *context)\n    -+{\n    -+\tstruct read_tree_ls_tree_data *data = context;\n    -+\tstruct expand_ls_tree_data my_data = {\n    + {\n    +-\tsize_t baselen = base->len;\n    ++\tsize_t baselen;\n    ++\tint recurse = 0;\n    ++\tstruct strbuf line = STRBUF_INIT;\n    ++\tenum object_type type = object_type(mode);\n    ++\n    ++\tstruct expand_ls_tree_data data = {\n     +\t\t.mode = mode,\n    -+\t\t.type = OBJ_BLOB,\n    ++\t\t.type = type,\n     +\t\t.oid = oid,\n     +\t\t.pathname = pathname,\n    -+\t\t.sb_scratch = &data->sb_scratch,\n    -+\t\t.sb_tmp = &data->sb_tmp,\n    ++\t\t.base = base,\n     +\t};\n    -+\tstruct strbuf sb = STRBUF_INIT;\n    -+\tint retval = 0;\n    -+\tsize_t baselen;\n     +\n    -+\tif (show_tree_init(&my_data.type, base, pathname, mode, &retval))\n    -+\t\treturn retval;\n    ++\tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n    ++\t\trecurse = READ_TREE_RECURSIVE;\n    ++\tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n    ++\t\treturn recurse;\n    ++\tif (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY))\n    ++\t\treturn 0;\n     +\n     +\tbaselen = base->len;\n    -+\tstrbuf_addstr(base, pathname);\n    -+\tstrbuf_reset(&sb);\n    -+\tmy_data.basebuf = base->buf;\n    -+\n    -+\tstrbuf_expand(&sb, data->format, expand_show_tree, &my_data);\n    -+\tstrbuf_addch(&sb, line_termination);\n    -+\tfwrite(sb.buf, sb.len, 1, stdout);\n    ++\tstrbuf_expand(&line, format, expand_show_tree, &data);\n    ++\tstrbuf_addch(&line, line_termination);\n    ++\tfwrite(line.buf, line.len, 1, stdout);\n    ++\tstrbuf_release(&line);\n     +\tstrbuf_setlen(base, baselen);\n    -+\n    -+\treturn retval;\n    ++\treturn recurse;\n     +}\n     +\n    - static int show_tree(const struct object_id *oid, struct strbuf *base,\n    - \t\tconst char *pathname, unsigned mode, void *context)\n    - {\n    ++static int show_default(struct expand_ls_tree_data *data)\n    ++{\n    ++\tsize_t baselen = data->base->len;\n    + \n    + \tif (shown_fields & FIELD_SIZE) {\n    + \t\tchar size_text[24];\n    +-\t\tif (type == OBJ_BLOB) {\n    ++\t\tif (data->type == OBJ_BLOB) {\n    + \t\t\tunsigned long size;\n    +-\t\t\tif (oid_object_info(the_repository, oid, &size) == OBJ_BAD)\n    ++\t\t\tif (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD)\n    + \t\t\t\txsnprintf(size_text, sizeof(size_text), \"BAD\");\n    + \t\t\telse\n    + \t\t\t\txsnprintf(size_text, sizeof(size_text),\n    +@@ builtin/ls-tree.c: static int show_default(const struct object_id *oid, enum object_type type,\n    + \t\t} else {\n    + \t\t\txsnprintf(size_text, sizeof(size_text), \"-\");\n    + \t\t}\n    +-\t\tprintf(\"%06o %s %s %7s\\t\", mode, type_name(type),\n    +-\t\tfind_unique_abbrev(oid, abbrev), size_text);\n    ++\t\tprintf(\"%06o %s %s %7s\\t\", data->mode, type_name(data->type),\n    ++\t\tfind_unique_abbrev(data->oid, abbrev), size_text);\n    + \t} else {\n    +-\t\tprintf(\"%06o %s %s\\t\", mode, type_name(type),\n    +-\t\tfind_unique_abbrev(oid, abbrev));\n    ++\t\tprintf(\"%06o %s %s\\t\", data->mode, type_name(data->type),\n    ++\t\tfind_unique_abbrev(data->oid, abbrev));\n    + \t}\n    +-\tbaselen = base->len;\n    +-\tstrbuf_addstr(base, pathname);\n    +-\twrite_name_quoted_relative(base->buf,\n    ++\tbaselen = data->base->len;\n    ++\tstrbuf_addstr(data->base, data->pathname);\n    ++\twrite_name_quoted_relative(data->base->buf,\n    + \t\t\t\t   chomp_prefix ? ls_tree_prefix : NULL, stdout,\n    + \t\t\t\t   line_termination);\n    +-\tstrbuf_setlen(base, baselen);\n    ++\tstrbuf_setlen(data->base, baselen);\n    + \treturn 1;\n    + }\n    + \n    +@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    + \tsize_t baselen;\n    + \tenum object_type type = object_type(mode);\n    + \n    ++\tstruct expand_ls_tree_data data = {\n    ++\t\t.mode = mode,\n    ++\t\t.type = type,\n    ++\t\t.oid = oid,\n    ++\t\t.pathname = pathname,\n    ++\t\t.base = base,\n    ++\t};\n    ++\n    + \tif (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname))\n    + \t\trecurse = READ_TREE_RECURSIVE;\n    + \tif (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES))\n    +@@ builtin/ls-tree.c: static int show_tree(const struct object_id *oid, struct strbuf *base,\n    + \t}\n    + \n    + \tif (shown_fields >= FIELD_DEFAULT)\n    +-\t\tshow_default(oid, type, pathname, mode, base);\n    ++\t\tshow_default(&data);\n    + \n    + \treturn recurse;\n    + }\n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n      \tstruct object_id oid;\n      \tstruct tree *tree;\n      \tint i, full_tree = 0;\n    -+\tconst char *implicit_format = NULL;\n    -+\tconst char *format = NULL;\n    -+\tstruct read_tree_ls_tree_data read_tree_cb_data = {\n    -+\t\t.sb_scratch = STRBUF_INIT,\n    -+\t\t.sb_tmp = STRBUF_INIT,\n    -+\t};\n    ++\tread_tree_fn_t fn = show_tree;\n      \tconst struct option ls_tree_options[] = {\n      \t\tOPT_BIT('d', NULL, &ls_options, N_(\"only show trees\"),\n      \t\t\tLS_TREE_ONLY),\n    @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *pref\n      \t\tOPT_BOOL(0, \"full-tree\", &full_tree,\n      \t\t\t N_(\"list entire tree; not just current directory \"\n      \t\t\t    \"(implies --full-name)\")),\n    -+\t\tOPT_STRING_F(0 , \"format\", &format, N_(\"format\"),\n    -+\t\t\t     N_(\"format to use for the output\"), PARSE_OPT_NONEG),\n    ++\t\tOPT_STRING_F(0, \"format\", &format, N_(\"format\"),\n    ++\t\t\t     N_(\"format to use for the output\"),\n    ++\t\t\t     PARSE_OPT_NONEG),\n      \t\tOPT__ABBREV(&abbrev),\n      \t\tOPT_END()\n      \t};\n    -+\tread_tree_fn_t fn = show_tree;\n    - \n    - \tgit_config(git_default_config, NULL);\n    - \tls_tree_prefix = prefix;\n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n    - \tif ( (LS_TREE_ONLY|LS_RECURSIVE) ==\n      \t    ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options))\n      \t\tls_options |= LS_SHOW_TREES;\n    -+\tif (ls_options & LS_NAME_ONLY)\n    -+\t\timplicit_format = ls_tree_format_n;\n    -+\tif (ls_options & LS_SHOW_SIZE)\n    -+\t\timplicit_format = ls_tree_format_l;\n    -+\n    -+\tif (format && implicit_format)\n    -+\t\tusage_msg_opt(_(\"providing --format cannot be combined with other format-altering options\"),\n    -+\t\t\t      ls_tree_usage, ls_tree_options);\n    -+\tif (implicit_format)\n    -+\t\tformat = implicit_format;\n    -+\tif (!format)\n    -+\t\tformat = ls_tree_format_d;\n      \n    ++\tif (format && cmdmode)\n    ++\t\tusage_msg_opt(\n    ++\t\t\t_(\"--format can't be combined with other format-altering options\"),\n    ++\t\t\tls_tree_usage, ls_tree_options);\n      \tif (argc < 1)\n      \t\tusage_with_options(ls_tree_usage, ls_tree_options);\n    + \tif (get_oid(argv[0], &oid))\n     @@ builtin/ls-tree.c: int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n      \ttree = parse_tree_indirect(&oid);\n      \tif (!tree)\n      \t\tdie(\"not a tree object\");\n    +-\treturn !!read_tree(the_repository, tree,\n    +-\t\t\t   &pathspec, show_tree, NULL);\n     +\n     +\t/*\n     +\t * The generic show_tree_fmt() is slower than show_tree(), so\n     +\t * take the fast path if possible.\n     +\t */\n    -+\tif (format && (!strcmp(format, ls_tree_format_d) ||\n    -+\t\t       !strcmp(format, ls_tree_format_l) ||\n    -+\t\t       !strcmp(format, ls_tree_format_n)))\n    ++\tif (format &&\n    ++\t    (!strcmp(format, ls_tree_format_d) ||\n    ++\t     !strcmp(format, ls_tree_format_l) ||\n    ++\t     !strcmp(format, ls_tree_format_n) ||\n    ++\t     !strcmp(format, ls_tree_format_o)))\n     +\t\tfn = show_tree;\n     +\telse if (format)\n     +\t\tfn = show_tree_fmt;\n    -+\t/*\n    -+\t * Allow forcing the show_tree_fmt(), to test that it can\n    -+\t * handle the test suite.\n    -+\t */\n    -+\tif (git_env_bool(\"GIT_TEST_LS_TREE_FORMAT_BACKEND\", 0))\n    -+\t\tfn = show_tree_fmt;\n     +\n    -+\tread_tree_cb_data.format = format;\n    - \treturn !!read_tree(the_repository, tree,\n    --\t\t\t   &pathspec, show_tree, NULL);\n    -+\t\t\t   &pathspec, fn, &read_tree_cb_data);\n    ++\treturn !!read_tree(the_repository, tree, &pathspec, fn, NULL);\n      }\n     \n      ## t/t3105-ls-tree-format.sh (new) ##\n    @@ t/t3105-ls-tree-format.sh (new)\n     +. ./test-lib.sh\n     +\n     +test_expect_success 'ls-tree --format usage' '\n    -+\ttest_expect_code 129 git ls-tree --format=fmt -l &&\n    -+\ttest_expect_code 129 git ls-tree --format=fmt --name-only &&\n    -+\ttest_expect_code 129 git ls-tree --format=fmt --name-status\n    ++\ttest_expect_code 129 git ls-tree --format=fmt -l HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --name-only HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --name-status HEAD &&\n    ++\ttest_expect_code 129 git ls-tree --format=fmt --object-only HEAD\n     +'\n     +\n     +test_expect_success 'setup' '\n    @@ t/t3105-ls-tree-format.sh (new)\n     +\ttest_ls_tree_format \\\n     +\t\t\"%(path)\" \\\n     +\t\t\"--name-only\"\n    ++'\n     +\n    ++test_expect_success 'ls-tree --format=<object-only-like>' '\n    ++\ttest_ls_tree_format \\\n    ++\t\t\"%(objectname)\" \\\n    ++\t\t\"--object-only\"\n     +'\n     +\n     +test_done\n"},{"id":"446207","messageId":"CADMgQSQ3oWAbDMK73hEwZKJHhO_k8CuJdC2TNWW0+kv1TzYfRQ@mail.gmail.com","threadId":"57172","inReplyTo":"220113.861r1cxgxx.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-14T07:59:01Z","receivedAt":"2022-01-14T07:59:14Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Thu, Jan 13, 2022 at 2:55 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> Re the $subject: Is \"optimize naming\" here just referring to the\n> s/retval/recurse/g?\n\nYes.\n\n> Personally I think just a s/retval/ret/g here would make more senes if\n> we're doing any change at all, and in either case having this variable\n> re-rename split up as its own commit would make the proposed control\n> flow changes clearer.\n\nDo you mean that I can split the current one into two commits,  one does\nthe renaming work and another one does the left work?\n\nIf so, I will do this in the next patch.\n\n>\n> This new function is a re-invention of the object_type() utility in\n> cache.h, and isn't needed. I.e....\n>\n> ...just drop it and do this:\n>\n>         -       enum object_type type = get_type(mode);\n>         +       enum object_type type = object_type(mode);\n\nYou are absolutely correct.\nI will replace get_type() to object_type() in the next patch.\n"},{"id":"446209","messageId":"CADMgQSQXLydvgpW+Vm_b-rkSWAuj9FezwveY-iRZnKfsGkOpCA@mail.gmail.com","threadId":"57172","inReplyTo":"220113.86wnj4w228.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-14T08:18:10Z","receivedAt":"2022-01-14T08:18:47Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Thu, Jan 13, 2022 at 3:02 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> In the RFC series I sent this was first implemented in terms of the\n> --format option, and I skipped the custom implementation you're adding\n> here:\n> https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/\n>\n> I think in terms of patch series structure it would make sense to do\n> that, and then have this custom --object-only implementation in terms of\n> not-\"--format \" follow from that, and thus with the tests for the two\n\nSorry, the \"not-\"--format\" means?\n\n> (we'd add the tests you're adding here first, just for a\n> --format=\"%(objectname)\" or whatever) we'd see that the two are 1=1\n> equivalent in terms of functionality, but that this one is <X>% more\n> optimized.\n\nPlease allow me to understand your advice,  if we put the commit of\nintroducing \"--format\" before the commit of introducing \"--object-only\", will\nbe better because it's possible to supply more optimized performance\n(if we have) information in the commit message.\n"},{"id":"446210","messageId":"CADMgQSQaE4EtiNXyGKebPyPS_0YTQ=HN+dU89_jD6BgQ1C470A@mail.gmail.com","threadId":"57172","inReplyTo":"220113.86sftsw1ut.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 7/9] ls-tree.c: introduce struct \"show_tree_data\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-14T09:12:48Z","receivedAt":"2022-01-14T09:13:02Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Thu, Jan 13, 2022 at 3:07 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> > \"show_tree_data\" is a struct that packages the necessary fields for\n> > \"show_tree()\". This commit is a pre-prepared commit for supporting\n> > \"--format\" option and it does not affect any existing functionality.\n>\n> Is the only reason this is split off from 9/9 because you're injecting a\n> 8/9 commit for the coccinelle rule change, and wanted to find some\n> logical cut-off between the two?\n\nI hope \"show_tree()\" and \"the show_tree_format()\" to share this structure,\nso I made this a pre-prepared and non-functional commit.\n\nAfter that, in the 9/9, the structure can be used directly and focus on the\nfunctionality changes. If we merge this commit with 9/9, 9/9 will contain a part\nof the changes that let \"show_tree()\" use the new structure, which has nothing\nto do with \"show_tree_format()\" actually, because we designed them to go\nthrough different execution logic. So, personally, I prefer not to mix them\ntogether.\n\nSo, the commit of \"show_tree_data()\" originally was not for \"coccinelle\".\nThe only thing that is certain is that coccinelle also should go before 9/9\nI think. With regard to 8/9 and 7/9, I think the current order is OK because\nthey're not related.\n>\n> For both this & 9/9 this seems to mostly/substantially be code I wrote\n> and submitted as part of\n> https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com;\n>\n> The convention we use in such cases is to retain the \"Author\" header and\n> just add your own Signed-off-by to patches you're modifying/splitting\n> up.\n\nOops. Sorry for that, I misunderstood it before and I'll be fixed in\nthe next path.\n\nThanks.\n"},{"id":"446211","messageId":"220114.86ilumttn2.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"CADMgQSQXLydvgpW+Vm_b-rkSWAuj9FezwveY-iRZnKfsGkOpCA@mail.gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-14T11:47:39Z","receivedAt":"2022-01-14T11:59:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Jan 14 2022, Teng Long wrote:\n\n> On Thu, Jan 13, 2022 at 3:02 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>\n>> In the RFC series I sent this was first implemented in terms of the\n>> --format option, and I skipped the custom implementation you're adding\n>> here:\n>> https://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/\n>>\n>> I think in terms of patch series structure it would make sense to do\n>> that, and then have this custom --object-only implementation in terms of\n>> not-\"--format \" follow from that, and thus with the tests for the two\n>\n> Sorry, the \"not-\"--format\" means?\n\nSorry about not being clear. I mean there's two potential\nimplementations. One that't is terms of --format='%(objectname)', and\nthe other with your custom (faster) code to implement it.\n\n>> (we'd add the tests you're adding here first, just for a\n>> --format=\"%(objectname)\" or whatever) we'd see that the two are 1=1\n>> equivalent in terms of functionality, but that this one is <X>% more\n>> optimized.\n>\n> Please allow me to understand your advice,  if we put the commit of\n> introducing \"--format\" before the commit of introducing \"--object-only\", will\n> be better because it's possible to supply more optimized performance\n> (if we have) information in the commit message.\n\nYes, you get the functionality you need with a simple alias of\n--format='%(objectname)' to --object-name (or whatever), so the only\nreason to carry the extra code is for optimization.\n\nI wonder if the extra difference in performance is still something you\ncare about, or if just the --format implementation would be OK.\n\nBut in any case, starting with a simpler implementation and testing it\nmakes the progression easier to reason about.\n"},{"id":"446212","messageId":"220114.86ee5attm4.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"CADMgQSQ3oWAbDMK73hEwZKJHhO_k8CuJdC2TNWW0+kv1TzYfRQ@mail.gmail.com","subject":"Re: [PATCH v10 5/9] ls-tree: optimize naming and handling of \"return\" in show_tree()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-14T12:00:05Z","receivedAt":"2022-01-14T12:00:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Jan 14 2022, Teng Long wrote:\n\n> On Thu, Jan 13, 2022 at 2:55 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>\n>> Re the $subject: Is \"optimize naming\" here just referring to the\n>> s/retval/recurse/g?\n>\n> Yes.\n>\n>> Personally I think just a s/retval/ret/g here would make more senes if\n>> we're doing any change at all, and in either case having this variable\n>> re-rename split up as its own commit would make the proposed control\n>> flow changes clearer.\n>\n> Do you mean that I can split the current one into two commits,  one does\n> the renaming work and another one does the left work?\n>\n> If so, I will do this in the next patch.\n\nYes, at least I would find it easier to read :)\n\n>>\n>> This new function is a re-invention of the object_type() utility in\n>> cache.h, and isn't needed. I.e....\n>>\n>> ...just drop it and do this:\n>>\n>>         -       enum object_type type = get_type(mode);\n>>         +       enum object_type type = object_type(mode);\n>\n> You are absolutely correct.\n> I will replace get_type() to object_type() in the next patch.\n\nThanks!\n"},{"id":"446396","messageId":"CADMgQSQzqW9m1XM7_EE09eBhkheMj6QCZ+pVjtHWW2HY=u_o2g@mail.gmail.com","threadId":"57172","inReplyTo":"220114.86ilumttn2.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-18T09:55:17Z","receivedAt":"2022-01-18T09:55:36Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Fri, Jan 14, 2022 at 7:59 PM Ævar Arnfjörð Bjarmason\n<avarab@gmail.com> wrote:\n\n> Yes, you get the functionality you need with a simple alias of\n> --format='%(objectname)' to --object-name (or whatever), so the only\n> reason to carry the extra code is for optimization.\n>\n> I wonder if the extra difference in performance is still something you\n> care about, or if just the --format implementation would be OK.\n>\n> But in any case, starting with a simpler implementation and testing it\n> makes the progression easier to reason about.\n\nActually, at first, I wanted to achieve this in a simple way, as the\n\"--object-only\" implementation.\n\nWith the discussion in the community, I think both of them can achieve\nthis purpose. \"--object-only\" is more intuitive, while \"--format \"is\nmore flexible.\nFor example, if the terminal supports automatic completion, the function of\nthis option can be clearly known with typing TAB and lower costs of use and\nunderstanding. \"--format\"  also works, but maybe have to check the help\ndocument to see if there are fields that support the same purpose.\n\nBecause the community had a different opinion about it. Junio, might prefer\nan \"--object-only\" approach, if I understand the context correctly.\n\nSo I have some inclination to support both. However, I can accept that only\n\"--format\" is supported.\n\nSo in the next patch, I hope to do some refactoring of the commit to support\n\"--object-only\" as the top commit. If in the end, we decide that \"--format\" is\nenough, we can discard the top \"--object-only\" commit.\n\nI know you guys currently are busy on the new 2.35 release, so a later reply\nis OK.\n\nThanks.\n"},{"id":"446406","messageId":"20220118125939.99956-1-dyroneteng@gmail.com","threadId":"57172","inReplyTo":"220113.86o84gvwfd.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 9/9] ls-tree.c: introduce \"--format\" option","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-01-18T12:59:39Z","receivedAt":"2022-01-18T12:59:55Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Thu, Jan 13, 2022 at 5:04 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n\n> > diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\n> > index 729370f235..ebdde6eae3 100644\n> > --- a/Documentation/git-ls-tree.txt\n> > +++ b/Documentation/git-ls-tree.txt\n> > @@ -10,9 +10,9 @@ SYNOPSIS\n> >  --------\n> >  [verse]\n> >  'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n> > -         [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]]\n> > -         <tree-ish> [<path>...]\n> > -\n> > +         [--name-only] [--name-status] [--object-only]\n> > +         [--full-name] [--full-tree] [--abbrev[=<n>]]\n>\n> Let's split up this re-flow only change into its own commit? I.e. the\n> only non-whitespace change here is beginning with [--format].\n>\n> If it was the right thing to do to re-flow this then we didn't need\n> [--format=<format>] to exist to do so...\n\nAgree, especially if \"--format\" comes earlier as you mentioned in\nanother reply.\n\nThe doc change should only include the new \"--format\" here, so\nwe just:\n\ndiff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt\nindex db02d6d79a..b02f028aca 100644\n--- a/Documentation/git-ls-tree.txt\n+++ b/Documentation/git-ls-tree.txt\n@@ -10,5 +10,5 @@ SYNOPSIS\n --------\n [verse]\n 'git ls-tree' [-d] [-r] [-t] [-l] [-z]\n-           [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=<n>]]\n+           [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=<n>]] [--format=<format>]\n            <tree-ish> [<path>...]\n\nis OK.\n\n> > +         [--format=<format>] <tree-ish> [<path>...]\n> >  DESCRIPTION\n>\n> Removing this \\n breaks the formatting in the file. See \"make man && man\n> ./Documentation/git-ls-tree.1\". The ./Documentation/doc-diff utility is\n> also handy for sanity checking the documentation formatting.\n\nThis is my mistake and will be corrected in the next patch.\n\n> >  -----------\n> >  Lists the contents of a given tree object, like what \"/bin/ls -a\" does\n> > @@ -79,6 +79,16 @@ OPTION\n> >       Do not limit the listing to the current working directory.\n> >       Implies --full-name.\n> >\n> > +--format=<format>::\n> > +     A string that interpolates `%(fieldname)` from the result\n> > +     being shown. It also interpolates `%%` to `%`, and\n> > +     `%xx` where `xx` are hex digits interpolates to character\n> > +     with hex code `xx`; for example `%00` interpolates to\n> > +     `\\0` (NUL), `%09` to `\\t` (TAB) and `%0a` to `\\n` (LF).\n> > +     When specified, `--format` cannot be combined with other\n> > +     format-altering options, including `--long`, `--name-only`\n> > +     and `--object-only`.\n> > +\n>\n> These new docs make sense & seem to cover all the basis, thanks!\n\nActually, the content is not from me, I borrowed it from other\ndocuments, but I'm glad if it's described and placed here correctly.\n\n> Here because we've added --format discussing the previous pseudo-format\n> as a \"default\" format becomes confusing. Let's instead say:\n>\n>         The output format of `ls-tree` is determined by either the `--format` option,\n>         or other format-altering options such as `--name-long` etc. (see `--format` above).\n>\n>         The use of certain `--format` directives is equivalent to using those options,\n>         but invoking the full formatting machinery can be slower than using an appropriate\n>         formatting option.\n>\n>         In cases where the `--format` would exactly map to an existing option `ls-tree` will\n>         use the appropriate faster path. Thus the default format is equivalent to:\n>         ---\n>         %(mode) %(type) %(object)%x09%(file)\n>         ---\n\nMake sense.\n\nI will use this paragraph instead in next patch except a tiny\nnit (s/--name-long/--name-only/).\n\n> > +The output format changes to:\n> > +\n> > +        {\"object\":\"<object>\", \"file\":\"<file>\"}\n>\n> This one-liner is guaranteed to result in invalid JSON on some\n> repositories, both because JSON is inherently a bad fit for git's data\n> model (JSON needs to be in one Unicode encoding, Git's tree data might\n> me in a mixture of encodings), and because it'll break if the file\n> includes a '\"'.\n\nCorrect and especially we use a \"quote_c\" style behind.\n\n> I think it's better to just replace this with some example involving -z,\n> or at least prominently note that this is broken in the general case,\n> but can be used ad-hoc to quickly check things with \"jq\" or whatever.\n\nYour suggestion is great, but personally I don't want to introduce more\ncomplexity and other tools in here, and try to describe it in a simple way.\nI think the below maybe is enough:\n\n@@ -117,14 +127,10 @@ Customized format:\n \n It is possible to print in a custom format by using the `--format` option,\n which is able to interpolate different fields using a `%(fieldname)` notation.\n-For example, if you want to only print the <object> and <file> fields with a\n-JSON style, executing with a specific \"--format\" like\n-\n-        git ls-tree --format='{\"object\":\"%(object)\", \"file\":\"%(file)\"}' <tree-ish>\n-\n-The output format changes to:\n+For example, if you only care about the <object> and <file> fields, you can\n+execute with a specific \"--format\" like\n \n-        {\"object\":\"<object>\", \"file\":\"<file>\"}\n+        git ls-tree --format=\"%(object) %(file)\" <tree-ish>\n \n FIELD NAMES\n -----------\n\n> > +FIELD NAMES\n> > +-----------\n> > +\n> > +Various values from structured fields can be used to interpolate\n> > +into the resulting output. For each outputing line, the following\n> > +names can be used:\n> > +\n> > +mode::\n> > +     The mode of the object.\n> > +type::\n> > +     The type of the object (`blob` or `tree`).\n> > +object::\n> > +     The name of the object.\n> > +size[:padded]::\n> > +     The size of the object (\"-\" if it's a tree).\n> > +     It also supports a padded format of size with \"%(size:padded)\".\n> > +file::\n> > +     The filename of the object.\n>\n> In\n> https://lore.kernel.org/git/cover.1641043500.git.dyroneteng@gmail.com/\n> you noted that you changed the field names of e.g. \"objectname\" to\n> \"object\" etc. You're right that I picked these as-is from the\n> git-for-each-ref formatting.\n>\n> 1/3 of your reasoning for doing so was to make it consistent with the\n> documentation examples of e.g.:\n>\n>      <mode> SP <type> SP <object> TAB <file>\n>\n> I think in any case (as noted above) we should change those to use the\n> --format), so that leaves just:\n>\n>  - \"I prefer to make the name more simple to memorize and type\"\n>  - \"I think the names with \"object\" prefix are [from git-for-each-ref\n>    and the object* prefixes aren't redundant there, but would be here]\".\n>\n> I think both of those still apply, but I think having these consistent\n> with git-for-each-ref outweighs the slight benefit of shorter names.\n>\n> Right now only a handful of things support these sort of --format\n> directives, but we've already got RFC/WIP patches to add that to\n> git-cat-file, and are likely to add more in the future.\n\nNew and important input for me on this.\n\n> I'd also like us to eventually be able to combine what are now separate\n> built-ins with their own --format to expose more deeply some internal\n> APIs via IPC. E.g. now you can do this:\n>\n>     git for-each-ref --format='%(refname) %(tree)'\n>\n> But to list each of those trees you'd need to pipe that output into this\n> new 'git ls-tree --format. But imagine being able to do something like:\n>\n>     git for-each-ref --format='%(refname) %(git-ls-tree --format %%(objectname) %(tree))'\n\nMake sense.\n\n> Where we'd just invoke git-ls-tree for you without running a full\n> sub-process. I think both for that hypothetical and working with the two\n> --formats now having to use %(type) in some places but %(objecttype)\n> etc. in others is just needlessly confusing. Let's just consistently use\n> the same format names everywhere.\n>\n> Specifically for your s/path/file/ name change, that's just inaccurate, consider:\n>\n>     $ ./git ls-tree --format=\"%(mode) %(type) %(file)\" -t HEAD -- t/README\n>     040000 tree t\n>     100644 blob t/README\n>\n> And:\n>\n>     $ $ (cd t && ../git ls-tree --format=\"%(mode) %(type) %(file)\" -t -r HEAD -- README)\n>     040000 tree ./\n>     100644 blob README\n>\n> I.e. we talk about <path> in the existing SYNOPSIS for a reason. That we\n> had a \"<file>\" in the existing format demo was a bug/shorthand that we\n> shouldn't be propagating further.\n\nShould use \"path\" instead of \"file\" in here.\n\nMake sense.\n\n> > [...]\n> > +static const char *format;\n> > +static const char *default_format = \"%(mode) %(type) %(object)%x09%(file)\";\n> > +static const char *long_format = \"%(mode) %(type) %(object) %(size:padded)%x09%(file)\";\n> > +static const char *name_only_format = \"%(file)\";\n> > +static const char *object_only_format = \"%(object)\";\n> > +\n>\n> One advantage of keeping the variable names I picked in\n> https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n> is that they align, so you can instantly see that the first two are\n> equivalent until the \"%x09\".\n\nHa. Thanks.I think they not align in here is because my variables' names not align :)  \n\nActually, I was hesitating to use like \"object\" or follow the \"objectname\" like\nrules. I would like git to have unified naming style on this, but I didn't have\nthat much input (other usage already on the way) at the time, so I chose to use\na shorter and probably more memorable name based on the document.\n\nBut now, I agree with you, to use the same naming conventions because on the whole,\nespecially multiple commands have the same appeal on format naming, uniformity is\nmore important than memorability of a single command, and I also think maybe we\nmight need to describe and maintain these rules of <fieldname> in a document\nsomewhere.\n\n> It also makes it easier to review to avoid such churn, to see what you\n> really changed I'm looking at a local version of a range-diff where I\n> renamed these, the struct you renamed etc. back just to see what you\n> /really/ changed. I.e. what are functional v.s. renaming changes.\n>\n> Here you changed my '\"%\"PRIuMAX' to '\"%\" PRIuMAX'. The former is the\n> prevailing style in this codebase, and avoiding the formatting churn\n> makes the inter-diff easier to read.\n\nWill fix it in next patch.\n\n> Ditto some harder to review interdiff due to renaming\n> churn. I.e. s/line/sb/ in both this and expand_show_tree(). I really\n> wouldn't care at all except because of all the manual work in reviewing\n> the inter-diff between my original version & this derived version.\n>\n> In the case of \"line\" that's not even an improvement. With a --format\n> we're not building a \"line\", the user is free to insert any arbitrary\n> directives including \\n's, so we might be working on multiple lines.\n\nMake sense.\n\nWill optimize in next patch.\n\n> As I noted in my RFC CL (https://lore.kernel.org/git/RFC-cover-0.7-00000000000-20211217T131635Z-avarab@gmail.com/):\n>\n>         \"the tests for ls-tree are really\n>         lacking. E.g. I seem to have a rather obvious bug in how -t and the\n>         --format interact here, but no test catches it.\"\n>\n> So first, in my version of adding --format I was careful to make\n> --name-only etc. imply a given --format, and then only at the last\n> minute would we take the \"fast path\":\n> https://lore.kernel.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/\n\nI'm not sure I understand all the meanings above. I think I have forgot about the content here.\nand I have to add some tests for other options like \"-t\" combined with \"--format\".\n\nPlease correct me if I misunderstood.\n\n> You rewrote that in\n> https://lore.kernel.org/git/e0add802fbbabde7e7b3743127b2d4047f1ce760.1641043500.git.dyroneteng@gmail.com/\n> and qremoved the limited \"GIT_TEST_LS_TREE_FORMAT_BACKEND\" testing I\n> added, so now the internal --format machinery can't be run through the\n> existing tests we do have.\n\nI thought the \"GIT_TEST_LS_TREE_FORMAT_BACKEND\" is only used in your RFC for testing conveniently,\nand should be removed in the end (non-RFC). So I removed it...\n\nSo should we add it back?\n\n> Even with that re-added I really wouldn't trust that this code is doing\n> the right thing (and as noted, I don't trust my own RFC version\n> either). I think e.g. our \"coverage\" Makefile targets would be a good\n> start as a first approximation, i.e. running the /ls-tree/ tests and\n> seeing if we have full coverage.\n\nYeah. I haven't tried it yet, but I will try to run coverage detection when\nthe next patch is completed\n\n> As I noted in 7/9 I think this patch is 9/9 still mostly something I\n> wrote, so that the \"author\" and Signed-off-by should be preserved. The\n> below is a range-diff of an amended version I've been looking at in\n> trying to review this. It undoes several (but not all) of your\n> formatting/renaming-only changes, just so that I could see what the\n> non-formatting changes were:\n\nSorry for that. I misunderstand something here.\nI will fix in the next patch.\n\nThanks.\n"},{"id":"447723","messageId":"220204.865ypu3hqm.gmgdl@evledraar.gmail.com","threadId":"57172","inReplyTo":"CADMgQSQzqW9m1XM7_EE09eBhkheMj6QCZ+pVjtHWW2HY=u_o2g@mail.gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-02-04T12:58:20Z","receivedAt":"2022-02-04T13:04:57Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 18 2022, Teng Long wrote:\n\n> On Fri, Jan 14, 2022 at 7:59 PM Ævar Arnfjörð Bjarmason\n> <avarab@gmail.com> wrote:\n>\n>> Yes, you get the functionality you need with a simple alias of\n>> --format='%(objectname)' to --object-name (or whatever), so the only\n>> reason to carry the extra code is for optimization.\n>>\n>> I wonder if the extra difference in performance is still something you\n>> care about, or if just the --format implementation would be OK.\n>>\n>> But in any case, starting with a simpler implementation and testing it\n>> makes the progression easier to reason about.\n>\n> Actually, at first, I wanted to achieve this in a simple way, as the\n> \"--object-only\" implementation.\n>\n> With the discussion in the community, I think both of them can achieve\n> this purpose. \"--object-only\" is more intuitive, while \"--format \"is\n> more flexible.\n> For example, if the terminal supports automatic completion, the function of\n> this option can be clearly known with typing TAB and lower costs of use and\n> understanding. \"--format\"  also works, but maybe have to check the help\n> document to see if there are fields that support the same purpose.\n>\n> Because the community had a different opinion about it. Junio, might prefer\n> an \"--object-only\" approach, if I understand the context correctly.\n>\n> So I have some inclination to support both. However, I can accept that only\n> \"--format\" is supported.\n\nI'm only talking about how it's implemented internally, not whether we\nhave an --object-only option in the UI. I think it's good to have the\noption for completion etc.\n\nI.e. in my RFC implementation of it here it's just a trivial wrapper\naround specifying a --format:\nhttps://lore.kernel.org/git/RFC-patch-7.7-5e34df4f8dd-20211217T131635Z-avarab@gmail.com/;\nImplementing it is 6 lines of trivial C code boilerplate.\n\nBut when you picked that up & ran with it you ended up carrying your\noriginal implementation:\nhttps://lore.kernel.org/git/e0274f079a7d381b9a936bfcd53bad64167c18b8.1641440700.git.dyroneteng@gmail.com/\n\nI'm not saying we shouldn't have that, but that in any case a sequence of:\n\n 1. Add a --format option\n 2. Add a --object-only alias for a --format (what my RFC 7/7 does)\n 3. Add a custom more optimized --object-only implementation\n\nWould make the patch progression much easier to read, and we'd consider\nthe correctness of --object-only (1 and 2) separate from the\noptimization question (3).\n\nBut maybe we won't need (3) at all in the end, i.e. is (1 and 2) fast\nenough for it not to matter (I think probably \"yes\", but I don't have a\nstrong opinion on that).\n\n> So in the next patch, I hope to do some refactoring of the commit to support\n> \"--object-only\" as the top commit. If in the end, we decide that \"--format\" is\n> enough, we can discard the top \"--object-only\" commit.\n\n*nod*, now that I read ahead I think you pretty much agree with that plan :)\n\n> I know you guys currently are busy on the new 2.35 release, so a later reply\n> is OK.\n\nNow would be a good time :)\n\nI was reminded of this because Junio's proposed it for next at\nhttps://lore.kernel.org/git/xmqqr18jnr2t.fsf@gitster.g/\n\nI think per the above & other replies of mine (including not matters of\ncode arrangement opinion, but e.g. the doc formatting bug) we'll need at\nleast one more re-roll of this. Thanks for sticking with this & working\non this!\n\nI'll indicate that in a reply to that \"What's Cooking\" report.\n"},{"id":"447864","messageId":"CADMgQSSNQFHhf3=K+PiaoonBnheoDcoKpWy9-zjSu90d9rDY2w@mail.gmail.com","threadId":"57172","inReplyTo":"220204.865ypu3hqm.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v10 6/9] ls-tree.c: support --object-only option for \"git-ls-tree\"","fromName":"Teng Long","fromEmail":"dyroneteng@gmail.com","sentAt":"2022-02-07T02:22:23Z","receivedAt":"2022-02-07T02:22:39Z","isPatch":true,"sender":{"key":"dyroneteng@gmail.com","avatar":"https://avatars.githubusercontent.com/u/7803958?v=4"},"body":"On Fri, Feb 4, 2022 at 9:04 PM Ævar Arnfjörð Bjarmason <avarab@gmail.com> wrote:\n\n> I'm not saying we shouldn't have that, but that in any case a sequence of:\n>\n>  1. Add a --format option\n>  2. Add a --object-only alias for a --format (what my RFC 7/7 does)\n>  3. Add a custom more optimized --object-only implementation\n>\n> Would make the patch progression much easier to read, and we'd consider\n> the correctness of --object-only (1 and 2) separate from the\n> optimization question (3).\n>\n> But maybe we won't need (3) at all in the end, i.e. is (1 and 2) fast\n> enough for it not to matter (I think probably \"yes\", but I don't have a\n> strong opinion on that).\n\nSorry for the late reply, I had a vacation in the last two weeks (Chinese\nNew Year).\n\nI have to say it's a very valuable recommendation and at the same time\nI recognise that\nspending more time on organizing commits ahead is important and make\nsmall steps(or commits) sufficiently.\n\n> Now would be a good time :)\n>\n> I was reminded of this because Junio's proposed it for next at\n> https://lore.kernel.org/git/xmqqr18jnr2t.fsf@gitster.g/\n>\n> I think per the above & other replies of mine (including not matters of\n> code arrangement opinion, but e.g. the doc formatting bug) we'll need at\n> least one more re-roll of this. Thanks for sticking with this & working\n> on this!\n>\n> I'll indicate that in a reply to that \"What's Cooking\" report.\n\nThanks for mentioning that. I will back work on it this week.\n\nThanks.\n"}]}