{"thread":{"id":"64099","subject":"[GSoC][PATCH v2 0/5] Add refs optimize subcommand","startedAt":"2025-09-06T07:52:01Z","lastAt":"2025-09-24T06:18:51Z","messageCount":36,"participants":["Meet Soni","Junio C Hamano","shejialuo","Patrick Steinhardt"],"isPatch":true,"patchVersion":2,"patchTotal":5},"messages":[{"id":"525685","messageId":"20250906075147.1076656-1-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":null,"subject":"[GSoC][PATCH v2 0/5] Add refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:42Z","receivedAt":"2025-09-06T07:52:01Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"This series introduces `git refs optimize` as a modern replacement for\n`git pack-refs`, continuing the effort to consolidate commands\nunder the `git refs` namespace.\n\nchanges in v2:\n- Updated documentation.\n- moved `pack_refs_core()` from `builtin/pack-refs.c` to `pack-refs.c`.\n\nMeet Soni (5):\n  builtin/pack-refs: factor out core logic into a shared library\n  doc: factor out common option\n  builtin/refs: add optimize subcommand\n  t0601: refactor tests to be shareable\n  t: add test for git refs optimize subcommand\n\n Documentation/git-pack-refs.adoc     |  54 +---\n Documentation/git-refs.adoc          |  10 +\n Documentation/pack-refs-options.adoc |  52 ++++\n Makefile                             |   1 +\n builtin/pack-refs.c                  |  55 +---\n builtin/refs.c                       |  16 +\n meson.build                          |   1 +\n pack-refs.c                          |  56 ++++\n pack-refs.h                          |  23 ++\n t/meson.build                        |   3 +-\n t/pack-refs-tests.sh                 | 431 +++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh        | 430 +-------------------------\n t/t1463-refs-optimize.sh             |  17 ++\n 13 files changed, 616 insertions(+), 533 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n create mode 100644 t/pack-refs-tests.sh\n create mode 100755 t/t1463-refs-optimize.sh\n\nRange-diff against v1:\n1:  0268933403 < -:  ---------- builtin/pack-refs: factor out core logic into a helper\n-:  ---------- > 1:  67a9187b59 builtin/pack-refs: factor out core logic into a shared library\n2:  ec14212c7b = 2:  422eaa0c8b doc: factor out common option\n3:  d3a4d7cb67 ! 3:  067a2baa97 builtin/refs: add optimize subcommand\n    @@ Documentation/git-refs.adoc: list::\n      \tlinkgit:git-for-each-ref[1] and offers identical functionality.\n      \n     +optimize::\n    -+        Pack references into a single file to improve repository performance\n    -+        and reduce storage usage. This subcommand is an alias for\n    -+        linkgit:git-pack-refs[1] and offers identical functionality.\n    ++\tOptimizes references to improve repository performance and reduce disk\n    ++\tusage. This subcommand is an alias for linkgit:git-pack-refs[1] and\n    ++\toffers identical functionality.\n     +\n      OPTIONS\n      -------\n4:  16fa3f5445 = 4:  640ebd9a4f t0601: refactor tests to be shareable\n5:  22620613d9 = 5:  37ca586bb9 t: add test for git refs optimize subcommand\n\nbase-commit: 1fa68948c3d76328236cac73d2adf33c905bd8e3\n-- \n2.34.1\n\n"},{"id":"525686","messageId":"20250906075147.1076656-2-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v2 1/5] builtin/pack-refs: factor out core logic into a shared library","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:43Z","receivedAt":"2025-09-06T07:52:04Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The implementation of `git pack-refs` is monolithic within\n`cmd_pack_refs()`, making it impossible to share its logic with other\ncommands. To enable code reuse for the upcoming `git refs optimize`\nsubcommand, refactor the core logic into a shared helper function.\n\nSplit the original `builtin/pack-refs.c` file into two parts:\n\n- A new shared library file, `pack-refs.c`, which contains the\n  core option parsing and packing logic in a new `pack_refs_core()`\n  helper function.\n\n- The original `builtin/pack-refs.c`, which is now a thin wrapper\n  responsible only for defining the `git pack-refs` command and\n  calling the shared helper.\n\nA new `pack-refs.h` header is also introduced to define the public\ninterface for this shared logic.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Makefile            |  1 +\n builtin/pack-refs.c | 55 ++++----------------------------------------\n meson.build         |  1 +\n pack-refs.c         | 56 +++++++++++++++++++++++++++++++++++++++++++++\n pack-refs.h         | 23 +++++++++++++++++++\n 5 files changed, 86 insertions(+), 50 deletions(-)\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n\ndiff --git a/Makefile b/Makefile\nindex e11340c1ae..5d315e287d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1094,6 +1094,7 @@ LIB_OBJS += pack-bitmap.o\n LIB_OBJS += pack-check.o\n LIB_OBJS += pack-mtimes.o\n LIB_OBJS += pack-objects.o\n+LIB_OBJS += pack-refs.o\n LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\ndiff --git a/builtin/pack-refs.c b/builtin/pack-refs.c\nindex 5e28d0f9e8..f9e0675c64 100644\n--- a/builtin/pack-refs.c\n+++ b/builtin/pack-refs.c\n@@ -1,60 +1,15 @@\n #include \"builtin.h\"\n-#include \"config.h\"\n-#include \"environment.h\"\n #include \"gettext.h\"\n-#include \"parse-options.h\"\n-#include \"refs.h\"\n-#include \"revision.h\"\n-\n-static char const * const pack_refs_usage[] = {\n-\tN_(\"git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"),\n-\tNULL\n-};\n+#include \"pack-refs.h\"\n \n int cmd_pack_refs(int argc,\n \t\t  const char **argv,\n \t\t  const char *prefix,\n \t\t  struct repository *repo)\n {\n-\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n-\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n-\tstruct pack_refs_opts pack_refs_opts = {\n-\t\t.exclusions = &excludes,\n-\t\t.includes = &included_refs,\n-\t\t.flags = PACK_REFS_PRUNE,\n-\t};\n-\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n-\tstruct string_list_item *item;\n-\tint pack_all = 0;\n-\tint ret;\n-\n-\tstruct option opts[] = {\n-\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n-\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n-\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n-\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n-\t\t\tN_(\"references to include\")),\n-\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n-\t\t\tN_(\"references to exclude\")),\n-\t\tOPT_END(),\n+\tstatic char const * const pack_refs_usage[] = {\n+\t\tN_(\"git pack-refs \" PACK_REFS_OPTS),\n+\t\tNULL\n \t};\n-\trepo_config(repo, git_default_config, NULL);\n-\tif (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))\n-\t\tusage_with_options(pack_refs_usage, opts);\n-\n-\tfor_each_string_list_item(item, &option_excluded_refs)\n-\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n-\n-\tif (pack_all)\n-\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n-\n-\tif (!pack_refs_opts.includes->nr)\n-\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n-\n-\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n-\n-\tclear_ref_exclusions(&excludes);\n-\tstring_list_clear(&included_refs, 0);\n-\tstring_list_clear(&option_excluded_refs, 0);\n-\treturn ret;\n+\treturn pack_refs_core(argc, argv, prefix, repo, pack_refs_usage);\n }\ndiff --git a/meson.build b/meson.build\nindex 5dd299b496..0f147f4b13 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -407,6 +407,7 @@ libgit_sources = [\n   'pack-check.c',\n   'pack-mtimes.c',\n   'pack-objects.c',\n+  'pack-refs.c',\n   'pack-revindex.c',\n   'pack-write.c',\n   'packfile.c',\ndiff --git a/pack-refs.c b/pack-refs.c\nnew file mode 100644\nindex 0000000000..a1f8ed1c47\n--- /dev/null\n+++ b/pack-refs.c\n@@ -0,0 +1,56 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"environment.h\"\n+#include \"pack-refs.h\"\n+#include \"parse-options.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts)\n+{\n+\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n+\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n+\tstruct pack_refs_opts pack_refs_opts = {\n+\t\t.exclusions = &excludes,\n+\t\t.includes = &included_refs,\n+\t\t.flags = PACK_REFS_PRUNE,\n+\t};\n+\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n+\tstruct string_list_item *item;\n+\tint pack_all = 0;\n+\tint ret;\n+\n+\tstruct option opts[] = {\n+\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n+\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n+\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n+\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n+\t\t\tN_(\"references to include\")),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n+\t\t\tN_(\"references to exclude\")),\n+\t\tOPT_END(),\n+\t};\n+\trepo_config(repo, git_default_config, NULL);\n+\tif (parse_options(argc, argv, prefix, opts, usage_opts, 0))\n+\t\tusage_with_options(usage_opts, opts);\n+\n+\tfor_each_string_list_item(item, &option_excluded_refs)\n+\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n+\n+\tif (pack_all)\n+\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n+\n+\tif (!pack_refs_opts.includes->nr)\n+\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n+\n+\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n+\n+\tclear_ref_exclusions(&excludes);\n+\tstring_list_clear(&included_refs, 0);\n+\tstring_list_clear(&option_excluded_refs, 0);\n+\treturn ret;\n+}\ndiff --git a/pack-refs.h b/pack-refs.h\nnew file mode 100644\nindex 0000000000..5de27e7da8\n--- /dev/null\n+++ b/pack-refs.h\n@@ -0,0 +1,23 @@\n+#ifndef PACK_REFS_H\n+#define PACK_REFS_H\n+\n+struct repository;\n+\n+/*\n+ * Shared usage string for options common to git-pack-refs(1)\n+ * and git-refs-optimize(1). The command-specific part (e.g., \"git refs optimize \")\n+ * must be prepended by the caller.\n+ */\n+#define PACK_REFS_OPTS \\\n+\t\"[--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"\n+\n+/*\n+ * The core logic for pack-refs and its clones.\n+ */\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts);\n+\n+#endif /* PACK_REFS_H */\n-- \n2.34.1\n\n"},{"id":"525687","messageId":"20250906075147.1076656-3-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v2 2/5] doc: factor out common option","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:44Z","receivedAt":"2025-09-06T07:52:07Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding documentation for `git refs optimize`, factor\nout the common options from the `git-pack-refs` man page into a\nshareable file `pack-refs-options.adoc` and update `git-pack-refs.adoc`\nto use an `include::` macro.\n\nThis change is a pure refactoring and results in no change to the\nfinal rendered documentation for `pack-refs`.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-pack-refs.adoc     | 54 +---------------------------\n Documentation/pack-refs-options.adoc | 52 +++++++++++++++++++++++++++\n 2 files changed, 53 insertions(+), 53 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n\ndiff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc\nindex 42b90051e6..dac63e5e12 100644\n--- a/Documentation/git-pack-refs.adoc\n+++ b/Documentation/git-pack-refs.adoc\n@@ -45,59 +45,7 @@ unpacked.\n OPTIONS\n -------\n \n---all::\n-\n-The command by default packs all tags and refs that are already\n-packed, and leaves other refs\n-alone.  This is because branches are expected to be actively\n-developed and packing their tips does not help performance.\n-This option causes all refs to be packed as well, with the exception\n-of hidden refs, broken refs, and symbolic refs. Useful for a repository\n-with many branches of historical interests.\n-\n---no-prune::\n-\n-The command usually removes loose refs under `$GIT_DIR/refs`\n-hierarchy after packing them.  This option tells it not to.\n-\n---auto::\n-\n-Pack refs as needed depending on the current state of the ref database. The\n-behavior depends on the ref format used by the repository and may change in the\n-future.\n-+\n-\t- \"files\": Loose references are packed into the `packed-refs` file\n-\t  based on the ratio of loose references to the size of the\n-\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n-\t  references need to exist before we repack.\n-+\n-\t- \"reftable\": Tables are compacted such that they form a geometric\n-\t  sequence. For two tables N and N+1, where N+1 is newer, this\n-\t  maintains the property that N is at least twice as big as N+1. Only\n-\t  tables that violate this property are compacted.\n-\n---include <pattern>::\n-\n-Pack refs based on a `glob(7)` pattern. Repetitions of this option\n-accumulate inclusion patterns. If a ref is both included in `--include` and\n-`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n-tags from being included by default. Symbolic refs and broken refs will never\n-be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n-and reset the list of patterns.\n-\n---exclude <pattern>::\n-\n-Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n-accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n-patterns. If a ref is already packed, including it with `--exclude` will not\n-unpack it.\n-+\n-When used with `--all`, pack only loose refs which do not match any of\n-the provided `--exclude` patterns.\n-+\n-When used with `--include`, refs provided to `--include`, minus refs that are\n-provided to `--exclude` will be packed.\n-\n+include::pack-refs-options.adoc[]\n \n BUGS\n ----\ndiff --git a/Documentation/pack-refs-options.adoc b/Documentation/pack-refs-options.adoc\nnew file mode 100644\nindex 0000000000..0b11282941\n--- /dev/null\n+++ b/Documentation/pack-refs-options.adoc\n@@ -0,0 +1,52 @@\n+--all::\n+\n+The command by default packs all tags and refs that are already\n+packed, and leaves other refs\n+alone.  This is because branches are expected to be actively\n+developed and packing their tips does not help performance.\n+This option causes all refs to be packed as well, with the exception\n+of hidden refs, broken refs, and symbolic refs. Useful for a repository\n+with many branches of historical interests.\n+\n+--no-prune::\n+\n+The command usually removes loose refs under `$GIT_DIR/refs`\n+hierarchy after packing them.  This option tells it not to.\n+\n+--auto::\n+\n+Pack refs as needed depending on the current state of the ref database. The\n+behavior depends on the ref format used by the repository and may change in the\n+future.\n++\n+\t- \"files\": Loose references are packed into the `packed-refs` file\n+\t  based on the ratio of loose references to the size of the\n+\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n+\t  references need to exist before we repack.\n++\n+\t- \"reftable\": Tables are compacted such that they form a geometric\n+\t  sequence. For two tables N and N+1, where N+1 is newer, this\n+\t  maintains the property that N is at least twice as big as N+1. Only\n+\t  tables that violate this property are compacted.\n+\n+--include <pattern>::\n+\n+Pack refs based on a `glob(7)` pattern. Repetitions of this option\n+accumulate inclusion patterns. If a ref is both included in `--include` and\n+`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n+tags from being included by default. Symbolic refs and broken refs will never\n+be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n+and reset the list of patterns.\n+\n+--exclude <pattern>::\n+\n+Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n+accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n+patterns. If a ref is already packed, including it with `--exclude` will not\n+unpack it.\n++\n+When used with `--all`, pack only loose refs which do not match any of\n+the provided `--exclude` patterns.\n++\n+When used with `--include`, refs provided to `--include`, minus refs that are\n+provided to `--exclude` will be packed.\n-- \n2.34.1\n\n"},{"id":"525688","messageId":"20250906075147.1076656-4-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v2 3/5] builtin/refs: add optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:45Z","receivedAt":"2025-09-06T07:52:09Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"As part of the ongoing effort to consolidate reference handling,\nintroduce a new `optimize` subcommand. This command provides the same\nfunctionality and exit-code behavior as `git pack-refs`, serving\nas its modern replacement.\n\nImplement `cmd_refs_optimize` by having it call the `pack_refs_core()`\nhelper function. This helper was factored out of the original\n`cmd_pack_refs` in a preceding commit, allowing both commands to\nshare the same core logic as independent peers.\n\nAdd documentation for the new command. The man page leverages the shared\noptions file, created in a previous commit, by using the AsciiDoc\n`include::` macro to ensure consistency with git-pack-refs(1).\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-refs.adoc | 10 ++++++++++\n builtin/refs.c              | 16 ++++++++++++++++\n 2 files changed, 26 insertions(+)\n\ndiff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc\nindex e608980711..91cfbc18de 100644\n--- a/Documentation/git-refs.adoc\n+++ b/Documentation/git-refs.adoc\n@@ -18,6 +18,7 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n \t\t   [--contains[=<object>]] [--no-contains[=<object>]]\n \t\t   [(--exclude=<pattern>)...] [--start-after=<marker>]\n \t\t   [ --stdin | <pattern>... ]\n+git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\n \n DESCRIPTION\n -----------\n@@ -38,6 +39,11 @@ list::\n \tformatting, and sorting. This subcommand is an alias for\n \tlinkgit:git-for-each-ref[1] and offers identical functionality.\n \n+optimize::\n+\tOptimizes references to improve repository performance and reduce disk\n+\tusage. This subcommand is an alias for linkgit:git-pack-refs[1] and\n+\toffers identical functionality.\n+\n OPTIONS\n -------\n \n@@ -73,6 +79,10 @@ The following options are specific to 'git refs list':\n \n include::for-each-ref-options.adoc[]\n \n+The following options are specific to 'git refs optimize':\n+\n+include::pack-refs-options.adoc[]\n+\n KNOWN LIMITATIONS\n -----------------\n \ndiff --git a/builtin/refs.c b/builtin/refs.c\nindex 76224feba4..e05fa5b8a6 100644\n--- a/builtin/refs.c\n+++ b/builtin/refs.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"fsck.h\"\n+#include \"pack-refs.h\"\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"strbuf.h\"\n@@ -14,6 +15,9 @@\n #define REFS_VERIFY_USAGE \\\n \tN_(\"git refs verify [--strict] [--verbose]\")\n \n+#define REFS_OPTIMIZE_USAGE \\\n+\tN_(\"git refs optimize \" PACK_REFS_OPTS)\n+\n static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,\n \t\t\t    struct repository *repo UNUSED)\n {\n@@ -113,6 +117,16 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix,\n \treturn for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);\n }\n \n+static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,\n+\t\t\t   struct repository *repo)\n+{\n+\tstatic char const * const refs_optimize_usage[] = {\n+\t\tREFS_OPTIMIZE_USAGE,\n+\t\tNULL\n+\t};\n+\treturn pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);\n+}\n+\n int cmd_refs(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n@@ -122,6 +136,7 @@ int cmd_refs(int argc,\n \t\tREFS_MIGRATE_USAGE,\n \t\tREFS_VERIFY_USAGE,\n \t\t\"git refs list \" COMMON_USAGE_FOR_EACH_REF,\n+\t\tREFS_OPTIMIZE_USAGE,\n \t\tNULL,\n \t};\n \tparse_opt_subcommand_fn *fn = NULL;\n@@ -129,6 +144,7 @@ int cmd_refs(int argc,\n \t\tOPT_SUBCOMMAND(\"migrate\", &fn, cmd_refs_migrate),\n \t\tOPT_SUBCOMMAND(\"verify\", &fn, cmd_refs_verify),\n \t\tOPT_SUBCOMMAND(\"list\", &fn, cmd_refs_list),\n+\t\tOPT_SUBCOMMAND(\"optimize\", &fn, cmd_refs_optimize),\n \t\tOPT_END(),\n \t};\n \n-- \n2.34.1\n\n"},{"id":"525689","messageId":"20250906075147.1076656-5-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v2 4/5] t0601: refactor tests to be shareable","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:46Z","receivedAt":"2025-09-06T07:52:13Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding tests for the new `git refs optimize` command,\nrefactor the existing t0601 test suite to make its logic shareable.\n\nMove the core test logic from `t0601-reffiles-pack-refs.sh` into a new\n`pack-refs-tests.sh` file. Inside this new script, replace hardcoded\ncalls to \"pack-refs\" with the `$pack_refs` variable.\n\nThe original `t0601-reffiles-pack-refs.sh` script now becomes a simple\n\"driver\". It is responsible for setting the default value of the\nvariable and then sourcing the test library.\n\nThis new structure follows the established pattern used for sharing\ntests between `git-for-each-ref` and `git-refs list` and prepares the test suite\nfor the `refs optimize` tests to be added in a subsequent commit.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/pack-refs-tests.sh          | 431 ++++++++++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh | 430 +--------------------------------\n 2 files changed, 432 insertions(+), 429 deletions(-)\n create mode 100644 t/pack-refs-tests.sh\n\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nnew file mode 100644\nindex 0000000000..3dbcc01718\n--- /dev/null\n+++ b/t/pack-refs-tests.sh\n@@ -0,0 +1,431 @@\n+pack_refs=${pack_refs:-pack-refs}\n+\n+test_expect_success 'enable reflogs' '\n+\tgit config core.logallrefupdates true\n+'\n+\n+test_expect_success 'prepare a trivial repository' '\n+\techo Hello > A &&\n+\tgit update-index --add A &&\n+\tgit commit -m \"Initial commit.\" &&\n+\tHEAD=$(git rev-parse --verify HEAD)\n+'\n+\n+test_expect_success '${pack_refs} --prune --all' '\n+\ttest_path_is_missing .git/packed-refs &&\n+\tgit ${pack_refs} --no-prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f | wc -l) &&\n+\ttest \"$N\" != 0 &&\n+\n+\tgit ${pack_refs} --prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f) &&\n+\ttest -z \"$N\"\n+'\n+\n+SHA1=\n+\n+test_expect_success 'see if git show-ref works as expected' '\n+\tgit branch a &&\n+\tSHA1=$(cat .git/refs/heads/a) &&\n+\techo \"$SHA1 refs/heads/a\" >expect &&\n+\tgit show-ref a >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if a branch still exists when packed' '\n+\tgit branch b &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/b &&\n+\techo \"$SHA1 refs/heads/b\" >expect &&\n+\tgit show-ref b >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'git branch c/d should barf if branch c exists' '\n+\tgit branch c &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/c &&\n+\ttest_must_fail git branch c/d\n+'\n+\n+test_expect_success 'see if a branch still exists after git ${pack_refs} --prune' '\n+\tgit branch e &&\n+\tgit ${pack_refs} --all --prune &&\n+\techo \"$SHA1 refs/heads/e\" >expect &&\n+\tgit show-ref e >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune remove ref files' '\n+\tgit branch f &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -f .git/refs/heads/f\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune removes empty dirs' '\n+\tgit branch r/s/t &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -e .git/refs/heads/r\n+'\n+\n+test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n+\tgit branch g/h &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d g/h &&\n+\tgit branch g &&\n+\tgit ${pack_refs} --all &&\n+\tgit branch -d g\n+'\n+\n+test_expect_success 'git branch i/j/k should barf if branch i exists' '\n+\tgit branch i &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_must_fail git branch i/j/k\n+'\n+\n+test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n+\tgit branch k/l &&\n+\tgit branch k/lm &&\n+\tgit branch -d k/l &&\n+\tgit branch k/l/m &&\n+\tgit branch -d k/l/m &&\n+\tgit branch -d k/lm &&\n+\tgit branch k\n+'\n+\n+test_expect_success 'test git branch n after some branch deletion and pruning' '\n+\tgit branch n/o &&\n+\tgit branch n/op &&\n+\tgit branch -d n/o &&\n+\tgit branch n/o/p &&\n+\tgit branch -d n/op &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d n/o/p &&\n+\tgit branch n\n+'\n+\n+test_expect_success 'test excluded refs are not packed' '\n+\tgit branch dont_pack1 &&\n+\tgit branch dont_pack2 &&\n+\tgit branch pack_this &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack1 &&\n+\ttest -f .git/refs/heads/dont_pack2 &&\n+\t! test -f .git/refs/heads/pack_this'\n+\n+test_expect_success 'test --no-exclude refs clears excluded refs' '\n+\tgit branch dont_pack3 &&\n+\tgit branch dont_pack4 &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n+\t! test -f .git/refs/heads/dont_pack3 &&\n+\t! test -f .git/refs/heads/dont_pack4'\n+\n+test_expect_success 'test only included refs are packed' '\n+\tgit branch pack_this1 &&\n+\tgit branch pack_this2 &&\n+\tgit tag dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack_this*\" &&\n+\ttest -f .git/refs/tags/dont_pack5 &&\n+\t! test -f .git/refs/heads/pack_this1 &&\n+\t! test -f .git/refs/heads/pack_this2'\n+\n+test_expect_success 'test --no-include refs clears included refs' '\n+\tgit branch pack1 &&\n+\tgit branch pack2 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --no-include &&\n+\ttest -f .git/refs/heads/pack1 &&\n+\ttest -f .git/refs/heads/pack2'\n+\n+test_expect_success 'test --exclude takes precedence over --include' '\n+\tgit branch dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack5'\n+\n+test_expect_success 'see if up-to-date packed refs are preserved' '\n+\tgit branch q &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit update-ref refs/heads/q refs/heads/q &&\n+\t! test -f .git/refs/heads/q\n+'\n+\n+test_expect_success 'pack, prune and repack' '\n+\tgit tag foo &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit show-ref >all-of-them &&\n+\tgit ${pack_refs} &&\n+\tgit show-ref >again &&\n+\ttest_cmp all-of-them again\n+'\n+\n+test_expect_success 'explicit ${pack_refs} with dangling packed reference' '\n+\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit ${pack_refs} --all 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref with dangling packed version' '\n+\tgit checkout -b lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout main &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref while another dangling packed ref' '\n+\tgit branch lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'pack ref directly below refs/' '\n+\tgit update-ref refs/top HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgrep refs/top .git/packed-refs &&\n+\ttest_path_is_missing .git/refs/top\n+'\n+\n+test_expect_success 'do not pack ref in refs/bisect' '\n+\tgit update-ref refs/bisect/local HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n+\ttest_path_is_file .git/refs/bisect/local\n+'\n+\n+test_expect_success 'disable reflogs' '\n+\tgit config core.logallrefupdates false &&\n+\trm -rf .git/logs\n+'\n+\n+test_expect_success 'create packed foo/bar/baz branch' '\n+\tgit branch foo/bar/baz &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n+\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n+'\n+\n+test_expect_success 'notice d/f conflict with existing directory' '\n+\ttest_must_fail git branch foo &&\n+\ttest_must_fail git branch foo/bar\n+'\n+\n+test_expect_success 'existing directory reports concrete ref' '\n+\ttest_must_fail git branch foo 2>stderr &&\n+\ttest_grep refs/heads/foo/bar/baz stderr\n+'\n+\n+test_expect_success 'notice d/f conflict with existing ref' '\n+\ttest_must_fail git branch foo/bar/baz/extra &&\n+\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n+'\n+\n+test_expect_success 'reject packed-refs with unterminated line' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n+\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs containing junk' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n+\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs with a short SHA-1' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n+\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'timeout if packed-refs.lock exists' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"rm -f $LOCK\" &&\n+\ttest_must_fail git ${pack_refs} --all --prune\n+'\n+\n+test_expect_success 'retry acquiring packed-refs.lock' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"wait && rm -f $LOCK\" &&\n+\t{\n+\t\t( sleep 1 && rm -f $LOCK ) &\n+\t} &&\n+\tgit -c core.packedrefstimeout=3000 ${pack_refs} --all --prune\n+'\n+\n+test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n+\t# First make sure that symlinking works when reading:\n+\tgit update-ref refs/heads/lossy refs/heads/main &&\n+\tgit for-each-ref >all-refs-before &&\n+\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n+\tln -s my-deviant-packed-refs .git/packed-refs &&\n+\tgit for-each-ref >all-refs-linked &&\n+\ttest_cmp all-refs-before all-refs-linked &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit for-each-ref >all-refs-packed &&\n+\ttest_cmp all-refs-before all-refs-packed &&\n+\ttest -h .git/packed-refs &&\n+\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n+'\n+\n+# The 'packed-refs' file is stored directly in .git/. This means it is global\n+# to the repository, and can only contain refs that are shared across all\n+# worktrees.\n+test_expect_success 'refs/worktree must not be packed' '\n+\ttest_commit initial &&\n+\ttest_commit wt1 &&\n+\ttest_commit wt2 &&\n+\tgit worktree add wt1 wt1 &&\n+\tgit worktree add wt2 wt2 &&\n+\tgit checkout initial &&\n+\tgit update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n+\tgit ${pack_refs} --all &&\n+\ttest_path_is_missing .git/refs/tags/wt1 &&\n+\ttest_path_is_file .git/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n+'\n+\n+# we do not want to count on running ${pack_refs} to\n+# actually pack it, as it is perfectly reasonable to\n+# skip processing a broken ref\n+test_expect_success 'create packed-refs file with broken ref' '\n+\ttest_tick && git commit --allow-empty -m one &&\n+\trecoverable=$(git rev-parse HEAD) &&\n+\ttest_tick && git commit --allow-empty -m two &&\n+\tmissing=$(git rev-parse HEAD) &&\n+\trm -f .git/refs/heads/main &&\n+\tcat >.git/packed-refs <<-EOF &&\n+\t$missing refs/heads/main\n+\t$recoverable refs/heads/other\n+\tEOF\n+\techo $missing >expect &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not silently delete broken packed ref' '\n+\tgit ${pack_refs} --all --prune &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not drop broken refs during deletion' '\n+\tgit update-ref -d refs/heads/other &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+for command in \"git ${pack_refs} --all --auto\" \"git maintenance run --task=${pack_refs} --auto\"\n+do\n+\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 14 additional references, which brings us to\n+\t\t\t# 15 together with the default branch.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\n+\t\t\t# Create the 16th reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_file .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create 15 loose references.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create the 16th loose reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 18 .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command scales with size of packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 99 packed refs. This should cause the heuristic\n+\t\t\t# to require more than the minimum amount of loose refs.\n+\t\t\ttest_seq 99 |\n+\t\t\twhile read i\n+\t\t\tdo\n+\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n+\t\t\tdone >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create another handful of refs to cross the border.\n+\t\t\t# Note that we explicitly do not check for strict\n+\t\t\t# boundaries here, as this also depends on the size of\n+\t\t\t# the object hash.\n+\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 135 .git/packed-refs\n+\t\t)\n+\t'\n+done\n+\n+test_done\ndiff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh\nindex aa7f6ecd81..12cf5d1dcb 100755\n--- a/t/t0601-reffiles-pack-refs.sh\n+++ b/t/t0601-reffiles-pack-refs.sh\n@@ -17,432 +17,4 @@ export GIT_TEST_DEFAULT_REF_FORMAT\n \n . ./test-lib.sh\n \n-test_expect_success 'enable reflogs' '\n-\tgit config core.logallrefupdates true\n-'\n-\n-test_expect_success 'prepare a trivial repository' '\n-\techo Hello > A &&\n-\tgit update-index --add A &&\n-\tgit commit -m \"Initial commit.\" &&\n-\tHEAD=$(git rev-parse --verify HEAD)\n-'\n-\n-test_expect_success 'pack-refs --prune --all' '\n-\ttest_path_is_missing .git/packed-refs &&\n-\tgit pack-refs --no-prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f | wc -l) &&\n-\ttest \"$N\" != 0 &&\n-\n-\tgit pack-refs --prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f) &&\n-\ttest -z \"$N\"\n-'\n-\n-SHA1=\n-\n-test_expect_success 'see if git show-ref works as expected' '\n-\tgit branch a &&\n-\tSHA1=$(cat .git/refs/heads/a) &&\n-\techo \"$SHA1 refs/heads/a\" >expect &&\n-\tgit show-ref a >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if a branch still exists when packed' '\n-\tgit branch b &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/b &&\n-\techo \"$SHA1 refs/heads/b\" >expect &&\n-\tgit show-ref b >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'git branch c/d should barf if branch c exists' '\n-\tgit branch c &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/c &&\n-\ttest_must_fail git branch c/d\n-'\n-\n-test_expect_success 'see if a branch still exists after git pack-refs --prune' '\n-\tgit branch e &&\n-\tgit pack-refs --all --prune &&\n-\techo \"$SHA1 refs/heads/e\" >expect &&\n-\tgit show-ref e >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if git pack-refs --prune remove ref files' '\n-\tgit branch f &&\n-\tgit pack-refs --all --prune &&\n-\t! test -f .git/refs/heads/f\n-'\n-\n-test_expect_success 'see if git pack-refs --prune removes empty dirs' '\n-\tgit branch r/s/t &&\n-\tgit pack-refs --all --prune &&\n-\t! test -e .git/refs/heads/r\n-'\n-\n-test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n-\tgit branch g/h &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d g/h &&\n-\tgit branch g &&\n-\tgit pack-refs --all &&\n-\tgit branch -d g\n-'\n-\n-test_expect_success 'git branch i/j/k should barf if branch i exists' '\n-\tgit branch i &&\n-\tgit pack-refs --all --prune &&\n-\ttest_must_fail git branch i/j/k\n-'\n-\n-test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n-\tgit branch k/l &&\n-\tgit branch k/lm &&\n-\tgit branch -d k/l &&\n-\tgit branch k/l/m &&\n-\tgit branch -d k/l/m &&\n-\tgit branch -d k/lm &&\n-\tgit branch k\n-'\n-\n-test_expect_success 'test git branch n after some branch deletion and pruning' '\n-\tgit branch n/o &&\n-\tgit branch n/op &&\n-\tgit branch -d n/o &&\n-\tgit branch n/o/p &&\n-\tgit branch -d n/op &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d n/o/p &&\n-\tgit branch n\n-'\n-\n-test_expect_success 'test excluded refs are not packed' '\n-\tgit branch dont_pack1 &&\n-\tgit branch dont_pack2 &&\n-\tgit branch pack_this &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack1 &&\n-\ttest -f .git/refs/heads/dont_pack2 &&\n-\t! test -f .git/refs/heads/pack_this'\n-\n-test_expect_success 'test --no-exclude refs clears excluded refs' '\n-\tgit branch dont_pack3 &&\n-\tgit branch dont_pack4 &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n-\t! test -f .git/refs/heads/dont_pack3 &&\n-\t! test -f .git/refs/heads/dont_pack4'\n-\n-test_expect_success 'test only included refs are packed' '\n-\tgit branch pack_this1 &&\n-\tgit branch pack_this2 &&\n-\tgit tag dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack_this*\" &&\n-\ttest -f .git/refs/tags/dont_pack5 &&\n-\t! test -f .git/refs/heads/pack_this1 &&\n-\t! test -f .git/refs/heads/pack_this2'\n-\n-test_expect_success 'test --no-include refs clears included refs' '\n-\tgit branch pack1 &&\n-\tgit branch pack2 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --no-include &&\n-\ttest -f .git/refs/heads/pack1 &&\n-\ttest -f .git/refs/heads/pack2'\n-\n-test_expect_success 'test --exclude takes precedence over --include' '\n-\tgit branch dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack5'\n-\n-test_expect_success 'see if up-to-date packed refs are preserved' '\n-\tgit branch q &&\n-\tgit pack-refs --all --prune &&\n-\tgit update-ref refs/heads/q refs/heads/q &&\n-\t! test -f .git/refs/heads/q\n-'\n-\n-test_expect_success 'pack, prune and repack' '\n-\tgit tag foo &&\n-\tgit pack-refs --all --prune &&\n-\tgit show-ref >all-of-them &&\n-\tgit pack-refs &&\n-\tgit show-ref >again &&\n-\ttest_cmp all-of-them again\n-'\n-\n-test_expect_success 'explicit pack-refs with dangling packed reference' '\n-\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit pack-refs --all 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref with dangling packed version' '\n-\tgit checkout -b lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit checkout main &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref while another dangling packed ref' '\n-\tgit branch lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'pack ref directly below refs/' '\n-\tgit update-ref refs/top HEAD &&\n-\tgit pack-refs --all --prune &&\n-\tgrep refs/top .git/packed-refs &&\n-\ttest_path_is_missing .git/refs/top\n-'\n-\n-test_expect_success 'do not pack ref in refs/bisect' '\n-\tgit update-ref refs/bisect/local HEAD &&\n-\tgit pack-refs --all --prune &&\n-\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n-\ttest_path_is_file .git/refs/bisect/local\n-'\n-\n-test_expect_success 'disable reflogs' '\n-\tgit config core.logallrefupdates false &&\n-\trm -rf .git/logs\n-'\n-\n-test_expect_success 'create packed foo/bar/baz branch' '\n-\tgit branch foo/bar/baz &&\n-\tgit pack-refs --all --prune &&\n-\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n-\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n-'\n-\n-test_expect_success 'notice d/f conflict with existing directory' '\n-\ttest_must_fail git branch foo &&\n-\ttest_must_fail git branch foo/bar\n-'\n-\n-test_expect_success 'existing directory reports concrete ref' '\n-\ttest_must_fail git branch foo 2>stderr &&\n-\ttest_grep refs/heads/foo/bar/baz stderr\n-'\n-\n-test_expect_success 'notice d/f conflict with existing ref' '\n-\ttest_must_fail git branch foo/bar/baz/extra &&\n-\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n-'\n-\n-test_expect_success 'reject packed-refs with unterminated line' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n-\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs containing junk' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n-\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs with a short SHA-1' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n-\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'timeout if packed-refs.lock exists' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"rm -f $LOCK\" &&\n-\ttest_must_fail git pack-refs --all --prune\n-'\n-\n-test_expect_success 'retry acquiring packed-refs.lock' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"wait && rm -f $LOCK\" &&\n-\t{\n-\t\t( sleep 1 && rm -f $LOCK ) &\n-\t} &&\n-\tgit -c core.packedrefstimeout=3000 pack-refs --all --prune\n-'\n-\n-test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n-\t# First make sure that symlinking works when reading:\n-\tgit update-ref refs/heads/lossy refs/heads/main &&\n-\tgit for-each-ref >all-refs-before &&\n-\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n-\tln -s my-deviant-packed-refs .git/packed-refs &&\n-\tgit for-each-ref >all-refs-linked &&\n-\ttest_cmp all-refs-before all-refs-linked &&\n-\tgit pack-refs --all --prune &&\n-\tgit for-each-ref >all-refs-packed &&\n-\ttest_cmp all-refs-before all-refs-packed &&\n-\ttest -h .git/packed-refs &&\n-\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n-'\n-\n-# The 'packed-refs' file is stored directly in .git/. This means it is global\n-# to the repository, and can only contain refs that are shared across all\n-# worktrees.\n-test_expect_success 'refs/worktree must not be packed' '\n-\ttest_commit initial &&\n-\ttest_commit wt1 &&\n-\ttest_commit wt2 &&\n-\tgit worktree add wt1 wt1 &&\n-\tgit worktree add wt2 wt2 &&\n-\tgit checkout initial &&\n-\tgit update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n-\tgit pack-refs --all &&\n-\ttest_path_is_missing .git/refs/tags/wt1 &&\n-\ttest_path_is_file .git/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n-'\n-\n-# we do not want to count on running pack-refs to\n-# actually pack it, as it is perfectly reasonable to\n-# skip processing a broken ref\n-test_expect_success 'create packed-refs file with broken ref' '\n-\ttest_tick && git commit --allow-empty -m one &&\n-\trecoverable=$(git rev-parse HEAD) &&\n-\ttest_tick && git commit --allow-empty -m two &&\n-\tmissing=$(git rev-parse HEAD) &&\n-\trm -f .git/refs/heads/main &&\n-\tcat >.git/packed-refs <<-EOF &&\n-\t$missing refs/heads/main\n-\t$recoverable refs/heads/other\n-\tEOF\n-\techo $missing >expect &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not silently delete broken packed ref' '\n-\tgit pack-refs --all --prune &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not drop broken refs during deletion' '\n-\tgit update-ref -d refs/heads/other &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-for command in \"git pack-refs --all --auto\" \"git maintenance run --task=pack-refs --auto\"\n-do\n-\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 14 additional references, which brings us to\n-\t\t\t# 15 together with the default branch.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\n-\t\t\t# Create the 16th reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_file .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create 15 loose references.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create the 16th loose reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 18 .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command scales with size of packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 99 packed refs. This should cause the heuristic\n-\t\t\t# to require more than the minimum amount of loose refs.\n-\t\t\ttest_seq 99 |\n-\t\t\twhile read i\n-\t\t\tdo\n-\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n-\t\t\tdone >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create another handful of refs to cross the border.\n-\t\t\t# Note that we explicitly do not check for strict\n-\t\t\t# boundaries here, as this also depends on the size of\n-\t\t\t# the object hash.\n-\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 135 .git/packed-refs\n-\t\t)\n-\t'\n-done\n-\n-test_done\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"525690","messageId":"20250906075147.1076656-6-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v2 5/5] t: add test for git refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-06T07:51:47Z","receivedAt":"2025-09-06T07:52:14Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Add a test script, `t/t1463-refs-optimize.sh`, for the new `git refs\noptimize` command.\n\nThis script acts as a simple driver, leveraging the shared test library\ncreated in the preceding commit. It works by overriding the\n`$pack_refs` variable to \"refs optimize\" and then sourcing the\nshared library (`t/pack-refs-tests.sh`).\n\nThis approach ensures that `git refs optimize` is tested against the\nentire comprehensive test suite of `git pack-refs`, verifying\nthat it acts as a compatible drop-in replacement.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/meson.build            |  3 ++-\n t/t1463-refs-optimize.sh | 17 +++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n create mode 100755 t/t1463-refs-optimize.sh\n\ndiff --git a/t/meson.build b/t/meson.build\nindex daf01fb5d0..48f83e12a7 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -210,6 +210,7 @@ integration_tests = [\n   't1451-fsck-buffer.sh',\n   't1460-refs-migrate.sh',\n   't1461-refs-list.sh',\n+  't1463-refs-optimize.sh',\n   't1500-rev-parse.sh',\n   't1501-work-tree.sh',\n   't1502-rev-parse-parseopt.sh',\n@@ -1216,4 +1217,4 @@ if perl.found() and time.found()\n       timeout: 0,\n     )\n   endforeach\n-endif\n\\ No newline at end of file\n+endif\ndiff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh\nnew file mode 100755\nindex 0000000000..c11c905d79\n--- /dev/null\n+++ b/t/t1463-refs-optimize.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+\n+test_description='git refs optimize should not change the branch semantic\n+\n+This test runs git refs optimize and git show-ref and checks that the branch\n+semantic is still the same.\n+'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+GIT_TEST_DEFAULT_REF_FORMAT=files\n+export GIT_TEST_DEFAULT_REF_FORMAT\n+\n+. ./test-lib.sh\n+\n+pack_refs='refs optimize'\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"525812","messageId":"xmqq348xyr5b.fsf@gitster.g","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v2 0/5] Add refs optimize subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-08T14:07:12Z","receivedAt":"2025-09-08T14:07:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Meet Soni <meetsoni3017@gmail.com> writes:\n\n> This series introduces `git refs optimize` as a modern replacement for\n> `git pack-refs`, continuing the effort to consolidate commands\n> under the `git refs` namespace.\n\nSorry, but I do not quite see the point of this change.  Is it your\ngoal to eventually remove \"git pack-refs\"?\n\nI would very much understand if this were:\n\n    \"git pack-refs\" is a command that is very specific to the files\n    backend to optimize the way refs are stored in that backend.  It\n    does not do anything to other backends.\n\n    Introduce \"git refs optimize\" as the end-user facing front-end\n    so that later different backends, including reftable backend,\n    can define their own way to optimize the way refs are stored in\n    them.  As the first step, switch on what backend is in use in\n    the repository, and invoke \"git pack-refs\" if the repository\n    uses the files backend.\n\nAnd when framed this way, I am not sure it is a good direction\nforward to have pack-refs.[ch] top-level files.  I have to wonder if\nthe approach should be more along this line?\n\n - Define the \"optimize\" action in the refs API.  What it really\n   means to \"optimize\" may differ from backend to backend.  There\n   may be refs_optimize(struct ref_store *refs) API entry point.\n\n - Add the new action to the vtable for refs backends.  There may be\n   no action defined for reftable backend for now, or you may find\n   there already are reftable specific optimizations you want to\n   trigger from there.\n\n - Figure out how this interacts with existing refs_pack_refs();\n   most likely it as the backend specific option, should go away,\n   and its implementation would move to the \"optimize\" action driven\n   from the vtable for files backend.\n\nOnce it is done, you do not necessarily need \"git refs optimize\",\nbut the \"git pack-refs\" could be the front-end to trigger the more\ngeneric \"optimize\" action.  In other words, in a repository whose\nrefs are stored in reftable, \"git pack-refs\" would cease to be a\nno-op but can perform optimizations suitable in that repository.\n\nThat way, users do not need to learn a new command, which may be\nalso an advantage over what is being proposed here.\n\nThanks.\n"},{"id":"525854","messageId":"xmqqfrcwyjzo.fsf@gitster.g","threadId":"64099","inReplyTo":"xmqq348xyr5b.fsf@gitster.g","subject":"Re: [GSoC][PATCH v2 0/5] Add refs optimize subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-08T16:41:47Z","receivedAt":"2025-09-08T16:41:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> And when framed this way, I am not sure it is a good direction\n> forward to have pack-refs.[ch] top-level files.  I have to wonder if\n> the approach should be more along this line?\n>\n>  - Define the \"optimize\" action in the refs API.  What it really\n>    means to \"optimize\" may differ from backend to backend.  There\n>    may be refs_optimize(struct ref_store *refs) API entry point.\n>\n>  - Add the new action to the vtable for refs backends.  There may be\n>    no action defined for reftable backend for now, or you may find\n>    there already are reftable specific optimizations you want to\n>    trigger from there.\n>\n>  - Figure out how this interacts with existing refs_pack_refs();\n>    most likely it as the backend specific option, should go away,\n>    and its implementation would move to the \"optimize\" action driven\n>    from the vtable for files backend.\n>\n> Once it is done, you do not necessarily need \"git refs optimize\",\n> but the \"git pack-refs\" could be the front-end to trigger the more\n> generic \"optimize\" action.  In other words, in a repository whose\n> refs are stored in reftable, \"git pack-refs\" would cease to be a\n> no-op but can perform optimizations suitable in that repository.\n>\n> That way, users do not need to learn a new command, which may be\n> also an advantage over what is being proposed here.\n\nI do not have strong opinions between keeping \"git pack-refs\" and\nadding \"git refs optimize\"; in other words, the UI exposed to\nend-user is secondary, so if people prefer the latter, that is fine\nby me.\n\nI do care about how the code is structured, though.\n\nThanks.\n"},{"id":"525860","messageId":"xmqqbjnkyjvd.fsf@gitster.g","threadId":"64099","inReplyTo":"20250906075147.1076656-3-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v2 2/5] doc: factor out common option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-08T16:44:22Z","receivedAt":"2025-09-08T16:44:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Meet Soni <meetsoni3017@gmail.com> writes:\n\n> Subject: Re: [GSoC][PATCH v2 2/5] doc: factor out common option\n\nBe careful to make sure that your title is sufficiently clear.  I do\nnot think the above is sufficiently clear to let readers tell what\ndoc was updated to factor out options that are common across which\nsubsystem.\n"},{"id":"526657","messageId":"20250918054704.544254-1-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250906075147.1076656-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 0/9] Add refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:46:55Z","receivedAt":"2025-09-18T05:47:11Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Hi everyone,\n\nThis series introduces `git refs optimize` as a modern replacement for\n`git pack-refs`, continuing the effort to consolidate commands\nunder the `git refs` namespace.\n\nChanges in v3:\n- Reworked the entire series from scratch to implement the\n  architectural feedback provided by Junio in the v2 review.\n- Instead of a simple UI alias, this version introduces a generic,\n  backend-agnostic `refs_optimize()` API.\n- Added implementations for both the 'files' backend (packing) and\n  the 'reftable' backend (compaction).\n\n\nMeet Soni (9):\n  refs: add a generic 'optimize' API\n  files-backend: implement 'optimize' action\n  reftable-backend: implement 'optimize' action\n  builtin/pack-refs: convert to use the generic refs_optimize() API\n  builtin/pack-refs: factor out core logic into a shared library\n  doc: pack-refs: factor out common options\n  builtin/refs: add optimize subcommand\n  t0601: refactor tests to be shareable\n  t: add test for git refs optimize subcommand\n\n Documentation/git-pack-refs.adoc     |  53 +---\n Documentation/git-refs.adoc          |  10 +\n Documentation/pack-refs-options.adoc |  52 ++++\n Makefile                             |   1 +\n builtin/pack-refs.c                  |  54 +---\n builtin/refs.c                       |  17 ++\n meson.build                          |   1 +\n pack-refs.c                          |  56 ++++\n pack-refs.h                          |  23 ++\n refs.c                               |   7 +\n refs.h                               |   6 +\n refs/files-backend.c                 |  10 +\n refs/refs-internal.h                 |   3 +\n refs/reftable-backend.c              |   7 +\n t/meson.build                        |   3 +-\n t/pack-refs-tests.sh                 | 431 +++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh        | 430 +-------------------------\n t/t1463-refs-optimize.sh             |  17 ++\n 18 files changed, 650 insertions(+), 531 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n create mode 100644 t/pack-refs-tests.sh\n create mode 100755 t/t1463-refs-optimize.sh\n\nRange-diff against v2:\n -:  ---------- >  1:  a837ae6f5d refs: add a generic 'optimize' API\n -:  ---------- >  2:  e0613b14b6 files-backend: implement 'optimize' action\n -:  ---------- >  3:  ae8d2d29d9 reftable-backend: implement 'optimize' action\n -:  ---------- >  4:  a8cba8a355 builtin/pack-refs: convert to use the generic refs_optimize() API\n 1:  67a9187b59 !  5:  ec1085ccd8 builtin/pack-refs: factor out core logic into a shared library\n    @@ builtin/pack-refs.c\n     -\tif (!pack_refs_opts.includes->nr)\n     -\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n     -\n    --\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n    --\n    +-\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n    + \n     -\tclear_ref_exclusions(&excludes);\n     -\tstring_list_clear(&included_refs, 0);\n     -\tstring_list_clear(&option_excluded_refs, 0);\n    @@ pack-refs.c (new)\n     +\tif (!pack_refs_opts.includes->nr)\n     +\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n     +\n    -+\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n    ++\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n     +\n     +\tclear_ref_exclusions(&excludes);\n     +\tstring_list_clear(&included_refs, 0);\n 2:  422eaa0c8b !  6:  e1758816bf doc: factor out common option\n    @@ Metadata\n     Author: Meet Soni <meetsoni3017@gmail.com>\n     \n      ## Commit message ##\n    -    doc: factor out common option\n    +    doc: pack-refs: factor out common options\n     \n         In preparation for adding documentation for `git refs optimize`, factor\n         out the common options from the `git-pack-refs` man page into a\n         shareable file `pack-refs-options.adoc` and update `git-pack-refs.adoc`\n         to use an `include::` macro.\n     \n    -    This change is a pure refactoring and results in no change to the\n    -    final rendered documentation for `pack-refs`.\n    +    This change is a pure refactoring and results in no change to the final\n    +    rendered documentation for `pack-refs`.\n     \n         Mentored-by: Patrick Steinhardt <ps@pks.im>\n         Mentored-by: shejialuo <shejialuo@gmail.com>\n    @@ Documentation/git-pack-refs.adoc: unpacked.\n     -+\n     -When used with `--include`, refs provided to `--include`, minus refs that are\n     -provided to `--exclude` will be packed.\n    --\n     +include::pack-refs-options.adoc[]\n      \n    + \n      BUGS\n    - ----\n     \n      ## Documentation/pack-refs-options.adoc (new) ##\n     @@\n 3:  067a2baa97 !  7:  e3a908fe72 builtin/refs: add optimize subcommand\n    @@ Commit message\n     \n         As part of the ongoing effort to consolidate reference handling,\n         introduce a new `optimize` subcommand. This command provides the same\n    -    functionality and exit-code behavior as `git pack-refs`, serving\n    -    as its modern replacement.\n    +    functionality and exit-code behavior as `git pack-refs`, serving as its\n    +    modern replacement.\n     \n         Implement `cmd_refs_optimize` by having it call the `pack_refs_core()`\n         helper function. This helper was factored out of the original\n    -    `cmd_pack_refs` in a preceding commit, allowing both commands to\n    -    share the same core logic as independent peers.\n    +    `cmd_pack_refs` in a preceding commit, allowing both commands to share\n    +    the same core logic as independent peers.\n     \n         Add documentation for the new command. The man page leverages the shared\n         options file, created in a previous commit, by using the AsciiDoc\n    @@ Documentation/git-refs.adoc\n     @@ Documentation/git-refs.adoc: git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n      \t\t   [--contains[=<object>]] [--no-contains[=<object>]]\n      \t\t   [(--exclude=<pattern>)...] [--start-after=<marker>]\n    - \t\t   [ --stdin | <pattern>... ]\n    + \t\t   [ --stdin | (<pattern>...)]\n     +git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\n      \n      DESCRIPTION\n    @@ builtin/refs.c: static int cmd_refs_list(int argc, const char **argv, const char\n     +\t\tREFS_OPTIMIZE_USAGE,\n     +\t\tNULL\n     +\t};\n    ++\n     +\treturn pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);\n     +}\n     +\n 4:  640ebd9a4f !  8:  4f63632ac2 t0601: refactor tests to be shareable\n    @@ Commit message\n         variable and then sourcing the test library.\n     \n         This new structure follows the established pattern used for sharing\n    -    tests between `git-for-each-ref` and `git-refs list` and prepares the test suite\n    -    for the `refs optimize` tests to be added in a subsequent commit.\n    +    tests between `git-for-each-ref` and `git-refs list` and prepares the\n    +    test suite for the `refs optimize` tests to be added in a subsequent\n    +    commit.\n     \n         Mentored-by: Patrick Steinhardt <ps@pks.im>\n         Mentored-by: shejialuo <shejialuo@gmail.com>\n 5:  37ca586bb9 =  9:  39eed2831a t: add test for git refs optimize subcommand\n\nbase-commit: f814da676ae46aac5be0a98b99373a76dee6cedb\n-- \n2.34.1\n\n"},{"id":"526658","messageId":"20250918054704.544254-2-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 1/9] refs: add a generic 'optimize' API","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:46:56Z","receivedAt":"2025-09-18T05:47:14Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Add a new generic refs_optimize() API function that dispatches to a\nbackend-specific implementation via a new 'optimize' vtable method.\n\nThis lays the architectural groundwork for different reference backends\n(like 'files' and 'reftable') to provide their own storage optimization\nlogic, which will be called from a single, generic entry point.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs.c               | 7 +++++++\n refs.h               | 6 ++++++\n refs/refs-internal.h | 3 +++\n 3 files changed, 16 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 4ff55cf24f..2ea6fd2218 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2282,6 +2282,13 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->pack_refs(refs, opts);\n }\n \n+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n+{\n+\tif (!refs->be->optimize)\n+\t\treturn 0;\n+\treturn refs->be->optimize(refs, opts);\n+}\n+\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\ndiff --git a/refs.h b/refs.h\nindex f29e486e33..d28c4ef0af 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -480,6 +480,12 @@ struct pack_refs_opts {\n  */\n int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts);\n \n+/*\n+ * Optimize the ref store. The exact behavior is up to the backend.\n+ * For the files backend, this is equivalent to packing refs.\n+ */\n+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts);\n+\n /*\n  * Setup reflog before using. Fill in err and return -1 on failure.\n  */\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 54c2079c12..4ef3bd75c6 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -447,6 +447,8 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n+typedef int optimize_fn(struct ref_store *ref_store,\n+\t\t\tstruct pack_refs_opts *opts);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -572,6 +574,7 @@ struct ref_storage_be {\n \tref_transaction_abort_fn *transaction_abort;\n \n \tpack_refs_fn *pack_refs;\n+\toptimize_fn *optimize;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \n-- \n2.34.1\n\n"},{"id":"526659","messageId":"20250918054704.544254-3-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 2/9] files-backend: implement 'optimize' action","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:46:57Z","receivedAt":"2025-09-18T05:47:17Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"With the generic `refs_optimize()` API now in place, provide the first\nimplementation for the 'files' reference backend. This makes the new API\nfunctional for existing repositories and serves as the foundation for\nmigrating user-facing commands to the new architecture.\n\nThe implementation simply calls the existing `files_pack_refs()`\nfunction, as 'packing' is the method used to optimize the files-based\nreference store.\n\nWire up the new `files_optimize()` function to the `optimize` slot in\nthe files backend's virtual table.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs/files-backend.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex dfc8e9bc50..1428d3a6f1 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1473,6 +1473,15 @@ static int files_pack_refs(struct ref_store *ref_store,\n \treturn 0;\n }\n \n+static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts)\n+{\n+\t/*\n+\t * For the \"files\" backend, \"optimizing\" is the same as \"packing\".\n+\t * So, we just call the existing worker function for packing.\n+\t */\n+\treturn files_pack_refs(ref_store, opts);\n+}\n+\n /*\n  * People using contrib's git-new-workdir have .git/logs/refs ->\n  * /some/other/path/.git/logs/refs, and that may live on another device.\n@@ -3909,6 +3918,7 @@ struct ref_storage_be refs_be_files = {\n \t.transaction_abort = files_transaction_abort,\n \n \t.pack_refs = files_pack_refs,\n+\t.optimize = files_optimize,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \n-- \n2.34.1\n\n"},{"id":"526660","messageId":"20250918054704.544254-4-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 3/9] reftable-backend: implement 'optimize' action","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:46:58Z","receivedAt":"2025-09-18T05:47:20Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"To make the new generic `optimize` API fully functional, provide an\nimplementation for the 'reftable' reference backend.\n\nFor the reftable backend, the 'optimize' action is to compact its\ntables. The existing `reftable_be_pack_refs()` function already provides\nthis logic, so the new `reftable_be_optimize()` function simply calls\nit.\n\nWire up the new function to the `optimize` slot in the reftable\nbackend's virtual table.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs/reftable-backend.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 570463da41..5dff1e08e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1721,6 +1721,12 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,\n \treturn ret;\n }\n \n+static int reftable_be_optimize(struct ref_store *ref_store,\n+\t\t\t\tstruct pack_refs_opts *opts)\n+{\n+\treturn reftable_be_pack_refs(ref_store, opts);\n+}\n+\n struct write_create_symref_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2702,6 +2708,7 @@ struct ref_storage_be refs_be_reftable = {\n \t.transaction_abort = reftable_be_transaction_abort,\n \n \t.pack_refs = reftable_be_pack_refs,\n+\t.optimize = reftable_be_optimize,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.34.1\n\n"},{"id":"526661","messageId":"20250918054704.544254-5-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 4/9] builtin/pack-refs: convert to use the generic refs_optimize() API","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:46:59Z","receivedAt":"2025-09-18T05:47:22Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The `git pack-refs` command is tied to the 'files' reference backend. In\na repository that uses a different backend (like 'reftable'), the\ncommand is a no-op.\n\nTo make `git pack-refs` a truly generic frontend for reference\noptimization, refactor it to use the new generic `refs_optimize()` API.\nThis will allow the command to automatically work with any backend\nthat implements the `optimize` action in the future.\n\nThe command continues to handle parsing its own command-line options,\nbut now calls the generic API to perform the action instead of a\nbackend-specific function.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n builtin/pack-refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-refs.c b/builtin/pack-refs.c\nindex 5e28d0f9e8..dfcf664524 100644\n--- a/builtin/pack-refs.c\n+++ b/builtin/pack-refs.c\n@@ -51,7 +51,7 @@ int cmd_pack_refs(int argc,\n \tif (!pack_refs_opts.includes->nr)\n \t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n \n-\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n+\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n \n \tclear_ref_exclusions(&excludes);\n \tstring_list_clear(&included_refs, 0);\n-- \n2.34.1\n\n"},{"id":"526662","messageId":"20250918054704.544254-6-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 5/9] builtin/pack-refs: factor out core logic into a shared library","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:47:00Z","receivedAt":"2025-09-18T05:47:25Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The implementation of `git pack-refs` is monolithic within\n`cmd_pack_refs()`, making it impossible to share its logic with other\ncommands. To enable code reuse for the upcoming `git refs optimize`\nsubcommand, refactor the core logic into a shared helper function.\n\nSplit the original `builtin/pack-refs.c` file into two parts:\n\n- A new shared library file, `pack-refs.c`, which contains the\n  core option parsing and packing logic in a new `pack_refs_core()`\n  helper function.\n\n- The original `builtin/pack-refs.c`, which is now a thin wrapper\n  responsible only for defining the `git pack-refs` command and\n  calling the shared helper.\n\nA new `pack-refs.h` header is also introduced to define the public\ninterface for this shared logic.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Makefile            |  1 +\n builtin/pack-refs.c | 54 ++++---------------------------------------\n meson.build         |  1 +\n pack-refs.c         | 56 +++++++++++++++++++++++++++++++++++++++++++++\n pack-refs.h         | 23 +++++++++++++++++++\n 5 files changed, 86 insertions(+), 49 deletions(-)\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n\ndiff --git a/Makefile b/Makefile\nindex 555b7f4dc3..f51297ffc3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1094,6 +1094,7 @@ LIB_OBJS += pack-bitmap.o\n LIB_OBJS += pack-check.o\n LIB_OBJS += pack-mtimes.o\n LIB_OBJS += pack-objects.o\n+LIB_OBJS += pack-refs.o\n LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\ndiff --git a/builtin/pack-refs.c b/builtin/pack-refs.c\nindex dfcf664524..3446b84cda 100644\n--- a/builtin/pack-refs.c\n+++ b/builtin/pack-refs.c\n@@ -1,60 +1,16 @@\n #include \"builtin.h\"\n-#include \"config.h\"\n-#include \"environment.h\"\n #include \"gettext.h\"\n-#include \"parse-options.h\"\n-#include \"refs.h\"\n-#include \"revision.h\"\n-\n-static char const * const pack_refs_usage[] = {\n-\tN_(\"git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"),\n-\tNULL\n-};\n+#include \"pack-refs.h\"\n \n int cmd_pack_refs(int argc,\n \t\t  const char **argv,\n \t\t  const char *prefix,\n \t\t  struct repository *repo)\n {\n-\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n-\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n-\tstruct pack_refs_opts pack_refs_opts = {\n-\t\t.exclusions = &excludes,\n-\t\t.includes = &included_refs,\n-\t\t.flags = PACK_REFS_PRUNE,\n-\t};\n-\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n-\tstruct string_list_item *item;\n-\tint pack_all = 0;\n-\tint ret;\n-\n-\tstruct option opts[] = {\n-\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n-\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n-\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n-\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n-\t\t\tN_(\"references to include\")),\n-\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n-\t\t\tN_(\"references to exclude\")),\n-\t\tOPT_END(),\n+\tstatic char const * const pack_refs_usage[] = {\n+\t\tN_(\"git pack-refs \" PACK_REFS_OPTS),\n+\t\tNULL\n \t};\n-\trepo_config(repo, git_default_config, NULL);\n-\tif (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))\n-\t\tusage_with_options(pack_refs_usage, opts);\n-\n-\tfor_each_string_list_item(item, &option_excluded_refs)\n-\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n-\n-\tif (pack_all)\n-\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n-\n-\tif (!pack_refs_opts.includes->nr)\n-\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n-\n-\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n \n-\tclear_ref_exclusions(&excludes);\n-\tstring_list_clear(&included_refs, 0);\n-\tstring_list_clear(&option_excluded_refs, 0);\n-\treturn ret;\n+\treturn pack_refs_core(argc, argv, prefix, repo, pack_refs_usage);\n }\ndiff --git a/meson.build b/meson.build\nindex e8ec0eca16..cedaadad2e 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -407,6 +407,7 @@ libgit_sources = [\n   'pack-check.c',\n   'pack-mtimes.c',\n   'pack-objects.c',\n+  'pack-refs.c',\n   'pack-revindex.c',\n   'pack-write.c',\n   'packfile.c',\ndiff --git a/pack-refs.c b/pack-refs.c\nnew file mode 100644\nindex 0000000000..1a5e07d8b8\n--- /dev/null\n+++ b/pack-refs.c\n@@ -0,0 +1,56 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"environment.h\"\n+#include \"pack-refs.h\"\n+#include \"parse-options.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts)\n+{\n+\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n+\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n+\tstruct pack_refs_opts pack_refs_opts = {\n+\t\t.exclusions = &excludes,\n+\t\t.includes = &included_refs,\n+\t\t.flags = PACK_REFS_PRUNE,\n+\t};\n+\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n+\tstruct string_list_item *item;\n+\tint pack_all = 0;\n+\tint ret;\n+\n+\tstruct option opts[] = {\n+\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n+\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n+\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n+\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n+\t\t\tN_(\"references to include\")),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n+\t\t\tN_(\"references to exclude\")),\n+\t\tOPT_END(),\n+\t};\n+\trepo_config(repo, git_default_config, NULL);\n+\tif (parse_options(argc, argv, prefix, opts, usage_opts, 0))\n+\t\tusage_with_options(usage_opts, opts);\n+\n+\tfor_each_string_list_item(item, &option_excluded_refs)\n+\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n+\n+\tif (pack_all)\n+\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n+\n+\tif (!pack_refs_opts.includes->nr)\n+\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n+\n+\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n+\n+\tclear_ref_exclusions(&excludes);\n+\tstring_list_clear(&included_refs, 0);\n+\tstring_list_clear(&option_excluded_refs, 0);\n+\treturn ret;\n+}\ndiff --git a/pack-refs.h b/pack-refs.h\nnew file mode 100644\nindex 0000000000..5de27e7da8\n--- /dev/null\n+++ b/pack-refs.h\n@@ -0,0 +1,23 @@\n+#ifndef PACK_REFS_H\n+#define PACK_REFS_H\n+\n+struct repository;\n+\n+/*\n+ * Shared usage string for options common to git-pack-refs(1)\n+ * and git-refs-optimize(1). The command-specific part (e.g., \"git refs optimize \")\n+ * must be prepended by the caller.\n+ */\n+#define PACK_REFS_OPTS \\\n+\t\"[--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"\n+\n+/*\n+ * The core logic for pack-refs and its clones.\n+ */\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts);\n+\n+#endif /* PACK_REFS_H */\n-- \n2.34.1\n\n"},{"id":"526663","messageId":"20250918054704.544254-7-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][GSoC][PATCH v3 6/9] doc: pack-refs: factor out common options","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:47:01Z","receivedAt":"2025-09-18T05:47:28Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding documentation for `git refs optimize`, factor\nout the common options from the `git-pack-refs` man page into a\nshareable file `pack-refs-options.adoc` and update `git-pack-refs.adoc`\nto use an `include::` macro.\n\nThis change is a pure refactoring and results in no change to the final\nrendered documentation for `pack-refs`.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-pack-refs.adoc     | 53 +---------------------------\n Documentation/pack-refs-options.adoc | 52 +++++++++++++++++++++++++++\n 2 files changed, 53 insertions(+), 52 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n\ndiff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc\nindex 42b90051e6..fde9f2f294 100644\n--- a/Documentation/git-pack-refs.adoc\n+++ b/Documentation/git-pack-refs.adoc\n@@ -45,58 +45,7 @@ unpacked.\n OPTIONS\n -------\n \n---all::\n-\n-The command by default packs all tags and refs that are already\n-packed, and leaves other refs\n-alone.  This is because branches are expected to be actively\n-developed and packing their tips does not help performance.\n-This option causes all refs to be packed as well, with the exception\n-of hidden refs, broken refs, and symbolic refs. Useful for a repository\n-with many branches of historical interests.\n-\n---no-prune::\n-\n-The command usually removes loose refs under `$GIT_DIR/refs`\n-hierarchy after packing them.  This option tells it not to.\n-\n---auto::\n-\n-Pack refs as needed depending on the current state of the ref database. The\n-behavior depends on the ref format used by the repository and may change in the\n-future.\n-+\n-\t- \"files\": Loose references are packed into the `packed-refs` file\n-\t  based on the ratio of loose references to the size of the\n-\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n-\t  references need to exist before we repack.\n-+\n-\t- \"reftable\": Tables are compacted such that they form a geometric\n-\t  sequence. For two tables N and N+1, where N+1 is newer, this\n-\t  maintains the property that N is at least twice as big as N+1. Only\n-\t  tables that violate this property are compacted.\n-\n---include <pattern>::\n-\n-Pack refs based on a `glob(7)` pattern. Repetitions of this option\n-accumulate inclusion patterns. If a ref is both included in `--include` and\n-`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n-tags from being included by default. Symbolic refs and broken refs will never\n-be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n-and reset the list of patterns.\n-\n---exclude <pattern>::\n-\n-Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n-accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n-patterns. If a ref is already packed, including it with `--exclude` will not\n-unpack it.\n-+\n-When used with `--all`, pack only loose refs which do not match any of\n-the provided `--exclude` patterns.\n-+\n-When used with `--include`, refs provided to `--include`, minus refs that are\n-provided to `--exclude` will be packed.\n+include::pack-refs-options.adoc[]\n \n \n BUGS\ndiff --git a/Documentation/pack-refs-options.adoc b/Documentation/pack-refs-options.adoc\nnew file mode 100644\nindex 0000000000..0b11282941\n--- /dev/null\n+++ b/Documentation/pack-refs-options.adoc\n@@ -0,0 +1,52 @@\n+--all::\n+\n+The command by default packs all tags and refs that are already\n+packed, and leaves other refs\n+alone.  This is because branches are expected to be actively\n+developed and packing their tips does not help performance.\n+This option causes all refs to be packed as well, with the exception\n+of hidden refs, broken refs, and symbolic refs. Useful for a repository\n+with many branches of historical interests.\n+\n+--no-prune::\n+\n+The command usually removes loose refs under `$GIT_DIR/refs`\n+hierarchy after packing them.  This option tells it not to.\n+\n+--auto::\n+\n+Pack refs as needed depending on the current state of the ref database. The\n+behavior depends on the ref format used by the repository and may change in the\n+future.\n++\n+\t- \"files\": Loose references are packed into the `packed-refs` file\n+\t  based on the ratio of loose references to the size of the\n+\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n+\t  references need to exist before we repack.\n++\n+\t- \"reftable\": Tables are compacted such that they form a geometric\n+\t  sequence. For two tables N and N+1, where N+1 is newer, this\n+\t  maintains the property that N is at least twice as big as N+1. Only\n+\t  tables that violate this property are compacted.\n+\n+--include <pattern>::\n+\n+Pack refs based on a `glob(7)` pattern. Repetitions of this option\n+accumulate inclusion patterns. If a ref is both included in `--include` and\n+`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n+tags from being included by default. Symbolic refs and broken refs will never\n+be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n+and reset the list of patterns.\n+\n+--exclude <pattern>::\n+\n+Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n+accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n+patterns. If a ref is already packed, including it with `--exclude` will not\n+unpack it.\n++\n+When used with `--all`, pack only loose refs which do not match any of\n+the provided `--exclude` patterns.\n++\n+When used with `--include`, refs provided to `--include`, minus refs that are\n+provided to `--exclude` will be packed.\n-- \n2.34.1\n\n"},{"id":"526664","messageId":"20250918054704.544254-8-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 7/9] builtin/refs: add optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:47:02Z","receivedAt":"2025-09-18T05:47:30Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"As part of the ongoing effort to consolidate reference handling,\nintroduce a new `optimize` subcommand. This command provides the same\nfunctionality and exit-code behavior as `git pack-refs`, serving as its\nmodern replacement.\n\nImplement `cmd_refs_optimize` by having it call the `pack_refs_core()`\nhelper function. This helper was factored out of the original\n`cmd_pack_refs` in a preceding commit, allowing both commands to share\nthe same core logic as independent peers.\n\nAdd documentation for the new command. The man page leverages the shared\noptions file, created in a previous commit, by using the AsciiDoc\n`include::` macro to ensure consistency with git-pack-refs(1).\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-refs.adoc | 10 ++++++++++\n builtin/refs.c              | 17 +++++++++++++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc\nindex d462953fb5..e233f21eeb 100644\n--- a/Documentation/git-refs.adoc\n+++ b/Documentation/git-refs.adoc\n@@ -18,6 +18,7 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n \t\t   [--contains[=<object>]] [--no-contains[=<object>]]\n \t\t   [(--exclude=<pattern>)...] [--start-after=<marker>]\n \t\t   [ --stdin | (<pattern>...)]\n+git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\n \n DESCRIPTION\n -----------\n@@ -38,6 +39,11 @@ list::\n \tformatting, and sorting. This subcommand is an alias for\n \tlinkgit:git-for-each-ref[1] and offers identical functionality.\n \n+optimize::\n+\tOptimizes references to improve repository performance and reduce disk\n+\tusage. This subcommand is an alias for linkgit:git-pack-refs[1] and\n+\toffers identical functionality.\n+\n OPTIONS\n -------\n \n@@ -73,6 +79,10 @@ The following options are specific to 'git refs list':\n \n include::for-each-ref-options.adoc[]\n \n+The following options are specific to 'git refs optimize':\n+\n+include::pack-refs-options.adoc[]\n+\n KNOWN LIMITATIONS\n -----------------\n \ndiff --git a/builtin/refs.c b/builtin/refs.c\nindex 76224feba4..ae395759bd 100644\n--- a/builtin/refs.c\n+++ b/builtin/refs.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"fsck.h\"\n+#include \"pack-refs.h\"\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"strbuf.h\"\n@@ -14,6 +15,9 @@\n #define REFS_VERIFY_USAGE \\\n \tN_(\"git refs verify [--strict] [--verbose]\")\n \n+#define REFS_OPTIMIZE_USAGE \\\n+\tN_(\"git refs optimize \" PACK_REFS_OPTS)\n+\n static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,\n \t\t\t    struct repository *repo UNUSED)\n {\n@@ -113,6 +117,17 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix,\n \treturn for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);\n }\n \n+static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,\n+\t\t\t   struct repository *repo)\n+{\n+\tstatic char const * const refs_optimize_usage[] = {\n+\t\tREFS_OPTIMIZE_USAGE,\n+\t\tNULL\n+\t};\n+\n+\treturn pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);\n+}\n+\n int cmd_refs(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n@@ -122,6 +137,7 @@ int cmd_refs(int argc,\n \t\tREFS_MIGRATE_USAGE,\n \t\tREFS_VERIFY_USAGE,\n \t\t\"git refs list \" COMMON_USAGE_FOR_EACH_REF,\n+\t\tREFS_OPTIMIZE_USAGE,\n \t\tNULL,\n \t};\n \tparse_opt_subcommand_fn *fn = NULL;\n@@ -129,6 +145,7 @@ int cmd_refs(int argc,\n \t\tOPT_SUBCOMMAND(\"migrate\", &fn, cmd_refs_migrate),\n \t\tOPT_SUBCOMMAND(\"verify\", &fn, cmd_refs_verify),\n \t\tOPT_SUBCOMMAND(\"list\", &fn, cmd_refs_list),\n+\t\tOPT_SUBCOMMAND(\"optimize\", &fn, cmd_refs_optimize),\n \t\tOPT_END(),\n \t};\n \n-- \n2.34.1\n\n"},{"id":"526665","messageId":"20250918054704.544254-9-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 8/9] t0601: refactor tests to be shareable","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:47:03Z","receivedAt":"2025-09-18T05:47:33Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding tests for the new `git refs optimize` command,\nrefactor the existing t0601 test suite to make its logic shareable.\n\nMove the core test logic from `t0601-reffiles-pack-refs.sh` into a new\n`pack-refs-tests.sh` file. Inside this new script, replace hardcoded\ncalls to \"pack-refs\" with the `$pack_refs` variable.\n\nThe original `t0601-reffiles-pack-refs.sh` script now becomes a simple\n\"driver\". It is responsible for setting the default value of the\nvariable and then sourcing the test library.\n\nThis new structure follows the established pattern used for sharing\ntests between `git-for-each-ref` and `git-refs list` and prepares the\ntest suite for the `refs optimize` tests to be added in a subsequent\ncommit.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/pack-refs-tests.sh          | 431 ++++++++++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh | 430 +--------------------------------\n 2 files changed, 432 insertions(+), 429 deletions(-)\n create mode 100644 t/pack-refs-tests.sh\n\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nnew file mode 100644\nindex 0000000000..3dbcc01718\n--- /dev/null\n+++ b/t/pack-refs-tests.sh\n@@ -0,0 +1,431 @@\n+pack_refs=${pack_refs:-pack-refs}\n+\n+test_expect_success 'enable reflogs' '\n+\tgit config core.logallrefupdates true\n+'\n+\n+test_expect_success 'prepare a trivial repository' '\n+\techo Hello > A &&\n+\tgit update-index --add A &&\n+\tgit commit -m \"Initial commit.\" &&\n+\tHEAD=$(git rev-parse --verify HEAD)\n+'\n+\n+test_expect_success '${pack_refs} --prune --all' '\n+\ttest_path_is_missing .git/packed-refs &&\n+\tgit ${pack_refs} --no-prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f | wc -l) &&\n+\ttest \"$N\" != 0 &&\n+\n+\tgit ${pack_refs} --prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f) &&\n+\ttest -z \"$N\"\n+'\n+\n+SHA1=\n+\n+test_expect_success 'see if git show-ref works as expected' '\n+\tgit branch a &&\n+\tSHA1=$(cat .git/refs/heads/a) &&\n+\techo \"$SHA1 refs/heads/a\" >expect &&\n+\tgit show-ref a >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if a branch still exists when packed' '\n+\tgit branch b &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/b &&\n+\techo \"$SHA1 refs/heads/b\" >expect &&\n+\tgit show-ref b >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'git branch c/d should barf if branch c exists' '\n+\tgit branch c &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/c &&\n+\ttest_must_fail git branch c/d\n+'\n+\n+test_expect_success 'see if a branch still exists after git ${pack_refs} --prune' '\n+\tgit branch e &&\n+\tgit ${pack_refs} --all --prune &&\n+\techo \"$SHA1 refs/heads/e\" >expect &&\n+\tgit show-ref e >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune remove ref files' '\n+\tgit branch f &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -f .git/refs/heads/f\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune removes empty dirs' '\n+\tgit branch r/s/t &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -e .git/refs/heads/r\n+'\n+\n+test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n+\tgit branch g/h &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d g/h &&\n+\tgit branch g &&\n+\tgit ${pack_refs} --all &&\n+\tgit branch -d g\n+'\n+\n+test_expect_success 'git branch i/j/k should barf if branch i exists' '\n+\tgit branch i &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_must_fail git branch i/j/k\n+'\n+\n+test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n+\tgit branch k/l &&\n+\tgit branch k/lm &&\n+\tgit branch -d k/l &&\n+\tgit branch k/l/m &&\n+\tgit branch -d k/l/m &&\n+\tgit branch -d k/lm &&\n+\tgit branch k\n+'\n+\n+test_expect_success 'test git branch n after some branch deletion and pruning' '\n+\tgit branch n/o &&\n+\tgit branch n/op &&\n+\tgit branch -d n/o &&\n+\tgit branch n/o/p &&\n+\tgit branch -d n/op &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d n/o/p &&\n+\tgit branch n\n+'\n+\n+test_expect_success 'test excluded refs are not packed' '\n+\tgit branch dont_pack1 &&\n+\tgit branch dont_pack2 &&\n+\tgit branch pack_this &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack1 &&\n+\ttest -f .git/refs/heads/dont_pack2 &&\n+\t! test -f .git/refs/heads/pack_this'\n+\n+test_expect_success 'test --no-exclude refs clears excluded refs' '\n+\tgit branch dont_pack3 &&\n+\tgit branch dont_pack4 &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n+\t! test -f .git/refs/heads/dont_pack3 &&\n+\t! test -f .git/refs/heads/dont_pack4'\n+\n+test_expect_success 'test only included refs are packed' '\n+\tgit branch pack_this1 &&\n+\tgit branch pack_this2 &&\n+\tgit tag dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack_this*\" &&\n+\ttest -f .git/refs/tags/dont_pack5 &&\n+\t! test -f .git/refs/heads/pack_this1 &&\n+\t! test -f .git/refs/heads/pack_this2'\n+\n+test_expect_success 'test --no-include refs clears included refs' '\n+\tgit branch pack1 &&\n+\tgit branch pack2 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --no-include &&\n+\ttest -f .git/refs/heads/pack1 &&\n+\ttest -f .git/refs/heads/pack2'\n+\n+test_expect_success 'test --exclude takes precedence over --include' '\n+\tgit branch dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack5'\n+\n+test_expect_success 'see if up-to-date packed refs are preserved' '\n+\tgit branch q &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit update-ref refs/heads/q refs/heads/q &&\n+\t! test -f .git/refs/heads/q\n+'\n+\n+test_expect_success 'pack, prune and repack' '\n+\tgit tag foo &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit show-ref >all-of-them &&\n+\tgit ${pack_refs} &&\n+\tgit show-ref >again &&\n+\ttest_cmp all-of-them again\n+'\n+\n+test_expect_success 'explicit ${pack_refs} with dangling packed reference' '\n+\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit ${pack_refs} --all 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref with dangling packed version' '\n+\tgit checkout -b lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout main &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref while another dangling packed ref' '\n+\tgit branch lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'pack ref directly below refs/' '\n+\tgit update-ref refs/top HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgrep refs/top .git/packed-refs &&\n+\ttest_path_is_missing .git/refs/top\n+'\n+\n+test_expect_success 'do not pack ref in refs/bisect' '\n+\tgit update-ref refs/bisect/local HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n+\ttest_path_is_file .git/refs/bisect/local\n+'\n+\n+test_expect_success 'disable reflogs' '\n+\tgit config core.logallrefupdates false &&\n+\trm -rf .git/logs\n+'\n+\n+test_expect_success 'create packed foo/bar/baz branch' '\n+\tgit branch foo/bar/baz &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n+\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n+'\n+\n+test_expect_success 'notice d/f conflict with existing directory' '\n+\ttest_must_fail git branch foo &&\n+\ttest_must_fail git branch foo/bar\n+'\n+\n+test_expect_success 'existing directory reports concrete ref' '\n+\ttest_must_fail git branch foo 2>stderr &&\n+\ttest_grep refs/heads/foo/bar/baz stderr\n+'\n+\n+test_expect_success 'notice d/f conflict with existing ref' '\n+\ttest_must_fail git branch foo/bar/baz/extra &&\n+\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n+'\n+\n+test_expect_success 'reject packed-refs with unterminated line' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n+\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs containing junk' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n+\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs with a short SHA-1' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n+\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'timeout if packed-refs.lock exists' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"rm -f $LOCK\" &&\n+\ttest_must_fail git ${pack_refs} --all --prune\n+'\n+\n+test_expect_success 'retry acquiring packed-refs.lock' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"wait && rm -f $LOCK\" &&\n+\t{\n+\t\t( sleep 1 && rm -f $LOCK ) &\n+\t} &&\n+\tgit -c core.packedrefstimeout=3000 ${pack_refs} --all --prune\n+'\n+\n+test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n+\t# First make sure that symlinking works when reading:\n+\tgit update-ref refs/heads/lossy refs/heads/main &&\n+\tgit for-each-ref >all-refs-before &&\n+\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n+\tln -s my-deviant-packed-refs .git/packed-refs &&\n+\tgit for-each-ref >all-refs-linked &&\n+\ttest_cmp all-refs-before all-refs-linked &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit for-each-ref >all-refs-packed &&\n+\ttest_cmp all-refs-before all-refs-packed &&\n+\ttest -h .git/packed-refs &&\n+\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n+'\n+\n+# The 'packed-refs' file is stored directly in .git/. This means it is global\n+# to the repository, and can only contain refs that are shared across all\n+# worktrees.\n+test_expect_success 'refs/worktree must not be packed' '\n+\ttest_commit initial &&\n+\ttest_commit wt1 &&\n+\ttest_commit wt2 &&\n+\tgit worktree add wt1 wt1 &&\n+\tgit worktree add wt2 wt2 &&\n+\tgit checkout initial &&\n+\tgit update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n+\tgit ${pack_refs} --all &&\n+\ttest_path_is_missing .git/refs/tags/wt1 &&\n+\ttest_path_is_file .git/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n+'\n+\n+# we do not want to count on running ${pack_refs} to\n+# actually pack it, as it is perfectly reasonable to\n+# skip processing a broken ref\n+test_expect_success 'create packed-refs file with broken ref' '\n+\ttest_tick && git commit --allow-empty -m one &&\n+\trecoverable=$(git rev-parse HEAD) &&\n+\ttest_tick && git commit --allow-empty -m two &&\n+\tmissing=$(git rev-parse HEAD) &&\n+\trm -f .git/refs/heads/main &&\n+\tcat >.git/packed-refs <<-EOF &&\n+\t$missing refs/heads/main\n+\t$recoverable refs/heads/other\n+\tEOF\n+\techo $missing >expect &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not silently delete broken packed ref' '\n+\tgit ${pack_refs} --all --prune &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not drop broken refs during deletion' '\n+\tgit update-ref -d refs/heads/other &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+for command in \"git ${pack_refs} --all --auto\" \"git maintenance run --task=${pack_refs} --auto\"\n+do\n+\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 14 additional references, which brings us to\n+\t\t\t# 15 together with the default branch.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\n+\t\t\t# Create the 16th reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_file .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create 15 loose references.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create the 16th loose reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 18 .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command scales with size of packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 99 packed refs. This should cause the heuristic\n+\t\t\t# to require more than the minimum amount of loose refs.\n+\t\t\ttest_seq 99 |\n+\t\t\twhile read i\n+\t\t\tdo\n+\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n+\t\t\tdone >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create another handful of refs to cross the border.\n+\t\t\t# Note that we explicitly do not check for strict\n+\t\t\t# boundaries here, as this also depends on the size of\n+\t\t\t# the object hash.\n+\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 135 .git/packed-refs\n+\t\t)\n+\t'\n+done\n+\n+test_done\ndiff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh\nindex aa7f6ecd81..12cf5d1dcb 100755\n--- a/t/t0601-reffiles-pack-refs.sh\n+++ b/t/t0601-reffiles-pack-refs.sh\n@@ -17,432 +17,4 @@ export GIT_TEST_DEFAULT_REF_FORMAT\n \n . ./test-lib.sh\n \n-test_expect_success 'enable reflogs' '\n-\tgit config core.logallrefupdates true\n-'\n-\n-test_expect_success 'prepare a trivial repository' '\n-\techo Hello > A &&\n-\tgit update-index --add A &&\n-\tgit commit -m \"Initial commit.\" &&\n-\tHEAD=$(git rev-parse --verify HEAD)\n-'\n-\n-test_expect_success 'pack-refs --prune --all' '\n-\ttest_path_is_missing .git/packed-refs &&\n-\tgit pack-refs --no-prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f | wc -l) &&\n-\ttest \"$N\" != 0 &&\n-\n-\tgit pack-refs --prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f) &&\n-\ttest -z \"$N\"\n-'\n-\n-SHA1=\n-\n-test_expect_success 'see if git show-ref works as expected' '\n-\tgit branch a &&\n-\tSHA1=$(cat .git/refs/heads/a) &&\n-\techo \"$SHA1 refs/heads/a\" >expect &&\n-\tgit show-ref a >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if a branch still exists when packed' '\n-\tgit branch b &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/b &&\n-\techo \"$SHA1 refs/heads/b\" >expect &&\n-\tgit show-ref b >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'git branch c/d should barf if branch c exists' '\n-\tgit branch c &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/c &&\n-\ttest_must_fail git branch c/d\n-'\n-\n-test_expect_success 'see if a branch still exists after git pack-refs --prune' '\n-\tgit branch e &&\n-\tgit pack-refs --all --prune &&\n-\techo \"$SHA1 refs/heads/e\" >expect &&\n-\tgit show-ref e >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if git pack-refs --prune remove ref files' '\n-\tgit branch f &&\n-\tgit pack-refs --all --prune &&\n-\t! test -f .git/refs/heads/f\n-'\n-\n-test_expect_success 'see if git pack-refs --prune removes empty dirs' '\n-\tgit branch r/s/t &&\n-\tgit pack-refs --all --prune &&\n-\t! test -e .git/refs/heads/r\n-'\n-\n-test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n-\tgit branch g/h &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d g/h &&\n-\tgit branch g &&\n-\tgit pack-refs --all &&\n-\tgit branch -d g\n-'\n-\n-test_expect_success 'git branch i/j/k should barf if branch i exists' '\n-\tgit branch i &&\n-\tgit pack-refs --all --prune &&\n-\ttest_must_fail git branch i/j/k\n-'\n-\n-test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n-\tgit branch k/l &&\n-\tgit branch k/lm &&\n-\tgit branch -d k/l &&\n-\tgit branch k/l/m &&\n-\tgit branch -d k/l/m &&\n-\tgit branch -d k/lm &&\n-\tgit branch k\n-'\n-\n-test_expect_success 'test git branch n after some branch deletion and pruning' '\n-\tgit branch n/o &&\n-\tgit branch n/op &&\n-\tgit branch -d n/o &&\n-\tgit branch n/o/p &&\n-\tgit branch -d n/op &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d n/o/p &&\n-\tgit branch n\n-'\n-\n-test_expect_success 'test excluded refs are not packed' '\n-\tgit branch dont_pack1 &&\n-\tgit branch dont_pack2 &&\n-\tgit branch pack_this &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack1 &&\n-\ttest -f .git/refs/heads/dont_pack2 &&\n-\t! test -f .git/refs/heads/pack_this'\n-\n-test_expect_success 'test --no-exclude refs clears excluded refs' '\n-\tgit branch dont_pack3 &&\n-\tgit branch dont_pack4 &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n-\t! test -f .git/refs/heads/dont_pack3 &&\n-\t! test -f .git/refs/heads/dont_pack4'\n-\n-test_expect_success 'test only included refs are packed' '\n-\tgit branch pack_this1 &&\n-\tgit branch pack_this2 &&\n-\tgit tag dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack_this*\" &&\n-\ttest -f .git/refs/tags/dont_pack5 &&\n-\t! test -f .git/refs/heads/pack_this1 &&\n-\t! test -f .git/refs/heads/pack_this2'\n-\n-test_expect_success 'test --no-include refs clears included refs' '\n-\tgit branch pack1 &&\n-\tgit branch pack2 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --no-include &&\n-\ttest -f .git/refs/heads/pack1 &&\n-\ttest -f .git/refs/heads/pack2'\n-\n-test_expect_success 'test --exclude takes precedence over --include' '\n-\tgit branch dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack5'\n-\n-test_expect_success 'see if up-to-date packed refs are preserved' '\n-\tgit branch q &&\n-\tgit pack-refs --all --prune &&\n-\tgit update-ref refs/heads/q refs/heads/q &&\n-\t! test -f .git/refs/heads/q\n-'\n-\n-test_expect_success 'pack, prune and repack' '\n-\tgit tag foo &&\n-\tgit pack-refs --all --prune &&\n-\tgit show-ref >all-of-them &&\n-\tgit pack-refs &&\n-\tgit show-ref >again &&\n-\ttest_cmp all-of-them again\n-'\n-\n-test_expect_success 'explicit pack-refs with dangling packed reference' '\n-\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit pack-refs --all 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref with dangling packed version' '\n-\tgit checkout -b lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit checkout main &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref while another dangling packed ref' '\n-\tgit branch lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'pack ref directly below refs/' '\n-\tgit update-ref refs/top HEAD &&\n-\tgit pack-refs --all --prune &&\n-\tgrep refs/top .git/packed-refs &&\n-\ttest_path_is_missing .git/refs/top\n-'\n-\n-test_expect_success 'do not pack ref in refs/bisect' '\n-\tgit update-ref refs/bisect/local HEAD &&\n-\tgit pack-refs --all --prune &&\n-\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n-\ttest_path_is_file .git/refs/bisect/local\n-'\n-\n-test_expect_success 'disable reflogs' '\n-\tgit config core.logallrefupdates false &&\n-\trm -rf .git/logs\n-'\n-\n-test_expect_success 'create packed foo/bar/baz branch' '\n-\tgit branch foo/bar/baz &&\n-\tgit pack-refs --all --prune &&\n-\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n-\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n-'\n-\n-test_expect_success 'notice d/f conflict with existing directory' '\n-\ttest_must_fail git branch foo &&\n-\ttest_must_fail git branch foo/bar\n-'\n-\n-test_expect_success 'existing directory reports concrete ref' '\n-\ttest_must_fail git branch foo 2>stderr &&\n-\ttest_grep refs/heads/foo/bar/baz stderr\n-'\n-\n-test_expect_success 'notice d/f conflict with existing ref' '\n-\ttest_must_fail git branch foo/bar/baz/extra &&\n-\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n-'\n-\n-test_expect_success 'reject packed-refs with unterminated line' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n-\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs containing junk' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n-\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs with a short SHA-1' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n-\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'timeout if packed-refs.lock exists' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"rm -f $LOCK\" &&\n-\ttest_must_fail git pack-refs --all --prune\n-'\n-\n-test_expect_success 'retry acquiring packed-refs.lock' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"wait && rm -f $LOCK\" &&\n-\t{\n-\t\t( sleep 1 && rm -f $LOCK ) &\n-\t} &&\n-\tgit -c core.packedrefstimeout=3000 pack-refs --all --prune\n-'\n-\n-test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n-\t# First make sure that symlinking works when reading:\n-\tgit update-ref refs/heads/lossy refs/heads/main &&\n-\tgit for-each-ref >all-refs-before &&\n-\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n-\tln -s my-deviant-packed-refs .git/packed-refs &&\n-\tgit for-each-ref >all-refs-linked &&\n-\ttest_cmp all-refs-before all-refs-linked &&\n-\tgit pack-refs --all --prune &&\n-\tgit for-each-ref >all-refs-packed &&\n-\ttest_cmp all-refs-before all-refs-packed &&\n-\ttest -h .git/packed-refs &&\n-\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n-'\n-\n-# The 'packed-refs' file is stored directly in .git/. This means it is global\n-# to the repository, and can only contain refs that are shared across all\n-# worktrees.\n-test_expect_success 'refs/worktree must not be packed' '\n-\ttest_commit initial &&\n-\ttest_commit wt1 &&\n-\ttest_commit wt2 &&\n-\tgit worktree add wt1 wt1 &&\n-\tgit worktree add wt2 wt2 &&\n-\tgit checkout initial &&\n-\tgit update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n-\tgit pack-refs --all &&\n-\ttest_path_is_missing .git/refs/tags/wt1 &&\n-\ttest_path_is_file .git/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n-'\n-\n-# we do not want to count on running pack-refs to\n-# actually pack it, as it is perfectly reasonable to\n-# skip processing a broken ref\n-test_expect_success 'create packed-refs file with broken ref' '\n-\ttest_tick && git commit --allow-empty -m one &&\n-\trecoverable=$(git rev-parse HEAD) &&\n-\ttest_tick && git commit --allow-empty -m two &&\n-\tmissing=$(git rev-parse HEAD) &&\n-\trm -f .git/refs/heads/main &&\n-\tcat >.git/packed-refs <<-EOF &&\n-\t$missing refs/heads/main\n-\t$recoverable refs/heads/other\n-\tEOF\n-\techo $missing >expect &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not silently delete broken packed ref' '\n-\tgit pack-refs --all --prune &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not drop broken refs during deletion' '\n-\tgit update-ref -d refs/heads/other &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-for command in \"git pack-refs --all --auto\" \"git maintenance run --task=pack-refs --auto\"\n-do\n-\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 14 additional references, which brings us to\n-\t\t\t# 15 together with the default branch.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\n-\t\t\t# Create the 16th reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_file .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create 15 loose references.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create the 16th loose reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 18 .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command scales with size of packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 99 packed refs. This should cause the heuristic\n-\t\t\t# to require more than the minimum amount of loose refs.\n-\t\t\ttest_seq 99 |\n-\t\t\twhile read i\n-\t\t\tdo\n-\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n-\t\t\tdone >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create another handful of refs to cross the border.\n-\t\t\t# Note that we explicitly do not check for strict\n-\t\t\t# boundaries here, as this also depends on the size of\n-\t\t\t# the object hash.\n-\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 135 .git/packed-refs\n-\t\t)\n-\t'\n-done\n-\n-test_done\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"526666","messageId":"20250918054704.544254-10-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v3 9/9] t: add test for git refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-18T05:47:04Z","receivedAt":"2025-09-18T05:47:36Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Add a test script, `t/t1463-refs-optimize.sh`, for the new `git refs\noptimize` command.\n\nThis script acts as a simple driver, leveraging the shared test library\ncreated in the preceding commit. It works by overriding the\n`$pack_refs` variable to \"refs optimize\" and then sourcing the\nshared library (`t/pack-refs-tests.sh`).\n\nThis approach ensures that `git refs optimize` is tested against the\nentire comprehensive test suite of `git pack-refs`, verifying\nthat it acts as a compatible drop-in replacement.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/meson.build            |  3 ++-\n t/t1463-refs-optimize.sh | 17 +++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n create mode 100755 t/t1463-refs-optimize.sh\n\ndiff --git a/t/meson.build b/t/meson.build\nindex baeeba2ce6..92327aabdf 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -211,6 +211,7 @@ integration_tests = [\n   't1451-fsck-buffer.sh',\n   't1460-refs-migrate.sh',\n   't1461-refs-list.sh',\n+  't1463-refs-optimize.sh',\n   't1500-rev-parse.sh',\n   't1501-work-tree.sh',\n   't1502-rev-parse-parseopt.sh',\n@@ -1219,4 +1220,4 @@ if perl.found() and time.found()\n       timeout: 0,\n     )\n   endforeach\n-endif\n\\ No newline at end of file\n+endif\ndiff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh\nnew file mode 100755\nindex 0000000000..c11c905d79\n--- /dev/null\n+++ b/t/t1463-refs-optimize.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+\n+test_description='git refs optimize should not change the branch semantic\n+\n+This test runs git refs optimize and git show-ref and checks that the branch\n+semantic is still the same.\n+'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+GIT_TEST_DEFAULT_REF_FORMAT=files\n+export GIT_TEST_DEFAULT_REF_FORMAT\n+\n+. ./test-lib.sh\n+\n+pack_refs='refs optimize'\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"526697","messageId":"aMviZmilaxXb2a35@ArchLinux","threadId":"64099","inReplyTo":"20250918054704.544254-5-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v3 4/9] builtin/pack-refs: convert to use the generic refs_optimize() API","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-09-18T10:43:50Z","receivedAt":"2025-09-18T10:43:43Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Sep 18, 2025 at 11:16:59AM +0530, Meet Soni wrote:\n> The `git pack-refs` command is tied to the 'files' reference backend. In\n> a repository that uses a different backend (like 'reftable'), the\n> command is a no-op.\n> \n\nI think `git pack-refs` would work for reftable backend. It would\neventually call `reftable_be_pack_refs`. And from my intuitive\nunderstanding, it would compact the reftable to reduce the size of the\ntable.\n\n> To make `git pack-refs` a truly generic frontend for reference\n> optimization, refactor it to use the new generic `refs_optimize()` API.\n> This will allow the command to automatically work with any backend\n> that implements the `optimize` action in the future.\n> \n\nIf my above understanding is correct, \"git pack-refs\" has already worked\nwith all the backends. But for reftable backend, the semantic of\n\"pack-refs\" is not clear, optimize is a much better semantic word.\n\nThanks,\nJialuo\n"},{"id":"526698","messageId":"aMvigMLPeQE-n-o_@ArchLinux","threadId":"64099","inReplyTo":"20250918054704.544254-2-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v3 1/9] refs: add a generic 'optimize' API","fromName":"shejialuo","fromEmail":"shejialuo@gmail.com","sentAt":"2025-09-18T10:44:16Z","receivedAt":"2025-09-18T10:44:08Z","isPatch":true,"sender":{"key":"shejialuo@gmail.com","avatar":"https://avatars.githubusercontent.com/u/56911263?v=4"},"body":"On Thu, Sep 18, 2025 at 11:16:56AM +0530, Meet Soni wrote:\n> Add a new generic refs_optimize() API function that dispatches to a\n> backend-specific implementation via a new 'optimize' vtable method.\n> \n\nShould \"API\" be enough instead of using \"API function\"? However, I think\nwe need to give the motivation.\n\n> This lays the architectural groundwork for different reference backends\n> (like 'files' and 'reftable') to provide their own storage optimization\n> logic, which will be called from a single, generic entry point.\n> \n> Mentored-by: Patrick Steinhardt <ps@pks.im>\n> Mentored-by: shejialuo <shejialuo@gmail.com>\n> Signed-off-by: Meet Soni <meetsoni3017@gmail.com>\n> ---\n>  refs.c               | 7 +++++++\n>  refs.h               | 6 ++++++\n>  refs/refs-internal.h | 3 +++\n>  3 files changed, 16 insertions(+)\n> \n> diff --git a/refs.c b/refs.c\n> index 4ff55cf24f..2ea6fd2218 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2282,6 +2282,13 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)\n>  \treturn refs->be->pack_refs(refs, opts);\n>  }\n>  \n> +int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n> +{\n> +\tif (!refs->be->optimize)\n> +\t\treturn 0;\n\nI don't think we need to check `refs->be->optimize`. Even though for\nsome backends, we won't do any optimization, we should register this\ncallback instead of assigning `NULL`.\n\n> +\treturn refs->be->optimize(refs, opts);\n> +}\n> +\n\nThanks,\nJialuo\n"},{"id":"526709","messageId":"xmqqjz1v93cm.fsf@gitster.g","threadId":"64099","inReplyTo":"aMvigMLPeQE-n-o_@ArchLinux","subject":"Re: [GSoC][PATCH v3 1/9] refs: add a generic 'optimize' API","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-18T15:39:53Z","receivedAt":"2025-09-18T15:39:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"shejialuo <shejialuo@gmail.com> writes:\n\n>> diff --git a/refs.c b/refs.c\n>> index 4ff55cf24f..2ea6fd2218 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -2282,6 +2282,13 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)\n>>  \treturn refs->be->pack_refs(refs, opts);\n>>  }\n>>  \n>> +int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n>> +{\n>> +\tif (!refs->be->optimize)\n>> +\t\treturn 0;\n>\n> I don't think we need to check `refs->be->optimize`. Even though for\n> some backends, we won't do any optimization, we should register this\n> callback instead of assigning `NULL`.\n\nYeah, all the existing functions at the refs.c level simply assumes\nthat refs->be->method always exists, and it would be sensible to be\ncoherent with them.\n\nThanks.\n"},{"id":"526710","messageId":"xmqqecs3923j.fsf@gitster.g","threadId":"64099","inReplyTo":"20250918054704.544254-8-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v3 7/9] builtin/refs: add optimize subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-18T16:06:56Z","receivedAt":"2025-09-18T16:06:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Meet Soni <meetsoni3017@gmail.com> writes:\n\n> diff --git a/builtin/refs.c b/builtin/refs.c\n> index 76224feba4..ae395759bd 100644\n> --- a/builtin/refs.c\n> +++ b/builtin/refs.c\n> ...\n> +#define REFS_OPTIMIZE_USAGE \\\n> +\tN_(\"git refs optimize \" PACK_REFS_OPTS)\n> +\n>  static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,\n>  \t\t\t    struct repository *repo UNUSED)\n>  {\n> @@ -113,6 +117,17 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix,\n>  \treturn for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);\n>  }\n>  \n> +static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,\n> +\t\t\t   struct repository *repo)\n\nThis line does not align; will locally fix (no need to resend only\nto fix this).\n\n> +{\n> +\tstatic char const * const refs_optimize_usage[] = {\n> +\t\tREFS_OPTIMIZE_USAGE,\n> +\t\tNULL\n> +\t};\n> +\n> +\treturn pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);\n> +}\n> +\n>  int cmd_refs(int argc,\n>  \t     const char **argv,\n>  \t     const char *prefix,\n> @@ -122,6 +137,7 @@ int cmd_refs(int argc,\n>  \t\tREFS_MIGRATE_USAGE,\n>  \t\tREFS_VERIFY_USAGE,\n>  \t\t\"git refs list \" COMMON_USAGE_FOR_EACH_REF,\n> +\t\tREFS_OPTIMIZE_USAGE,\n>  \t\tNULL,\n>  \t};\n\nWe may want to clean this up outside this topic, perhaps a clean-up\npatch after all the dust settles.  Lack of REFS_LIST_USAGE stands\nout like a sore thumb.\n"},{"id":"526758","messageId":"20250919082647.535213-1-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250918054704.544254-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 0/9] Add refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:38Z","receivedAt":"2025-09-19T08:26:54Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Hi everyone,\n\nThis series introduces `git refs optimize` as a modern replacement for\n`git pack-refs`, continuing the effort to consolidate commands under the\n`git refs` namespace.\n\nChanges in v4:\n- Improved commit messages and formatting.\n- Removed the NULL check from the refs_optimize() dispatcher in refs.c\n  to align with the project's conventions for API functions\n\nMeet Soni (9):\n  refs: add a generic 'optimize' API\n  files-backend: implement 'optimize' action\n  reftable-backend: implement 'optimize' action\n  builtin/pack-refs: convert to use the generic refs_optimize() API\n  builtin/pack-refs: factor out core logic into a shared library\n  doc: pack-refs: factor out common options\n  builtin/refs: add optimize subcommand\n  t0601: refactor tests to be shareable\n  t: add test for git refs optimize subcommand\n\n Documentation/git-pack-refs.adoc     |  53 +---\n Documentation/git-refs.adoc          |  10 +\n Documentation/pack-refs-options.adoc |  52 ++++\n Makefile                             |   1 +\n builtin/pack-refs.c                  |  54 +---\n builtin/refs.c                       |  17 ++\n meson.build                          |   1 +\n pack-refs.c                          |  56 ++++\n pack-refs.h                          |  23 ++\n refs.c                               |   5 +\n refs.h                               |   6 +\n refs/files-backend.c                 |  10 +\n refs/refs-internal.h                 |   3 +\n refs/reftable-backend.c              |   7 +\n t/meson.build                        |   3 +-\n t/pack-refs-tests.sh                 | 431 +++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh        | 430 +-------------------------\n t/t1463-refs-optimize.sh             |  17 ++\n 18 files changed, 648 insertions(+), 531 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n create mode 100644 t/pack-refs-tests.sh\n create mode 100755 t/t1463-refs-optimize.sh\n\nRange-diff against v3:\n 1:  a837ae6f5d !  1:  8d8aa56fe9 refs: add a generic 'optimize' API\n    @@ Metadata\n      ## Commit message ##\n         refs: add a generic 'optimize' API\n     \n    -    Add a new generic refs_optimize() API function that dispatches to a\n    +    The existing `pack-refs` API is conceptually tied to the 'files'\n    +    backend, but its behavior is generic (e.g., it triggers compaction for\n    +    reftable). This naming is confusing.\n    +\n    +    Introduce a new generic refs_optimize() API that dispatches to a\n         backend-specific implementation via a new 'optimize' vtable method.\n     \n         This lays the architectural groundwork for different reference backends\n    @@ refs.c: int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)\n      \n     +int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n     +{\n    -+\tif (!refs->be->optimize)\n    -+\t\treturn 0;\n     +\treturn refs->be->optimize(refs, opts);\n     +}\n     +\n 2:  e0613b14b6 =  2:  aebdf90fef files-backend: implement 'optimize' action\n 3:  ae8d2d29d9 =  3:  0b41b52e36 reftable-backend: implement 'optimize' action\n 4:  a8cba8a355 !  4:  bcb76f2460 builtin/pack-refs: convert to use the generic refs_optimize() API\n    @@ Metadata\n      ## Commit message ##\n         builtin/pack-refs: convert to use the generic refs_optimize() API\n     \n    -    The `git pack-refs` command is tied to the 'files' reference backend. In\n    -    a repository that uses a different backend (like 'reftable'), the\n    -    command is a no-op.\n    +    The `git pack-refs` command behaves generically, triggering a pack for\n    +    the 'files' backend and a compaction for the 'reftable' backend.\n    +    However, the name of the command and its corresponding API is\n    +    conceptually tied to the 'files' backend implementation.\n     \n    -    To make `git pack-refs` a truly generic frontend for reference\n    -    optimization, refactor it to use the new generic `refs_optimize()` API.\n    -    This will allow the command to automatically work with any backend\n    -    that implements the `optimize` action in the future.\n    +    To create a cleaner, more generic interface, refactor `git pack-refs` to\n    +    use the new `refs_optimize()` API. \"Optimize\" is a better semantic term\n    +    for this generic action.\n     \n    -    The command continues to handle parsing its own command-line options,\n    -    but now calls the generic API to perform the action instead of a\n    -    backend-specific function.\n    +    This change allows `git pack-refs` to act as a backend-agnostic frontend\n    +    for reference optimization, and paves the way for the new `git refs\n    +    optimize` command to do the same.\n     \n         Mentored-by: Patrick Steinhardt <ps@pks.im>\n         Mentored-by: shejialuo <shejialuo@gmail.com>\n 5:  ec1085ccd8 =  5:  6c6d63edff builtin/pack-refs: factor out core logic into a shared library\n 6:  e1758816bf =  6:  5c5d3e2699 doc: pack-refs: factor out common options\n 7:  e3a908fe72 !  7:  d2bff276b8 builtin/refs: add optimize subcommand\n    @@ builtin/refs.c: static int cmd_refs_list(int argc, const char **argv, const char\n      }\n      \n     +static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,\n    -+\t\t\t   struct repository *repo)\n    ++\t\t\t     struct repository *repo)\n     +{\n     +\tstatic char const * const refs_optimize_usage[] = {\n     +\t\tREFS_OPTIMIZE_USAGE,\n 8:  4f63632ac2 =  8:  5a865d2828 t0601: refactor tests to be shareable\n 9:  39eed2831a =  9:  3d7d40b510 t: add test for git refs optimize subcommand\n\nbase-commit: f814da676ae46aac5be0a98b99373a76dee6cedb\n-- \n2.34.1\n\n"},{"id":"526759","messageId":"20250919082647.535213-2-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 1/9] refs: add a generic 'optimize' API","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:39Z","receivedAt":"2025-09-19T08:26:56Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The existing `pack-refs` API is conceptually tied to the 'files'\nbackend, but its behavior is generic (e.g., it triggers compaction for\nreftable). This naming is confusing.\n\nIntroduce a new generic refs_optimize() API that dispatches to a\nbackend-specific implementation via a new 'optimize' vtable method.\n\nThis lays the architectural groundwork for different reference backends\n(like 'files' and 'reftable') to provide their own storage optimization\nlogic, which will be called from a single, generic entry point.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs.c               | 5 +++++\n refs.h               | 6 ++++++\n refs/refs-internal.h | 3 +++\n 3 files changed, 14 insertions(+)\n\ndiff --git a/refs.c b/refs.c\nindex 4ff55cf24f..191b95b4a3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2282,6 +2282,11 @@ int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts)\n \treturn refs->be->pack_refs(refs, opts);\n }\n \n+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts)\n+{\n+\treturn refs->be->optimize(refs, opts);\n+}\n+\n int peel_iterated_oid(struct repository *r, const struct object_id *base, struct object_id *peeled)\n {\n \tif (current_ref_iter &&\ndiff --git a/refs.h b/refs.h\nindex f29e486e33..d28c4ef0af 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -480,6 +480,12 @@ struct pack_refs_opts {\n  */\n int refs_pack_refs(struct ref_store *refs, struct pack_refs_opts *opts);\n \n+/*\n+ * Optimize the ref store. The exact behavior is up to the backend.\n+ * For the files backend, this is equivalent to packing refs.\n+ */\n+int refs_optimize(struct ref_store *refs, struct pack_refs_opts *opts);\n+\n /*\n  * Setup reflog before using. Fill in err and return -1 on failure.\n  */\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 54c2079c12..4ef3bd75c6 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -447,6 +447,8 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n+typedef int optimize_fn(struct ref_store *ref_store,\n+\t\t\tstruct pack_refs_opts *opts);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -572,6 +574,7 @@ struct ref_storage_be {\n \tref_transaction_abort_fn *transaction_abort;\n \n \tpack_refs_fn *pack_refs;\n+\toptimize_fn *optimize;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \n-- \n2.34.1\n\n"},{"id":"526760","messageId":"20250919082647.535213-3-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 2/9] files-backend: implement 'optimize' action","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:40Z","receivedAt":"2025-09-19T08:26:59Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"With the generic `refs_optimize()` API now in place, provide the first\nimplementation for the 'files' reference backend. This makes the new API\nfunctional for existing repositories and serves as the foundation for\nmigrating user-facing commands to the new architecture.\n\nThe implementation simply calls the existing `files_pack_refs()`\nfunction, as 'packing' is the method used to optimize the files-based\nreference store.\n\nWire up the new `files_optimize()` function to the `optimize` slot in\nthe files backend's virtual table.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs/files-backend.c | 10 ++++++++++\n 1 file changed, 10 insertions(+)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex dfc8e9bc50..1428d3a6f1 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1473,6 +1473,15 @@ static int files_pack_refs(struct ref_store *ref_store,\n \treturn 0;\n }\n \n+static int files_optimize(struct ref_store *ref_store, struct pack_refs_opts *opts)\n+{\n+\t/*\n+\t * For the \"files\" backend, \"optimizing\" is the same as \"packing\".\n+\t * So, we just call the existing worker function for packing.\n+\t */\n+\treturn files_pack_refs(ref_store, opts);\n+}\n+\n /*\n  * People using contrib's git-new-workdir have .git/logs/refs ->\n  * /some/other/path/.git/logs/refs, and that may live on another device.\n@@ -3909,6 +3918,7 @@ struct ref_storage_be refs_be_files = {\n \t.transaction_abort = files_transaction_abort,\n \n \t.pack_refs = files_pack_refs,\n+\t.optimize = files_optimize,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \n-- \n2.34.1\n\n"},{"id":"526761","messageId":"20250919082647.535213-4-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 3/9] reftable-backend: implement 'optimize' action","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:41Z","receivedAt":"2025-09-19T08:27:01Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"To make the new generic `optimize` API fully functional, provide an\nimplementation for the 'reftable' reference backend.\n\nFor the reftable backend, the 'optimize' action is to compact its\ntables. The existing `reftable_be_pack_refs()` function already provides\nthis logic, so the new `reftable_be_optimize()` function simply calls\nit.\n\nWire up the new function to the `optimize` slot in the reftable\nbackend's virtual table.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n refs/reftable-backend.c | 7 +++++++\n 1 file changed, 7 insertions(+)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 570463da41..5dff1e08e5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1721,6 +1721,12 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,\n \treturn ret;\n }\n \n+static int reftable_be_optimize(struct ref_store *ref_store,\n+\t\t\t\tstruct pack_refs_opts *opts)\n+{\n+\treturn reftable_be_pack_refs(ref_store, opts);\n+}\n+\n struct write_create_symref_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2702,6 +2708,7 @@ struct ref_storage_be refs_be_reftable = {\n \t.transaction_abort = reftable_be_transaction_abort,\n \n \t.pack_refs = reftable_be_pack_refs,\n+\t.optimize = reftable_be_optimize,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.34.1\n\n"},{"id":"526762","messageId":"20250919082647.535213-5-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 4/9] builtin/pack-refs: convert to use the generic refs_optimize() API","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:42Z","receivedAt":"2025-09-19T08:27:04Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The `git pack-refs` command behaves generically, triggering a pack for\nthe 'files' backend and a compaction for the 'reftable' backend.\nHowever, the name of the command and its corresponding API is\nconceptually tied to the 'files' backend implementation.\n\nTo create a cleaner, more generic interface, refactor `git pack-refs` to\nuse the new `refs_optimize()` API. \"Optimize\" is a better semantic term\nfor this generic action.\n\nThis change allows `git pack-refs` to act as a backend-agnostic frontend\nfor reference optimization, and paves the way for the new `git refs\noptimize` command to do the same.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n builtin/pack-refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/pack-refs.c b/builtin/pack-refs.c\nindex 5e28d0f9e8..dfcf664524 100644\n--- a/builtin/pack-refs.c\n+++ b/builtin/pack-refs.c\n@@ -51,7 +51,7 @@ int cmd_pack_refs(int argc,\n \tif (!pack_refs_opts.includes->nr)\n \t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n \n-\tret = refs_pack_refs(get_main_ref_store(repo), &pack_refs_opts);\n+\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n \n \tclear_ref_exclusions(&excludes);\n \tstring_list_clear(&included_refs, 0);\n-- \n2.34.1\n\n"},{"id":"526763","messageId":"20250919082647.535213-6-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 5/9] builtin/pack-refs: factor out core logic into a shared library","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:43Z","receivedAt":"2025-09-19T08:27:07Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"The implementation of `git pack-refs` is monolithic within\n`cmd_pack_refs()`, making it impossible to share its logic with other\ncommands. To enable code reuse for the upcoming `git refs optimize`\nsubcommand, refactor the core logic into a shared helper function.\n\nSplit the original `builtin/pack-refs.c` file into two parts:\n\n- A new shared library file, `pack-refs.c`, which contains the\n  core option parsing and packing logic in a new `pack_refs_core()`\n  helper function.\n\n- The original `builtin/pack-refs.c`, which is now a thin wrapper\n  responsible only for defining the `git pack-refs` command and\n  calling the shared helper.\n\nA new `pack-refs.h` header is also introduced to define the public\ninterface for this shared logic.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Makefile            |  1 +\n builtin/pack-refs.c | 54 ++++---------------------------------------\n meson.build         |  1 +\n pack-refs.c         | 56 +++++++++++++++++++++++++++++++++++++++++++++\n pack-refs.h         | 23 +++++++++++++++++++\n 5 files changed, 86 insertions(+), 49 deletions(-)\n create mode 100644 pack-refs.c\n create mode 100644 pack-refs.h\n\ndiff --git a/Makefile b/Makefile\nindex 555b7f4dc3..f51297ffc3 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1094,6 +1094,7 @@ LIB_OBJS += pack-bitmap.o\n LIB_OBJS += pack-check.o\n LIB_OBJS += pack-mtimes.o\n LIB_OBJS += pack-objects.o\n+LIB_OBJS += pack-refs.o\n LIB_OBJS += pack-revindex.o\n LIB_OBJS += pack-write.o\n LIB_OBJS += packfile.o\ndiff --git a/builtin/pack-refs.c b/builtin/pack-refs.c\nindex dfcf664524..3446b84cda 100644\n--- a/builtin/pack-refs.c\n+++ b/builtin/pack-refs.c\n@@ -1,60 +1,16 @@\n #include \"builtin.h\"\n-#include \"config.h\"\n-#include \"environment.h\"\n #include \"gettext.h\"\n-#include \"parse-options.h\"\n-#include \"refs.h\"\n-#include \"revision.h\"\n-\n-static char const * const pack_refs_usage[] = {\n-\tN_(\"git pack-refs [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"),\n-\tNULL\n-};\n+#include \"pack-refs.h\"\n \n int cmd_pack_refs(int argc,\n \t\t  const char **argv,\n \t\t  const char *prefix,\n \t\t  struct repository *repo)\n {\n-\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n-\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n-\tstruct pack_refs_opts pack_refs_opts = {\n-\t\t.exclusions = &excludes,\n-\t\t.includes = &included_refs,\n-\t\t.flags = PACK_REFS_PRUNE,\n-\t};\n-\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n-\tstruct string_list_item *item;\n-\tint pack_all = 0;\n-\tint ret;\n-\n-\tstruct option opts[] = {\n-\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n-\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n-\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n-\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n-\t\t\tN_(\"references to include\")),\n-\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n-\t\t\tN_(\"references to exclude\")),\n-\t\tOPT_END(),\n+\tstatic char const * const pack_refs_usage[] = {\n+\t\tN_(\"git pack-refs \" PACK_REFS_OPTS),\n+\t\tNULL\n \t};\n-\trepo_config(repo, git_default_config, NULL);\n-\tif (parse_options(argc, argv, prefix, opts, pack_refs_usage, 0))\n-\t\tusage_with_options(pack_refs_usage, opts);\n-\n-\tfor_each_string_list_item(item, &option_excluded_refs)\n-\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n-\n-\tif (pack_all)\n-\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n-\n-\tif (!pack_refs_opts.includes->nr)\n-\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n-\n-\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n \n-\tclear_ref_exclusions(&excludes);\n-\tstring_list_clear(&included_refs, 0);\n-\tstring_list_clear(&option_excluded_refs, 0);\n-\treturn ret;\n+\treturn pack_refs_core(argc, argv, prefix, repo, pack_refs_usage);\n }\ndiff --git a/meson.build b/meson.build\nindex e8ec0eca16..cedaadad2e 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -407,6 +407,7 @@ libgit_sources = [\n   'pack-check.c',\n   'pack-mtimes.c',\n   'pack-objects.c',\n+  'pack-refs.c',\n   'pack-revindex.c',\n   'pack-write.c',\n   'packfile.c',\ndiff --git a/pack-refs.c b/pack-refs.c\nnew file mode 100644\nindex 0000000000..1a5e07d8b8\n--- /dev/null\n+++ b/pack-refs.c\n@@ -0,0 +1,56 @@\n+#include \"builtin.h\"\n+#include \"config.h\"\n+#include \"environment.h\"\n+#include \"pack-refs.h\"\n+#include \"parse-options.h\"\n+#include \"refs.h\"\n+#include \"revision.h\"\n+\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts)\n+{\n+\tstruct ref_exclusions excludes = REF_EXCLUSIONS_INIT;\n+\tstruct string_list included_refs = STRING_LIST_INIT_NODUP;\n+\tstruct pack_refs_opts pack_refs_opts = {\n+\t\t.exclusions = &excludes,\n+\t\t.includes = &included_refs,\n+\t\t.flags = PACK_REFS_PRUNE,\n+\t};\n+\tstruct string_list option_excluded_refs = STRING_LIST_INIT_NODUP;\n+\tstruct string_list_item *item;\n+\tint pack_all = 0;\n+\tint ret;\n+\n+\tstruct option opts[] = {\n+\t\tOPT_BOOL(0, \"all\",   &pack_all, N_(\"pack everything\")),\n+\t\tOPT_BIT(0, \"prune\", &pack_refs_opts.flags, N_(\"prune loose refs (default)\"), PACK_REFS_PRUNE),\n+\t\tOPT_BIT(0, \"auto\", &pack_refs_opts.flags, N_(\"auto-pack refs as needed\"), PACK_REFS_AUTO),\n+\t\tOPT_STRING_LIST(0, \"include\", pack_refs_opts.includes, N_(\"pattern\"),\n+\t\t\tN_(\"references to include\")),\n+\t\tOPT_STRING_LIST(0, \"exclude\", &option_excluded_refs, N_(\"pattern\"),\n+\t\t\tN_(\"references to exclude\")),\n+\t\tOPT_END(),\n+\t};\n+\trepo_config(repo, git_default_config, NULL);\n+\tif (parse_options(argc, argv, prefix, opts, usage_opts, 0))\n+\t\tusage_with_options(usage_opts, opts);\n+\n+\tfor_each_string_list_item(item, &option_excluded_refs)\n+\t\tadd_ref_exclusion(pack_refs_opts.exclusions, item->string);\n+\n+\tif (pack_all)\n+\t\tstring_list_append(pack_refs_opts.includes, \"*\");\n+\n+\tif (!pack_refs_opts.includes->nr)\n+\t\tstring_list_append(pack_refs_opts.includes, \"refs/tags/*\");\n+\n+\tret = refs_optimize(get_main_ref_store(repo), &pack_refs_opts);\n+\n+\tclear_ref_exclusions(&excludes);\n+\tstring_list_clear(&included_refs, 0);\n+\tstring_list_clear(&option_excluded_refs, 0);\n+\treturn ret;\n+}\ndiff --git a/pack-refs.h b/pack-refs.h\nnew file mode 100644\nindex 0000000000..5de27e7da8\n--- /dev/null\n+++ b/pack-refs.h\n@@ -0,0 +1,23 @@\n+#ifndef PACK_REFS_H\n+#define PACK_REFS_H\n+\n+struct repository;\n+\n+/*\n+ * Shared usage string for options common to git-pack-refs(1)\n+ * and git-refs-optimize(1). The command-specific part (e.g., \"git refs optimize \")\n+ * must be prepended by the caller.\n+ */\n+#define PACK_REFS_OPTS \\\n+\t\"[--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"\n+\n+/*\n+ * The core logic for pack-refs and its clones.\n+ */\n+int pack_refs_core(int argc,\n+\t\t   const char **argv,\n+\t\t   const char *prefix,\n+\t\t   struct repository *repo,\n+\t\t   const char * const *usage_opts);\n+\n+#endif /* PACK_REFS_H */\n-- \n2.34.1\n\n"},{"id":"526764","messageId":"20250919082647.535213-7-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 6/9] doc: pack-refs: factor out common options","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:44Z","receivedAt":"2025-09-19T08:27:09Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding documentation for `git refs optimize`, factor\nout the common options from the `git-pack-refs` man page into a\nshareable file `pack-refs-options.adoc` and update `git-pack-refs.adoc`\nto use an `include::` macro.\n\nThis change is a pure refactoring and results in no change to the final\nrendered documentation for `pack-refs`.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-pack-refs.adoc     | 53 +---------------------------\n Documentation/pack-refs-options.adoc | 52 +++++++++++++++++++++++++++\n 2 files changed, 53 insertions(+), 52 deletions(-)\n create mode 100644 Documentation/pack-refs-options.adoc\n\ndiff --git a/Documentation/git-pack-refs.adoc b/Documentation/git-pack-refs.adoc\nindex 42b90051e6..fde9f2f294 100644\n--- a/Documentation/git-pack-refs.adoc\n+++ b/Documentation/git-pack-refs.adoc\n@@ -45,58 +45,7 @@ unpacked.\n OPTIONS\n -------\n \n---all::\n-\n-The command by default packs all tags and refs that are already\n-packed, and leaves other refs\n-alone.  This is because branches are expected to be actively\n-developed and packing their tips does not help performance.\n-This option causes all refs to be packed as well, with the exception\n-of hidden refs, broken refs, and symbolic refs. Useful for a repository\n-with many branches of historical interests.\n-\n---no-prune::\n-\n-The command usually removes loose refs under `$GIT_DIR/refs`\n-hierarchy after packing them.  This option tells it not to.\n-\n---auto::\n-\n-Pack refs as needed depending on the current state of the ref database. The\n-behavior depends on the ref format used by the repository and may change in the\n-future.\n-+\n-\t- \"files\": Loose references are packed into the `packed-refs` file\n-\t  based on the ratio of loose references to the size of the\n-\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n-\t  references need to exist before we repack.\n-+\n-\t- \"reftable\": Tables are compacted such that they form a geometric\n-\t  sequence. For two tables N and N+1, where N+1 is newer, this\n-\t  maintains the property that N is at least twice as big as N+1. Only\n-\t  tables that violate this property are compacted.\n-\n---include <pattern>::\n-\n-Pack refs based on a `glob(7)` pattern. Repetitions of this option\n-accumulate inclusion patterns. If a ref is both included in `--include` and\n-`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n-tags from being included by default. Symbolic refs and broken refs will never\n-be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n-and reset the list of patterns.\n-\n---exclude <pattern>::\n-\n-Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n-accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n-patterns. If a ref is already packed, including it with `--exclude` will not\n-unpack it.\n-+\n-When used with `--all`, pack only loose refs which do not match any of\n-the provided `--exclude` patterns.\n-+\n-When used with `--include`, refs provided to `--include`, minus refs that are\n-provided to `--exclude` will be packed.\n+include::pack-refs-options.adoc[]\n \n \n BUGS\ndiff --git a/Documentation/pack-refs-options.adoc b/Documentation/pack-refs-options.adoc\nnew file mode 100644\nindex 0000000000..0b11282941\n--- /dev/null\n+++ b/Documentation/pack-refs-options.adoc\n@@ -0,0 +1,52 @@\n+--all::\n+\n+The command by default packs all tags and refs that are already\n+packed, and leaves other refs\n+alone.  This is because branches are expected to be actively\n+developed and packing their tips does not help performance.\n+This option causes all refs to be packed as well, with the exception\n+of hidden refs, broken refs, and symbolic refs. Useful for a repository\n+with many branches of historical interests.\n+\n+--no-prune::\n+\n+The command usually removes loose refs under `$GIT_DIR/refs`\n+hierarchy after packing them.  This option tells it not to.\n+\n+--auto::\n+\n+Pack refs as needed depending on the current state of the ref database. The\n+behavior depends on the ref format used by the repository and may change in the\n+future.\n++\n+\t- \"files\": Loose references are packed into the `packed-refs` file\n+\t  based on the ratio of loose references to the size of the\n+\t  `packed-refs` file. The bigger the `packed-refs` file, the more loose\n+\t  references need to exist before we repack.\n++\n+\t- \"reftable\": Tables are compacted such that they form a geometric\n+\t  sequence. For two tables N and N+1, where N+1 is newer, this\n+\t  maintains the property that N is at least twice as big as N+1. Only\n+\t  tables that violate this property are compacted.\n+\n+--include <pattern>::\n+\n+Pack refs based on a `glob(7)` pattern. Repetitions of this option\n+accumulate inclusion patterns. If a ref is both included in `--include` and\n+`--exclude`, `--exclude` takes precedence. Using `--include` will preclude all\n+tags from being included by default. Symbolic refs and broken refs will never\n+be packed. When used with `--all`, it will be a noop. Use `--no-include` to clear\n+and reset the list of patterns.\n+\n+--exclude <pattern>::\n+\n+Do not pack refs matching the given `glob(7)` pattern. Repetitions of this option\n+accumulate exclusion patterns. Use `--no-exclude` to clear and reset the list of\n+patterns. If a ref is already packed, including it with `--exclude` will not\n+unpack it.\n++\n+When used with `--all`, pack only loose refs which do not match any of\n+the provided `--exclude` patterns.\n++\n+When used with `--include`, refs provided to `--include`, minus refs that are\n+provided to `--exclude` will be packed.\n-- \n2.34.1\n\n"},{"id":"526765","messageId":"20250919082647.535213-8-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 7/9] builtin/refs: add optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:45Z","receivedAt":"2025-09-19T08:27:12Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"As part of the ongoing effort to consolidate reference handling,\nintroduce a new `optimize` subcommand. This command provides the same\nfunctionality and exit-code behavior as `git pack-refs`, serving as its\nmodern replacement.\n\nImplement `cmd_refs_optimize` by having it call the `pack_refs_core()`\nhelper function. This helper was factored out of the original\n`cmd_pack_refs` in a preceding commit, allowing both commands to share\nthe same core logic as independent peers.\n\nAdd documentation for the new command. The man page leverages the shared\noptions file, created in a previous commit, by using the AsciiDoc\n`include::` macro to ensure consistency with git-pack-refs(1).\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n Documentation/git-refs.adoc | 10 ++++++++++\n builtin/refs.c              | 17 +++++++++++++++++\n 2 files changed, 27 insertions(+)\n\ndiff --git a/Documentation/git-refs.adoc b/Documentation/git-refs.adoc\nindex d462953fb5..e233f21eeb 100644\n--- a/Documentation/git-refs.adoc\n+++ b/Documentation/git-refs.adoc\n@@ -18,6 +18,7 @@ git refs list [--count=<count>] [--shell|--perl|--python|--tcl]\n \t\t   [--contains[=<object>]] [--no-contains[=<object>]]\n \t\t   [(--exclude=<pattern>)...] [--start-after=<marker>]\n \t\t   [ --stdin | (<pattern>...)]\n+git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\n \n DESCRIPTION\n -----------\n@@ -38,6 +39,11 @@ list::\n \tformatting, and sorting. This subcommand is an alias for\n \tlinkgit:git-for-each-ref[1] and offers identical functionality.\n \n+optimize::\n+\tOptimizes references to improve repository performance and reduce disk\n+\tusage. This subcommand is an alias for linkgit:git-pack-refs[1] and\n+\toffers identical functionality.\n+\n OPTIONS\n -------\n \n@@ -73,6 +79,10 @@ The following options are specific to 'git refs list':\n \n include::for-each-ref-options.adoc[]\n \n+The following options are specific to 'git refs optimize':\n+\n+include::pack-refs-options.adoc[]\n+\n KNOWN LIMITATIONS\n -----------------\n \ndiff --git a/builtin/refs.c b/builtin/refs.c\nindex 76224feba4..785f476e4b 100644\n--- a/builtin/refs.c\n+++ b/builtin/refs.c\n@@ -2,6 +2,7 @@\n #include \"builtin.h\"\n #include \"config.h\"\n #include \"fsck.h\"\n+#include \"pack-refs.h\"\n #include \"parse-options.h\"\n #include \"refs.h\"\n #include \"strbuf.h\"\n@@ -14,6 +15,9 @@\n #define REFS_VERIFY_USAGE \\\n \tN_(\"git refs verify [--strict] [--verbose]\")\n \n+#define REFS_OPTIMIZE_USAGE \\\n+\tN_(\"git refs optimize \" PACK_REFS_OPTS)\n+\n static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,\n \t\t\t    struct repository *repo UNUSED)\n {\n@@ -113,6 +117,17 @@ static int cmd_refs_list(int argc, const char **argv, const char *prefix,\n \treturn for_each_ref_core(argc, argv, prefix, repo, refs_list_usage);\n }\n \n+static int cmd_refs_optimize(int argc, const char **argv, const char *prefix,\n+\t\t\t     struct repository *repo)\n+{\n+\tstatic char const * const refs_optimize_usage[] = {\n+\t\tREFS_OPTIMIZE_USAGE,\n+\t\tNULL\n+\t};\n+\n+\treturn pack_refs_core(argc, argv, prefix, repo, refs_optimize_usage);\n+}\n+\n int cmd_refs(int argc,\n \t     const char **argv,\n \t     const char *prefix,\n@@ -122,6 +137,7 @@ int cmd_refs(int argc,\n \t\tREFS_MIGRATE_USAGE,\n \t\tREFS_VERIFY_USAGE,\n \t\t\"git refs list \" COMMON_USAGE_FOR_EACH_REF,\n+\t\tREFS_OPTIMIZE_USAGE,\n \t\tNULL,\n \t};\n \tparse_opt_subcommand_fn *fn = NULL;\n@@ -129,6 +145,7 @@ int cmd_refs(int argc,\n \t\tOPT_SUBCOMMAND(\"migrate\", &fn, cmd_refs_migrate),\n \t\tOPT_SUBCOMMAND(\"verify\", &fn, cmd_refs_verify),\n \t\tOPT_SUBCOMMAND(\"list\", &fn, cmd_refs_list),\n+\t\tOPT_SUBCOMMAND(\"optimize\", &fn, cmd_refs_optimize),\n \t\tOPT_END(),\n \t};\n \n-- \n2.34.1\n\n"},{"id":"526766","messageId":"20250919082647.535213-9-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 8/9] t0601: refactor tests to be shareable","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:46Z","receivedAt":"2025-09-19T08:27:15Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"In preparation for adding tests for the new `git refs optimize` command,\nrefactor the existing t0601 test suite to make its logic shareable.\n\nMove the core test logic from `t0601-reffiles-pack-refs.sh` into a new\n`pack-refs-tests.sh` file. Inside this new script, replace hardcoded\ncalls to \"pack-refs\" with the `$pack_refs` variable.\n\nThe original `t0601-reffiles-pack-refs.sh` script now becomes a simple\n\"driver\". It is responsible for setting the default value of the\nvariable and then sourcing the test library.\n\nThis new structure follows the established pattern used for sharing\ntests between `git-for-each-ref` and `git-refs list` and prepares the\ntest suite for the `refs optimize` tests to be added in a subsequent\ncommit.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/pack-refs-tests.sh          | 431 ++++++++++++++++++++++++++++++++++\n t/t0601-reffiles-pack-refs.sh | 430 +--------------------------------\n 2 files changed, 432 insertions(+), 429 deletions(-)\n create mode 100644 t/pack-refs-tests.sh\n\ndiff --git a/t/pack-refs-tests.sh b/t/pack-refs-tests.sh\nnew file mode 100644\nindex 0000000000..3dbcc01718\n--- /dev/null\n+++ b/t/pack-refs-tests.sh\n@@ -0,0 +1,431 @@\n+pack_refs=${pack_refs:-pack-refs}\n+\n+test_expect_success 'enable reflogs' '\n+\tgit config core.logallrefupdates true\n+'\n+\n+test_expect_success 'prepare a trivial repository' '\n+\techo Hello > A &&\n+\tgit update-index --add A &&\n+\tgit commit -m \"Initial commit.\" &&\n+\tHEAD=$(git rev-parse --verify HEAD)\n+'\n+\n+test_expect_success '${pack_refs} --prune --all' '\n+\ttest_path_is_missing .git/packed-refs &&\n+\tgit ${pack_refs} --no-prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f | wc -l) &&\n+\ttest \"$N\" != 0 &&\n+\n+\tgit ${pack_refs} --prune --all &&\n+\ttest_path_is_file .git/packed-refs &&\n+\tN=$(find .git/refs -type f) &&\n+\ttest -z \"$N\"\n+'\n+\n+SHA1=\n+\n+test_expect_success 'see if git show-ref works as expected' '\n+\tgit branch a &&\n+\tSHA1=$(cat .git/refs/heads/a) &&\n+\techo \"$SHA1 refs/heads/a\" >expect &&\n+\tgit show-ref a >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if a branch still exists when packed' '\n+\tgit branch b &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/b &&\n+\techo \"$SHA1 refs/heads/b\" >expect &&\n+\tgit show-ref b >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'git branch c/d should barf if branch c exists' '\n+\tgit branch c &&\n+\tgit ${pack_refs} --all &&\n+\trm -f .git/refs/heads/c &&\n+\ttest_must_fail git branch c/d\n+'\n+\n+test_expect_success 'see if a branch still exists after git ${pack_refs} --prune' '\n+\tgit branch e &&\n+\tgit ${pack_refs} --all --prune &&\n+\techo \"$SHA1 refs/heads/e\" >expect &&\n+\tgit show-ref e >result &&\n+\ttest_cmp expect result\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune remove ref files' '\n+\tgit branch f &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -f .git/refs/heads/f\n+'\n+\n+test_expect_success 'see if git ${pack_refs} --prune removes empty dirs' '\n+\tgit branch r/s/t &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! test -e .git/refs/heads/r\n+'\n+\n+test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n+\tgit branch g/h &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d g/h &&\n+\tgit branch g &&\n+\tgit ${pack_refs} --all &&\n+\tgit branch -d g\n+'\n+\n+test_expect_success 'git branch i/j/k should barf if branch i exists' '\n+\tgit branch i &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_must_fail git branch i/j/k\n+'\n+\n+test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n+\tgit branch k/l &&\n+\tgit branch k/lm &&\n+\tgit branch -d k/l &&\n+\tgit branch k/l/m &&\n+\tgit branch -d k/l/m &&\n+\tgit branch -d k/lm &&\n+\tgit branch k\n+'\n+\n+test_expect_success 'test git branch n after some branch deletion and pruning' '\n+\tgit branch n/o &&\n+\tgit branch n/op &&\n+\tgit branch -d n/o &&\n+\tgit branch n/o/p &&\n+\tgit branch -d n/op &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit branch -d n/o/p &&\n+\tgit branch n\n+'\n+\n+test_expect_success 'test excluded refs are not packed' '\n+\tgit branch dont_pack1 &&\n+\tgit branch dont_pack2 &&\n+\tgit branch pack_this &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack1 &&\n+\ttest -f .git/refs/heads/dont_pack2 &&\n+\t! test -f .git/refs/heads/pack_this'\n+\n+test_expect_success 'test --no-exclude refs clears excluded refs' '\n+\tgit branch dont_pack3 &&\n+\tgit branch dont_pack4 &&\n+\tgit ${pack_refs} --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n+\t! test -f .git/refs/heads/dont_pack3 &&\n+\t! test -f .git/refs/heads/dont_pack4'\n+\n+test_expect_success 'test only included refs are packed' '\n+\tgit branch pack_this1 &&\n+\tgit branch pack_this2 &&\n+\tgit tag dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack_this*\" &&\n+\ttest -f .git/refs/tags/dont_pack5 &&\n+\t! test -f .git/refs/heads/pack_this1 &&\n+\t! test -f .git/refs/heads/pack_this2'\n+\n+test_expect_success 'test --no-include refs clears included refs' '\n+\tgit branch pack1 &&\n+\tgit branch pack2 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --no-include &&\n+\ttest -f .git/refs/heads/pack1 &&\n+\ttest -f .git/refs/heads/pack2'\n+\n+test_expect_success 'test --exclude takes precedence over --include' '\n+\tgit branch dont_pack5 &&\n+\tgit ${pack_refs} --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n+\ttest -f .git/refs/heads/dont_pack5'\n+\n+test_expect_success 'see if up-to-date packed refs are preserved' '\n+\tgit branch q &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit update-ref refs/heads/q refs/heads/q &&\n+\t! test -f .git/refs/heads/q\n+'\n+\n+test_expect_success 'pack, prune and repack' '\n+\tgit tag foo &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit show-ref >all-of-them &&\n+\tgit ${pack_refs} &&\n+\tgit show-ref >again &&\n+\ttest_cmp all-of-them again\n+'\n+\n+test_expect_success 'explicit ${pack_refs} with dangling packed reference' '\n+\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit ${pack_refs} --all 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref with dangling packed version' '\n+\tgit checkout -b lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit checkout main &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'delete ref while another dangling packed ref' '\n+\tgit branch lamb &&\n+\tgit commit --allow-empty -m \"future garbage\" &&\n+\tgit ${pack_refs} --all &&\n+\tgit reset --hard HEAD^ &&\n+\tgit reflog expire --expire=all --all &&\n+\tgit prune --expire=all &&\n+\tgit branch -d lamb 2>result &&\n+\ttest_must_be_empty result\n+'\n+\n+test_expect_success 'pack ref directly below refs/' '\n+\tgit update-ref refs/top HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgrep refs/top .git/packed-refs &&\n+\ttest_path_is_missing .git/refs/top\n+'\n+\n+test_expect_success 'do not pack ref in refs/bisect' '\n+\tgit update-ref refs/bisect/local HEAD &&\n+\tgit ${pack_refs} --all --prune &&\n+\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n+\ttest_path_is_file .git/refs/bisect/local\n+'\n+\n+test_expect_success 'disable reflogs' '\n+\tgit config core.logallrefupdates false &&\n+\trm -rf .git/logs\n+'\n+\n+test_expect_success 'create packed foo/bar/baz branch' '\n+\tgit branch foo/bar/baz &&\n+\tgit ${pack_refs} --all --prune &&\n+\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n+\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n+'\n+\n+test_expect_success 'notice d/f conflict with existing directory' '\n+\ttest_must_fail git branch foo &&\n+\ttest_must_fail git branch foo/bar\n+'\n+\n+test_expect_success 'existing directory reports concrete ref' '\n+\ttest_must_fail git branch foo 2>stderr &&\n+\ttest_grep refs/heads/foo/bar/baz stderr\n+'\n+\n+test_expect_success 'notice d/f conflict with existing ref' '\n+\ttest_must_fail git branch foo/bar/baz/extra &&\n+\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n+'\n+\n+test_expect_success 'reject packed-refs with unterminated line' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n+\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs containing junk' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n+\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'reject packed-refs with a short SHA-1' '\n+\tcp .git/packed-refs .git/packed-refs.bak &&\n+\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n+\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n+\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n+\ttest_must_fail git for-each-ref >out 2>err &&\n+\ttest_cmp expected_err err\n+'\n+\n+test_expect_success 'timeout if packed-refs.lock exists' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"rm -f $LOCK\" &&\n+\ttest_must_fail git ${pack_refs} --all --prune\n+'\n+\n+test_expect_success 'retry acquiring packed-refs.lock' '\n+\tLOCK=.git/packed-refs.lock &&\n+\t>\"$LOCK\" &&\n+\ttest_when_finished \"wait && rm -f $LOCK\" &&\n+\t{\n+\t\t( sleep 1 && rm -f $LOCK ) &\n+\t} &&\n+\tgit -c core.packedrefstimeout=3000 ${pack_refs} --all --prune\n+'\n+\n+test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n+\t# First make sure that symlinking works when reading:\n+\tgit update-ref refs/heads/lossy refs/heads/main &&\n+\tgit for-each-ref >all-refs-before &&\n+\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n+\tln -s my-deviant-packed-refs .git/packed-refs &&\n+\tgit for-each-ref >all-refs-linked &&\n+\ttest_cmp all-refs-before all-refs-linked &&\n+\tgit ${pack_refs} --all --prune &&\n+\tgit for-each-ref >all-refs-packed &&\n+\ttest_cmp all-refs-before all-refs-packed &&\n+\ttest -h .git/packed-refs &&\n+\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n+'\n+\n+# The 'packed-refs' file is stored directly in .git/. This means it is global\n+# to the repository, and can only contain refs that are shared across all\n+# worktrees.\n+test_expect_success 'refs/worktree must not be packed' '\n+\ttest_commit initial &&\n+\ttest_commit wt1 &&\n+\ttest_commit wt2 &&\n+\tgit worktree add wt1 wt1 &&\n+\tgit worktree add wt2 wt2 &&\n+\tgit checkout initial &&\n+\tgit update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n+\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n+\tgit ${pack_refs} --all &&\n+\ttest_path_is_missing .git/refs/tags/wt1 &&\n+\ttest_path_is_file .git/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n+\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n+'\n+\n+# we do not want to count on running ${pack_refs} to\n+# actually pack it, as it is perfectly reasonable to\n+# skip processing a broken ref\n+test_expect_success 'create packed-refs file with broken ref' '\n+\ttest_tick && git commit --allow-empty -m one &&\n+\trecoverable=$(git rev-parse HEAD) &&\n+\ttest_tick && git commit --allow-empty -m two &&\n+\tmissing=$(git rev-parse HEAD) &&\n+\trm -f .git/refs/heads/main &&\n+\tcat >.git/packed-refs <<-EOF &&\n+\t$missing refs/heads/main\n+\t$recoverable refs/heads/other\n+\tEOF\n+\techo $missing >expect &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not silently delete broken packed ref' '\n+\tgit ${pack_refs} --all --prune &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success '${pack_refs} does not drop broken refs during deletion' '\n+\tgit update-ref -d refs/heads/other &&\n+\tgit rev-parse refs/heads/main >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+for command in \"git ${pack_refs} --all --auto\" \"git maintenance run --task=${pack_refs} --auto\"\n+do\n+\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 14 additional references, which brings us to\n+\t\t\t# 15 together with the default branch.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_missing .git/packed-refs &&\n+\n+\t\t\t# Create the 16th reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_path_is_file .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create 15 loose references.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 2 .git/packed-refs &&\n+\n+\t\t\t# Create the 16th loose reference, which should cause us to repack.\n+\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 18 .git/packed-refs\n+\t\t)\n+\t'\n+\n+\ttest_expect_success \"$command scales with size of packed-refs\" '\n+\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\tgit init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\t\t\tgit config set maintenance.auto false &&\n+\t\t\tgit commit --allow-empty --message \"initial\" &&\n+\n+\t\t\t# Create 99 packed refs. This should cause the heuristic\n+\t\t\t# to require more than the minimum amount of loose refs.\n+\t\t\ttest_seq 99 |\n+\t\t\twhile read i\n+\t\t\tdo\n+\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n+\t\t\tdone >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n+\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 101 .git/packed-refs &&\n+\n+\t\t\t# Create another handful of refs to cross the border.\n+\t\t\t# Note that we explicitly do not check for strict\n+\t\t\t# boundaries here, as this also depends on the size of\n+\t\t\t# the object hash.\n+\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n+\t\t\tgit update-ref --stdin <stdin &&\n+\t\t\tgit ${pack_refs} --auto --all &&\n+\t\t\ttest_line_count = 135 .git/packed-refs\n+\t\t)\n+\t'\n+done\n+\n+test_done\ndiff --git a/t/t0601-reffiles-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh\nindex aa7f6ecd81..12cf5d1dcb 100755\n--- a/t/t0601-reffiles-pack-refs.sh\n+++ b/t/t0601-reffiles-pack-refs.sh\n@@ -17,432 +17,4 @@ export GIT_TEST_DEFAULT_REF_FORMAT\n \n . ./test-lib.sh\n \n-test_expect_success 'enable reflogs' '\n-\tgit config core.logallrefupdates true\n-'\n-\n-test_expect_success 'prepare a trivial repository' '\n-\techo Hello > A &&\n-\tgit update-index --add A &&\n-\tgit commit -m \"Initial commit.\" &&\n-\tHEAD=$(git rev-parse --verify HEAD)\n-'\n-\n-test_expect_success 'pack-refs --prune --all' '\n-\ttest_path_is_missing .git/packed-refs &&\n-\tgit pack-refs --no-prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f | wc -l) &&\n-\ttest \"$N\" != 0 &&\n-\n-\tgit pack-refs --prune --all &&\n-\ttest_path_is_file .git/packed-refs &&\n-\tN=$(find .git/refs -type f) &&\n-\ttest -z \"$N\"\n-'\n-\n-SHA1=\n-\n-test_expect_success 'see if git show-ref works as expected' '\n-\tgit branch a &&\n-\tSHA1=$(cat .git/refs/heads/a) &&\n-\techo \"$SHA1 refs/heads/a\" >expect &&\n-\tgit show-ref a >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if a branch still exists when packed' '\n-\tgit branch b &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/b &&\n-\techo \"$SHA1 refs/heads/b\" >expect &&\n-\tgit show-ref b >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'git branch c/d should barf if branch c exists' '\n-\tgit branch c &&\n-\tgit pack-refs --all &&\n-\trm -f .git/refs/heads/c &&\n-\ttest_must_fail git branch c/d\n-'\n-\n-test_expect_success 'see if a branch still exists after git pack-refs --prune' '\n-\tgit branch e &&\n-\tgit pack-refs --all --prune &&\n-\techo \"$SHA1 refs/heads/e\" >expect &&\n-\tgit show-ref e >result &&\n-\ttest_cmp expect result\n-'\n-\n-test_expect_success 'see if git pack-refs --prune remove ref files' '\n-\tgit branch f &&\n-\tgit pack-refs --all --prune &&\n-\t! test -f .git/refs/heads/f\n-'\n-\n-test_expect_success 'see if git pack-refs --prune removes empty dirs' '\n-\tgit branch r/s/t &&\n-\tgit pack-refs --all --prune &&\n-\t! test -e .git/refs/heads/r\n-'\n-\n-test_expect_success 'git branch g should work when git branch g/h has been deleted' '\n-\tgit branch g/h &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d g/h &&\n-\tgit branch g &&\n-\tgit pack-refs --all &&\n-\tgit branch -d g\n-'\n-\n-test_expect_success 'git branch i/j/k should barf if branch i exists' '\n-\tgit branch i &&\n-\tgit pack-refs --all --prune &&\n-\ttest_must_fail git branch i/j/k\n-'\n-\n-test_expect_success 'test git branch k after branch k/l/m and k/lm have been deleted' '\n-\tgit branch k/l &&\n-\tgit branch k/lm &&\n-\tgit branch -d k/l &&\n-\tgit branch k/l/m &&\n-\tgit branch -d k/l/m &&\n-\tgit branch -d k/lm &&\n-\tgit branch k\n-'\n-\n-test_expect_success 'test git branch n after some branch deletion and pruning' '\n-\tgit branch n/o &&\n-\tgit branch n/op &&\n-\tgit branch -d n/o &&\n-\tgit branch n/o/p &&\n-\tgit branch -d n/op &&\n-\tgit pack-refs --all --prune &&\n-\tgit branch -d n/o/p &&\n-\tgit branch n\n-'\n-\n-test_expect_success 'test excluded refs are not packed' '\n-\tgit branch dont_pack1 &&\n-\tgit branch dont_pack2 &&\n-\tgit branch pack_this &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack1 &&\n-\ttest -f .git/refs/heads/dont_pack2 &&\n-\t! test -f .git/refs/heads/pack_this'\n-\n-test_expect_success 'test --no-exclude refs clears excluded refs' '\n-\tgit branch dont_pack3 &&\n-\tgit branch dont_pack4 &&\n-\tgit pack-refs --all --exclude \"refs/heads/dont_pack*\" --no-exclude &&\n-\t! test -f .git/refs/heads/dont_pack3 &&\n-\t! test -f .git/refs/heads/dont_pack4'\n-\n-test_expect_success 'test only included refs are packed' '\n-\tgit branch pack_this1 &&\n-\tgit branch pack_this2 &&\n-\tgit tag dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack_this*\" &&\n-\ttest -f .git/refs/tags/dont_pack5 &&\n-\t! test -f .git/refs/heads/pack_this1 &&\n-\t! test -f .git/refs/heads/pack_this2'\n-\n-test_expect_success 'test --no-include refs clears included refs' '\n-\tgit branch pack1 &&\n-\tgit branch pack2 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --no-include &&\n-\ttest -f .git/refs/heads/pack1 &&\n-\ttest -f .git/refs/heads/pack2'\n-\n-test_expect_success 'test --exclude takes precedence over --include' '\n-\tgit branch dont_pack5 &&\n-\tgit pack-refs --include \"refs/heads/pack*\" --exclude \"refs/heads/pack*\" &&\n-\ttest -f .git/refs/heads/dont_pack5'\n-\n-test_expect_success 'see if up-to-date packed refs are preserved' '\n-\tgit branch q &&\n-\tgit pack-refs --all --prune &&\n-\tgit update-ref refs/heads/q refs/heads/q &&\n-\t! test -f .git/refs/heads/q\n-'\n-\n-test_expect_success 'pack, prune and repack' '\n-\tgit tag foo &&\n-\tgit pack-refs --all --prune &&\n-\tgit show-ref >all-of-them &&\n-\tgit pack-refs &&\n-\tgit show-ref >again &&\n-\ttest_cmp all-of-them again\n-'\n-\n-test_expect_success 'explicit pack-refs with dangling packed reference' '\n-\tgit commit --allow-empty -m \"soon to be garbage-collected\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit pack-refs --all 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref with dangling packed version' '\n-\tgit checkout -b lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit checkout main &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'delete ref while another dangling packed ref' '\n-\tgit branch lamb &&\n-\tgit commit --allow-empty -m \"future garbage\" &&\n-\tgit pack-refs --all &&\n-\tgit reset --hard HEAD^ &&\n-\tgit reflog expire --expire=all --all &&\n-\tgit prune --expire=all &&\n-\tgit branch -d lamb 2>result &&\n-\ttest_must_be_empty result\n-'\n-\n-test_expect_success 'pack ref directly below refs/' '\n-\tgit update-ref refs/top HEAD &&\n-\tgit pack-refs --all --prune &&\n-\tgrep refs/top .git/packed-refs &&\n-\ttest_path_is_missing .git/refs/top\n-'\n-\n-test_expect_success 'do not pack ref in refs/bisect' '\n-\tgit update-ref refs/bisect/local HEAD &&\n-\tgit pack-refs --all --prune &&\n-\t! grep refs/bisect/local .git/packed-refs >/dev/null &&\n-\ttest_path_is_file .git/refs/bisect/local\n-'\n-\n-test_expect_success 'disable reflogs' '\n-\tgit config core.logallrefupdates false &&\n-\trm -rf .git/logs\n-'\n-\n-test_expect_success 'create packed foo/bar/baz branch' '\n-\tgit branch foo/bar/baz &&\n-\tgit pack-refs --all --prune &&\n-\ttest_path_is_missing .git/refs/heads/foo/bar/baz &&\n-\ttest_must_fail git reflog exists refs/heads/foo/bar/baz\n-'\n-\n-test_expect_success 'notice d/f conflict with existing directory' '\n-\ttest_must_fail git branch foo &&\n-\ttest_must_fail git branch foo/bar\n-'\n-\n-test_expect_success 'existing directory reports concrete ref' '\n-\ttest_must_fail git branch foo 2>stderr &&\n-\ttest_grep refs/heads/foo/bar/baz stderr\n-'\n-\n-test_expect_success 'notice d/f conflict with existing ref' '\n-\ttest_must_fail git branch foo/bar/baz/extra &&\n-\ttest_must_fail git branch foo/bar/baz/lots/of/extra/components\n-'\n-\n-test_expect_success 'reject packed-refs with unterminated line' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\" \"$HEAD refs/zzzzz\" >>.git/packed-refs &&\n-\techo \"fatal: unterminated line in .git/packed-refs: $HEAD refs/zzzzz\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs containing junk' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%s\\n\" \"bogus content\" >>.git/packed-refs &&\n-\techo \"fatal: unexpected line in .git/packed-refs: bogus content\" >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'reject packed-refs with a short SHA-1' '\n-\tcp .git/packed-refs .git/packed-refs.bak &&\n-\ttest_when_finished \"mv .git/packed-refs.bak .git/packed-refs\" &&\n-\tprintf \"%.7s %s\\n\" $HEAD refs/zzzzz >>.git/packed-refs &&\n-\tprintf \"fatal: unexpected line in .git/packed-refs: %.7s %s\\n\" $HEAD refs/zzzzz >expected_err &&\n-\ttest_must_fail git for-each-ref >out 2>err &&\n-\ttest_cmp expected_err err\n-'\n-\n-test_expect_success 'timeout if packed-refs.lock exists' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"rm -f $LOCK\" &&\n-\ttest_must_fail git pack-refs --all --prune\n-'\n-\n-test_expect_success 'retry acquiring packed-refs.lock' '\n-\tLOCK=.git/packed-refs.lock &&\n-\t>\"$LOCK\" &&\n-\ttest_when_finished \"wait && rm -f $LOCK\" &&\n-\t{\n-\t\t( sleep 1 && rm -f $LOCK ) &\n-\t} &&\n-\tgit -c core.packedrefstimeout=3000 pack-refs --all --prune\n-'\n-\n-test_expect_success SYMLINKS 'pack symlinked packed-refs' '\n-\t# First make sure that symlinking works when reading:\n-\tgit update-ref refs/heads/lossy refs/heads/main &&\n-\tgit for-each-ref >all-refs-before &&\n-\tmv .git/packed-refs .git/my-deviant-packed-refs &&\n-\tln -s my-deviant-packed-refs .git/packed-refs &&\n-\tgit for-each-ref >all-refs-linked &&\n-\ttest_cmp all-refs-before all-refs-linked &&\n-\tgit pack-refs --all --prune &&\n-\tgit for-each-ref >all-refs-packed &&\n-\ttest_cmp all-refs-before all-refs-packed &&\n-\ttest -h .git/packed-refs &&\n-\ttest \"$(test_readlink .git/packed-refs)\" = \"my-deviant-packed-refs\"\n-'\n-\n-# The 'packed-refs' file is stored directly in .git/. This means it is global\n-# to the repository, and can only contain refs that are shared across all\n-# worktrees.\n-test_expect_success 'refs/worktree must not be packed' '\n-\ttest_commit initial &&\n-\ttest_commit wt1 &&\n-\ttest_commit wt2 &&\n-\tgit worktree add wt1 wt1 &&\n-\tgit worktree add wt2 wt2 &&\n-\tgit checkout initial &&\n-\tgit update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt1 update-ref refs/worktree/foo HEAD &&\n-\tgit -C wt2 update-ref refs/worktree/foo HEAD &&\n-\tgit pack-refs --all &&\n-\ttest_path_is_missing .git/refs/tags/wt1 &&\n-\ttest_path_is_file .git/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt1/refs/worktree/foo &&\n-\ttest_path_is_file .git/worktrees/wt2/refs/worktree/foo\n-'\n-\n-# we do not want to count on running pack-refs to\n-# actually pack it, as it is perfectly reasonable to\n-# skip processing a broken ref\n-test_expect_success 'create packed-refs file with broken ref' '\n-\ttest_tick && git commit --allow-empty -m one &&\n-\trecoverable=$(git rev-parse HEAD) &&\n-\ttest_tick && git commit --allow-empty -m two &&\n-\tmissing=$(git rev-parse HEAD) &&\n-\trm -f .git/refs/heads/main &&\n-\tcat >.git/packed-refs <<-EOF &&\n-\t$missing refs/heads/main\n-\t$recoverable refs/heads/other\n-\tEOF\n-\techo $missing >expect &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not silently delete broken packed ref' '\n-\tgit pack-refs --all --prune &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-test_expect_success 'pack-refs does not drop broken refs during deletion' '\n-\tgit update-ref -d refs/heads/other &&\n-\tgit rev-parse refs/heads/main >actual &&\n-\ttest_cmp expect actual\n-'\n-\n-for command in \"git pack-refs --all --auto\" \"git maintenance run --task=pack-refs --auto\"\n-do\n-\ttest_expect_success \"$command does not repack below 16 refs without packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 14 additional references, which brings us to\n-\t\t\t# 15 together with the default branch.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 14) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_missing .git/packed-refs &&\n-\n-\t\t\t# Create the 16th reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-15 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_path_is_file .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command does not repack below 16 refs with small packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create 15 loose references.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 15) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 2 .git/packed-refs &&\n-\n-\t\t\t# Create the 16th loose reference, which should cause us to repack.\n-\t\t\tgit update-ref refs/heads/loose-17 HEAD &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 18 .git/packed-refs\n-\t\t)\n-\t'\n-\n-\ttest_expect_success \"$command scales with size of packed-refs\" '\n-\t\ttest_when_finished \"rm -rf repo\" &&\n-\t\tgit init repo &&\n-\t\t(\n-\t\t\tcd repo &&\n-\t\t\tgit config set maintenance.auto false &&\n-\t\t\tgit commit --allow-empty --message \"initial\" &&\n-\n-\t\t\t# Create 99 packed refs. This should cause the heuristic\n-\t\t\t# to require more than the minimum amount of loose refs.\n-\t\t\ttest_seq 99 |\n-\t\t\twhile read i\n-\t\t\tdo\n-\t\t\t\tprintf \"create refs/heads/packed-%d HEAD\\n\" $i || return 1\n-\t\t\tdone >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create 24 loose refs, which should not yet cause us to repack.\n-\t\t\tprintf \"create refs/heads/loose-%d HEAD\\n\" $(test_seq 24) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 101 .git/packed-refs &&\n-\n-\t\t\t# Create another handful of refs to cross the border.\n-\t\t\t# Note that we explicitly do not check for strict\n-\t\t\t# boundaries here, as this also depends on the size of\n-\t\t\t# the object hash.\n-\t\t\tprintf \"create refs/heads/addn-%d HEAD\\n\" $(test_seq 10) >stdin &&\n-\t\t\tgit update-ref --stdin <stdin &&\n-\t\t\tgit pack-refs --auto --all &&\n-\t\t\ttest_line_count = 135 .git/packed-refs\n-\t\t)\n-\t'\n-done\n-\n-test_done\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"526767","messageId":"20250919082647.535213-10-meetsoni3017@gmail.com","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"[GSoC][PATCH v4 9/9] t: add test for git refs optimize subcommand","fromName":"Meet Soni","fromEmail":"meetsoni3017@gmail.com","sentAt":"2025-09-19T08:26:47Z","receivedAt":"2025-09-19T08:27:17Z","isPatch":true,"sender":{"key":"meetsoni3017@gmail.com","avatar":"https://avatars.githubusercontent.com/u/92802561?v=4"},"body":"Add a test script, `t/t1463-refs-optimize.sh`, for the new `git refs\noptimize` command.\n\nThis script acts as a simple driver, leveraging the shared test library\ncreated in the preceding commit. It works by overriding the\n`$pack_refs` variable to \"refs optimize\" and then sourcing the\nshared library (`t/pack-refs-tests.sh`).\n\nThis approach ensures that `git refs optimize` is tested against the\nentire comprehensive test suite of `git pack-refs`, verifying\nthat it acts as a compatible drop-in replacement.\n\nMentored-by: Patrick Steinhardt <ps@pks.im>\nMentored-by: shejialuo <shejialuo@gmail.com>\nSigned-off-by: Meet Soni <meetsoni3017@gmail.com>\n---\n t/meson.build            |  3 ++-\n t/t1463-refs-optimize.sh | 17 +++++++++++++++++\n 2 files changed, 19 insertions(+), 1 deletion(-)\n create mode 100755 t/t1463-refs-optimize.sh\n\ndiff --git a/t/meson.build b/t/meson.build\nindex baeeba2ce6..92327aabdf 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -211,6 +211,7 @@ integration_tests = [\n   't1451-fsck-buffer.sh',\n   't1460-refs-migrate.sh',\n   't1461-refs-list.sh',\n+  't1463-refs-optimize.sh',\n   't1500-rev-parse.sh',\n   't1501-work-tree.sh',\n   't1502-rev-parse-parseopt.sh',\n@@ -1219,4 +1220,4 @@ if perl.found() and time.found()\n       timeout: 0,\n     )\n   endforeach\n-endif\n\\ No newline at end of file\n+endif\ndiff --git a/t/t1463-refs-optimize.sh b/t/t1463-refs-optimize.sh\nnew file mode 100755\nindex 0000000000..c11c905d79\n--- /dev/null\n+++ b/t/t1463-refs-optimize.sh\n@@ -0,0 +1,17 @@\n+#!/bin/sh\n+\n+test_description='git refs optimize should not change the branch semantic\n+\n+This test runs git refs optimize and git show-ref and checks that the branch\n+semantic is still the same.\n+'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+GIT_TEST_DEFAULT_REF_FORMAT=files\n+export GIT_TEST_DEFAULT_REF_FORMAT\n+\n+. ./test-lib.sh\n+\n+pack_refs='refs optimize'\n+. \"$TEST_DIRECTORY\"/pack-refs-tests.sh\n-- \n2.34.1\n\n"},{"id":"526797","messageId":"xmqq7bxu5llt.fsf@gitster.g","threadId":"64099","inReplyTo":"20250919082647.535213-1-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v4 0/9] Add refs optimize subcommand","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-09-19T18:43:42Z","receivedAt":"2025-09-19T18:43:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Meet Soni <meetsoni3017@gmail.com> writes:\n\n> Hi everyone,\n>\n> This series introduces `git refs optimize` as a modern replacement for\n> `git pack-refs`, continuing the effort to consolidate commands under the\n> `git refs` namespace.\n>\n> Changes in v4:\n> - Improved commit messages and formatting.\n> - Removed the NULL check from the refs_optimize() dispatcher in refs.c\n>   to align with the project's conventions for API functions\n\nI haven't read these patches carefully myself, but if you need\nfurther updates to this topic, you may want to rebase on a slightly\nnewer commit that already has \"git refs exists\".  Either 07f29476de\n(i.e. the merge of that topic to 'master'), or 92c87bdc40 (the batch\nthat contains that merge) would work fine.\n\nI can keep resolving the conflicts between this and the other topic\njust fine, but I'd rather see you tested and proofread these patches\nyourself in the context of the codebase that these changes will\neventually be used with.\n\nThanks.\n"},{"id":"527179","messageId":"aNONOM4W7kUQNm1y@pks.im","threadId":"64099","inReplyTo":"20250919082647.535213-2-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v4 1/9] refs: add a generic 'optimize' API","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-24T06:18:32Z","receivedAt":"2025-09-24T06:18:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Sep 19, 2025 at 01:56:39PM +0530, Meet Soni wrote:\n> The existing `pack-refs` API is conceptually tied to the 'files'\n> backend, but its behavior is generic (e.g., it triggers compaction for\n> reftable). This naming is confusing.\n> \n> Introduce a new generic refs_optimize() API that dispatches to a\n> backend-specific implementation via a new 'optimize' vtable method.\n> \n> This lays the architectural groundwork for different reference backends\n> (like 'files' and 'reftable') to provide their own storage optimization\n> logic, which will be called from a single, generic entry point.\n\nI agree with this change in the architecture in general -- \"packing\nrefs\" is certainly a term that is specific to the \"files\" backend. So\nrenaming that infrastructure to instead say \"optimizing refs\" feels like\na sensible step as it adjusts naming to reality.\n\nBut what I don't quite get is why we end up with both a `pack_refs_fn`\nand an `optimize_fn` after this series, where the latter is always\ncalling the former. Wouldn't it be more sensible step to make this a\ncouple of simple renames? E.g.:\n\n  - `pack_refs_fn` -> `optimize_refs_fn`\n  - `refs_pack_refs()` -> `refs_optimize()`\n  - `struct pack_refs_opts` -> `struct refs_optimize_opts`\n\nIt would probably be a bit of the bigger patch to do all these renames\nat once. But there aren't _that_ many users of this infra, and I'd quite\nwelcome those changes.\n\nPatrick\n"},{"id":"527180","messageId":"aNONRY10f6R-3Il0@pks.im","threadId":"64099","inReplyTo":"20250919082647.535213-6-meetsoni3017@gmail.com","subject":"Re: [GSoC][PATCH v4 5/9] builtin/pack-refs: factor out core logic into a shared library","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2025-09-24T06:18:45Z","receivedAt":"2025-09-24T06:18:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Sep 19, 2025 at 01:56:43PM +0530, Meet Soni wrote:\n> The implementation of `git pack-refs` is monolithic within\n> `cmd_pack_refs()`, making it impossible to share its logic with other\n> commands. To enable code reuse for the upcoming `git refs optimize`\n> subcommand, refactor the core logic into a shared helper function.\n> \n> Split the original `builtin/pack-refs.c` file into two parts:\n> \n> - A new shared library file, `pack-refs.c`, which contains the\n>   core option parsing and packing logic in a new `pack_refs_core()`\n>   helper function.\n\nShould we maybe host that file in \"refs/pack.c\"? This ensures that all\nref-related infra continues to sit in one place. We could also build on\nyour previous steps and call it \"refs/optimize.c\" right away.\n\n> diff --git a/pack-refs.c b/pack-refs.c\n> new file mode 100644\n> index 0000000000..1a5e07d8b8\n> --- /dev/null\n> +++ b/pack-refs.c\n> @@ -0,0 +1,56 @@\n> +#include \"builtin.h\"\n> +#include \"config.h\"\n> +#include \"environment.h\"\n> +#include \"pack-refs.h\"\n> +#include \"parse-options.h\"\n> +#include \"refs.h\"\n> +#include \"revision.h\"\n> +\n> +int pack_refs_core(int argc,\n\nIf we want to go with \"refs/optimize.c\" I'd call this\n`refs_optimize_core()`.\n\n> diff --git a/pack-refs.h b/pack-refs.h\n> new file mode 100644\n> index 0000000000..5de27e7da8\n> --- /dev/null\n> +++ b/pack-refs.h\n> @@ -0,0 +1,23 @@\n> +#ifndef PACK_REFS_H\n> +#define PACK_REFS_H\n> +\n> +struct repository;\n> +\n> +/*\n> + * Shared usage string for options common to git-pack-refs(1)\n> + * and git-refs-optimize(1). The command-specific part (e.g., \"git refs optimize \")\n> + * must be prepended by the caller.\n> + */\n> +#define PACK_REFS_OPTS \\\n\nThis would become `REFS_OPTIMIZE_OPTS`.\n\n> +\t\"[--all] [--no-prune] [--auto] [--include <pattern>] [--exclude <pattern>]\"\n> +\n> +/*\n> + * The core logic for pack-refs and its clones.\n> + */\n> +int pack_refs_core(int argc,\n> +\t\t   const char **argv,\n> +\t\t   const char *prefix,\n> +\t\t   struct repository *repo,\n> +\t\t   const char * const *usage_opts);\n> +\n> +#endif /* PACK_REFS_H */\n\nPatrick\n"}]}