From a53343e0fd72ee871aef994bf1352be320dbf710 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:01 +0800 Subject: ls-tree tests: add tests for --name-status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --name-status synonym for --name-only added in c639a5548a5 (ls-tree: --name-only, 2005-12-01) had no tests, let's make sure it works the same way as its sibling. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- t/t3101-ls-tree-dirname.sh | 55 ++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 05fde64225..217006d1bf 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -201,31 +201,34 @@ EOF test_cmp expected check ' -test_expect_success 'ls-tree --name-only' ' - git ls-tree --name-only $tree >current && - cat >expected <<\EOF && -1.txt -2.txt -path0 -path1 -path2 -path3 -EOF - test_output -' - -test_expect_success 'ls-tree --name-only -r' ' - git ls-tree --name-only -r $tree >current && - cat >expected <<\EOF && -1.txt -2.txt -path0/a/b/c/1.txt -path1/b/c/1.txt -path2/1.txt -path3/1.txt -path3/2.txt -EOF - test_output -' +for opt in --name-only --name-status +do + test_expect_success "ls-tree $opt" ' + git ls-tree $opt $tree >current && + cat >expected <<-\EOF && + 1.txt + 2.txt + path0 + path1 + path2 + path3 + EOF + test_output + ' + + test_expect_success "ls-tree $opt -r" ' + git ls-tree $opt -r $tree >current && + cat >expected <<-\EOF && + 1.txt + 2.txt + path0/a/b/c/1.txt + path1/b/c/1.txt + path2/1.txt + path3/1.txt + path3/2.txt + EOF + test_output + ' +done test_done -- cgit v1.2.3 From 4e4566f67e9433b7b0f475c4f16e1fe77fb527f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:02 +0800 Subject: ls-tree: remove commented-out code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove code added in f35a6d3bce7 (Teach core object handling functions about gitlinks, 2007-04-09), later patched in 7d0b18a4da1 (Add output flushing before fork(), 2008-08-04), and then finally ending up in its current form in d3bee161fef (tree.c: allow read_tree_recursive() to traverse gitlink entries, 2009-01-25). All while being commented-out! Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3a442631c7..5f7c84950c 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -69,15 +69,6 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, const char *type = blob_type; if (S_ISGITLINK(mode)) { - /* - * Maybe we want to have some recursive version here? - * - * Something similar to this incomplete example: - * - if (show_subprojects(base, baselen, pathname)) - retval = READ_TREE_RECURSIVE; - * - */ type = commit_type; } else if (S_ISDIR(mode)) { if (show_recursive(base->buf, base->len, pathname)) { -- cgit v1.2.3 From 82e69b0cb5efff33f9359aa0141b05c93001157e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:03 +0800 Subject: ls-tree: add missing braces to "else" arms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing {} to the "else" arms in show_tree() per the CodingGuidelines. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 5f7c84950c..0a28f32ccb 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -92,14 +92,16 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, else xsnprintf(size_text, sizeof(size_text), "%"PRIuMAX, (uintmax_t)size); - } else + } else { xsnprintf(size_text, sizeof(size_text), "-"); + } printf("%06o %s %s %7s\t", mode, type, find_unique_abbrev(oid, abbrev), size_text); - } else + } else { printf("%06o %s %s\t", mode, type, find_unique_abbrev(oid, abbrev)); + } } baselen = base->len; strbuf_addstr(base, pathname); -- cgit v1.2.3 From 26f6d4d5a015ae95b0818140b6edf297e78e4e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:04 +0800 Subject: ls-tree: use "enum object_type", not {blob,tree,commit}_type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Change the ls-tree.c code to use type_name() on the enum instead of using the string constants. This doesn't matter either way for performance, but makes this a bit easier to read as we'll no longer need a strcmp() here. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 0a28f32ccb..3f0225b097 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -66,17 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, { int retval = 0; int baselen; - const char *type = blob_type; + enum object_type type = OBJ_BLOB; if (S_ISGITLINK(mode)) { - type = commit_type; + type = OBJ_COMMIT; } else if (S_ISDIR(mode)) { if (show_recursive(base->buf, base->len, pathname)) { retval = READ_TREE_RECURSIVE; if (!(ls_options & LS_SHOW_TREES)) return retval; } - type = tree_type; + type = OBJ_TREE; } else if (ls_options & LS_TREE_ONLY) return 0; @@ -84,7 +84,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, if (!(ls_options & LS_NAME_ONLY)) { if (ls_options & LS_SHOW_SIZE) { char size_text[24]; - if (!strcmp(type, blob_type)) { + if (type == OBJ_BLOB) { unsigned long size; if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) xsnprintf(size_text, sizeof(size_text), @@ -95,11 +95,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, } else { xsnprintf(size_text, sizeof(size_text), "-"); } - printf("%06o %s %s %7s\t", mode, type, + printf("%06o %s %s %7s\t", mode, type_name(type), find_unique_abbrev(oid, abbrev), size_text); } else { - printf("%06o %s %s\t", mode, type, + printf("%06o %s %s\t", mode, type_name(type), find_unique_abbrev(oid, abbrev)); } } -- cgit v1.2.3 From 132ceda40f5fe6e53ff7189a941f7e77a165cb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:05 +0800 Subject: ls-tree: use "size_t", not "int" for "struct strbuf"'s "len" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "struct strbuf"'s "len" member is a "size_t", not an "int", so let's change our corresponding types accordingly. This also changes the "len" and "speclen" variables, which are likewise used to store the return value of strlen(), which returns "size_t", not "int". Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3f0225b097..eecc7482d5 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -31,7 +31,7 @@ static const char * const ls_tree_usage[] = { NULL }; -static int show_recursive(const char *base, int baselen, const char *pathname) +static int show_recursive(const char *base, size_t baselen, const char *pathname) { int i; @@ -43,7 +43,7 @@ static int show_recursive(const char *base, int baselen, const char *pathname) for (i = 0; i < pathspec.nr; i++) { const char *spec = pathspec.items[i].match; - int len, speclen; + size_t len, speclen; if (strncmp(base, spec, baselen)) continue; @@ -65,7 +65,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { int retval = 0; - int baselen; + size_t baselen; enum object_type type = OBJ_BLOB; if (S_ISGITLINK(mode)) { -- cgit v1.2.3 From 889f78383eb1e93663ad38a231607e04a1593713 Mon Sep 17 00:00:00 2001 From: Teng Long Date: Wed, 23 Mar 2022 17:13:06 +0800 Subject: ls-tree: rename "retval" to "recurse" in "show_tree()" The variable which "show_tree()" return is named "retval", a name that's a little hard to understand. The commit rename "retval" to "recurse" which is a more meaningful name than before in the context. We do not need to take a look at "read_tree_at()" in "tree.c" to make sure what does "retval" mean. Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index eecc7482d5..ef8c414f61 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -64,7 +64,7 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname static int show_tree(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { - int retval = 0; + int recurse = 0; size_t baselen; enum object_type type = OBJ_BLOB; @@ -72,9 +72,9 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, type = OBJ_COMMIT; } else if (S_ISDIR(mode)) { if (show_recursive(base->buf, base->len, pathname)) { - retval = READ_TREE_RECURSIVE; + recurse = READ_TREE_RECURSIVE; if (!(ls_options & LS_SHOW_TREES)) - return retval; + return recurse; } type = OBJ_TREE; } @@ -109,7 +109,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, chomp_prefix ? ls_tree_prefix : NULL, stdout, line_termination); strbuf_setlen(base, baselen); - return retval; + return recurse; } int cmd_ls_tree(int argc, const char **argv, const char *prefix) -- cgit v1.2.3 From 87af0ddf5f311e9b6e361daaa7fd591a230261a0 Mon Sep 17 00:00:00 2001 From: Teng Long Date: Wed, 23 Mar 2022 17:13:07 +0800 Subject: ls-tree: simplify nesting if/else logic in "show_tree()" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the object_type() function to determine the object type from the "mode" passed to us by read_tree(), instead of doing so with the S_*() macros. Helped-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index ef8c414f61..d4be71bad2 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -66,20 +66,17 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, { int recurse = 0; size_t baselen; - enum object_type type = OBJ_BLOB; + enum object_type type = object_type(mode); - if (S_ISGITLINK(mode)) { - type = OBJ_COMMIT; - } else if (S_ISDIR(mode)) { - if (show_recursive(base->buf, base->len, pathname)) { - recurse = READ_TREE_RECURSIVE; - if (!(ls_options & LS_SHOW_TREES)) - return recurse; - } - type = OBJ_TREE; + if (type == OBJ_BLOB) { + if (ls_options & LS_TREE_ONLY) + return 0; + } else if (type == OBJ_TREE && + show_recursive(base->buf, base->len, pathname)) { + recurse = READ_TREE_RECURSIVE; + if (!(ls_options & LS_SHOW_TREES)) + return recurse; } - else if (ls_options & LS_TREE_ONLY) - return 0; if (!(ls_options & LS_NAME_ONLY)) { if (ls_options & LS_SHOW_SIZE) { -- cgit v1.2.3 From f6b224d5ebfe9fcf58ef34eddea68d9fb4384d50 Mon Sep 17 00:00:00 2001 From: Teng Long Date: Wed, 23 Mar 2022 17:13:08 +0800 Subject: ls-tree: fix "--name-only" and "--long" combined use bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we execute "git ls-tree" with combined "--name-only" and "--long" , only the pathname will be printed, the size is omitted (the original discoverer was Peff in [1]). This commit fix this issue by using `OPT_CMDMODE()` instead to make both of them mutually exclusive. [1] https://public-inbox.org/git/YZK0MKCYAJmG+pSU@coredump.intra.peff.net/ Helped-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 12 ++++++------ t/t3103-ls-tree-misc.sh | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index d4be71bad2..7be4c13228 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -123,12 +123,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) LS_SHOW_TREES), OPT_SET_INT('z', NULL, &line_termination, N_("terminate entries with NUL byte"), 0), - OPT_BIT('l', "long", &ls_options, N_("include object size"), - LS_SHOW_SIZE), - OPT_BIT(0, "name-only", &ls_options, N_("list only filenames"), - LS_NAME_ONLY), - OPT_BIT(0, "name-status", &ls_options, N_("list only filenames"), - LS_NAME_ONLY), + OPT_CMDMODE('l', "long", &ls_options, N_("include object size"), + LS_SHOW_SIZE), + OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"), + LS_NAME_ONLY), + OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"), + LS_NAME_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, N_("use full path names"), 0), OPT_BOOL(0, "full-tree", &full_tree, diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh index d18ba1bd84..d9d7fa932f 100755 --- a/t/t3103-ls-tree-misc.sh +++ b/t/t3103-ls-tree-misc.sh @@ -23,4 +23,13 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' test_must_fail git ls-tree -r HEAD ' +for opts in \ + "--name-only --long" \ + "--name-status --long" +do + test_expect_success "usage: incompatible options: $opts" ' + test_expect_code 129 git ls-tree $opts $tree + ' +done + test_done -- cgit v1.2.3 From 315f22c853c08b1f3473fc50985d76618f31ebd0 Mon Sep 17 00:00:00 2001 From: Teng Long Date: Wed, 23 Mar 2022 17:13:09 +0800 Subject: ls-tree: slightly refactor `show_tree()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a non-functional change, we introduce an enum "ls_tree_cmdmode" then use it to mark which columns to output. This has the advantage of making the show_tree logic simpler and more readable, as well as making it easier to extend new options (for example, if we want to add a "--object-only" option, we just need to add a similar "short-circuit logic in "show_tree()"). Helped-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 98 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7be4c13228..173a534e92 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -16,10 +16,8 @@ static int line_termination = '\n'; #define LS_RECURSIVE 1 -#define LS_TREE_ONLY 2 -#define LS_SHOW_TREES 4 -#define LS_NAME_ONLY 8 -#define LS_SHOW_SIZE 16 +#define LS_TREE_ONLY (1 << 1) +#define LS_SHOW_TREES (1 << 2) static int abbrev; static int ls_options; static struct pathspec pathspec; @@ -31,6 +29,11 @@ static const char * const ls_tree_usage[] = { NULL }; +static enum ls_tree_cmdmode { + MODE_LONG = 1, + MODE_NAME_ONLY, +} cmdmode; + static int show_recursive(const char *base, size_t baselen, const char *pathname) { int i; @@ -61,6 +64,39 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname return 0; } +static int show_default(const struct object_id *oid, enum object_type type, + const char *pathname, unsigned mode, + struct strbuf *base) +{ + size_t baselen = base->len; + + if (cmdmode == MODE_LONG) { + char size_text[24]; + if (type == OBJ_BLOB) { + unsigned long size; + if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) + xsnprintf(size_text, sizeof(size_text), "BAD"); + else + xsnprintf(size_text, sizeof(size_text), + "%" PRIuMAX, (uintmax_t)size); + } else { + xsnprintf(size_text, sizeof(size_text), "-"); + } + printf("%06o %s %s %7s\t", mode, type_name(type), + find_unique_abbrev(oid, abbrev), size_text); + } else { + printf("%06o %s %s\t", mode, type_name(type), + find_unique_abbrev(oid, abbrev)); + } + baselen = base->len; + strbuf_addstr(base, pathname); + write_name_quoted_relative(base->buf, + chomp_prefix ? ls_tree_prefix : NULL, stdout, + line_termination); + strbuf_setlen(base, baselen); + return 1; +} + static int show_tree(const struct object_id *oid, struct strbuf *base, const char *pathname, unsigned mode, void *context) { @@ -78,34 +114,22 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, return recurse; } - if (!(ls_options & LS_NAME_ONLY)) { - if (ls_options & LS_SHOW_SIZE) { - char size_text[24]; - if (type == OBJ_BLOB) { - unsigned long size; - if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) - xsnprintf(size_text, sizeof(size_text), - "BAD"); - else - xsnprintf(size_text, sizeof(size_text), - "%"PRIuMAX, (uintmax_t)size); - } else { - xsnprintf(size_text, sizeof(size_text), "-"); - } - printf("%06o %s %s %7s\t", mode, type_name(type), - find_unique_abbrev(oid, abbrev), - size_text); - } else { - printf("%06o %s %s\t", mode, type_name(type), - find_unique_abbrev(oid, abbrev)); - } + if (cmdmode == MODE_NAME_ONLY) { + baselen = base->len; + strbuf_addstr(base, pathname); + write_name_quoted_relative(base->buf, + chomp_prefix ? ls_tree_prefix : NULL, + stdout, line_termination); + strbuf_setlen(base, baselen); + return recurse; } - baselen = base->len; - strbuf_addstr(base, pathname); - write_name_quoted_relative(base->buf, - chomp_prefix ? ls_tree_prefix : NULL, - stdout, line_termination); - strbuf_setlen(base, baselen); + + if (cmdmode == MODE_LONG || + (!ls_options || (ls_options & LS_RECURSIVE) + || (ls_options & LS_SHOW_TREES) + || (ls_options & LS_TREE_ONLY))) + show_default(oid, type, pathname, mode, base); + return recurse; } @@ -123,12 +147,12 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) LS_SHOW_TREES), OPT_SET_INT('z', NULL, &line_termination, N_("terminate entries with NUL byte"), 0), - OPT_CMDMODE('l', "long", &ls_options, N_("include object size"), - LS_SHOW_SIZE), - OPT_CMDMODE(0, "name-only", &ls_options, N_("list only filenames"), - LS_NAME_ONLY), - OPT_CMDMODE(0, "name-status", &ls_options, N_("list only filenames"), - LS_NAME_ONLY), + OPT_CMDMODE('l', "long", &cmdmode, N_("include object size"), + MODE_LONG), + OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"), + MODE_NAME_ONLY), + OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"), + MODE_NAME_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, N_("use full path names"), 0), OPT_BOOL(0, "full-tree", &full_tree, -- cgit v1.2.3 From e81517155e0370ae5433d256046ab287bb10d04d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:10 +0800 Subject: ls-tree: introduce struct "show_tree_data" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "show_tree_data" is a struct that packages the necessary fields for "show_tree()". This commit is a pre-prepared commit for supporting "--format" option and it does not affect any existing functionality. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 173a534e92..edc8bf53a6 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -23,6 +23,13 @@ static int ls_options; static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; +struct show_tree_data { + unsigned mode; + enum object_type type; + const struct object_id *oid; + const char *pathname; + struct strbuf *base; +}; static const char * const ls_tree_usage[] = { N_("git ls-tree [] [...]"), @@ -64,17 +71,15 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname return 0; } -static int show_default(const struct object_id *oid, enum object_type type, - const char *pathname, unsigned mode, - struct strbuf *base) +static int show_default(struct show_tree_data *data) { - size_t baselen = base->len; + size_t baselen = data->base->len; if (cmdmode == MODE_LONG) { char size_text[24]; - if (type == OBJ_BLOB) { + if (data->type == OBJ_BLOB) { unsigned long size; - if (oid_object_info(the_repository, oid, &size) == OBJ_BAD) + if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD) xsnprintf(size_text, sizeof(size_text), "BAD"); else xsnprintf(size_text, sizeof(size_text), @@ -82,18 +87,18 @@ static int show_default(const struct object_id *oid, enum object_type type, } else { xsnprintf(size_text, sizeof(size_text), "-"); } - printf("%06o %s %s %7s\t", mode, type_name(type), - find_unique_abbrev(oid, abbrev), size_text); + printf("%06o %s %s %7s\t", data->mode, type_name(data->type), + find_unique_abbrev(data->oid, abbrev), size_text); } else { - printf("%06o %s %s\t", mode, type_name(type), - find_unique_abbrev(oid, abbrev)); + printf("%06o %s %s\t", data->mode, type_name(data->type), + find_unique_abbrev(data->oid, abbrev)); } - baselen = base->len; - strbuf_addstr(base, pathname); - write_name_quoted_relative(base->buf, + baselen = data->base->len; + strbuf_addstr(data->base, data->pathname); + write_name_quoted_relative(data->base->buf, chomp_prefix ? ls_tree_prefix : NULL, stdout, line_termination); - strbuf_setlen(base, baselen); + strbuf_setlen(data->base, baselen); return 1; } @@ -103,6 +108,13 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, int recurse = 0; size_t baselen; enum object_type type = object_type(mode); + struct show_tree_data data = { + .mode = mode, + .type = type, + .oid = oid, + .pathname = pathname, + .base = base, + }; if (type == OBJ_BLOB) { if (ls_options & LS_TREE_ONLY) @@ -128,7 +140,7 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, (!ls_options || (ls_options & LS_RECURSIVE) || (ls_options & LS_SHOW_TREES) || (ls_options & LS_TREE_ONLY))) - show_default(oid, type, pathname, mode, base); + show_default(&data); return recurse; } -- cgit v1.2.3 From 22184af2cb89f74b7245c77f1c3c10dae6098bcf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 23 Mar 2022 17:13:11 +0800 Subject: cocci: allow padding with `strbuf_addf()` A convenient way to pad strings is to use something like `strbuf_addf(&buf, "%20s", "Hello, world!")`. However, the Coccinelle rule that forbids a format `"%s"` with a constant string argument cast too wide a net, and also forbade such padding. The original rule was introduced by commit: 28c23cd4c39 (strbuf.cocci: suggest strbuf_addbuf() to add one strbuf to an other, 2019-01-25) Signed-off-by: Johannes Schindelin Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- contrib/coccinelle/strbuf.cocci | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci index d9ada69b43..0970d98ad7 100644 --- a/contrib/coccinelle/strbuf.cocci +++ b/contrib/coccinelle/strbuf.cocci @@ -15,7 +15,7 @@ constant fmt !~ "%"; @@ expression E; struct strbuf SB; -format F =~ "s"; +format F =~ "^s$"; @@ - strbuf_addf(E, "%@F@", SB.buf); + strbuf_addbuf(E, &SB); @@ -23,7 +23,7 @@ format F =~ "s"; @@ expression E; struct strbuf *SBP; -format F =~ "s"; +format F =~ "^s$"; @@ - strbuf_addf(E, "%@F@", SBP->buf); + strbuf_addbuf(E, SBP); @@ -44,7 +44,7 @@ struct strbuf *SBP; @@ expression E1, E2; -format F =~ "s"; +format F =~ "^s$"; @@ - strbuf_addf(E1, "%@F@", E2); + strbuf_addstr(E1, E2); -- cgit v1.2.3 From 455923e0a152420054ad74f1af36336d5fa7be75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:12 +0800 Subject: ls-tree: introduce "--format" option MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a --format option to ls-tree. It has an existing default output, and then --long and --name-only options to emit the default output along with the objectsize and, or to only emit object paths. Rather than add --type-only, --object-only etc. we can just support a --format using a strbuf_expand() similar to "for-each-ref --format". We might still add such options in the future for convenience. The --format implementation is slower than the existing code, but this change does not cause any performance regressions. We'll leave the existing show_tree() unchanged, and only run show_tree_fmt() in if a --format different than the hardcoded built-in ones corresponding to the existing modes is provided. I.e. something like the "--long" output would be much slower with this, mainly due to how we need to allocate various things to do with quote.c instead of spewing the output directly to stdout. The new option of '--format' comes from Ævar Arnfjörð Bjarmasonn's idea and suggestion, this commit makes modifications in terms of the original discussion on community [1]. In [1] there was a "GIT_TEST_LS_TREE_FORMAT_BACKEND" variable to ensure that we had test coverage for passing tests that would otherwise use show_tree() through show_tree_fmt(), and thus that the formatting mechanism could handle all the same cases as the non-formatting options. Somewhere in subsequent re-rolls of that we seem to have drifted away from what the goal of these tests should be. We're trying to ensure correctness of show_tree_fmt(). We can't tell if we "hit [the] fast-path" here, and instead of having an explicit test for that, we can just add it to something our "test_ls_tree_format" tests for. Here is the statistics about performance tests: 1. Default format (hitten the builtin formats): "git ls-tree " vs "--format='%(mode) %(type) %(object)%x09%(file)'" $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r HEAD" Benchmark 1: /opt/git/master/bin/git ls-tree -r HEAD Time (mean ± σ): 105.2 ms ± 3.3 ms [User: 84.3 ms, System: 20.8 ms] Range (min … max): 99.2 ms … 113.2 ms 28 runs $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)' HEAD" Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object)%x09%(file)' HEAD Time (mean ± σ): 106.4 ms ± 2.7 ms [User: 86.1 ms, System: 20.2 ms] Range (min … max): 100.2 ms … 110.5 ms 29 runs 2. Default format includes object size (hitten the builtin formats): "git ls-tree -l " vs "--format='%(mode) %(type) %(object) %(size:padded)%x09%(file)'" $hyperfine --warmup=10 "/opt/git/master/bin/git ls-tree -r -l HEAD" Benchmark 1: /opt/git/master/bin/git ls-tree -r -l HEAD Time (mean ± σ): 335.1 ms ± 6.5 ms [User: 304.6 ms, System: 30.4 ms] Range (min … max): 327.5 ms … 348.4 ms 10 runs $hyperfine --warmup=10 "/opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)' HEAD" Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(mode) %(type) %(object) %(size:padded)%x09%(file)' HEAD Time (mean ± σ): 337.2 ms ± 8.2 ms [User: 309.2 ms, System: 27.9 ms] Range (min … max): 328.8 ms … 349.4 ms 10 runs Links: [1] https://public-inbox.org/git/RFC-patch-6.7-eac299f06ff-20211217T131635Z-avarab@gmail.com/ [2] https://lore.kernel.org/git/cb717d08be87e3239117c6c667cb32caabaad33d.1646390152.git.dyroneteng@gmail.com/ Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- Documentation/git-ls-tree.txt | 59 ++++++++++++++-- builtin/ls-tree.c | 153 +++++++++++++++++++++++++++++++++++++++++- t/t3104-ls-tree-format.sh | 67 ++++++++++++++++++ 3 files changed, 272 insertions(+), 7 deletions(-) create mode 100755 t/t3104-ls-tree-format.sh diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index db02d6d79a..68bf1cf325 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git ls-tree' [-d] [-r] [-t] [-l] [-z] - [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=]] + [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=]] [--format=] [...] DESCRIPTION @@ -74,6 +74,16 @@ OPTIONS Do not limit the listing to the current working directory. Implies --full-name. +--format=:: + A string that interpolates `%(fieldname)` from the result + being shown. It also interpolates `%%` to `%`, and + `%xx` where `xx` are hex digits interpolates to character + with hex code `xx`; for example `%00` interpolates to + `\0` (NUL), `%09` to `\t` (TAB) and `%0a` to `\n` (LF). + When specified, `--format` cannot be combined with other + format-altering options, including `--long`, `--name-only` + and `--object-only`. + [...]:: When paths are given, show them (note that this isn't really raw pathnames, but rather a list of patterns to match). Otherwise @@ -82,16 +92,29 @@ OPTIONS Output Format ------------- - SP SP TAB + +The output format of `ls-tree` is determined by either the `--format` +option, or other format-altering options such as `--name-only` etc. +(see `--format` above). + +The use of certain `--format` directives is equivalent to using those +options, but invoking the full formatting machinery can be slower than +using an appropriate formatting option. + +In cases where the `--format` would exactly map to an existing option +`ls-tree` will use the appropriate faster path. Thus the default format +is equivalent to: + + %(objectmode) %(objecttype) %(objectname)%x09%(path) This output format is compatible with what `--index-info --stdin` of 'git update-index' expects. When the `-l` option is used, format changes to - SP SP SP TAB + %(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path) -Object size identified by is given in bytes, and right-justified +Object size identified by is given in bytes, and right-justified with minimum width of 7 characters. Object size is given only for blobs (file) entries; for other entries `-` character is used in place of size. @@ -100,6 +123,34 @@ quoted as explained for the configuration variable `core.quotePath` (see linkgit:git-config[1]). Using `-z` the filename is output verbatim and the line is terminated by a NUL byte. +Customized format: + +It is possible to print in a custom format by using the `--format` option, +which is able to interpolate different fields using a `%(fieldname)` notation. +For example, if you only care about the "objectname" and "path" fields, you +can execute with a specific "--format" like + + git ls-tree --format='%(objectname) %(path)' + +FIELD NAMES +----------- + +Various values from structured fields can be used to interpolate +into the resulting output. For each outputing line, the following +names can be used: + +objectmode:: + The mode of the object. +objecttype:: + The type of the object (`blob` or `tree`). +objectname:: + The name of the object. +objectsize[:padded]:: + The size of the object ("-" if it's a tree). + It also supports a padded format of size with "%(size:padded)". +path:: + The pathname of the object. + GIT --- Part of the linkgit:git[1] suite diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index edc8bf53a6..8a1bf2fa4d 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -23,6 +23,8 @@ static int ls_options; static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; +static const char *format; + struct show_tree_data { unsigned mode; enum object_type type; @@ -37,10 +39,77 @@ static const char * const ls_tree_usage[] = { }; static enum ls_tree_cmdmode { - MODE_LONG = 1, + MODE_DEFAULT = 0, + MODE_LONG, MODE_NAME_ONLY, } cmdmode; +static void expand_objectsize(struct strbuf *line, const struct object_id *oid, + const enum object_type type, unsigned int padded) +{ + if (type == OBJ_BLOB) { + unsigned long size; + if (oid_object_info(the_repository, oid, &size) < 0) + die(_("could not get object info about '%s'"), + oid_to_hex(oid)); + if (padded) + strbuf_addf(line, "%7"PRIuMAX, (uintmax_t)size); + else + strbuf_addf(line, "%"PRIuMAX, (uintmax_t)size); + } else if (padded) { + strbuf_addf(line, "%7s", "-"); + } else { + strbuf_addstr(line, "-"); + } +} + +static size_t expand_show_tree(struct strbuf *sb, const char *start, + void *context) +{ + struct show_tree_data *data = context; + const char *end; + const char *p; + unsigned int errlen; + size_t len = strbuf_expand_literal_cb(sb, start, NULL); + + if (len) + return len; + if (*start != '(') + die(_("bad ls-tree format: element '%s' does not start with '('"), start); + + end = strchr(start + 1, ')'); + if (!end) + die(_("bad ls-tree format: element '%s' does not end in ')'"), start); + + len = end - start + 1; + if (skip_prefix(start, "(objectmode)", &p)) { + strbuf_addf(sb, "%06o", data->mode); + } else if (skip_prefix(start, "(objecttype)", &p)) { + strbuf_addstr(sb, type_name(data->type)); + } else if (skip_prefix(start, "(objectsize:padded)", &p)) { + expand_objectsize(sb, data->oid, data->type, 1); + } else if (skip_prefix(start, "(objectsize)", &p)) { + expand_objectsize(sb, data->oid, data->type, 0); + } else if (skip_prefix(start, "(objectname)", &p)) { + strbuf_add_unique_abbrev(sb, data->oid, abbrev); + } else if (skip_prefix(start, "(path)", &p)) { + const char *name = data->base->buf; + const char *prefix = chomp_prefix ? ls_tree_prefix : NULL; + struct strbuf quoted = STRBUF_INIT; + struct strbuf sbuf = STRBUF_INIT; + strbuf_addstr(data->base, data->pathname); + name = relative_path(data->base->buf, prefix, &sbuf); + quote_c_style(name, "ed, NULL, 0); + strbuf_addbuf(sb, "ed); + strbuf_release(&sbuf); + strbuf_release("ed); + } else { + errlen = (unsigned long)len; + die(_("bad ls-tree format: %%%.*s"), errlen, start); + } + return len; +} + static int show_recursive(const char *base, size_t baselen, const char *pathname) { int i; @@ -71,6 +140,38 @@ static int show_recursive(const char *base, size_t baselen, const char *pathname return 0; } +static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context) +{ + size_t baselen; + int recurse = 0; + struct strbuf sb = STRBUF_INIT; + enum object_type type = object_type(mode); + + struct show_tree_data data = { + .mode = mode, + .type = type, + .oid = oid, + .pathname = pathname, + .base = base, + }; + + if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname)) + recurse = READ_TREE_RECURSIVE; + if (type == OBJ_TREE && recurse && !(ls_options & LS_SHOW_TREES)) + return recurse; + if (type == OBJ_BLOB && (ls_options & LS_TREE_ONLY)) + return 0; + + baselen = base->len; + strbuf_expand(&sb, format, expand_show_tree, &data); + strbuf_addch(&sb, line_termination); + fwrite(sb.buf, sb.len, 1, stdout); + strbuf_release(&sb); + strbuf_setlen(base, baselen); + return recurse; +} + static int show_default(struct show_tree_data *data) { size_t baselen = data->base->len; @@ -145,11 +246,33 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, return recurse; } +struct ls_tree_cmdmode_to_fmt { + enum ls_tree_cmdmode mode; + const char *const fmt; +}; + +static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = { + { + .mode = MODE_DEFAULT, + .fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)", + }, + { + .mode = MODE_LONG, + .fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)", + }, + { + .mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */ + .fmt = "%(path)", + }, + { 0 }, +}; + int cmd_ls_tree(int argc, const char **argv, const char *prefix) { struct object_id oid; struct tree *tree; int i, full_tree = 0; + read_tree_fn_t fn = show_tree; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, N_("only show trees"), LS_TREE_ONLY), @@ -170,6 +293,9 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "full-tree", &full_tree, N_("list entire tree; not just current directory " "(implies --full-name)")), + OPT_STRING_F(0, "format", &format, N_("format"), + N_("format to use for the output"), + PARSE_OPT_NONEG), OPT__ABBREV(&abbrev), OPT_END() }; @@ -190,6 +316,10 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) ls_options |= LS_SHOW_TREES; + if (format && cmdmode) + usage_msg_opt( + _("--format can't be combined with other format-altering options"), + ls_tree_usage, ls_tree_options); if (argc < 1) usage_with_options(ls_tree_usage, ls_tree_options); if (get_oid(argv[0], &oid)) @@ -211,6 +341,23 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree(the_repository, tree, - &pathspec, show_tree, NULL); + /* + * The generic show_tree_fmt() is slower than show_tree(), so + * take the fast path if possible. + */ + if (format) { + struct ls_tree_cmdmode_to_fmt *m2f; + + fn = show_tree_fmt; + for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) { + if (strcmp(format, m2f->fmt)) + continue; + + cmdmode = m2f->mode; + fn = show_tree; + break; + } + } + + return !!read_tree(the_repository, tree, &pathspec, fn, NULL); } diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh new file mode 100755 index 0000000000..7f1eb699d3 --- /dev/null +++ b/t/t3104-ls-tree-format.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='ls-tree --format' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'ls-tree --format usage' ' + test_expect_code 129 git ls-tree --format=fmt -l HEAD && + test_expect_code 129 git ls-tree --format=fmt --name-only HEAD && + test_expect_code 129 git ls-tree --format=fmt --name-status HEAD +' + +test_expect_success 'setup' ' + mkdir dir && + test_commit dir/sub-file && + test_commit top-file +' + +test_ls_tree_format () { + format=$1 && + opts=$2 && + fmtopts=$3 && + shift 2 && + + test_expect_success "ls-tree '--format=<$format>' is like options '$opts $fmtopts'" ' + git ls-tree $opts -r HEAD >expect && + git ls-tree --format="$format" -r $fmtopts HEAD >actual && + test_cmp expect actual + ' + + test_expect_success "ls-tree '--format=<$format>' on optimized v.s. non-optimized path" ' + git ls-tree --format="$format" -r $fmtopts HEAD >expect && + git ls-tree --format="> $format" -r $fmtopts HEAD >actual.raw && + sed "s/^> //" >actual Date: Wed, 23 Mar 2022 17:13:13 +0800 Subject: ls-tree: support --object-only option for "git-ls-tree" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit '--object-only' is an alias for '--format=%(objectname)'. It cannot be used together other format-altering options like '--name-only', '--long' or '--format', they are mutually exclusive. The "--name-only" option outputs only. Likewise, is another high frequency used field, so implement '--object-only' option will bring intuitive and clear semantics for this scenario. Using '--format=%(objectname)' we can achieve a similar effect, but the former is with a lower learning cost(without knowing the format requirement of '--format' option). Even so, if a user is prefer to use "--format=%(objectname)", this is entirely welcome because they are not only equivalent in function, but also have almost identical performance. The reason is this commit also add the specific of "--format=%(objectname)" to the current fast-pathes (builtin formats) to avoid running unnecessary parsing mechanisms. The following performance benchmarks are based on torvalds/linux.git: When hit the fast-path: Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --object-only HEAD Time (mean ± σ): 83.6 ms ± 2.0 ms [User: 59.4 ms, System: 24.1 ms] Range (min … max): 80.4 ms … 87.2 ms 35 runs Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='%(objectname)' HEAD Time (mean ± σ): 84.1 ms ± 1.8 ms [User: 61.7 ms, System: 22.3 ms] Range (min … max): 80.9 ms … 87.5 ms 35 runs But for a customized format, it will be slower: Benchmark 1: /opt/git/ls-tree-oid-only/bin/git ls-tree -r --format='oid: %(objectname)' HEAD Time (mean ± σ): 96.5 ms ± 2.5 ms [User: 72.9 ms, System: 23.5 ms] Range (min … max): 93.1 ms … 104.1 ms 31 runs Helped-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- Documentation/git-ls-tree.txt | 11 ++++++++++- builtin/ls-tree.c | 13 ++++++++++++- t/t3103-ls-tree-misc.sh | 7 +++++-- t/t3104-ls-tree-format.sh | 9 +++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Documentation/git-ls-tree.txt b/Documentation/git-ls-tree.txt index 68bf1cf325..43aebb9938 100644 --- a/Documentation/git-ls-tree.txt +++ b/Documentation/git-ls-tree.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git ls-tree' [-d] [-r] [-t] [-l] [-z] - [--name-only] [--name-status] [--full-name] [--full-tree] [--abbrev[=]] [--format=] + [--name-only] [--name-status] [--object-only] [--full-name] [--full-tree] [--abbrev[=]] [--format=] [...] DESCRIPTION @@ -59,6 +59,15 @@ OPTIONS --name-only:: --name-status:: List only filenames (instead of the "long" output), one per line. + Cannot be combined with `--object-only`. + +--object-only:: + List only names of the objects, one per line. Cannot be combined + with `--name-only` or `--name-status`. + This is equivalent to specifying `--format='%(objectname)'`, but + for both this option and that exact format the command takes a + hand-optimized codepath instead of going through the generic + formatting mechanism. --abbrev[=]:: Instead of showing the full 40-byte hexadecimal object diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 8a1bf2fa4d..3474f8c3d6 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -24,7 +24,6 @@ static struct pathspec pathspec; static int chomp_prefix; static const char *ls_tree_prefix; static const char *format; - struct show_tree_data { unsigned mode; enum object_type type; @@ -42,6 +41,7 @@ static enum ls_tree_cmdmode { MODE_DEFAULT = 0, MODE_LONG, MODE_NAME_ONLY, + MODE_OBJECT_ONLY, } cmdmode; static void expand_objectsize(struct strbuf *line, const struct object_id *oid, @@ -227,6 +227,11 @@ static int show_tree(const struct object_id *oid, struct strbuf *base, return recurse; } + if (cmdmode == MODE_OBJECT_ONLY) { + printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination); + return recurse; + } + if (cmdmode == MODE_NAME_ONLY) { baselen = base->len; strbuf_addstr(base, pathname); @@ -264,6 +269,10 @@ static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = { .mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */ .fmt = "%(path)", }, + { + .mode = MODE_OBJECT_ONLY, + .fmt = "%(objectname)", + }, { 0 }, }; @@ -288,6 +297,8 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) MODE_NAME_ONLY), OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"), MODE_NAME_ONLY), + OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"), + MODE_OBJECT_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, N_("use full path names"), 0), OPT_BOOL(0, "full-tree", &full_tree, diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh index d9d7fa932f..d979c0df5d 100755 --- a/t/t3103-ls-tree-misc.sh +++ b/t/t3103-ls-tree-misc.sh @@ -25,11 +25,14 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' for opts in \ "--name-only --long" \ - "--name-status --long" + "--name-status --long" \ + "--name-only --object-only" \ + "--name-status --object-only" \ + "--object-only --long" \ + "--object-only --format" do test_expect_success "usage: incompatible options: $opts" ' test_expect_code 129 git ls-tree $opts $tree ' done - test_done diff --git a/t/t3104-ls-tree-format.sh b/t/t3104-ls-tree-format.sh index 7f1eb699d3..0769a933d6 100755 --- a/t/t3104-ls-tree-format.sh +++ b/t/t3104-ls-tree-format.sh @@ -49,6 +49,15 @@ test_ls_tree_format \ "%(path)" \ "--name-only" +test_ls_tree_format \ + "%(objectname)" \ + "--object-only" + +test_ls_tree_format \ + "%(objectname)" \ + "--object-only --abbrev" \ + "--abbrev" + test_ls_tree_format \ "%(objectmode) %(objecttype) %(objectname)%x09%(path)" \ "-t" \ -- cgit v1.2.3 From 0f8878359207191195867ebd499abba3710f9f4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:14 +0800 Subject: ls-tree: detect and error on --name-only --name-status MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The --name-only and --name-status options are synonyms, but let's detect and error if both are provided. In addition let's add explicit --format tests for the combination of these various options. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 11 ++++++++++- t/t3103-ls-tree-misc.sh | 15 +++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 3474f8c3d6..6550f27dfe 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -41,6 +41,7 @@ static enum ls_tree_cmdmode { MODE_DEFAULT = 0, MODE_LONG, MODE_NAME_ONLY, + MODE_NAME_STATUS, MODE_OBJECT_ONLY, } cmdmode; @@ -296,7 +297,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "name-only", &cmdmode, N_("list only filenames"), MODE_NAME_ONLY), OPT_CMDMODE(0, "name-status", &cmdmode, N_("list only filenames"), - MODE_NAME_ONLY), + MODE_NAME_STATUS), OPT_CMDMODE(0, "object-only", &cmdmode, N_("list only objects"), MODE_OBJECT_ONLY), OPT_SET_INT(0, "full-name", &chomp_prefix, @@ -322,6 +323,14 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) ls_tree_prefix = prefix = NULL; chomp_prefix = 0; } + /* + * We wanted to detect conflicts between --name-only and + * --name-status, but once we're done with that subsequent + * code should only need to check the primary name. + */ + if (cmdmode == MODE_NAME_STATUS) + cmdmode = MODE_NAME_ONLY; + /* -d -r should imply -t, but -d by itself should not have to. */ if ( (LS_TREE_ONLY|LS_RECURSIVE) == ((LS_TREE_ONLY|LS_RECURSIVE) & ls_options)) diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh index d979c0df5d..81c6343962 100755 --- a/t/t3103-ls-tree-misc.sh +++ b/t/t3103-ls-tree-misc.sh @@ -24,15 +24,18 @@ test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' ' for opts in \ - "--name-only --long" \ - "--name-status --long" \ - "--name-only --object-only" \ + "--long --name-only" \ + "--name-only --name-status" \ "--name-status --object-only" \ - "--object-only --long" \ - "--object-only --format" + "--object-only --long" do test_expect_success "usage: incompatible options: $opts" ' test_expect_code 129 git ls-tree $opts $tree - ' + ' + + one_opt=$(echo "$opts" | cut -d' ' -f1) + test_expect_success "usage: incompatible options: $one_opt and --format" ' + test_expect_code 129 git ls-tree $one_opt --format=fmt $tree + ' done test_done -- cgit v1.2.3 From 9c4d58ff2c385f49585197c8650356955e1fa02e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 23 Mar 2022 17:13:15 +0800 Subject: ls-tree: split up "fast path" callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the various if/else in the callbacks for the "fast path" a lot easier to read by just using common functions for the parts that are common, and have per-format callbacks for those parts that are different. Signed-off-by: Ævar Arnfjörð Bjarmason Signed-off-by: Teng Long Signed-off-by: Junio C Hamano --- builtin/ls-tree.c | 199 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 125 insertions(+), 74 deletions(-) diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 6550f27dfe..44a91cf9d0 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -173,108 +173,157 @@ static int show_tree_fmt(const struct object_id *oid, struct strbuf *base, return recurse; } -static int show_default(struct show_tree_data *data) +static int show_tree_common(struct show_tree_data *data, int *recurse, + const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode) { - size_t baselen = data->base->len; - - if (cmdmode == MODE_LONG) { - char size_text[24]; - if (data->type == OBJ_BLOB) { - unsigned long size; - if (oid_object_info(the_repository, data->oid, &size) == OBJ_BAD) - xsnprintf(size_text, sizeof(size_text), "BAD"); - else - xsnprintf(size_text, sizeof(size_text), - "%" PRIuMAX, (uintmax_t)size); - } else { - xsnprintf(size_text, sizeof(size_text), "-"); - } - printf("%06o %s %s %7s\t", data->mode, type_name(data->type), - find_unique_abbrev(data->oid, abbrev), size_text); - } else { - printf("%06o %s %s\t", data->mode, type_name(data->type), - find_unique_abbrev(data->oid, abbrev)); - } - baselen = data->base->len; - strbuf_addstr(data->base, data->pathname); - write_name_quoted_relative(data->base->buf, - chomp_prefix ? ls_tree_prefix : NULL, stdout, - line_termination); - strbuf_setlen(data->base, baselen); - return 1; -} - -static int show_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, void *context) -{ - int recurse = 0; - size_t baselen; enum object_type type = object_type(mode); - struct show_tree_data data = { - .mode = mode, - .type = type, - .oid = oid, - .pathname = pathname, - .base = base, - }; + int ret = -1; + + *recurse = 0; + data->mode = mode; + data->type = type; + data->oid = oid; + data->pathname = pathname; + data->base = base; if (type == OBJ_BLOB) { if (ls_options & LS_TREE_ONLY) - return 0; + ret = 0; } else if (type == OBJ_TREE && show_recursive(base->buf, base->len, pathname)) { - recurse = READ_TREE_RECURSIVE; + *recurse = READ_TREE_RECURSIVE; if (!(ls_options & LS_SHOW_TREES)) - return recurse; + ret = *recurse; } - if (cmdmode == MODE_OBJECT_ONLY) { - printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination); - return recurse; - } + return ret; +} - if (cmdmode == MODE_NAME_ONLY) { - baselen = base->len; - strbuf_addstr(base, pathname); - write_name_quoted_relative(base->buf, - chomp_prefix ? ls_tree_prefix : NULL, - stdout, line_termination); - strbuf_setlen(base, baselen); - return recurse; +static void show_tree_common_default_long(struct strbuf *base, + const char *pathname, + const size_t baselen) +{ + strbuf_addstr(base, pathname); + write_name_quoted_relative(base->buf, + chomp_prefix ? ls_tree_prefix : NULL, stdout, + line_termination); + strbuf_setlen(base, baselen); +} + +static int show_tree_default(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + int early; + int recurse; + struct show_tree_data data = { 0 }; + + early = show_tree_common(&data, &recurse, oid, base, pathname, mode); + if (early >= 0) + return early; + + printf("%06o %s %s\t", data.mode, type_name(data.type), + find_unique_abbrev(data.oid, abbrev)); + show_tree_common_default_long(base, pathname, data.base->len); + return recurse; +} + +static int show_tree_long(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context) +{ + int early; + int recurse; + struct show_tree_data data = { 0 }; + char size_text[24]; + + early = show_tree_common(&data, &recurse, oid, base, pathname, mode); + if (early >= 0) + return early; + + if (data.type == OBJ_BLOB) { + unsigned long size; + if (oid_object_info(the_repository, data.oid, &size) == OBJ_BAD) + xsnprintf(size_text, sizeof(size_text), "BAD"); + else + xsnprintf(size_text, sizeof(size_text), + "%" PRIuMAX, (uintmax_t)size); + } else { + xsnprintf(size_text, sizeof(size_text), "-"); } - if (cmdmode == MODE_LONG || - (!ls_options || (ls_options & LS_RECURSIVE) - || (ls_options & LS_SHOW_TREES) - || (ls_options & LS_TREE_ONLY))) - show_default(&data); + printf("%06o %s %s %7s\t", data.mode, type_name(data.type), + find_unique_abbrev(data.oid, abbrev), size_text); + show_tree_common_default_long(base, pathname, data.base->len); + return 1; +} +static int show_tree_name_only(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context) +{ + int early; + int recurse; + const size_t baselen = base->len; + struct show_tree_data data = { 0 }; + + early = show_tree_common(&data, &recurse, oid, base, pathname, mode); + if (early >= 0) + return early; + + strbuf_addstr(base, pathname); + write_name_quoted_relative(base->buf, + chomp_prefix ? ls_tree_prefix : NULL, + stdout, line_termination); + strbuf_setlen(base, baselen); + return recurse; +} + +static int show_tree_object(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, void *context) +{ + int early; + int recurse; + struct show_tree_data data = { 0 }; + + early = show_tree_common(&data, &recurse, oid, base, pathname, mode); + if (early >= 0) + return early; + + printf("%s%c", find_unique_abbrev(oid, abbrev), line_termination); return recurse; } struct ls_tree_cmdmode_to_fmt { enum ls_tree_cmdmode mode; const char *const fmt; + read_tree_fn_t fn; }; static struct ls_tree_cmdmode_to_fmt ls_tree_cmdmode_format[] = { { .mode = MODE_DEFAULT, .fmt = "%(objectmode) %(objecttype) %(objectname)%x09%(path)", + .fn = show_tree_default, }, { .mode = MODE_LONG, .fmt = "%(objectmode) %(objecttype) %(objectname) %(objectsize:padded)%x09%(path)", + .fn = show_tree_long, }, { .mode = MODE_NAME_ONLY, /* And MODE_NAME_STATUS */ .fmt = "%(path)", + .fn = show_tree_name_only, }, { .mode = MODE_OBJECT_ONLY, .fmt = "%(objectname)", + .fn = show_tree_object + }, + { + /* fallback */ + .fn = show_tree_default, }, - { 0 }, }; int cmd_ls_tree(int argc, const char **argv, const char *prefix) @@ -282,7 +331,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) struct object_id oid; struct tree *tree; int i, full_tree = 0; - read_tree_fn_t fn = show_tree; + read_tree_fn_t fn = NULL; const struct option ls_tree_options[] = { OPT_BIT('d', NULL, &ls_options, N_("only show trees"), LS_TREE_ONLY), @@ -311,6 +360,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) OPT__ABBREV(&abbrev), OPT_END() }; + struct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format; git_config(git_default_config, NULL); ls_tree_prefix = prefix; @@ -365,18 +415,19 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) * The generic show_tree_fmt() is slower than show_tree(), so * take the fast path if possible. */ - if (format) { - struct ls_tree_cmdmode_to_fmt *m2f; - - fn = show_tree_fmt; - for (m2f = ls_tree_cmdmode_format; m2f->fmt; m2f++) { - if (strcmp(format, m2f->fmt)) - continue; - + while (m2f) { + if (!m2f->fmt) { + fn = format ? show_tree_fmt : show_tree_default; + } else if (format && !strcmp(format, m2f->fmt)) { cmdmode = m2f->mode; - fn = show_tree; - break; + fn = m2f->fn; + } else if (!format && cmdmode == m2f->mode) { + fn = m2f->fn; + } else { + m2f++; + continue; } + break; } return !!read_tree(the_repository, tree, &pathspec, fn, NULL); -- cgit v1.2.3