{"thread":{"id":"65921","subject":"[GSoC Patch] repo: support category-based prefix querying for info keys","startedAt":"2026-07-03T16:47:39Z","lastAt":"2026-07-12T02:52:20Z","messageCount":3,"participants":["K Jayatheerth","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"547108","messageId":"20260703164709.22723-1-jayatheerthkulkarni2005@gmail.com","threadId":"65921","inReplyTo":null,"subject":"[GSoC Patch] repo: support category-based prefix querying for info keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-07-03T16:47:09Z","receivedAt":"2026-07-03T16:47:39Z","isPatch":true,"body":"Currently, git repo info relies on an all-or-nothing query model\nwhere users must either know the exact, fully-qualified key name or use\nthe --all flag to dump the entire repository state.\nAs the number of supported keys expands, dumping all metadata and\nrelying on external filters like grep becomes an inefficient bottleneck\nfor a plumbing command.\n\nEnable category-based prefix querying so users can request\nentire groups of related keys natively\n\nMentored-by: Justin Tobler <jltobler@gmail.com>\nMentored-by: Lucas Seiki Oshiro <lucasseikioshiro@gmail.com>\nSigned-off-by: K Jayatheerth <jayatheerthkulkarni2005@gmail.com>\n---\nHi!\n\nThis patch adds category-based prefix querying to\n`git repo info` as part of my GSoC project.\nA quick note on the implementation: I replaced the `bsearch` with\na linear search to find the initial prefix match.\nI discussed this with my mentors, and we decided to fall back\nto a linear search because the overhead of a custom binary search\n(to find the *first* match in a block) wasn't justified given\nthe currently small size of the `repo_info_field` array.\n\nSince the array is strictly sorted alphabetically,\nthe loop safely short-circuits via `strncmp`\nonce it steps outside the matching prefix block.\n\nLooking forward to feedback!\n\n Documentation/git-repo.adoc | 12 ++++++++\n builtin/repo.c              | 58 +++++++++++++++++++++++--------------\n t/t1900-repo-info.sh        | 16 ++++++++++\n 3 files changed, 64 insertions(+), 22 deletions(-)\n\ndiff --git a/Documentation/git-repo.adoc b/Documentation/git-repo.adoc\nindex 42262c1983..3e840d6323 100644\n--- a/Documentation/git-repo.adoc\n+++ b/Documentation/git-repo.adoc\n@@ -25,6 +25,12 @@ COMMANDS\n \tthe requested data will be returned based on their keys (see \"INFO KEYS\"\n \tsection below).\n +\n+If a `<key>` argument matches a category prefix (i.e. a namespace that ends at\n+a `.` boundary), all keys within that namespace are returned. For example,\n+`layout` returns both `layout.bare` and `layout.shallow`. The prefix must\n+align with a namespace boundary; partial prefixes that do not end at a `.`\n+separator (e.g. `lay`) are treated as unknown keys and will produce an error.\n++\n The values are returned in the same order in which their respective keys were\n requested. The `--all` flag requests the values for all the available keys.\n +\n@@ -126,6 +132,12 @@ using the `nul` format:\n git repo info --format=nul layout.bare layout.shallow\n ------------\n \n+* Retrieves all keys under the `layout` category prefix:\n++\n+------------\n+git repo info layout\n+------------\n+\n SEE ALSO\n --------\n linkgit:git-rev-parse[1]\ndiff --git a/builtin/repo.c b/builtin/repo.c\nindex 71a5c1c29c..91ea5b5459 100644\n--- a/builtin/repo.c\n+++ b/builtin/repo.c\n@@ -90,24 +90,27 @@ static const struct repo_info_field repo_info_field[] = {\n \t{ \"references.format\", get_references_format },\n };\n \n-static int repo_info_field_cmp(const void *va, const void *vb)\n+static int is_valid_prefix_match(const char *key, const char *prefix)\n {\n-\tconst struct repo_info_field *a = va;\n-\tconst struct repo_info_field *b = vb;\n+\tsize_t prefix_len = strlen(prefix);\n \n-\treturn strcmp(a->key, b->key);\n+\tif (!prefix_len)\n+\t\treturn 0;\n+\n+\tif (strncmp(key, prefix, prefix_len))\n+\t\treturn 0;\n+\n+\treturn key[prefix_len] == '\\0' || prefix[prefix_len - 1] == '.' || key[prefix_len] == '.';\n }\n \n-static const struct repo_info_field *get_repo_info_field(const char *key)\n+static size_t find_first_repo_info_field_match(const char *prefix)\n {\n-\tconst struct repo_info_field search_key = { key, NULL };\n-\tconst struct repo_info_field *found = bsearch(&search_key,\n-\t\t\t\t\t\t      repo_info_field,\n-\t\t\t\t\t\t      ARRAY_SIZE(repo_info_field),\n-\t\t\t\t\t\t      sizeof(*found),\n-\t\t\t\t\t\t      repo_info_field_cmp);\n-\n-\treturn found;\n+\tfor (size_t i = 0; i < ARRAY_SIZE(repo_info_field); i++) {\n+\t\tif (is_valid_prefix_match(repo_info_field[i].key, prefix)) {\n+\t\t\treturn i;\n+\t\t}\n+\t}\n+\treturn SIZE_MAX;\n }\n \n static void print_field(enum output_format format, const char *key,\n@@ -135,17 +138,28 @@ static int print_fields(int argc, const char **argv,\n \tstruct strbuf valbuf = STRBUF_INIT;\n \n \tfor (int i = 0; i < argc; i++) {\n-\t\tconst char *key = argv[i];\n-\t\tconst struct repo_info_field *field = get_repo_info_field(key);\n-\n-\t\tif (!field) {\n-\t\t\tret = error(_(\"key '%s' not found\"), key);\n-\t\t\tcontinue;\n+\t\tconst char *prefix = argv[i];\n+\t\tsize_t prefix_len = strlen(prefix);\n+\t\tsize_t idx = find_first_repo_info_field_match(prefix);\n+\t\tint found = 0;\n+\n+\t\tfor (; idx < ARRAY_SIZE(repo_info_field); idx++) {\n+\t\t\tconst struct repo_info_field *field = &repo_info_field[idx];\n+\n+\t\t\tif (strncmp(field->key, prefix, prefix_len))\n+\t\t\t\tbreak;\n+\n+\t\t\tif (is_valid_prefix_match(field->key, prefix)) {\n+\t\t\t\tstrbuf_reset(&valbuf);\n+\t\t\t\tfield->get_value(repo, &valbuf);\n+\t\t\t\tprint_field(format, field->key, valbuf.buf);\n+\t\t\t\tfound = 1;\n+\t\t\t}\n \t\t}\n \n-\t\tstrbuf_reset(&valbuf);\n-\t\tfield->get_value(repo, &valbuf);\n-\t\tprint_field(format, key, valbuf.buf);\n+\t\tif (!found) {\n+\t\t\tret = error(_(\"key '%s' not found\"), prefix);\n+\t\t}\n \t}\n \n \tstrbuf_release(&valbuf);\ndiff --git a/t/t1900-repo-info.sh b/t/t1900-repo-info.sh\nindex 39bb77dda0..80ae8f8396 100755\n--- a/t/t1900-repo-info.sh\n+++ b/t/t1900-repo-info.sh\n@@ -149,6 +149,22 @@ test_expect_success 'git repo info --keys uses lines as its default output forma\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git repo info with category prefix returns all keys in namespace' '\n+\tcat >expect <<-\\EOF &&\n+\tlayout.bare=false\n+\tlayout.shallow=false\n+\tEOF\n+\tgit init prefix-repo &&\n+\tgit -C prefix-repo repo info layout >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'git repo info with invalid partial boundary fails' '\n+\techo \"error: key ${SQ}lay${SQ} not found\" >expect &&\n+\ttest_must_fail git -C prefix-repo repo info lay 2>actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'git repo info -h shows only repo info usage' '\n \ttest_must_fail git repo info -h >actual &&\n \ttest_grep \"git repo info\" actual &&\n-- \n2.55.0-rc1\n"},{"id":"547124","messageId":"xmqq7bnbheo1.fsf@gitster.g","threadId":"65921","inReplyTo":"20260703164709.22723-1-jayatheerthkulkarni2005@gmail.com","subject":"Re: [GSoC Patch] repo: support category-based prefix querying for info keys","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-03T22:49:34Z","receivedAt":"2026-07-03T22:49:36Z","isPatch":true,"body":"K Jayatheerth <jayatheerthkulkarni2005@gmail.com> writes:\n\n> Currently, git repo info relies on an all-or-nothing query model\n> where users must either know the exact, fully-qualified key name or use\n> the --all flag to dump the entire repository state.\n> As the number of supported keys expands, dumping all metadata and\n> relying on external filters like grep becomes an inefficient bottleneck\n> for a plumbing command.\n>\n> Enable category-based prefix querying so users can request\n> entire groups of related keys natively\n\nYou mean \"repo info\" takes layout.bare and layout.shallow (right\nnow, later we may gain a lot more), so you want to say \"everything\nunder 'layout' to grab these two values?\n\nWhy should we limit ourselves to \"prefix match\"?  Would a glob like\n\"layout.*\", or \"path.*.absolute\", work better?  Especially the\nlatter, i.e., \"I want the path variables, but am not interested in\ntheir .relative values, only the .absolute ones.\"  It is especially\npuzzling as you are going to do a dumb linear search in this mode\nanyway.\n\nPerhaps during each iteration of the loop over argv[], you can first\nlook for exact match using the existing bsearch() codepath.  If that\nsucceeds, you have a single key to return the value for.  If it does\nnot match exactly any key, use the new \"prefix\" (or \"glob\" which I\nthink would make far more sense) match codepath to find which key(s)\nto return values for, so iterate over them (or say \"Hey, that pattern\ndoes not match any key!\" and fail).\n"},{"id":"547872","messageId":"CA+rGoLdYm3aHnfXWiCh4YQyYyz=TDCB-JWZ51v0nwzxzfACcQg@mail.gmail.com","threadId":"65921","inReplyTo":"xmqq7bnbheo1.fsf@gitster.g","subject":"Re: [GSoC Patch] repo: support category-based prefix querying for info keys","fromName":"K Jayatheerth","fromEmail":"jayatheerthkulkarni2005@gmail.com","sentAt":"2026-07-12T02:52:07Z","receivedAt":"2026-07-12T02:52:20Z","isPatch":true,"body":"Hey Junio,\n\nOn Sat, Jul 4, 2026 at 4:19 AM Junio C Hamano <gitster@pobox.com> wrote:\n\n>\n> You mean \"repo info\" takes layout.bare and layout.shallow (right\n> now, later we may gain a lot more), so you want to say \"everything\n> under 'layout' to grab these two values?\n>\n> Why should we limit ourselves to \"prefix match\"?  Would a glob like\n> \"layout.*\", or \"path.*.absolute\", work better?  Especially the\n> latter, i.e., \"I want the path variables, but am not interested in\n> their .relative values, only the .absolute ones.\"  It is especially\n> puzzling as you are going to do a dumb linear search in this mode\n> anyway.\n>\n> Perhaps during each iteration of the loop over argv[], you can first\n> look for exact match using the existing bsearch() codepath.  If that\n> succeeds, you have a single key to return the value for.  If it does\n> not match exactly any key, use the new \"prefix\" (or \"glob\" which I\n> think would make far more sense) match codepath to find which key(s)\n> to return values for, so iterate over them (or say \"Hey, that pattern\n> does not match any key!\" and fail).\n\n\nSorry this response took a long time but I have given a good thought about this\nYou are right that adding globs makes much more sense in this case.\nI was initially skeptical about globs, but looking at the direction we took\nin path.* keys it makes much more sense.\n\nBut I think adding a query system is not a good idea anymore.\nI have discussed this with my mentors at length.\nSince we are building a plumbing command, we couldn't think of a use-case where\npeople would need globs over hard-coded value in scripts.\nAlso globing might introduce uncertainties if the script doesn't have\nan appropriate fall back.\nI am also wondering if there will be any significant performance difference\nwith --all vs globs.\n\nA query system in itself is meant to simplify commands for user usage,\nbut I don't think adding it makes sense \"yet\".\n\nI also wanted your opinion on this\n\nFor my GSoC I can pick the histograms patch in the git repo structure\ninstead of this.\n\nRegards,\n- K Jayatheerth\n"}]}