{"thread":{"id":"65021","subject":"[PATCH v7 1/6] setup: don't modify repo in `create_reference_database()`","startedAt":"2026-02-19T09:38:30Z","lastAt":"2026-02-22T20:15:43Z","messageCount":15,"participants":["Karthik Nayak","Patrick Steinhardt","Toon Claes","Junio C Hamano"],"isPatch":true,"patchVersion":7,"patchTotal":6},"messages":[{"id":"536384","messageId":"20260219-kn-alternate-ref-dir-v7-1-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 1/6] setup: don't modify repo in `create_reference_database()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:20Z","receivedAt":"2026-02-19T09:38:30Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"The `create_reference_database()` function is used to create the\nreference database during initialization of a repository. The function\ncalls `repo_set_ref_storage_format()` to set the repositories reference\nformat. This is an unexpected side-effect of the function. More so\nbecause the function is only called in two locations:\n\n  1. During git-init(1) where the value is propagated from the `struct\n     repository_format repo_fmt` value.\n\n  2. During git-clone(1) where the value is propagated from the\n     `the_repository` value.\n\nThe former is valid, however the flow already calls\n`repo_set_ref_storage_format()`, so this effort is simply duplicated.\nThe latter sets the existing value in `the_repository` back to itself.\nWhile this is okay for now, introduction of more fields in\n`repo_set_ref_storage_format()` would cause issues, especially\ndynamically allocated strings, where we would free/allocate the same\nstring back into `the_repostiory`.\n\nTo avoid all this confusion, clean up the function to no longer take in\nand set the repo's reference storage format.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/clone.c | 2 +-\n setup.c         | 7 ++-----\n setup.h         | 3 +--\n 3 files changed, 4 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex b40cee5968..cd43bb5aa2 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1442,7 +1442,7 @@ int cmd_clone(int argc,\n \thash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \tinitialize_repository_version(hash_algo, the_repository->ref_storage_format, 1);\n \trepo_set_hash_algo(the_repository, hash_algo);\n-\tcreate_reference_database(the_repository->ref_storage_format, NULL, 1);\n+\tcreate_reference_database(NULL, 1);\n \n \t/*\n \t * Before fetching from the remote, download and install bundle\ndiff --git a/setup.c b/setup.c\nindex b723f8b339..1fc9ae3872 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -2359,14 +2359,12 @@ static int is_reinit(void)\n \treturn ret;\n }\n \n-void create_reference_database(enum ref_storage_format ref_storage_format,\n-\t\t\t       const char *initial_branch, int quiet)\n+void create_reference_database(const char *initial_branch, int quiet)\n {\n \tstruct strbuf err = STRBUF_INIT;\n \tchar *to_free = NULL;\n \tint reinit = is_reinit();\n \n-\trepo_set_ref_storage_format(the_repository, ref_storage_format);\n \tif (ref_store_create_on_disk(get_main_ref_store(the_repository), 0, &err))\n \t\tdie(\"failed to set up refs db: %s\", err.buf);\n \n@@ -2701,8 +2699,7 @@ int init_db(const char *git_dir, const char *real_git_dir,\n \t\t\t\t      &repo_fmt, init_shared_repository);\n \n \tif (!(flags & INIT_DB_SKIP_REFDB))\n-\t\tcreate_reference_database(repo_fmt.ref_storage_format,\n-\t\t\t\t\t  initial_branch, flags & INIT_DB_QUIET);\n+\t\tcreate_reference_database(initial_branch, flags & INIT_DB_QUIET);\n \tcreate_object_directory();\n \n \tif (repo_settings_get_shared_repository(the_repository)) {\ndiff --git a/setup.h b/setup.h\nindex d55dcc6608..ddb9f6701c 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -240,8 +240,7 @@ int init_db(const char *git_dir, const char *real_git_dir,\n void initialize_repository_version(int hash_algo,\n \t\t\t\t   enum ref_storage_format ref_storage_format,\n \t\t\t\t   int reinit);\n-void create_reference_database(enum ref_storage_format ref_storage_format,\n-\t\t\t       const char *initial_branch, int quiet);\n+void create_reference_database(const char *initial_branch, int quiet);\n \n /*\n  * NOTE NOTE NOTE!!\n\n-- \n2.53.GIT\n\n"},{"id":"536385","messageId":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20251119-kn-alternate-ref-dir-v1-0-4cf4a94c8bed@gmail.com","subject":"[PATCH v7 0/6] refs: allow setting the reference directory","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:19Z","receivedAt":"2026-02-19T09:38:30Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"While Git allows users to select different reference backends, unlike\nwith objects, there is no flexibility in selecting the reference\ndirectory. Currently, the reference format is obtained from the config\nof the repository and the reference directory is set to the $GIT_DIR.\n\nThis patch extends the config 'extensions.refStorage' to take in\nmultiple forms of inputs:\n\n  - A format name alone (e.g., `reftable` or `files`).\n\n  - A URI format `<format>://<payload>` explicitly specifies both the\n    format and payload (e.g., `reftable:///foo/bar`).\n\nWe also add in a new ENV variable GIT_REFERENCE_BACKEND which can be\nused to override the config.\n\nOne use case for this is migration between different backends. On the\nserver side, migrating from the files backend to the newly introduced\nreftable backend can be achieved by running 'git refs migrate'. However,\nfor large repositories with millions of references, this migration can\ntake from seconds to minutes.\n\nFor some background, at GitLab, the criteria for our migration was to\nreduce the downtime of the migrate ideally to zero. So running 'git refs\nmigrate --ref-format=reftable' by itself wouldn't work, since it scales\nwith the number of references and we have repos with millions of\nreferences, so we need to migrate without loosing any information. We\ncame up with the following plan:\n\n  1. Run git-pack-refs(1) and note timestamp of the generated packed-refs\n     file.\n  2. Run git refs migrate –dry-run.\n  3. If there are no ongoing reference requests (read/write)\n     a. Lock the repository by blocking incoming requests (done on a\n        layer above git, in Gitaly [1]).\n     b. If the timestamp of the packed-refs file has changed, unlock\n        the repo and repeat from step 1.\n     c. Apply all the loose refs to the dry-run reftable folder (this\n        requires support in Git to write refs to arbitrary folder).\n     d. Move the reftable dry-run folder into the GIT_DIR.\n     e. Swap the repo config\n     f. Unlock repo access\n\nUsing such a route, scales much better since we only have to worry about\nblocking the repository by O(ref written between #1 and #3a) and not\nO(refs in repo). But for doing so, we need to be able to write to a\narbitrary reference backend + path. This is to add the missing\nreferences to the dry-run reftable folder. This series, achieves that.\n\nSince there was a long gap between v3 <> v4, the version 4 onward is\nbased on top of 2258446484 (RelNotes: correct \"fast-import\" option name,\n2026-01-30).\n\n[1]: https://gitlab.com/gitlab-org/gitaly\n\n---\nChanges in v7:\n- Add more details in the commit messages.\n- Cleanup some whitespace.\n- Reorder the commits to be group related changes together.\n- Add checks for stubs in the tests when creating new repos.\n- Link to v6: https://patch.msgid.link/20260214-kn-alternate-ref-dir-v6-0-86a82c77cf59@gmail.com\n\nChanges in v6:\n- The biggest change in this version is that we now support using the\n  environment variable with 'git-clone(1)' and 'git-init(1)'. In such\n  situations, the alternate reference directory is created and the\n  config is added to the repository.\n- Add a new commit which moves stub creation/removal to the generic\n  layer.\n- Cleanup logic flow in `refs_compute_filesystem_location()`.\n- Add more tests for usage with 'git-clone(1)', 'git-init(1)' and\n  migration of repositories using alternate refs backend.\n- Fixup documentation, commit messages and typos.\n- Link to v5: https://patch.msgid.link/20260209-kn-alternate-ref-dir-v5-0-740899834ceb@gmail.com\n\nChanges in v5:\n- Moved around the commits, to ensure that the code to handle the config\n  in the backend is first. Previously, we added the config first, which\n  meant the commit allowed users to provide a URI but it was simply\n  ignore.\n- Fix typos and grammar and rename variables.\n- Clean up the description and documentation to actually specify\n  protocol over location.\n- Avoid an extra memory allocation by detaching the strbuf value.\n- Link to v4: https://patch.msgid.link/20260202-kn-alternate-ref-dir-v4-0-3b30430411e3@gmail.com\n\nChanges in v4:\n- Mostly re-wrote the code to also support worktree. Now, the existing\n  backends will store worktree references in 'ref_dir/worktrees/wt_id'\n  and add corresponding stubs in 'git_dir/worktrees/wt_id'.\n- We also support relative paths in the reference directories. These\n  relative paths are resolved relative to the GIT_DIR.\n- Link to v3: https://patch.msgid.link/20251201-kn-alternate-ref-dir-v3-0-c11b946bc2fa@gmail.com\n\nChanges in v3:\n- Cleanup some stale code which wasn't removed.\n- Localize strings which will be output to the user.\n- Remove additional defensive checks which are not needed.\n- Link to v2: https://patch.msgid.link/20251126-kn-alternate-ref-dir-v2-0-8b9f6f18f635@gmail.com\n\nChanges in v2:\n- Added more clarification and proper intent in the cover message.\n- Changed the format from '<ref_backend>://<path>' to\n  `<ref_backend>://<URI-for-resource>` as it much clearer.\n- Added logic to check for the '//' in the provided URI and a test for\n  the same.\n- In the tests:\n  - Use test_must_fail() instead of ! git\n  - Fix looped tests not using the variables correctly and ensure that\n    the test description is correct.\n- Link to v1: https://patch.msgid.link/20251119-kn-alternate-ref-dir-v1-0-4cf4a94c8bed@gmail.com\n\n---\n Documentation/config/extensions.adoc |  16 +-\n Documentation/git.adoc               |   5 +\n builtin/clone.c                      |   9 +-\n builtin/worktree.c                   |  34 +++++\n environment.h                        |   1 +\n refs.c                               | 126 +++++++++++++++-\n refs.h                               |  13 ++\n refs/files-backend.c                 |  23 ++-\n refs/packed-backend.c                |   5 +\n refs/packed-backend.h                |   1 +\n refs/refs-internal.h                 |  14 ++\n refs/reftable-backend.c              |  61 ++------\n repository.c                         |   9 +-\n repository.h                         |   8 +-\n setup.c                              |  96 ++++++++++--\n setup.h                              |   4 +-\n t/meson.build                        |   1 +\n t/t1423-ref-backend.sh               | 282 +++++++++++++++++++++++++++++++++++\n 18 files changed, 627 insertions(+), 81 deletions(-)\n\nKarthik Nayak (6):\n      setup: don't modify repo in `create_reference_database()`\n      refs: extract out `refs_create_refdir_stubs()`\n      refs: move out stub modification to generic layer\n      refs: receive and use the reference storage payload\n      refs: allow reference location in refstorage config\n      refs: add GIT_REFERENCE_BACKEND to specify reference backend\n\nRange-diff versus v6:\n\n1:  bbc8d0678c ! 1:  d958e2597e setup: don't modify repo in `create_reference_database()`\n    @@ Commit message\n         dynamically allocated strings, where we would free/allocate the same\n         string back into `the_repostiory`.\n     \n    -    To avoid all this confusion, clean up the function to longer take in and\n    -    set the repo's reference storage format.\n    +    To avoid all this confusion, clean up the function to no longer take in\n    +    and set the repo's reference storage format.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n2:  4f868d2f4c = 2:  f6583f00b1 refs: extract out `refs_create_refdir_stubs()`\n4:  6e4c372402 ! 3:  bffb0ede9d refs: move out stub modification to generic layer\n    @@ Commit message\n         ensure that the directory can be recognized as a Git repository. This is\n         done by calling `refs_create_refdir_stubs()`. Move this to the generic\n         layer as this is needed for all backends excluding from the files\n    -    backends. In an upcoming commit, we'll also need to extend this logic to\n    -    create stubs when using alternate reference directories.\n    +    backends. In an upcoming commit where we introduce alternate reference\n    +    backend locations, we'll have to also create stubs in the $GIT_DIR\n    +    irrespective of the backend being used. This commit builds the base to\n    +    add that logic.\n     \n         Similarly, move the logic for deletion of stubs to the generic layer.\n         The files backend recursively calls the remove function of the\n    @@ refs.c: void refs_create_refdir_stubs(struct repository *repo, const char *refdi\n     -\treturn refs->be->remove_on_disk(refs, err);\n     +\tint ret = refs->be->remove_on_disk(refs, err);\n     +\n    -+\tif (!ret) {\n    -+\t\tenum ref_storage_format format = ref_storage_format_by_name(refs->be->name);\n    ++\tif (!ret &&\n    ++\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n     +\t\tstruct strbuf sb = STRBUF_INIT;\n     +\n    -+\t\t/* Backends apart from the files backend create stubs. */\n    -+\t\tif (format == REF_STORAGE_FORMAT_FILES)\n    -+\t\t\treturn ret;\n    -+\n     +\t\tstrbuf_addf(&sb, \"%s/HEAD\", refs->gitdir);\n     +\t\tif (unlink(sb.buf) < 0) {\n     +\t\t\tstrbuf_addf(err, \"could not delete stub HEAD: %s\",\n3:  363e36875b ! 4:  a7d4c22861 refs: receive and use the reference storage payload\n    @@ refs.c: const char *ref_transaction_error_msg(enum ref_transaction_error err)\n     +\tif (*is_worktree) {\n     +\t\tconst char *wt_id = strrchr(gitdir, '/');\n     +\t\tif (!wt_id)\n    -+\t\t\tBUG(\"worktree path does not contain slash \");\n    ++\t\t\tBUG(\"worktree path does not contain slash\");\n     +\t\tstrbuf_addf(refdir, \"/worktrees/%s\", wt_id + 1);\n     +\t}\n     +\n5:  da04b77ed6 = 5:  dca8fc6b77 refs: allow reference location in refstorage config\n6:  78b9589da7 ! 6:  d515ab78e3 refs: add GIT_REFERENCE_BACKEND to specify reference backend\n    @@ refs.c: int ref_store_create_on_disk(struct ref_store *refs, int flags, struct s\n      \t}\n      \n      \treturn ret;\n    -+\n    - }\n    - \n    - int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)\n     @@ refs.c: int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)\n    - \t\tif (format == REF_STORAGE_FORMAT_FILES)\n    - \t\t\treturn ret;\n    + {\n    + \tint ret = refs->be->remove_on_disk(refs, err);\n    + \n    +-\tif (!ret &&\n    +-\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n    ++\tif (!ret) {\n    ++\t\tenum ref_storage_format format = ref_storage_format_by_name(refs->be->name);\n    + \t\tstruct strbuf sb = STRBUF_INIT;\n      \n    ++\t\t/* Backends apart from the files backend create stubs. */\n    ++\t\tif (format == REF_STORAGE_FORMAT_FILES)\n    ++\t\t\treturn ret;\n    ++\n     +\t\t/* Alternate refs backend require stubs in the gitdir. */\n     +\t\tif (refs->repo->ref_storage_payload)\n     +\t\t\treturn ret;\n    @@ t/t1423-ref-backend.sh: run_with_uri() {\n      }\n      \n     -test_expect_success 'URI is invalid' '\n    ++# Verify that the expected files are present in the gitdir and the refsdir.\n    ++# Usage: verify_files_exist <gitdir> <refdir>\n    ++#   <gitdir> is the path for the gitdir.\n    ++#   <refdir> is the path for the refdir.\n    ++verify_files_exist() {\n    ++\tgitdir=$1 &&\n    ++\trefdir=$2 &&\n    ++\n    ++\t# verify that the stubs were added to the $GITDIR.\n    ++\tcat $gitdir/refs/heads >actual &&\n    ++\techo \"repository uses alternate refs storage\" >expect &&\n    ++\ttest_cmp expect actual &&\n    ++\tcat $gitdir/HEAD >actual &&\n    ++\techo \"ref: refs/heads/.invalid\" >expect &&\n    ++\ttest_cmp expect actual\n    ++\n    ++\t# verify that backend specific files exist.\n    ++\tcase \"$GIT_DEFAULT_REF_FORMAT\" in\n    ++\tfiles)\n    ++\t\ttest_path_is_dir $refdir/refs/heads &&\n    ++\t\ttest_path_is_file $refdir/HEAD;;\n    ++\treftable)\n    ++\t\ttest_path_is_dir $refdir/reftable &&\n    ++\t\ttest_path_is_file $refdir/reftable/tables.list;;\n    ++\t*)\n    ++\t\tBUG \"unhandled ref format $GIT_DEFAULT_REF_FORMAT\";;\n    ++\tesac\n    ++}\n    ++\n     +methods=\"config env\"\n     +for method in $methods\n     +do\n    @@ t/t1423-ref-backend.sh: do\n     +\tmkdir refdir &&\n     +\tBACKEND=\"$(test_detect_ref_format)://$(pwd)/refdir\" &&\n     +\tGIT_REFERENCE_BACKEND=$BACKEND git init repo &&\n    ++\tverify_files_exist repo/.git refdir &&\n     +\t(\n     +\t\tcd repo &&\n     +\n    @@ t/t1423-ref-backend.sh: do\n     +\techo $BACKEND >actual &&\n     +\ttest_cmp expect actual &&\n     +\n    ++\tverify_files_exist repo/.git refdir &&\n    ++\n     +\tgit -C source for-each-ref refs/tags/ >expect &&\n     +\tgit -C repo for-each-ref refs/tags/ >actual &&\n     +\ttest_cmp expect actual\n\n\nbase-commit: 22584464849815268419fd9d2eba307362360db1\nchange-id: 20251105-kn-alternate-ref-dir-3e572e8cd0ef\n\nThanks\n- Karthik\n\n"},{"id":"536386","messageId":"20260219-kn-alternate-ref-dir-v7-2-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 2/6] refs: extract out `refs_create_refdir_stubs()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:21Z","receivedAt":"2026-02-19T09:38:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"For Git to recognize a directory as a Git directory, it requires the\ndirectory to contain:\n\n  1. 'HEAD' file\n  2. 'objects/' directory\n  3. 'refs/' directory\n\nHere, #1 and #3 are part of the reference storage mechanism,\nspecifically the files backend. Since then, newer backends such as the\nreftable backend have moved to using their own path ('reftable/') for\nstoring references. But to ensure Git still recognizes the directory as\na Git directory, we create stubs.\n\nThere are two locations where we create stubs:\n\n- In 'refs/reftable-backend.c' when creating the reftable backend.\n- In 'clone.c' before spawning transport helpers.\n\nIn a following commit, we'll add another instance. So instead of\nrepeating the code, let's extract out this code to\n`refs_create_refdir_stubs()` and use it.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/clone.c         |  7 +------\n refs.c                  | 23 +++++++++++++++++++++++\n refs.h                  | 13 +++++++++++++\n refs/reftable-backend.c | 14 ++------------\n 4 files changed, 39 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex cd43bb5aa2..697c5bb5cb 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1225,12 +1225,7 @@ int cmd_clone(int argc,\n \tinitialize_repository_version(GIT_HASH_UNKNOWN,\n \t\t\t\t      the_repository->ref_storage_format, 1);\n \n-\tstrbuf_addf(&buf, \"%s/HEAD\", git_dir);\n-\twrite_file(buf.buf, \"ref: refs/heads/.invalid\");\n-\n-\tstrbuf_reset(&buf);\n-\tstrbuf_addf(&buf, \"%s/refs\", git_dir);\n-\tsafe_create_dir(the_repository, buf.buf, 1);\n+\trefs_create_refdir_stubs(the_repository, git_dir, NULL);\n \n \t/*\n \t * additional config can be injected with -c, make sure it's included\ndiff --git a/refs.c b/refs.c\nindex 627b7f8698..77b93d655b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2163,6 +2163,29 @@ const char *refs_resolve_ref_unsafe(struct ref_store *refs,\n \treturn NULL;\n }\n \n+void refs_create_refdir_stubs(struct repository *repo, const char *refdir,\n+\t\t\t      const char *refs_heads_content)\n+{\n+\tstruct strbuf path = STRBUF_INIT;\n+\n+\tstrbuf_addf(&path, \"%s/HEAD\", refdir);\n+\twrite_file(path.buf, \"ref: refs/heads/.invalid\");\n+\tadjust_shared_perm(repo, path.buf);\n+\n+\tstrbuf_reset(&path);\n+\tstrbuf_addf(&path, \"%s/refs\", refdir);\n+\tsafe_create_dir(repo, path.buf, 1);\n+\n+\tif (refs_heads_content) {\n+\t\tstrbuf_reset(&path);\n+\t\tstrbuf_addf(&path, \"%s/refs/heads\", refdir);\n+\t\twrite_file(path.buf, \"%s\", refs_heads_content);\n+\t\tadjust_shared_perm(repo, path.buf);\n+\t}\n+\n+\tstrbuf_release(&path);\n+}\n+\n /* backend functions */\n int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)\n {\ndiff --git a/refs.h b/refs.h\nindex f0abfa1d93..9d8890fdff 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -1427,4 +1427,17 @@ void ref_iterator_free(struct ref_iterator *ref_iterator);\n int do_for_each_ref_iterator(struct ref_iterator *iter,\n \t\t\t     each_ref_fn fn, void *cb_data);\n \n+/*\n+ * Git only recognizes a directory as a repository if it contains:\n+ * - HEAD file\n+ * - refs/ folder\n+ * While it is necessary within the files backend, newer backends may not\n+ * follow the same structure. To go around this, we create stubs as necessary.\n+ *\n+ * If provided with a 'refs_heads_msg', we create the 'refs/heads/head' file\n+ * with the provided message.\n+ */\n+void refs_create_refdir_stubs(struct repository *repo, const char *refdir,\n+\t\t\t      const char *refs_heads_msg);\n+\n #endif /* REFS_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex fe74af73af..d8651fe779 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -491,18 +491,8 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store,\n \tsafe_create_dir(the_repository, sb.buf, 1);\n \tstrbuf_reset(&sb);\n \n-\tstrbuf_addf(&sb, \"%s/HEAD\", refs->base.gitdir);\n-\twrite_file(sb.buf, \"ref: refs/heads/.invalid\");\n-\tadjust_shared_perm(the_repository, sb.buf);\n-\tstrbuf_reset(&sb);\n-\n-\tstrbuf_addf(&sb, \"%s/refs\", refs->base.gitdir);\n-\tsafe_create_dir(the_repository, sb.buf, 1);\n-\tstrbuf_reset(&sb);\n-\n-\tstrbuf_addf(&sb, \"%s/refs/heads\", refs->base.gitdir);\n-\twrite_file(sb.buf, \"this repository uses the reftable format\");\n-\tadjust_shared_perm(the_repository, sb.buf);\n+\trefs_create_refdir_stubs(the_repository, refs->base.gitdir,\n+\t\t\t\t \"this repository uses the reftable format\");\n \n \tstrbuf_release(&sb);\n \treturn 0;\n\n-- \n2.53.GIT\n\n"},{"id":"536387","messageId":"20260219-kn-alternate-ref-dir-v7-3-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 3/6] refs: move out stub modification to generic layer","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:22Z","receivedAt":"2026-02-19T09:38:32Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"When creating the reftable reference backend on disk, we create stubs to\nensure that the directory can be recognized as a Git repository. This is\ndone by calling `refs_create_refdir_stubs()`. Move this to the generic\nlayer as this is needed for all backends excluding from the files\nbackends. In an upcoming commit where we introduce alternate reference\nbackend locations, we'll have to also create stubs in the $GIT_DIR\nirrespective of the backend being used. This commit builds the base to\nadd that logic.\n\nSimilarly, move the logic for deletion of stubs to the generic layer.\nThe files backend recursively calls the remove function of the\n'packed-backend', here skip calling the generic function since that\nwould try to delete stubs.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 47 +++++++++++++++++++++++++++++++++++++++++++++--\n refs/files-backend.c    |  6 +++++-\n refs/reftable-backend.c | 27 ---------------------------\n 3 files changed, 50 insertions(+), 30 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 77b93d655b..c83af63dc5 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2189,12 +2189,55 @@ void refs_create_refdir_stubs(struct repository *repo, const char *refdir,\n /* backend functions */\n int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)\n {\n-\treturn refs->be->create_on_disk(refs, flags, err);\n+\tint ret = refs->be->create_on_disk(refs, flags, err);\n+\n+\tif (!ret &&\n+\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n+\t\tstruct strbuf msg = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&msg, \"this repository uses the %s format\", refs->be->name);\n+\t\trefs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);\n+\t\tstrbuf_release(&msg);\n+\t}\n+\n+\treturn ret;\n }\n \n int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)\n {\n-\treturn refs->be->remove_on_disk(refs, err);\n+\tint ret = refs->be->remove_on_disk(refs, err);\n+\n+\tif (!ret &&\n+\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n+\t\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&sb, \"%s/HEAD\", refs->gitdir);\n+\t\tif (unlink(sb.buf) < 0) {\n+\t\t\tstrbuf_addf(err, \"could not delete stub HEAD: %s\",\n+\t\t\t\t    strerror(errno));\n+\t\t\tret = -1;\n+\t\t}\n+\t\tstrbuf_reset(&sb);\n+\n+\t\tstrbuf_addf(&sb, \"%s/refs/heads\", refs->gitdir);\n+\t\tif (unlink(sb.buf) < 0) {\n+\t\t\tstrbuf_addf(err, \"could not delete stub heads: %s\",\n+\t\t\t\t    strerror(errno));\n+\t\t\tret = -1;\n+\t\t}\n+\t\tstrbuf_reset(&sb);\n+\n+\t\tstrbuf_addf(&sb, \"%s/refs\", refs->gitdir);\n+\t\tif (rmdir(sb.buf) < 0) {\n+\t\t\tstrbuf_addf(err, \"could not delete refs directory: %s\",\n+\t\t\t\t    strerror(errno));\n+\t\t\tret = -1;\n+\t\t}\n+\n+\t\tstrbuf_release(&sb);\n+\t}\n+\n+\treturn ret;\n }\n \n int repo_resolve_gitlink_ref(struct repository *r,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 240d3c3b26..d3f6423261 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -3700,7 +3700,11 @@ static int files_ref_store_remove_on_disk(struct ref_store *ref_store,\n \tif (for_each_root_ref(refs, remove_one_root_ref, &data) < 0)\n \t\tret = -1;\n \n-\tif (ref_store_remove_on_disk(refs->packed_ref_store, err) < 0)\n+\t/*\n+\t * Directly access the cleanup functions for packed-refs as the generic function\n+\t * would try to clear stubs which isn't required for the files backend.\n+\t */\n+\tif (refs->packed_ref_store->be->remove_on_disk(refs->packed_ref_store, err) < 0)\n \t\tret = -1;\n \n \tstrbuf_release(&sb);\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex d8651fe779..6ce7f9bb8e 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -491,9 +491,6 @@ static int reftable_be_create_on_disk(struct ref_store *ref_store,\n \tsafe_create_dir(the_repository, sb.buf, 1);\n \tstrbuf_reset(&sb);\n \n-\trefs_create_refdir_stubs(the_repository, refs->base.gitdir,\n-\t\t\t\t \"this repository uses the reftable format\");\n-\n \tstrbuf_release(&sb);\n \treturn 0;\n }\n@@ -519,30 +516,6 @@ static int reftable_be_remove_on_disk(struct ref_store *ref_store,\n \t\t\t    strerror(errno));\n \t\tret = -1;\n \t}\n-\tstrbuf_reset(&sb);\n-\n-\tstrbuf_addf(&sb, \"%s/HEAD\", refs->base.gitdir);\n-\tif (unlink(sb.buf) < 0) {\n-\t\tstrbuf_addf(err, \"could not delete stub HEAD: %s\",\n-\t\t\t    strerror(errno));\n-\t\tret = -1;\n-\t}\n-\tstrbuf_reset(&sb);\n-\n-\tstrbuf_addf(&sb, \"%s/refs/heads\", refs->base.gitdir);\n-\tif (unlink(sb.buf) < 0) {\n-\t\tstrbuf_addf(err, \"could not delete stub heads: %s\",\n-\t\t\t    strerror(errno));\n-\t\tret = -1;\n-\t}\n-\tstrbuf_reset(&sb);\n-\n-\tstrbuf_addf(&sb, \"%s/refs\", refs->base.gitdir);\n-\tif (rmdir(sb.buf) < 0) {\n-\t\tstrbuf_addf(err, \"could not delete refs directory: %s\",\n-\t\t\t    strerror(errno));\n-\t\tret = -1;\n-\t}\n \n \tstrbuf_release(&sb);\n \treturn ret;\n\n-- \n2.53.GIT\n\n"},{"id":"536388","messageId":"20260219-kn-alternate-ref-dir-v7-4-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 4/6] refs: receive and use the reference storage payload","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:23Z","receivedAt":"2026-02-19T09:38:33Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"An upcoming commit will add support for providing an URI via the\n'extensions.refStorage' config. The URI will contain the reference\nbackend and a corresponding payload. The payload can be then used for\nproviding an alternate locations for the reference backend.\n\nTo prepare for this, modify the existing backends to accept such an\nargument when initializing via the 'init()' function. Both the files\nand reftable backends will parse the information to be filesystem paths\nto store references. Given that no callers pass any payload yet this is\nessentially a no-op change for now.\n\nTo enable this, provide a 'refs_compute_filesystem_location()' function\nwhich will parse the current 'gitdir' and the 'payload' to provide the\nfinal reference directory and common reference directory (if working in\na linked worktree).\n\nThe documentation and tests will be added alongside the extension of the\nconfig variable.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  | 40 +++++++++++++++++++++++++++++++++++++++-\n refs/files-backend.c    | 17 ++++++++++++-----\n refs/packed-backend.c   |  5 +++++\n refs/packed-backend.h   |  1 +\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c | 24 ++++++++++++++----------\n 6 files changed, 85 insertions(+), 16 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex c83af63dc5..ba2573eb7a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -5,6 +5,7 @@\n #define USE_THE_REPOSITORY_VARIABLE\n \n #include \"git-compat-util.h\"\n+#include \"abspath.h\"\n #include \"advice.h\"\n #include \"config.h\"\n #include \"environment.h\"\n@@ -2290,7 +2291,7 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tif (!be)\n \t\tBUG(\"reference backend is unknown\");\n \n-\trefs = be->init(repo, gitdir, flags);\n+\trefs = be->init(repo, NULL, gitdir, flags);\n \treturn refs;\n }\n \n@@ -3468,3 +3469,40 @@ const char *ref_transaction_error_msg(enum ref_transaction_error err)\n \t\treturn \"unknown failure\";\n \t}\n }\n+\n+void refs_compute_filesystem_location(const char *gitdir, const char *payload,\n+\t\t\t\t      bool *is_worktree, struct strbuf *refdir,\n+\t\t\t\t      struct strbuf *ref_common_dir)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\n+\t*is_worktree = get_common_dir_noenv(ref_common_dir, gitdir);\n+\n+\tif (!payload) {\n+\t\t/*\n+\t\t * We can use the 'gitdir' as the 'refdir' without appending the\n+\t\t * worktree path, as the 'gitdir' here is already the worktree\n+\t\t * path and is different from 'commondir' denoted by 'ref_common_dir'.\n+\t\t */\n+\t\tstrbuf_addstr(refdir, gitdir);\n+\t\treturn;\n+\t}\n+\n+\tif (!is_absolute_path(payload)) {\n+\t\tstrbuf_addf(&sb, \"%s/%s\", ref_common_dir->buf, payload);\n+\t\tstrbuf_realpath(ref_common_dir, sb.buf, 1);\n+\t} else {\n+\t\tstrbuf_realpath(ref_common_dir, payload, 1);\n+\t}\n+\n+\tstrbuf_addbuf(refdir, ref_common_dir);\n+\n+\tif (*is_worktree) {\n+\t\tconst char *wt_id = strrchr(gitdir, '/');\n+\t\tif (!wt_id)\n+\t\t\tBUG(\"worktree path does not contain slash\");\n+\t\tstrbuf_addf(refdir, \"/worktrees/%s\", wt_id + 1);\n+\t}\n+\n+\tstrbuf_release(&sb);\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex d3f6423261..9cde3ba724 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -106,19 +106,24 @@ static void clear_loose_ref_cache(struct files_ref_store *refs)\n  * set of caches.\n  */\n static struct ref_store *files_ref_store_init(struct repository *repo,\n+\t\t\t\t\t      const char *payload,\n \t\t\t\t\t      const char *gitdir,\n \t\t\t\t\t      unsigned int flags)\n {\n \tstruct files_ref_store *refs = xcalloc(1, sizeof(*refs));\n \tstruct ref_store *ref_store = (struct ref_store *)refs;\n-\tstruct strbuf sb = STRBUF_INIT;\n+\tstruct strbuf ref_common_dir = STRBUF_INIT;\n+\tstruct strbuf refdir = STRBUF_INIT;\n+\tbool is_worktree;\n+\n+\trefs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,\n+\t\t\t\t\t &ref_common_dir);\n \n-\tbase_ref_store_init(ref_store, repo, gitdir, &refs_be_files);\n+\tbase_ref_store_init(ref_store, repo, refdir.buf, &refs_be_files);\n \trefs->store_flags = flags;\n-\tget_common_dir_noenv(&sb, gitdir);\n-\trefs->gitcommondir = strbuf_detach(&sb, NULL);\n+\trefs->gitcommondir = strbuf_detach(&ref_common_dir, NULL);\n \trefs->packed_ref_store =\n-\t\tpacked_ref_store_init(repo, refs->gitcommondir, flags);\n+\t\tpacked_ref_store_init(repo, NULL, refs->gitcommondir, flags);\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n \trepo_config_get_bool(repo, \"core.prefersymlinkrefs\", &refs->prefer_symlink_refs);\n \n@@ -126,6 +131,8 @@ static struct ref_store *files_ref_store_init(struct repository *repo,\n \tchdir_notify_reparent(\"files-backend $GIT_COMMONDIR\",\n \t\t\t      &refs->gitcommondir);\n \n+\tstrbuf_release(&refdir);\n+\n \treturn ref_store;\n }\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4ea0c12299..e7bb9f10f9 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -211,7 +211,12 @@ static size_t snapshot_hexsz(const struct snapshot *snapshot)\n \treturn snapshot->refs->base.repo->hash_algo->hexsz;\n }\n \n+/*\n+ * Since packed-refs is only stored in the common dir, don't parse the\n+ * payload and rely on the files-backend to set 'gitdir' correctly.\n+ */\n struct ref_store *packed_ref_store_init(struct repository *repo,\n+\t\t\t\t\tconst char *payload UNUSED,\n \t\t\t\t\tconst char *gitdir,\n \t\t\t\t\tunsigned int store_flags)\n {\ndiff --git a/refs/packed-backend.h b/refs/packed-backend.h\nindex 9481d5e7c2..2c2377a356 100644\n--- a/refs/packed-backend.h\n+++ b/refs/packed-backend.h\n@@ -14,6 +14,7 @@ struct ref_transaction;\n  */\n \n struct ref_store *packed_ref_store_init(struct repository *repo,\n+\t\t\t\t\tconst char *payload,\n \t\t\t\t\tconst char *gitdir,\n \t\t\t\t\tunsigned int store_flags);\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex c7d2a6e50b..9a635f4e6c 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -389,6 +389,7 @@ struct ref_store;\n  * the ref_store and to record the ref_store for later lookup.\n  */\n typedef struct ref_store *ref_store_init_fn(struct repository *repo,\n+\t\t\t\t\t    const char *payload,\n \t\t\t\t\t    const char *gitdir,\n \t\t\t\t\t    unsigned int flags);\n /*\n@@ -666,4 +667,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n \t\t\t\t\t  unsigned int initial_transaction,\n \t\t\t\t\t  struct strbuf *err);\n \n+/*\n+ * Given a gitdir and the reference storage payload provided, retrieve the\n+ * 'refdir' and 'ref_common_dir'. The former is where references should be\n+ * stored for the current worktree, the latter is the common reference\n+ * directory if working with a linked worktree. If working with the main\n+ * worktree, both values will be the same.\n+ *\n+ * This is used by backends that store store files in the repository directly.\n+ */\n+void refs_compute_filesystem_location(const char *gitdir, const char *payload,\n+\t\t\t\t      bool *is_worktree, struct strbuf *refdir,\n+\t\t\t\t      struct strbuf *ref_common_dir);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6ce7f9bb8e..0e220d6bb5 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -372,18 +372,24 @@ static int reftable_be_fsync(int fd)\n }\n \n static struct ref_store *reftable_be_init(struct repository *repo,\n+\t\t\t\t\t  const char *payload,\n \t\t\t\t\t  const char *gitdir,\n \t\t\t\t\t  unsigned int store_flags)\n {\n \tstruct reftable_ref_store *refs = xcalloc(1, sizeof(*refs));\n+\tstruct strbuf ref_common_dir = STRBUF_INIT;\n+\tstruct strbuf refdir = STRBUF_INIT;\n \tstruct strbuf path = STRBUF_INIT;\n-\tint is_worktree;\n+\tbool is_worktree;\n \tmode_t mask;\n \n \tmask = umask(0);\n \tumask(mask);\n \n-\tbase_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable);\n+\trefs_compute_filesystem_location(gitdir, payload, &is_worktree, &refdir,\n+\t\t\t\t\t &ref_common_dir);\n+\n+\tbase_ref_store_init(&refs->base, repo, refdir.buf, &refs_be_reftable);\n \tstrmap_init(&refs->worktree_backends);\n \trefs->store_flags = store_flags;\n \trefs->log_all_ref_updates = repo_settings_get_log_all_ref_updates(repo);\n@@ -419,14 +425,11 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \t/*\n \t * Set up the main reftable stack that is hosted in GIT_COMMON_DIR.\n \t * This stack contains both the shared and the main worktree refs.\n-\t *\n-\t * Note that we don't try to resolve the path in case we have a\n-\t * worktree because `get_common_dir_noenv()` already does it for us.\n \t */\n-\tis_worktree = get_common_dir_noenv(&path, gitdir);\n+\tstrbuf_addbuf(&path, &ref_common_dir);\n \tif (!is_worktree) {\n \t\tstrbuf_reset(&path);\n-\t\tstrbuf_realpath(&path, gitdir, 0);\n+\t\tstrbuf_realpath(&path, ref_common_dir.buf, 0);\n \t}\n \tstrbuf_addstr(&path, \"/reftable\");\n \trefs->err = reftable_backend_init(&refs->main_backend, path.buf,\n@@ -443,10 +446,9 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \t * do it efficiently.\n \t */\n \tif (is_worktree) {\n-\t\tstrbuf_reset(&path);\n-\t\tstrbuf_addf(&path, \"%s/reftable\", gitdir);\n+\t\tstrbuf_addstr(&refdir, \"/reftable\");\n \n-\t\trefs->err = reftable_backend_init(&refs->worktree_backend, path.buf,\n+\t\trefs->err = reftable_backend_init(&refs->worktree_backend, refdir.buf,\n \t\t\t\t\t\t  &refs->write_options);\n \t\tif (refs->err)\n \t\t\tgoto done;\n@@ -456,6 +458,8 @@ static struct ref_store *reftable_be_init(struct repository *repo,\n \n done:\n \tassert(refs->err != REFTABLE_API_ERROR);\n+\tstrbuf_release(&ref_common_dir);\n+\tstrbuf_release(&refdir);\n \tstrbuf_release(&path);\n \treturn &refs->base;\n }\n\n-- \n2.53.GIT\n\n"},{"id":"536389","messageId":"20260219-kn-alternate-ref-dir-v7-5-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 5/6] refs: allow reference location in refstorage config","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:24Z","receivedAt":"2026-02-19T09:38:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"The 'extensions.refStorage' config is used to specify the reference\nbackend for a given repository. Both the 'files' and 'reftable' backends\nutilize the $GIT_DIR as the reference folder by default in\n`get_main_ref_store()`.\n\nSince the reference backends are pluggable, this means that they could\nwork with out-of-tree reference directories too. Extend the 'refStorage'\nconfig to also support taking an URI input, where users can specify the\nreference backend and the location.\n\nAdd the required changes to obtain and propagate this value to the\nindividual backends. Add the necessary documentation and tests.\n\nTraditionally, for linked worktrees, references were stored in the\n'$GIT_DIR/worktrees/<wt_id>' path. But when using an alternate reference\nstorage path, it doesn't make sense to store the main worktree\nreferences in the new path, and the linked worktree references in the\n$GIT_DIR. So, let's store linked worktree references in\n'$ALTERNATE_REFERENCE_DIR/worktrees/<wt_id>'. To do this, create the\nnecessary files and folders while also adding stubs in the $GIT_DIR path\nto ensure that it is still considered a Git directory.\n\nIdeally, we would want to pass in a `struct worktree *` to individual\nbackends, instead of passing the `gitdir`. This allows them to handle\nworktree specific logic. Currently, that is not possible since the\nworktree code is:\n\n  - Tied to using the global `the_repository` variable.\n\n  - Is not setup before the reference database during initialization of\n    the repository.\n\nAdd a TODO in 'refs.c' to ensure we can eventually make that change.\n\nHelped-by: Patrick Steinhardt <ps@pks.im>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/config/extensions.adoc |  16 +++-\n builtin/worktree.c                   |  34 ++++++++\n refs.c                               |   6 +-\n repository.c                         |   9 +-\n repository.h                         |   8 +-\n setup.c                              |  34 +++++++-\n setup.h                              |   1 +\n t/meson.build                        |   1 +\n t/t1423-ref-backend.sh               | 159 +++++++++++++++++++++++++++++++++++\n 9 files changed, 259 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc\nindex 532456644b..3e51da36d3 100644\n--- a/Documentation/config/extensions.adoc\n+++ b/Documentation/config/extensions.adoc\n@@ -57,10 +57,24 @@ For historical reasons, this extension is respected regardless of the\n `core.repositoryFormatVersion` setting.\n \n refStorage:::\n-\tSpecify the ref storage format to use. The acceptable values are:\n+\tSpecify the ref storage format and a corresponding payload. The value\n+\tcan be either a format name or a URI:\n +\n --\n+* A format name alone (e.g., `reftable` or `files`).\n+\n+* A URI format `<format>://<payload>` explicitly specifies both the\n+  format and payload (e.g., `reftable:///foo/bar`).\n+\n+Supported format names are:\n++\n include::../ref-storage-format.adoc[]\n++\n+The payload is passed directly to the reference backend. For the files and\n+reftable backends, this must be a filesystem path where the references will\n+be stored. Defaulting to the commondir when no payload is provided. Relative\n+paths are resolved relative to the $GIT_DIR. Future backends may support\n+other payload schemes, e.g., postgres://127.0.0.1:5432?database=myrepo.\n --\n +\n Note that this setting should only be set by linkgit:git-init[1] or\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex fbdaf2eb2e..293e808379 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -425,6 +425,39 @@ static int make_worktree_orphan(const char * ref, const struct add_opts *opts,\n \treturn run_command(&cp);\n }\n \n+/*\n+ * References for worktrees are generally stored in '$GIT_DIR/worktrees/<wt_id>'.\n+ * But when using alternate reference directories, we want to store the worktree\n+ * references in '$ALTERNATE_REFERENCE_DIR/worktrees/<wt_id>'.\n+ *\n+ * Create the necessary folder structure to facilitate the same. But to ensure\n+ * that the former path is still considered a Git directory, add stubs.\n+ */\n+static void setup_alternate_ref_dir(struct worktree *wt, const char *wt_git_path)\n+{\n+\tstruct strbuf sb = STRBUF_INIT;\n+\tchar *path;\n+\n+\tpath = wt->repo->ref_storage_payload;\n+\tif (!path)\n+\t\treturn;\n+\n+\tif (!is_absolute_path(path))\n+\t\tstrbuf_addf(&sb, \"%s/\", wt->repo->commondir);\n+\n+\tstrbuf_addf(&sb, \"%s/worktrees\", path);\n+\tsafe_create_dir(wt->repo, sb.buf, 1);\n+\tstrbuf_addf(&sb, \"/%s\", wt->id);\n+\tsafe_create_dir(wt->repo, sb.buf, 1);\n+\tstrbuf_reset(&sb);\n+\n+\tstrbuf_addf(&sb, \"this worktree stores references in %s/worktrees/%s\",\n+\t\t    path, wt->id);\n+\trefs_create_refdir_stubs(wt->repo, wt_git_path, sb.buf);\n+\n+\tstrbuf_release(&sb);\n+}\n+\n static int add_worktree(const char *path, const char *refname,\n \t\t\tconst struct add_opts *opts)\n {\n@@ -518,6 +551,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = error(_(\"could not find created worktree '%s'\"), name);\n \t\tgoto done;\n \t}\n+\tsetup_alternate_ref_dir(wt, sb_repo.buf);\n \twt_refs = get_worktree_ref_store(wt);\n \n \tret = ref_store_create_on_disk(wt_refs, REF_STORE_CREATE_ON_DISK_IS_WORKTREE, &sb);\ndiff --git a/refs.c b/refs.c\nindex ba2573eb7a..ef1902e85c 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2291,7 +2291,11 @@ static struct ref_store *ref_store_init(struct repository *repo,\n \tif (!be)\n \t\tBUG(\"reference backend is unknown\");\n \n-\trefs = be->init(repo, NULL, gitdir, flags);\n+\t/*\n+\t * TODO Send in a 'struct worktree' instead of a 'gitdir', and\n+\t * allow the backend to handle how it wants to deal with worktrees.\n+\t */\n+\trefs = be->init(repo, repo->ref_storage_payload, gitdir, flags);\n \treturn refs;\n }\n \ndiff --git a/repository.c b/repository.c\nindex c7e75215ac..9815f081ef 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -193,9 +193,12 @@ void repo_set_compat_hash_algo(struct repository *repo, int algo)\n }\n \n void repo_set_ref_storage_format(struct repository *repo,\n-\t\t\t\t enum ref_storage_format format)\n+\t\t\t\t enum ref_storage_format format,\n+\t\t\t\t const char *payload)\n {\n \trepo->ref_storage_format = format;\n+\tfree(repo->ref_storage_payload);\n+\trepo->ref_storage_payload = xstrdup_or_null(payload);\n }\n \n /*\n@@ -277,7 +280,8 @@ int repo_init(struct repository *repo,\n \n \trepo_set_hash_algo(repo, format.hash_algo);\n \trepo_set_compat_hash_algo(repo, format.compat_hash_algo);\n-\trepo_set_ref_storage_format(repo, format.ref_storage_format);\n+\trepo_set_ref_storage_format(repo, format.ref_storage_format,\n+\t\t\t\t    format.ref_storage_payload);\n \trepo->repository_format_worktree_config = format.worktree_config;\n \trepo->repository_format_relative_worktrees = format.relative_worktrees;\n \trepo->repository_format_precious_objects = format.precious_objects;\n@@ -369,6 +373,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->index_file);\n \tFREE_AND_NULL(repo->worktree);\n \tFREE_AND_NULL(repo->submodule_prefix);\n+\tFREE_AND_NULL(repo->ref_storage_payload);\n \n \todb_free(repo->objects);\n \trepo->objects = NULL;\ndiff --git a/repository.h b/repository.h\nindex 6063c4b846..95e2333bad 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -150,6 +150,11 @@ struct repository {\n \n \t/* Repository's reference storage format, as serialized on disk. */\n \tenum ref_storage_format ref_storage_format;\n+\t/*\n+\t * Reference storage information as needed for the backend. This contains\n+\t * only the payload from the reference URI without the schema.\n+\t */\n+\tchar *ref_storage_payload;\n \n \t/* A unique-id for tracing purposes. */\n \tint trace2_repo_id;\n@@ -204,7 +209,8 @@ void repo_set_worktree(struct repository *repo, const char *path);\n void repo_set_hash_algo(struct repository *repo, int algo);\n void repo_set_compat_hash_algo(struct repository *repo, int compat_algo);\n void repo_set_ref_storage_format(struct repository *repo,\n-\t\t\t\t enum ref_storage_format format);\n+\t\t\t\t enum ref_storage_format format,\n+\t\t\t\t const char *payload);\n void initialize_repository(struct repository *repo);\n RESULT_MUST_BE_USED\n int repo_init(struct repository *r, const char *gitdir, const char *worktree);\ndiff --git a/setup.c b/setup.c\nindex 1fc9ae3872..d407f3347b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -632,6 +632,21 @@ static enum extension_result handle_extension_v0(const char *var,\n \t\treturn EXTENSION_UNKNOWN;\n }\n \n+static void parse_reference_uri(const char *value, char **format,\n+\t\t\t\tchar **payload)\n+{\n+\tconst char *schema_end;\n+\n+\tschema_end = strstr(value, \"://\");\n+\tif (!schema_end) {\n+\t\t*format = xstrdup(value);\n+\t\t*payload = NULL;\n+\t} else {\n+\t\t*format = xstrndup(value, schema_end - value);\n+\t\t*payload = xstrdup_or_null(schema_end + 3);\n+\t}\n+}\n+\n /*\n  * Record any new extensions in this function.\n  */\n@@ -674,10 +689,17 @@ static enum extension_result handle_extension(const char *var,\n \t\treturn EXTENSION_OK;\n \t} else if (!strcmp(ext, \"refstorage\")) {\n \t\tunsigned int format;\n+\t\tchar *format_str;\n \n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n-\t\tformat = ref_storage_format_by_name(value);\n+\n+\t\tparse_reference_uri(value, &format_str,\n+\t\t\t\t    &data->ref_storage_payload);\n+\n+\t\tformat = ref_storage_format_by_name(format_str);\n+\t\tfree(format_str);\n+\n \t\tif (format == REF_STORAGE_FORMAT_UNKNOWN)\n \t\t\treturn error(_(\"invalid value for '%s': '%s'\"),\n \t\t\t\t     \"extensions.refstorage\", value);\n@@ -850,6 +872,7 @@ void clear_repository_format(struct repository_format *format)\n \tstring_list_clear(&format->v1_only_extensions, 0);\n \tfree(format->work_tree);\n \tfree(format->partial_clone);\n+\tfree(format->ref_storage_payload);\n \tinit_repository_format(format);\n }\n \n@@ -1942,7 +1965,8 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\t\trepo_set_compat_hash_algo(the_repository,\n \t\t\t\t\t\t  repo_fmt.compat_hash_algo);\n \t\t\trepo_set_ref_storage_format(the_repository,\n-\t\t\t\t\t\t    repo_fmt.ref_storage_format);\n+\t\t\t\t\t\t    repo_fmt.ref_storage_format,\n+\t\t\t\t\t\t    repo_fmt.ref_storage_payload);\n \t\t\tthe_repository->repository_format_worktree_config =\n \t\t\t\trepo_fmt.worktree_config;\n \t\t\tthe_repository->repository_format_relative_worktrees =\n@@ -2042,7 +2066,8 @@ void check_repository_format(struct repository_format *fmt)\n \trepo_set_hash_algo(the_repository, fmt->hash_algo);\n \trepo_set_compat_hash_algo(the_repository, fmt->compat_hash_algo);\n \trepo_set_ref_storage_format(the_repository,\n-\t\t\t\t    fmt->ref_storage_format);\n+\t\t\t\t    fmt->ref_storage_format,\n+\t\t\t\t    fmt->ref_storage_payload);\n \tthe_repository->repository_format_worktree_config =\n \t\tfmt->worktree_config;\n \tthe_repository->repository_format_relative_worktrees =\n@@ -2643,7 +2668,8 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t} else {\n \t\trepo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;\n \t}\n-\trepo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format);\n+\trepo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format,\n+\t\t\t\t    repo_fmt->ref_storage_payload);\n }\n \n int init_db(const char *git_dir, const char *real_git_dir,\ndiff --git a/setup.h b/setup.h\nindex ddb9f6701c..093af39e84 100644\n--- a/setup.h\n+++ b/setup.h\n@@ -171,6 +171,7 @@ struct repository_format {\n \tint hash_algo;\n \tint compat_hash_algo;\n \tenum ref_storage_format ref_storage_format;\n+\tchar *ref_storage_payload;\n \tint sparse_index;\n \tchar *work_tree;\n \tstruct string_list unknown_extensions;\ndiff --git a/t/meson.build b/t/meson.build\nindex 459c52a489..11fc5a49ee 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -210,6 +210,7 @@ integration_tests = [\n   't1420-lost-found.sh',\n   't1421-reflog-write.sh',\n   't1422-show-ref-exists.sh',\n+  't1423-ref-backend.sh',\n   't1430-bad-ref-name.sh',\n   't1450-fsck.sh',\n   't1451-fsck-buffer.sh',\ndiff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh\nnew file mode 100755\nindex 0000000000..9912433b8c\n--- /dev/null\n+++ b/t/t1423-ref-backend.sh\n@@ -0,0 +1,159 @@\n+#!/bin/sh\n+\n+test_description='Test reference backend URIs'\n+\n+. ./test-lib.sh\n+\n+# Run a git command with the provided reference storage. Reset the backend\n+# post running the command.\n+# Usage: run_with_uri <repo> <backend> <uri> <cmd>\n+#   <repo> is the relative path to the repo to run the command in.\n+#   <backend> is the original ref storage of the repo.\n+#   <uri> is the new URI to be set for the ref storage.\n+#   <cmd> is the git subcommand to be run in the repository.\n+run_with_uri() {\n+\trepo=$1 &&\n+\tbackend=$2 &&\n+\turi=$3 &&\n+\tcmd=$4 &&\n+\n+\tgit -C \"$repo\" config set core.repositoryformatversion 1\n+\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n+\tgit -C \"$repo\" $cmd &&\n+\tgit -C \"$repo\" config set extensions.refStorage \"$backend\"\n+}\n+\n+# Test a repository with a given reference storage by running and comparing\n+# 'git refs list' before and after setting the new reference backend. If\n+# err_msg is set, expect the command to fail and grep for the provided err_msg.\n+# Usage: run_with_uri <repo> <backend> <uri> <cmd>\n+#   <repo> is the relative path to the repo to run the command in.\n+#   <backend> is the original ref storage of the repo.\n+#   <uri> is the new URI to be set for the ref storage.\n+#   <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.\n+test_refs_backend() {\n+\trepo=$1 &&\n+\tbackend=$2 &&\n+\turi=$3 &&\n+\terr_msg=$4 &&\n+\n+\tgit -C \"$repo\" config set core.repositoryformatversion 1 &&\n+\tif test -n \"$err_msg\";\n+\tthen\n+\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n+\t\ttest_must_fail git -C \"$repo\" refs list 2>err &&\n+\t\ttest_grep \"$err_msg\" err\n+\telse\n+\t\tgit -C \"$repo\" refs list >expect &&\n+\t\trun_with_uri \"$repo\" \"$backend\" \"$uri\" \"refs list\" >actual &&\n+\t\ttest_cmp expect actual\n+\tfi\n+}\n+\n+test_expect_success 'URI is invalid' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\ttest_refs_backend repo files \"reftable@/home/reftable\" \\\n+\t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n+'\n+\n+test_expect_success 'URI ends with colon' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\ttest_refs_backend repo files \"reftable:\" \\\n+\t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n+'\n+\n+test_expect_success 'unknown reference backend' '\n+\ttest_when_finished \"rm -rf repo\" &&\n+\tgit init repo &&\n+\ttest_refs_backend repo files \"db://.git\" \\\n+\t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n+'\n+\n+ref_formats=\"files reftable\"\n+for from_format in $ref_formats\n+do\n+\n+for to_format in $ref_formats\n+do\n+\tif test \"$from_format\" = \"$to_format\"\n+\tthen\n+\t\tcontinue\n+\tfi\n+\n+\n+\tfor dir in \"$(pwd)/repo/.git\" \".\"\n+\tdo\n+\n+\t\ttest_expect_success \"read from $to_format backend, $dir dir\" '\n+\t\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\t\tgit init --ref-format=$from_format repo &&\n+\t\t\t(\n+\t\t\t\tcd repo &&\n+\t\t\t\ttest_commit 1 &&\n+\t\t\t\ttest_commit 2 &&\n+\t\t\t\ttest_commit 3 &&\n+\n+\t\t\t\tgit refs migrate --dry-run --ref-format=$to_format >out &&\n+\t\t\t\tBACKEND_PATH=\"$dir/$(sed \"s/.* ${SQ}.git\\/\\(.*\\)${SQ}/\\1/\" out)\" &&\n+\t\t\t\ttest_refs_backend . $from_format \"$to_format://$BACKEND_PATH\" \"$method\"\n+\t\t\t)\n+\t\t'\n+\n+\t\ttest_expect_success \"write to $to_format backend, $dir dir\" '\n+\t\t\ttest_when_finished \"rm -rf repo\" &&\n+\t\t\tgit init --ref-format=$from_format repo &&\n+\t\t\t(\n+\t\t\t\tcd repo &&\n+\t\t\t\ttest_commit 1 &&\n+\t\t\t\ttest_commit 2 &&\n+\t\t\t\ttest_commit 3 &&\n+\n+\t\t\t\tgit refs migrate --dry-run --ref-format=$to_format >out &&\n+\t\t\t\tBACKEND_PATH=\"$dir/$(sed \"s/.* ${SQ}.git\\/\\(.*\\)${SQ}/\\1/\" out)\" &&\n+\n+\t\t\t\ttest_refs_backend . $from_format \"$to_format://$BACKEND_PATH\" &&\n+\n+\t\t\t\tgit refs list >expect &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \"tag -d 1\" &&\n+\t\t\t\tgit refs list >actual &&\n+\t\t\t\ttest_cmp expect actual &&\n+\n+\t\t\t\tgit refs list | grep -v \"refs/tags/1\" >expect &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \"refs list\" >actual &&\n+\t\t\t\ttest_cmp expect actual\n+\t\t\t)\n+\t\t'\n+\n+\t\ttest_expect_success \"with worktree and $to_format backend, $dir dir\" '\n+\t\t\ttest_when_finished \"rm -rf repo wt\" &&\n+\t\t\tgit init --ref-format=$from_format repo &&\n+\t\t\t(\n+\t\t\t\tcd repo &&\n+\t\t\t\ttest_commit 1 &&\n+\t\t\t\ttest_commit 2 &&\n+\t\t\t\ttest_commit 3 &&\n+\n+\t\t\t\tgit refs migrate --dry-run --ref-format=$to_format >out &&\n+\t\t\t\tBACKEND_PATH=\"$dir/$(sed \"s/.* ${SQ}.git\\/\\(.*\\)${SQ}/\\1/\" out)\" &&\n+\n+\t\t\t\tgit config set core.repositoryformatversion 1 &&\n+\t\t\t\tgit config set extensions.refStorage \"$to_format://$BACKEND_PATH\" &&\n+\n+\t\t\t\tgit worktree add ../wt 2\n+\t\t\t) &&\n+\n+\t\t\tgit -C repo for-each-ref --include-root-refs >expect &&\n+\t\t\tgit -C wt for-each-ref --include-root-refs >expect &&\n+\t\t\t! test_cmp expect actual &&\n+\n+\t\t\tgit -C wt rev-parse 2 >expect &&\n+\t\t\tgit -C wt rev-parse HEAD >actual &&\n+\t\t\ttest_cmp expect actual\n+\t\t'\n+\tdone # closes dir\n+done # closes to_format\n+done # closes from_format\n+\n+test_done\n\n-- \n2.53.GIT\n\n"},{"id":"536390","messageId":"20260219-kn-alternate-ref-dir-v7-6-16f27860dbdf@gmail.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-0-16f27860dbdf@gmail.com","subject":"[PATCH v7 6/6] refs: add GIT_REFERENCE_BACKEND to specify reference backend","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-19T09:38:25Z","receivedAt":"2026-02-19T09:38:35Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Git allows setting a different object directory via\n'GIT_OBJECT_DIRECTORY', but provides no equivalent for references. In\nthe previous commit we extended the 'extensions.refStorage' config to\nalso support an URI input for reference backend with location.\n\nLet's also add a new environment variable 'GIT_REFERENCE_BACKEND' that\ntakes in the same input as the config variable. Having an environment\nvariable allows us to modify the reference backend and location on the\nfly for individual Git commands.\n\nThe environment variable also allows usage of alternate reference\ndirectories during 'git-clone(1)' and 'git-init(1)'. Add the config to\nthe repository when created with the environment variable set.\n\nWhen initializing the repository with an alternate reference folder,\ncreate the required stubs in the repositories $GIT_DIR. The inverse,\ni.e. removal of the ref store doesn't clean up the stubs in the $GIT_DIR\nsince that would render it unusable. Removal of ref store is only used\nwhen migrating between ref formats and cleanup of the $GIT_DIR doesn't\nmake sense in such a situation.\n\nHelped-by: Jean-Noël Avila <jn.avila@free.fr>\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git.adoc |   5 ++\n environment.h          |   1 +\n refs.c                 |  30 +++++---\n setup.c                |  55 +++++++++++++-\n t/t1423-ref-backend.sh | 189 ++++++++++++++++++++++++++++++++++++++++---------\n 5 files changed, 235 insertions(+), 45 deletions(-)\n\ndiff --git a/Documentation/git.adoc b/Documentation/git.adoc\nindex ce099e78b8..66442735ea 100644\n--- a/Documentation/git.adoc\n+++ b/Documentation/git.adoc\n@@ -584,6 +584,11 @@ double-quotes and respecting backslash escapes. E.g., the value\n \trepositories will be set to this value. The default is \"files\".\n \tSee `--ref-format` in linkgit:git-init[1].\n \n+`GIT_REFERENCE_BACKEND`::\n+    Specify which reference backend to be used along with its URI.\n+    See `extensions.refStorage` option in linkgit:git-config[1] for more\n+    details. Overrides the config variable when used.\n+\n Git Commits\n ~~~~~~~~~~~\n `GIT_AUTHOR_NAME`::\ndiff --git a/environment.h b/environment.h\nindex 27f657af04..540e0a7f6d 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -42,6 +42,7 @@\n #define GIT_OPTIONAL_LOCKS_ENVIRONMENT \"GIT_OPTIONAL_LOCKS\"\n #define GIT_TEXT_DOMAIN_DIR_ENVIRONMENT \"GIT_TEXTDOMAINDIR\"\n #define GIT_ATTR_SOURCE_ENVIRONMENT \"GIT_ATTR_SOURCE\"\n+#define GIT_REFERENCE_BACKEND_ENVIRONMENT \"GIT_REFERENCE_BACKEND\"\n \n /*\n  * Environment variable used to propagate the --no-advice global option to the\ndiff --git a/refs.c b/refs.c\nindex ef1902e85c..a700a66f08 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2192,13 +2192,17 @@ int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *e\n {\n \tint ret = refs->be->create_on_disk(refs, flags, err);\n \n-\tif (!ret &&\n-\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n-\t\tstruct strbuf msg = STRBUF_INIT;\n-\n-\t\tstrbuf_addf(&msg, \"this repository uses the %s format\", refs->be->name);\n-\t\trefs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);\n-\t\tstrbuf_release(&msg);\n+\tif (!ret) {\n+\t\t/* Creation of stubs for linked worktrees are handled in the worktree code. */\n+\t\tif (!(flags & REF_STORE_CREATE_ON_DISK_IS_WORKTREE) && refs->repo->ref_storage_payload) {\n+\t\t\trefs_create_refdir_stubs(refs->repo, refs->repo->gitdir,\n+\t\t\t\t\t\t \"repository uses alternate refs storage\");\n+\t\t} else if (ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n+\t\t\tstruct strbuf msg = STRBUF_INIT;\n+\t\t\tstrbuf_addf(&msg, \"this repository uses the %s format\", refs->be->name);\n+\t\t\trefs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);\n+\t\t\tstrbuf_release(&msg);\n+\t\t}\n \t}\n \n \treturn ret;\n@@ -2208,10 +2212,18 @@ int ref_store_remove_on_disk(struct ref_store *refs, struct strbuf *err)\n {\n \tint ret = refs->be->remove_on_disk(refs, err);\n \n-\tif (!ret &&\n-\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n+\tif (!ret) {\n+\t\tenum ref_storage_format format = ref_storage_format_by_name(refs->be->name);\n \t\tstruct strbuf sb = STRBUF_INIT;\n \n+\t\t/* Backends apart from the files backend create stubs. */\n+\t\tif (format == REF_STORAGE_FORMAT_FILES)\n+\t\t\treturn ret;\n+\n+\t\t/* Alternate refs backend require stubs in the gitdir. */\n+\t\tif (refs->repo->ref_storage_payload)\n+\t\t\treturn ret;\n+\n \t\tstrbuf_addf(&sb, \"%s/HEAD\", refs->gitdir);\n \t\tif (unlink(sb.buf) < 0) {\n \t\t\tstrbuf_addf(err, \"could not delete stub HEAD: %s\",\ndiff --git a/setup.c b/setup.c\nindex d407f3347b..90cb9be578 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -1838,6 +1838,7 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \tstatic struct strbuf cwd = STRBUF_INIT;\n \tstruct strbuf dir = STRBUF_INIT, gitdir = STRBUF_INIT, report = STRBUF_INIT;\n \tconst char *prefix = NULL;\n+\tconst char *ref_backend_uri;\n \tstruct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;\n \n \t/*\n@@ -1995,6 +1996,25 @@ const char *setup_git_directory_gently(int *nongit_ok)\n \t\tsetenv(GIT_PREFIX_ENVIRONMENT, \"\", 1);\n \t}\n \n+\t/*\n+\t * The env variable should override the repository config\n+\t * for 'extensions.refStorage'.\n+\t */\n+\tref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);\n+\tif (ref_backend_uri) {\n+\t\tchar *backend, *payload;\n+\t\tenum ref_storage_format format;\n+\n+\t\tparse_reference_uri(ref_backend_uri, &backend, &payload);\n+\t\tformat = ref_storage_format_by_name(backend);\n+\t\tif (format == REF_STORAGE_FORMAT_UNKNOWN)\n+\t\t\tdie(_(\"unknown ref storage format: '%s'\"), backend);\n+\t\trepo_set_ref_storage_format(the_repository, format, payload);\n+\n+\t\tfree(backend);\n+\t\tfree(payload);\n+\t}\n+\n \tsetup_original_cwd();\n \n \tstrbuf_release(&dir);\n@@ -2337,7 +2357,8 @@ void initialize_repository_version(int hash_algo,\n \t * the remote repository's format.\n \t */\n \tif (hash_algo != GIT_HASH_SHA1_LEGACY ||\n-\t    ref_storage_format != REF_STORAGE_FORMAT_FILES)\n+\t    ref_storage_format != REF_STORAGE_FORMAT_FILES ||\n+\t    the_repository->ref_storage_payload)\n \t\ttarget_version = GIT_REPO_VERSION_READ;\n \n \tif (hash_algo != GIT_HASH_SHA1_LEGACY && hash_algo != GIT_HASH_UNKNOWN)\n@@ -2346,11 +2367,20 @@ void initialize_repository_version(int hash_algo,\n \telse if (reinit)\n \t\trepo_config_set_gently(the_repository, \"extensions.objectformat\", NULL);\n \n-\tif (ref_storage_format != REF_STORAGE_FORMAT_FILES)\n+\tif (the_repository->ref_storage_payload) {\n+\t\tstruct strbuf ref_uri = STRBUF_INIT;\n+\n+\t\tstrbuf_addf(&ref_uri, \"%s://%s\",\n+\t\t\t    ref_storage_format_to_name(ref_storage_format),\n+\t\t\t    the_repository->ref_storage_payload);\n+\t\trepo_config_set(the_repository, \"extensions.refstorage\", ref_uri.buf);\n+\t\tstrbuf_release(&ref_uri);\n+\t} else if (ref_storage_format != REF_STORAGE_FORMAT_FILES) {\n \t\trepo_config_set(the_repository, \"extensions.refstorage\",\n \t\t\t\tref_storage_format_to_name(ref_storage_format));\n-\telse if (reinit)\n+\t} else if (reinit) {\n \t\trepo_config_set_gently(the_repository, \"extensions.refstorage\", NULL);\n+\t}\n \n \tif (reinit) {\n \t\tstruct strbuf config = STRBUF_INIT;\n@@ -2623,6 +2653,7 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t\t.ignore_repo = 1,\n \t\t.ignore_worktree = 1,\n \t};\n+\tconst char *ref_backend_uri;\n \tconst char *env;\n \n \tconfig_with_options(read_default_format_config, &cfg, NULL, NULL, &opts);\n@@ -2668,6 +2699,24 @@ static void repository_format_configure(struct repository_format *repo_fmt,\n \t} else {\n \t\trepo_fmt->ref_storage_format = REF_STORAGE_FORMAT_DEFAULT;\n \t}\n+\n+\n+\tref_backend_uri = getenv(GIT_REFERENCE_BACKEND_ENVIRONMENT);\n+\tif (ref_backend_uri) {\n+\t\tchar *backend, *payload;\n+\t\tenum ref_storage_format format;\n+\n+\t\tparse_reference_uri(ref_backend_uri, &backend, &payload);\n+\t\tformat = ref_storage_format_by_name(backend);\n+\t\tif (format == REF_STORAGE_FORMAT_UNKNOWN)\n+\t\t\tdie(_(\"unknown ref storage format: '%s'\"), backend);\n+\n+\t\trepo_fmt->ref_storage_format = format;\n+\t\trepo_fmt->ref_storage_payload = payload;\n+\n+\t\tfree(backend);\n+\t}\n+\n \trepo_set_ref_storage_format(the_repository, repo_fmt->ref_storage_format,\n \t\t\t\t    repo_fmt->ref_storage_payload);\n }\ndiff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh\nindex 9912433b8c..d69aea3f7f 100755\n--- a/t/t1423-ref-backend.sh\n+++ b/t/t1423-ref-backend.sh\n@@ -11,16 +11,25 @@ test_description='Test reference backend URIs'\n #   <backend> is the original ref storage of the repo.\n #   <uri> is the new URI to be set for the ref storage.\n #   <cmd> is the git subcommand to be run in the repository.\n+#   <via> if 'config', set the backend via the 'extensions.refStorage' config.\n+#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.\n run_with_uri() {\n \trepo=$1 &&\n \tbackend=$2 &&\n \turi=$3 &&\n \tcmd=$4 &&\n+\tvia=$5 &&\n \n-\tgit -C \"$repo\" config set core.repositoryformatversion 1\n-\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n-\tgit -C \"$repo\" $cmd &&\n-\tgit -C \"$repo\" config set extensions.refStorage \"$backend\"\n+\tgit -C \"$repo\" config set core.repositoryformatversion 1 &&\n+\tif test \"$via\" = \"env\"\n+\tthen\n+\t\ttest_env GIT_REFERENCE_BACKEND=\"$uri\" git -C \"$repo\" $cmd\n+\telif test \"$via\" = \"config\"\n+\tthen\n+\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n+\t\tgit -C \"$repo\" $cmd &&\n+\t\tgit -C \"$repo\" config set extensions.refStorage \"$backend\"\n+\tfi\n }\n \n # Test a repository with a given reference storage by running and comparing\n@@ -30,44 +39,86 @@ run_with_uri() {\n #   <repo> is the relative path to the repo to run the command in.\n #   <backend> is the original ref storage of the repo.\n #   <uri> is the new URI to be set for the ref storage.\n+#   <via> if 'config', set the backend via the 'extensions.refStorage' config.\n+#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.\n #   <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.\n test_refs_backend() {\n \trepo=$1 &&\n \tbackend=$2 &&\n \turi=$3 &&\n-\terr_msg=$4 &&\n+\tvia=$4 &&\n+\terr_msg=$5 &&\n+\n \n-\tgit -C \"$repo\" config set core.repositoryformatversion 1 &&\n \tif test -n \"$err_msg\";\n \tthen\n-\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n-\t\ttest_must_fail git -C \"$repo\" refs list 2>err &&\n-\t\ttest_grep \"$err_msg\" err\n+\t\tif test \"$via\" = \"env\"\n+\t\tthen\n+\t\t\ttest_env GIT_REFERENCE_BACKEND=\"$uri\" test_must_fail git -C \"$repo\" refs list 2>err\n+\t\telif test \"$via\" = \"config\"\n+\t\tthen\n+\t\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n+\t\t\ttest_must_fail git -C \"$repo\" refs list 2>err &&\n+\t\t\ttest_grep \"$err_msg\" err\n+\t\tfi\n \telse\n \t\tgit -C \"$repo\" refs list >expect &&\n-\t\trun_with_uri \"$repo\" \"$backend\" \"$uri\" \"refs list\" >actual &&\n+\t\trun_with_uri \"$repo\" \"$backend\" \"$uri\" \"refs list\" \"$via\">actual &&\n \t\ttest_cmp expect actual\n \tfi\n }\n \n-test_expect_success 'URI is invalid' '\n+# Verify that the expected files are present in the gitdir and the refsdir.\n+# Usage: verify_files_exist <gitdir> <refdir>\n+#   <gitdir> is the path for the gitdir.\n+#   <refdir> is the path for the refdir.\n+verify_files_exist() {\n+\tgitdir=$1 &&\n+\trefdir=$2 &&\n+\n+\t# verify that the stubs were added to the $GITDIR.\n+\tcat $gitdir/refs/heads >actual &&\n+\techo \"repository uses alternate refs storage\" >expect &&\n+\ttest_cmp expect actual &&\n+\tcat $gitdir/HEAD >actual &&\n+\techo \"ref: refs/heads/.invalid\" >expect &&\n+\ttest_cmp expect actual\n+\n+\t# verify that backend specific files exist.\n+\tcase \"$GIT_DEFAULT_REF_FORMAT\" in\n+\tfiles)\n+\t\ttest_path_is_dir $refdir/refs/heads &&\n+\t\ttest_path_is_file $refdir/HEAD;;\n+\treftable)\n+\t\ttest_path_is_dir $refdir/reftable &&\n+\t\ttest_path_is_file $refdir/reftable/tables.list;;\n+\t*)\n+\t\tBUG \"unhandled ref format $GIT_DEFAULT_REF_FORMAT\";;\n+\tesac\n+}\n+\n+methods=\"config env\"\n+for method in $methods\n+do\n+\n+test_expect_success \"$method: URI is invalid\" '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-\ttest_refs_backend repo files \"reftable@/home/reftable\" \\\n+\ttest_refs_backend repo files \"reftable@/home/reftable\" \"$method\" \\\n \t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n '\n \n-test_expect_success 'URI ends with colon' '\n+test_expect_success \"$method: URI ends with colon\" '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-\ttest_refs_backend repo files \"reftable:\" \\\n+\ttest_refs_backend repo files \"reftable:\" \"$method\" \\\n \t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n '\n \n-test_expect_success 'unknown reference backend' '\n+test_expect_success \"$method: unknown reference backend\" '\n \ttest_when_finished \"rm -rf repo\" &&\n \tgit init repo &&\n-\ttest_refs_backend repo files \"db://.git\" \\\n+\ttest_refs_backend repo files \"db://.git\" \"$method\" \\\n \t\t\"invalid value for ${SQ}extensions.refstorage${SQ}\"\n '\n \n@@ -86,7 +137,7 @@ do\n \tfor dir in \"$(pwd)/repo/.git\" \".\"\n \tdo\n \n-\t\ttest_expect_success \"read from $to_format backend, $dir dir\" '\n+\t\ttest_expect_success \"$method: read from $to_format backend, $dir dir\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-format=$from_format repo &&\n \t\t\t(\n@@ -101,7 +152,7 @@ do\n \t\t\t)\n \t\t'\n \n-\t\ttest_expect_success \"write to $to_format backend, $dir dir\" '\n+\t\ttest_expect_success \"$method: write to $to_format backend, $dir dir\" '\n \t\t\ttest_when_finished \"rm -rf repo\" &&\n \t\t\tgit init --ref-format=$from_format repo &&\n \t\t\t(\n@@ -113,20 +164,22 @@ do\n \t\t\t\tgit refs migrate --dry-run --ref-format=$to_format >out &&\n \t\t\t\tBACKEND_PATH=\"$dir/$(sed \"s/.* ${SQ}.git\\/\\(.*\\)${SQ}/\\1/\" out)\" &&\n \n-\t\t\t\ttest_refs_backend . $from_format \"$to_format://$BACKEND_PATH\" &&\n+\t\t\t\ttest_refs_backend . $from_format \"$to_format://$BACKEND_PATH\" \"$method\" &&\n \n \t\t\t\tgit refs list >expect &&\n-\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \"tag -d 1\" &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"tag -d 1\" \"$method\" &&\n \t\t\t\tgit refs list >actual &&\n \t\t\t\ttest_cmp expect actual &&\n \n \t\t\t\tgit refs list | grep -v \"refs/tags/1\" >expect &&\n-\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \"refs list\" >actual &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"refs list\" \"$method\" >actual &&\n \t\t\t\ttest_cmp expect actual\n \t\t\t)\n \t\t'\n \n-\t\ttest_expect_success \"with worktree and $to_format backend, $dir dir\" '\n+\t\ttest_expect_success \"$method: with worktree and $to_format backend, $dir dir\" '\n \t\t\ttest_when_finished \"rm -rf repo wt\" &&\n \t\t\tgit init --ref-format=$from_format repo &&\n \t\t\t(\n@@ -138,22 +191,92 @@ do\n \t\t\t\tgit refs migrate --dry-run --ref-format=$to_format >out &&\n \t\t\t\tBACKEND_PATH=\"$dir/$(sed \"s/.* ${SQ}.git\\/\\(.*\\)${SQ}/\\1/\" out)\" &&\n \n-\t\t\t\tgit config set core.repositoryformatversion 1 &&\n-\t\t\t\tgit config set extensions.refStorage \"$to_format://$BACKEND_PATH\" &&\n-\n-\t\t\t\tgit worktree add ../wt 2\n-\t\t\t) &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"worktree add ../wt 2\" \"$method\" &&\n \n-\t\t\tgit -C repo for-each-ref --include-root-refs >expect &&\n-\t\t\tgit -C wt for-each-ref --include-root-refs >expect &&\n-\t\t\t! test_cmp expect actual &&\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"for-each-ref --include-root-refs\" \"$method\" >actual &&\n+\t\t\t\trun_with_uri ../wt \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"for-each-ref --include-root-refs\" \"$method\" >expect &&\n+\t\t\t\t! test_cmp expect actual &&\n \n-\t\t\tgit -C wt rev-parse 2 >expect &&\n-\t\t\tgit -C wt rev-parse HEAD >actual &&\n-\t\t\ttest_cmp expect actual\n+\t\t\t\trun_with_uri . \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"rev-parse 2\" \"$method\" >actual &&\n+\t\t\t\trun_with_uri ../wt \"$from_format\" \"$to_format://$BACKEND_PATH\" \\\n+\t\t\t\t\t\"rev-parse HEAD\" \"$method\" >expect &&\n+\t\t\t\ttest_cmp expect actual\n+\t\t\t)\n \t\t'\n \tdone # closes dir\n+\n+\ttest_expect_success \"migrating repository to $to_format with alternate refs directory\" '\n+\t\ttest_when_finished \"rm -rf repo refdir\" &&\n+\t\tmkdir refdir &&\n+\t\tGIT_REFERENCE_BACKEND=\"${from_format}://$(pwd)/refdir\" git init repo &&\n+\t\t(\n+\t\t\tcd repo &&\n+\n+\t\t\ttest_commit 1 &&\n+\t\t\ttest_commit 2 &&\n+\t\t\ttest_commit 3 &&\n+\n+\t\t\tgit refs migrate --ref-format=$to_format &&\n+\t\t\tgit refs list >out &&\n+\t\t\ttest_grep \"refs/tags/1\"\tout &&\n+\t\t\ttest_grep \"refs/tags/2\"\tout &&\n+\t\t\ttest_grep \"refs/tags/3\"\tout\n+\t\t)\n+\t'\n+\n done # closes to_format\n done # closes from_format\n \n+done # closes method\n+\n+test_expect_success 'initializing repository with alt ref directory' '\n+\ttest_when_finished \"rm -rf repo refdir\" &&\n+\tmkdir refdir &&\n+\tBACKEND=\"$(test_detect_ref_format)://$(pwd)/refdir\" &&\n+\tGIT_REFERENCE_BACKEND=$BACKEND git init repo &&\n+\tverify_files_exist repo/.git refdir &&\n+\t(\n+\t\tcd repo &&\n+\n+\t\tgit config get extensions.refstorage >expect &&\n+\t\techo $BACKEND >actual &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\ttest_commit 1 &&\n+\t\ttest_commit 2 &&\n+\t\ttest_commit 3 &&\n+\t\tgit refs list >out &&\n+\t\ttest_grep \"refs/tags/1\"\tout &&\n+\t\ttest_grep \"refs/tags/2\"\tout &&\n+\t\ttest_grep \"refs/tags/3\"\tout\n+\t)\n+'\n+\n+test_expect_success 'cloning repository with alt ref directory' '\n+\ttest_when_finished \"rm -rf source repo refdir\" &&\n+\tmkdir refdir &&\n+\n+\tgit init source &&\n+\ttest_commit -C source 1 &&\n+\ttest_commit -C source 2 &&\n+\ttest_commit -C source 3 &&\n+\n+\tBACKEND=\"$(test_detect_ref_format)://$(pwd)/refdir\" &&\n+\tGIT_REFERENCE_BACKEND=$BACKEND git clone source repo &&\n+\n+\tgit -C repo config get extensions.refstorage >expect &&\n+\techo $BACKEND >actual &&\n+\ttest_cmp expect actual &&\n+\n+\tverify_files_exist repo/.git refdir &&\n+\n+\tgit -C source for-each-ref refs/tags/ >expect &&\n+\tgit -C repo for-each-ref refs/tags/ >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n\n-- \n2.53.GIT\n\n"},{"id":"536418","messageId":"aZct2M3sbQSduK2q@pks.im","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-6-16f27860dbdf@gmail.com","subject":"Re: [PATCH v7 6/6] refs: add GIT_REFERENCE_BACKEND to specify reference backend","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-02-19T15:35:52Z","receivedAt":"2026-02-19T15:35:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Feb 19, 2026 at 10:38:25AM +0100, Karthik Nayak wrote:\n> diff --git a/t/t1423-ref-backend.sh b/t/t1423-ref-backend.sh\n> index 9912433b8c..d69aea3f7f 100755\n> --- a/t/t1423-ref-backend.sh\n> +++ b/t/t1423-ref-backend.sh\n> @@ -30,44 +39,86 @@ run_with_uri() {\n>  #   <repo> is the relative path to the repo to run the command in.\n>  #   <backend> is the original ref storage of the repo.\n>  #   <uri> is the new URI to be set for the ref storage.\n> +#   <via> if 'config', set the backend via the 'extensions.refStorage' config.\n> +#         if 'env', set the backend via the 'GIT_REFERENCE_BACKEND' env.\n>  #   <err_msg> (optional) if set, check if 'git-refs(1)' failed with the provided msg.\n>  test_refs_backend() {\n>  \trepo=$1 &&\n>  \tbackend=$2 &&\n>  \turi=$3 &&\n> -\terr_msg=$4 &&\n> +\tvia=$4 &&\n> +\terr_msg=$5 &&\n> +\n>  \n> -\tgit -C \"$repo\" config set core.repositoryformatversion 1 &&\n>  \tif test -n \"$err_msg\";\n>  \tthen\n> -\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n> -\t\ttest_must_fail git -C \"$repo\" refs list 2>err &&\n> -\t\ttest_grep \"$err_msg\" err\n> +\t\tif test \"$via\" = \"env\"\n> +\t\tthen\n> +\t\t\ttest_env GIT_REFERENCE_BACKEND=\"$uri\" test_must_fail git -C \"$repo\" refs list 2>err\n> +\t\telif test \"$via\" = \"config\"\n> +\t\tthen\n> +\t\t\tgit -C \"$repo\" config set extensions.refStorage \"$uri\" &&\n> +\t\t\ttest_must_fail git -C \"$repo\" refs list 2>err &&\n> +\t\t\ttest_grep \"$err_msg\" err\n> +\t\tfi\n>  \telse\n>  \t\tgit -C \"$repo\" refs list >expect &&\n> -\t\trun_with_uri \"$repo\" \"$backend\" \"$uri\" \"refs list\" >actual &&\n> +\t\trun_with_uri \"$repo\" \"$backend\" \"$uri\" \"refs list\" \"$via\">actual &&\n>  \t\ttest_cmp expect actual\n>  \tfi\n>  }\n>  \n> -test_expect_success 'URI is invalid' '\n> +# Verify that the expected files are present in the gitdir and the refsdir.\n> +# Usage: verify_files_exist <gitdir> <refdir>\n> +#   <gitdir> is the path for the gitdir.\n> +#   <refdir> is the path for the refdir.\n> +verify_files_exist() {\n> +\tgitdir=$1 &&\n> +\trefdir=$2 &&\n> +\n> +\t# verify that the stubs were added to the $GITDIR.\n> +\tcat $gitdir/refs/heads >actual &&\n> +\techo \"repository uses alternate refs storage\" >expect &&\n> +\ttest_cmp expect actual &&\n\nTiny nit, not worth addressing on its own: we could simply `test_cmp\nexpect \"$gitdir/refs/heads\", without the need to copy that file first.\n\n> +\tcat $gitdir/HEAD >actual &&\n> +\techo \"ref: refs/heads/.invalid\" >expect &&\n> +\ttest_cmp expect actual\n\nSame here, no need to copy the file around.\n\nOther than that I'm happy with this patch series now, thanks!\n\nPatrick\n"},{"id":"536495","messageId":"CAOLa=ZTKa6uvpPG7S+kkLpzey36J9zBjzV2sneFxcmzMmPyzHw@mail.gmail.com","threadId":"65021","inReplyTo":"aZct2M3sbQSduK2q@pks.im","subject":"Re: [PATCH v7 6/6] refs: add GIT_REFERENCE_BACKEND to specify reference backend","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-20T09:15:08Z","receivedAt":"2026-02-20T09:15:11Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n[snip]\n\n>> -test_expect_success 'URI is invalid' '\n>> +# Verify that the expected files are present in the gitdir and the refsdir.\n>> +# Usage: verify_files_exist <gitdir> <refdir>\n>> +#   <gitdir> is the path for the gitdir.\n>> +#   <refdir> is the path for the refdir.\n>> +verify_files_exist() {\n>> +\tgitdir=$1 &&\n>> +\trefdir=$2 &&\n>> +\n>> +\t# verify that the stubs were added to the $GITDIR.\n>> +\tcat $gitdir/refs/heads >actual &&\n>> +\techo \"repository uses alternate refs storage\" >expect &&\n>> +\ttest_cmp expect actual &&\n>\n> Tiny nit, not worth addressing on its own: we could simply `test_cmp\n> expect \"$gitdir/refs/heads\", without the need to copy that file first.\n>\n\nIndeed. I'll make this change locally, but hold off on re-rolling for\nnow.\n\n>> +\tcat $gitdir/HEAD >actual &&\n>> +\techo \"ref: refs/heads/.invalid\" >expect &&\n>> +\ttest_cmp expect actual\n>\n> Same here, no need to copy the file around.\n>\n> Other than that I'm happy with this patch series now, thanks!\n>\n> Patrick\n\nI'm also quite happy with how its turned out. Thanks for the continuous\nreviews.\n\nKarthik\n"},{"id":"536524","messageId":"877bs7fnkr.fsf@iotcl.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-3-16f27860dbdf@gmail.com","subject":"Re: [PATCH v7 3/6] refs: move out stub modification to generic layer","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-02-20T15:21:08Z","receivedAt":"2026-02-20T15:21:30Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> When creating the reftable reference backend on disk, we create stubs to\n> ensure that the directory can be recognized as a Git repository. This is\n> done by calling `refs_create_refdir_stubs()`. Move this to the generic\n> layer as this is needed for all backends excluding from the files\n> backends. In an upcoming commit where we introduce alternate reference\n> backend locations, we'll have to also create stubs in the $GIT_DIR\n> irrespective of the backend being used. This commit builds the base to\n> add that logic.\n>\n> Similarly, move the logic for deletion of stubs to the generic layer.\n> The files backend recursively calls the remove function of the\n> 'packed-backend', here skip calling the generic function since that\n> would try to delete stubs.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs.c                  | 47 +++++++++++++++++++++++++++++++++++++++++++++--\n>  refs/files-backend.c    |  6 +++++-\n>  refs/reftable-backend.c | 27 ---------------------------\n>  3 files changed, 50 insertions(+), 30 deletions(-)\n>\n> diff --git a/refs.c b/refs.c\n> index 77b93d655b..c83af63dc5 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2189,12 +2189,55 @@ void refs_create_refdir_stubs(struct repository *repo, const char *refdir,\n>  /* backend functions */\n>  int ref_store_create_on_disk(struct ref_store *refs, int flags, struct strbuf *err)\n>  {\n> -\treturn refs->be->create_on_disk(refs, flags, err);\n> +\tint ret = refs->be->create_on_disk(refs, flags, err);\n> +\n> +\tif (!ret &&\n> +\t    ref_storage_format_by_name(refs->be->name) != REF_STORAGE_FORMAT_FILES) {\n> +\t\tstruct strbuf msg = STRBUF_INIT;\n> +\n> +\t\tstrbuf_addf(&msg, \"this repository uses the %s format\", refs->be->name);\n> +\t\trefs_create_refdir_stubs(refs->repo, refs->gitdir, msg.buf);\n> +\t\tstrbuf_release(&msg);\n\nTo me it would feel nicer to have an early return instead of wrapping\nthis code inside the if block. But because we only need the strbuf\ninstead this block, having a block to scope it makes more sense.\n\nLooks good to me.\n\n-- \nCheers,\nToon\n"},{"id":"536525","messageId":"875x7rfn1j.fsf@iotcl.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-4-16f27860dbdf@gmail.com","subject":"Re: [PATCH v7 4/6] refs: receive and use the reference storage payload","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-02-20T15:32:40Z","receivedAt":"2026-02-20T15:32:52Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index c7d2a6e50b..9a635f4e6c 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -666,4 +667,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n>  \t\t\t\t\t  unsigned int initial_transaction,\n>  \t\t\t\t\t  struct strbuf *err);\n>  \n> +/*\n> + * Given a gitdir and the reference storage payload provided, retrieve the\n> + * 'refdir' and 'ref_common_dir'. The former is where references should be\n> + * stored for the current worktree, the latter is the common reference\n> + * directory if working with a linked worktree. If working with the main\n> + * worktree, both values will be the same.\n> + *\n> + * This is used by backends that store store files in the repository directly.\n\ns/store store/store/\n\nAlso, shouldn't it be this?\n\n* This is used by backends that store references in the repository directly.\n\n-- \nCheers,\nToon\n"},{"id":"536526","messageId":"87342vfmud.fsf@iotcl.com","threadId":"65021","inReplyTo":"20260219-kn-alternate-ref-dir-v7-5-16f27860dbdf@gmail.com","subject":"Re: [PATCH v7 5/6] refs: allow reference location in refstorage config","fromName":"Toon Claes","fromEmail":"toon@iotcl.com","sentAt":"2026-02-20T15:36:58Z","receivedAt":"2026-02-20T15:37:15Z","isPatch":true,"sender":{"key":"toon@iotcl.com","avatar":"https://avatars.githubusercontent.com/u/121621?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> diff --git a/setup.c b/setup.c\n> index 1fc9ae3872..d407f3347b 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -632,6 +632,21 @@ static enum extension_result handle_extension_v0(const char *var,\n>  \t\treturn EXTENSION_UNKNOWN;\n>  }\n>  \n> +static void parse_reference_uri(const char *value, char **format,\n> +\t\t\t\tchar **payload)\n> +{\n> +\tconst char *schema_end;\n> +\n> +\tschema_end = strstr(value, \"://\");\n> +\tif (!schema_end) {\n> +\t\t*format = xstrdup(value);\n> +\t\t*payload = NULL;\n> +\t} else {\n> +\t\t*format = xstrndup(value, schema_end - value);\n> +\t\t*payload = xstrdup_or_null(schema_end + 3);\n\nAlso here, why did you put the negated condition in the if clause?\n\n> +\t}\n> +}\n> +\n\n-- \nCheers,\nToon\n"},{"id":"536538","messageId":"xmqqbjhjz793.fsf@gitster.g","threadId":"65021","inReplyTo":"87342vfmud.fsf@iotcl.com","subject":"Re: [PATCH v7 5/6] refs: allow reference location in refstorage config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-02-20T16:53:28Z","receivedAt":"2026-02-20T16:53:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Toon Claes <toon@iotcl.com> writes:\n\n>> +static void parse_reference_uri(const char *value, char **format,\n>> +\t\t\t\tchar **payload)\n>> +{\n>> +\tconst char *schema_end;\n>> +\n>> +\tschema_end = strstr(value, \"://\");\n>> +\tif (!schema_end) {\n>> +\t\t*format = xstrdup(value);\n>> +\t\t*payload = NULL;\n>> +\t} else {\n>> +\t\t*format = xstrndup(value, schema_end - value);\n>> +\t\t*payload = xstrdup_or_null(schema_end + 3);\n>\n> Also here, why did you put the negated condition in the if clause?\n\nHmph, would it make it easier to follow if you swap them?\n\n\tif (schema_end) {\n\t\t*format = xstrndup(value, schema_end - value);\n\t\t*payload = xstrdup_or_null(schema_end + 3);\n\t} else {\n\t\t*format = xstrdup(value);\n\t\t*payload = NULL;\n\t}\n\nMaybe it is just me, but I often find it easier to follow if the\ncase that require shorter and/or simpler body, or the case that is\nnarrower (e.g., error condition), comes first before the main logic.\nIt is in line with preferring an early return on a more specific\ncondition.  It frees readers from having to worry about these cases\nearly and let them concentrate on what is expected to usually happen\nin the code.\n\nIn this particular case, I do not know which one I would prefer,\nthough.\n\nThanks.\n"},{"id":"536660","messageId":"CAOLa=ZS3smkTXERsZjiGMh5-K2t7OUpyMtRoagvwQm=Ab9VENw@mail.gmail.com","threadId":"65021","inReplyTo":"875x7rfn1j.fsf@iotcl.com","subject":"Re: [PATCH v7 4/6] refs: receive and use the reference storage payload","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-22T20:12:40Z","receivedAt":"2026-02-22T20:12:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Toon Claes <toon@iotcl.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index c7d2a6e50b..9a635f4e6c 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -666,4 +667,17 @@ enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs\n>>  \t\t\t\t\t  unsigned int initial_transaction,\n>>  \t\t\t\t\t  struct strbuf *err);\n>>\n>> +/*\n>> + * Given a gitdir and the reference storage payload provided, retrieve the\n>> + * 'refdir' and 'ref_common_dir'. The former is where references should be\n>> + * stored for the current worktree, the latter is the common reference\n>> + * directory if working with a linked worktree. If working with the main\n>> + * worktree, both values will be the same.\n>> + *\n>> + * This is used by backends that store store files in the repository directly.\n>\n> s/store store/store/\n>\n> Also, shouldn't it be this?\n>\n> * This is used by backends that store references in the repository directly.\n>\n> --\n> Cheers,\n> Toon\n\nI mean, it also stores the files themselves in the repository, but yours\nis clearer, will change!\n"},{"id":"536661","messageId":"CAOLa=ZQsfOpP1cxFCjLWqbfxQ_upzuKHDojhNYtU=oFmeZsjVw@mail.gmail.com","threadId":"65021","inReplyTo":"xmqqbjhjz793.fsf@gitster.g","subject":"Re: [PATCH v7 5/6] refs: allow reference location in refstorage config","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2026-02-22T20:15:41Z","receivedAt":"2026-02-22T20:15:43Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Toon Claes <toon@iotcl.com> writes:\n>\n>>> +static void parse_reference_uri(const char *value, char **format,\n>>> +\t\t\t\tchar **payload)\n>>> +{\n>>> +\tconst char *schema_end;\n>>> +\n>>> +\tschema_end = strstr(value, \"://\");\n>>> +\tif (!schema_end) {\n>>> +\t\t*format = xstrdup(value);\n>>> +\t\t*payload = NULL;\n>>> +\t} else {\n>>> +\t\t*format = xstrndup(value, schema_end - value);\n>>> +\t\t*payload = xstrdup_or_null(schema_end + 3);\n>>\n>> Also here, why did you put the negated condition in the if clause?\n>\n> Hmph, would it make it easier to follow if you swap them?\n>\n> \tif (schema_end) {\n> \t\t*format = xstrndup(value, schema_end - value);\n> \t\t*payload = xstrdup_or_null(schema_end + 3);\n> \t} else {\n> \t\t*format = xstrdup(value);\n> \t\t*payload = NULL;\n> \t}\n>\n> Maybe it is just me, but I often find it easier to follow if the\n> case that require shorter and/or simpler body, or the case that is\n> narrower (e.g., error condition), comes first before the main logic.\n> It is in line with preferring an early return on a more specific\n> condition.  It frees readers from having to worry about these cases\n> early and let them concentrate on what is expected to usually happen\n> in the code.\n>\n> In this particular case, I do not know which one I would prefer,\n> though.\n>\n> Thanks.\n\nKinda similar thought process. Since the URI format is new, the most\nlikely case here is that 'strstr' will not find a match. That's why the\nnegative case is first.\n\nBut I'd be happy to change, if others feel differently.\n\nKarthik\n"}]}