* [PATCH 1/8] builtin/clone: defer setup of the object database
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 2/8] builtin/clone: move around `setup_reference()` Patrick Steinhardt
` (10 subsequent siblings)
11 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
When cloning a repository we defer initialization of the reference
database. This is because we don't yet know all details required for us
to initialize the refdb in the first place. Most importantly, what we
are missing is information about the object hash.
We don't do the same thing for the object database yet, but here we
essentially have the same problem. While the "files" database does not
need any information about the object format at creation time, alternate
backends are likely to require that information so that they can
properly set up their data structures.
Besides this forward-looking future proofing though, we also have a
second use case for deferring initialization of the object database,
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
the user has passed "--refernce{,-if-able}", then we will end up writing
alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
already have access to the user-provided URI, that URI can be very well
rewritten via "url.<base>.insteadOf". We can of course read the global-
and system-level configuration to resolve it. But we explicitly resolve
the URI a second time after we have initialized the repository because
it can happen that we copy a ".git/config" over from our templates, and
that file may cause us to rewrite the path.
In a subsequent commit though we'll start to write alternates as part of
the repository initialization, so we'll need to have the URI properly
resolved before we can initialize the object database. This is ugly, but
as mentioned above it makes sense for us to defer its initialization
anyway so that we also know about the object hash already.
Introduce a new flag that makes `init_db()` skip initializing the object
database. Expose `create_object_database()` and make use of it after we
have resolved the URI.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 12 ++++++++----
setup.c | 8 +++++---
setup.h | 15 ++++++++++-----
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..0a67492ebd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
* database. We do not yet know about the object format of the
* repository, and reference backends may persist that information into
* their on-disk data structures.
+ *
+ * Furthermore, we skip initializing the object database so that we can
+ * first resolve potential alternates before creating it.
*/
init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
GIT_HASH_UNKNOWN, ref_storage_format, NULL,
do_not_override_repo_unix_permissions,
- INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+ INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
if (real_git_dir) {
free((char *)git_dir);
@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
strbuf_reset(&key);
}
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
-
remote = remote_get_early(remote_name);
if (!option_rev)
@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
+ create_object_database(the_repository);
+ if (option_required_reference.nr || option_optional_reference.nr)
+ setup_reference();
+
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
diff --git a/setup.c b/setup.c
index d90654f584..e654e27d05 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
{
/*
* Create the "objects" directory in the common directory. This is done
@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,
const char *template_dir, int hash,
enum ref_storage_format ref_storage_format,
const char *initial_branch,
- int init_shared_repository, unsigned int flags)
+ int init_shared_repository,
+ enum init_db_flags flags)
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,
if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- create_object_database(repo);
+ if (!(flags & INIT_DB_SKIP_ODB))
+ create_object_database(repo);
startup_info->have_repository = 1;
diff --git a/setup.h b/setup.h
index 763fd384e8..570ebcd150 100644
--- a/setup.h
+++ b/setup.h
@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,
const char *get_template_dir(const char *option_template);
-#define INIT_DB_QUIET (1 << 0)
-#define INIT_DB_EXIST_OK (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
+enum init_db_flags {
+ INIT_DB_QUIET = (1 << 0),
+ INIT_DB_EXIST_OK = (1 << 1),
+ INIT_DB_SKIP_REFDB = (1 << 2),
+ INIT_DB_SKIP_ODB = (1 << 3),
+};
int init_db(struct repository *repo,
const char *git_dir,
@@ -266,13 +269,15 @@ int init_db(struct repository *repo,
const char *worktree,
const char *template_dir, int hash_algo,
enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
+ const char *initial_branch,
+ int init_shared_repository,
+ enum init_db_flags flags);
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
/*
* NOTE NOTE NOTE!!
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 2/8] builtin/clone: move around `setup_reference()`
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
` (9 subsequent siblings)
11 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
In a subsequent commit, `setup_reference()` will start to call
`copy_alternates()`. Prepare for this by moving the function further
down so that we can avoid adding a declaration.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 0a67492ebd..8c990ce0cc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
return 0;
}
-static void setup_reference(void)
-{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
-}
-
static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
+static void setup_reference(void)
+{
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
+}
+
static void mkdir_if_missing(const char *pathname, mode_t mode)
{
struct stat st;
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 2/8] builtin/clone: move around `setup_reference()` Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-28 14:52 ` Toon Claes
2026-08-25 14:11 ` [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
` (8 subsequent siblings)
11 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
to set up alternates for the newly created repository. This allows it to
reuse objects from the source repository so that in the best case we
don't have to clone all objects over.
Those options are handled by the confusingly named `setup_reference()`
function -- without the above context, one might rightfully believe that
it was about refs, not about alternates. The function itself is rather
simple: we loop through all provided alternate paths and then, if such
an alternate is valid, we write it to the object database.
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
the alterantes into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8c990ce0cc..8eae3ac7d9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+ struct strvec *alternates;
+ int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
{
+ struct add_one_alternate_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
if (!ref_git) {
- if (*required)
+ if (data->required)
die("%s", err.buf);
else
fprintf(stderr,
_("info: Could not add alternate for '%s': %s\n"),
item->string, err.buf);
} else {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%s/objects", ref_git);
- odb_add_to_alternates_file(the_repository->objects, sb.buf);
- strbuf_release(&sb);
+ strvec_pushf(data->alternates, "%s/objects", ref_git);
}
strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
+ struct add_one_alternate_data data = {
+ .alternates = alternates,
+ .required = 1,
+ };
+
+ for_each_string_list(&option_required_reference,
+ add_one_alternate, &data);
+ data.required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_alternate, &data);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
};
+ struct strvec alternates = STRVEC_INIT;
const char * const builtin_clone_usage[] = {
N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
+ collect_alternates(&alternates);
+
+ for (size_t i = 0; i < alternates.nr; i++)
+ odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);
+ strvec_clear(&alternates);
free(remote_name);
strbuf_release(&reflog_msg);
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-08-25 14:11 ` [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-08-28 14:52 ` Toon Claes
0 siblings, 0 replies; 84+ messages in thread
From: Toon Claes @ 2026-08-28 14:52 UTC (permalink / raw)
To: Patrick Steinhardt, git
Patrick Steinhardt <ps@pks.im> writes:
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8c990ce0cc..8eae3ac7d9 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> fclose(in);
> }
>
> -static void setup_reference(void)
> +static void collect_alternates(struct strvec *alternates)
> {
> - int required = 1;
> - for_each_string_list(&option_required_reference,
> - add_one_reference, &required);
> - required = 0;
> - for_each_string_list(&option_optional_reference,
> - add_one_reference, &required);
> + if (option_required_reference.nr || option_optional_reference.nr) {
I was about to ask why you're adding this guard, but it looks a lot more
clean with the next commit on top.
> + struct add_one_alternate_data data = {
> + .alternates = alternates,
> + .required = 1,
> + };
> +
> + for_each_string_list(&option_required_reference,
> + add_one_alternate, &data);
> + data.required = 0;
> + for_each_string_list(&option_optional_reference,
> + add_one_alternate, &data);
> + }
> }
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (2 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-28 14:52 ` Toon Claes
2026-08-25 14:11 ` [PATCH 5/8] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
` (7 subsequent siblings)
11 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
When cloning a local repository with "--shared" we add that repository
to the new repository's alternates. This is done in `clone_local()`,
which is responsible for performing local clones.
Move the logic into `collect_alternates()` to unify our setup of
alternates. Furthermore, this will allow us to set up alternates right
at creation time of the object database.
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8eae3ac7d9..08c8f5a94f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+ const char *src_repo, bool is_local)
{
if (option_required_reference.nr || option_optional_reference.nr) {
struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
add_one_alternate, &data);
}
+
+ if (is_local) {
+ struct strbuf commondir = STRBUF_INIT;
+
+ get_common_dir(&commondir, src_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+ strbuf_release(&commondir);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
static void clone_local(const char *src_repo, const char *dest_repo)
{
- if (option_shared) {
- struct strbuf alt = STRBUF_INIT;
- get_common_dir(&alt, src_repo);
- strbuf_addstr(&alt, "/objects");
- odb_add_to_alternates_file(the_repository->objects, alt.buf);
- strbuf_release(&alt);
- } else {
+ if (!option_shared) {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
get_common_dir(&src, src_repo);
@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- collect_alternates(&alternates);
+ collect_alternates(&alternates, path, is_local);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones
2026-08-25 14:11 ` [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-08-28 14:52 ` Toon Claes
0 siblings, 0 replies; 84+ messages in thread
From: Toon Claes @ 2026-08-28 14:52 UTC (permalink / raw)
To: Patrick Steinhardt, git
Patrick Steinhardt <ps@pks.im> writes:
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8eae3ac7d9..08c8f5a94f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> fclose(in);
> }
>
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> + const char *src_repo, bool is_local)
> {
> if (option_required_reference.nr || option_optional_reference.nr) {
> struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
> for_each_string_list(&option_optional_reference,
> add_one_alternate, &data);
> }
> +
> + if (is_local) {
> + struct strbuf commondir = STRBUF_INIT;
> +
> + get_common_dir(&commondir, src_repo);
> + if (option_shared)
> + strvec_pushf(alternates, "%s/objects", commondir.buf);
Also the use of `commondir` seems to be only used if option_shared is
set, but it will be used in an else in a subsequential commit.
> +
> + strbuf_release(&commondir);
> + }
> }
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (3 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-28 14:52 ` Toon Claes
2026-08-25 14:11 ` [PATCH 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
` (6 subsequent siblings)
11 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
Similar as in the preceding commit, move the setup of alternates for
local clones with "--no-shared" into `collect_alternates()`. With this
step, the complete setup of alternates is now handled by that function.
Note that besides moving stuff around, it also fixes a bug: previously,
we did not know to resolve the referenced repository's common directory.
Consequently, when referencing a worktree we failed to resolve
alternates. But as `collect_alternates()` already knows to resolve the
commondir for "--local" we can simply reuse this resolved path for our
purpose.
Add two tests, the first one of which exercises this bug to avoid future
regressions. The second patch ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 34 +++++++++++++++++++++++-----------
t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 08c8f5a94f..2e3473fddf 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
- FILE *in = xfopen(src->buf, "r");
+ FILE *in;
+ struct strbuf path = STRBUF_INIT;
struct strbuf line = STRBUF_INIT;
+ strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+ in = fopen(path.buf, "r");
+ if (!in) {
+ if (errno == ENOENT)
+ goto out;
+ die_errno("could not read alternates file '%s'", path.buf);
+ }
+
while (strbuf_getline(&line, in) != EOF) {
char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
- odb_add_to_alternates_file(the_repository->objects,
- line.buf);
+ strvec_push(alternates, line.buf);
continue;
}
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
if (!normalize_path_copy(abs_path, abs_path))
- odb_add_to_alternates_file(the_repository->objects,
- abs_path);
+ strvec_push(alternates, abs_path);
else
warning("skipping invalid relative alternate: %s/%s",
src_repo, line.buf);
free(abs_path);
}
+
+out:
+ strbuf_release(&path);
strbuf_release(&line);
- fclose(in);
+ if (in)
+ fclose(in);
}
static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
get_common_dir(&commondir, src_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
+ read_alternates(alternates, commondir.buf);
strbuf_release(&commondir);
}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
continue;
}
- /* Files that cannot be copied bit-for-bit... */
- if (!fspathcmp(iter->relative_path, "info/alternates")) {
- copy_alternates(src, src_repo);
+ /* Alternates were already handled earlier. */
+ if (!fspathcmp(iter->relative_path, "info/alternates"))
continue;
- }
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
git clone --no-local --reference graph.git --dissociate orig clone
'
+test_expect_success 'local clone from linked worktree carries over alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
test_done
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones
2026-08-25 14:11 ` [PATCH 5/8] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-08-28 14:52 ` Toon Claes
2026-08-31 8:13 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Toon Claes @ 2026-08-28 14:52 UTC (permalink / raw)
To: Patrick Steinhardt, git
Patrick Steinhardt <ps@pks.im> writes:
> Similar as in the preceding commit, move the setup of alternates for
> local clones with "--no-shared" into `collect_alternates()`. With this
> step, the complete setup of alternates is now handled by that function.
>
> Note that besides moving stuff around, it also fixes a bug: previously,
> we did not know to resolve the referenced repository's common directory.
> Consequently, when referencing a worktree we failed to resolve
> alternates. But as `collect_alternates()` already knows to resolve the
> commondir for "--local" we can simply reuse this resolved path for our
> purpose.
>
> Add two tests, the first one of which exercises this bug to avoid future
> regressions. The second patch ensures that we properly handle relative
> alternates for a referenced worktree.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 34 +++++++++++++++++++++++-----------
> t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
> 2 files changed, 48 insertions(+), 11 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 08c8f5a94f..2e3473fddf 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
> return 0;
> }
>
> -static void copy_alternates(struct strbuf *src, const char *src_repo)
> +static void read_alternates(struct strvec *alternates, const char *src_repo)
> {
> /*
> * Read from the source objects/info/alternates file
> @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> * to turn entries with paths relative to the original
> * absolute, so that they can be used in the new repository.
> */
> - FILE *in = xfopen(src->buf, "r");
> + FILE *in;
> + struct strbuf path = STRBUF_INIT;
> struct strbuf line = STRBUF_INIT;
>
> + strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
> +
> + in = fopen(path.buf, "r");
> + if (!in) {
> + if (errno == ENOENT)
> + goto out;
> + die_errno("could not read alternates file '%s'", path.buf);
> + }
> +
> while (strbuf_getline(&line, in) != EOF) {
> char *abs_path;
> if (!line.len || line.buf[0] == '#')
> continue;
> if (is_absolute_path(line.buf)) {
> - odb_add_to_alternates_file(the_repository->objects,
> - line.buf);
> + strvec_push(alternates, line.buf);
> continue;
> }
> abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
> if (!normalize_path_copy(abs_path, abs_path))
> - odb_add_to_alternates_file(the_repository->objects,
> - abs_path);
> + strvec_push(alternates, abs_path);
> else
> warning("skipping invalid relative alternate: %s/%s",
> src_repo, line.buf);
> free(abs_path);
> }
> +
> +out:
> + strbuf_release(&path);
> strbuf_release(&line);
> - fclose(in);
> + if (in)
> + fclose(in);
Why not put this before the `out` label and remove the if?
> }
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones
2026-08-28 14:52 ` Toon Claes
@ 2026-08-31 8:13 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 8:13 UTC (permalink / raw)
To: Toon Claes; +Cc: git
On Fri, Aug 28, 2026 at 04:52:57PM +0200, Toon Claes wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index 08c8f5a94f..2e3473fddf 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> > * to turn entries with paths relative to the original
> > * absolute, so that they can be used in the new repository.
> > */
> > - FILE *in = xfopen(src->buf, "r");
> > + FILE *in;
> > + struct strbuf path = STRBUF_INIT;
> > struct strbuf line = STRBUF_INIT;
> >
> > + strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
> > +
> > + in = fopen(path.buf, "r");
> > + if (!in) {
> > + if (errno == ENOENT)
> > + goto out;
> > + die_errno("could not read alternates file '%s'", path.buf);
> > + }
> > +
> > while (strbuf_getline(&line, in) != EOF) {
> > char *abs_path;
> > if (!line.len || line.buf[0] == '#')
> > continue;
> > if (is_absolute_path(line.buf)) {
> > - odb_add_to_alternates_file(the_repository->objects,
> > - line.buf);
> > + strvec_push(alternates, line.buf);
> > continue;
> > }
> > abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
> > if (!normalize_path_copy(abs_path, abs_path))
> > - odb_add_to_alternates_file(the_repository->objects,
> > - abs_path);
> > + strvec_push(alternates, abs_path);
> > else
> > warning("skipping invalid relative alternate: %s/%s",
> > src_repo, line.buf);
> > free(abs_path);
> > }
> > +
> > +out:
> > + strbuf_release(&path);
> > strbuf_release(&line);
> > - fclose(in);
> > + if (in)
> > + fclose(in);
>
> Why not put this before the `out` label and remove the if?
Mostly because it feels fragile to me. If we were to ever extend this
function to have another `goto out` it's easy to miss that we don't
close `in` anymore.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH 6/8] odb/source: support writing alternates when creating the database
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (4 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 5/8] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-28 14:53 ` Toon Claes
2026-08-28 19:13 ` Junio C Hamano
2026-08-25 14:11 ` [PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
` (5 subsequent siblings)
11 siblings, 2 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
Add the ability to write alternates when creating the object database.
This change allows us to remove the `write_alternates()` callback in a
subsequent patch.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
odb/source.h | 18 +++++++++++---
setup.c | 4 ++-
3 files changed, 89 insertions(+), 6 deletions(-)
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..5e77b21d9f 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
+#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
+ FILE *f = NULL;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
+ f = fopen(path.buf, "r");
+ if (f) {
+ while (strbuf_getline(&line, f) != EOF)
+ strset_add(&seen, line.buf);
+
+ if (ferror(f)) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ fclose(f);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ f = fopen(path.buf, "a");
+ if (!f) {
+ ret = error_errno(_("unable to open alternates file for writing"));
+ goto out;
+ }
+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
+
+ if (!strset_add(&seen, alternate))
+ continue;
+
+ fprintf(f, "%s\n", alternate);
+ }
+
+ if (ferror(f)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
+ ret = fclose(f);
+ f = NULL;
+ if (ret) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+ }
+
+ /* Reprepare the object database to activate alternates. */
+ odb_reprepare(source->odb);
+
+ ret = 0;
+
+out:
+ if (f)
+ fclose(f);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
+ strset_clear(&seen);
+ return ret;
}
static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..86b82fab3b 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -5,6 +5,7 @@
#include "object.h"
#include "odb.h"
#include "odb/transaction.h"
+#include "strvec.h"
enum odb_source_type {
/*
@@ -26,6 +27,15 @@ enum odb_source_type {
ODB_SOURCE_INMEMORY,
};
+struct odb_create_on_disk_options {
+ /*
+ * Alternates that shall be written into the newly created object
+ * database. Whether or not this option can be handled is specific to
+ * the backend.
+ */
+ const struct strvec *alternates;
+};
+
/*
* Convert between the enum and its name. Returns the equivalent of "unknown"
* for unknown types.
@@ -106,7 +116,8 @@ struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
- int (*create_on_disk)(struct odb_source *source);
+ int (*create_on_disk)(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts);
/*
* This callback is expected to prepare the source so that it becomes
@@ -356,11 +367,12 @@ static inline void odb_source_close(struct odb_source *source)
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
if (!source->create_on_disk)
return 0;
- return source->create_on_disk(source);
+ return source->create_on_disk(source, opts);
}
/*
diff --git a/setup.c b/setup.c
index e654e27d05..426cc7dff8 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
void create_object_database(struct repository *repo)
{
+ struct odb_create_on_disk_options opts = { 0 };
+
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
- if (odb_source_create_on_disk(repo->objects->sources) < 0)
+ if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
die(_("failed creating object database"));
}
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 6/8] odb/source: support writing alternates when creating the database
2026-08-25 14:11 ` [PATCH 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-08-28 14:53 ` Toon Claes
2026-08-31 8:14 ` Patrick Steinhardt
2026-08-28 19:13 ` Junio C Hamano
1 sibling, 1 reply; 84+ messages in thread
From: Toon Claes @ 2026-08-28 14:53 UTC (permalink / raw)
To: Patrick Steinhardt, git
Patrick Steinhardt <ps@pks.im> writes:
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> odb/source-files.c | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
> odb/source.h | 18 +++++++++++---
> setup.c | 4 ++-
> 3 files changed, 89 insertions(+), 6 deletions(-)
>
> diff --git a/odb/source.h b/odb/source.h
> index ea8675247e..86b82fab3b 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -5,6 +5,7 @@
> #include "object.h"
> #include "odb.h"
> #include "odb/transaction.h"
> +#include "strvec.h"
I see `struct strvec;` is already defined below
odb_source_type_to_name(). Can we not include this and put the new
struct below the strvec definition?
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread
* Re: [PATCH 6/8] odb/source: support writing alternates when creating the database
2026-08-28 14:53 ` Toon Claes
@ 2026-08-31 8:14 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 8:14 UTC (permalink / raw)
To: Toon Claes; +Cc: git
On Fri, Aug 28, 2026 at 04:53:30PM +0200, Toon Claes wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source.h b/odb/source.h
> > index ea8675247e..86b82fab3b 100644
> > --- a/odb/source.h
> > +++ b/odb/source.h
> > @@ -5,6 +5,7 @@
> > #include "object.h"
> > #include "odb.h"
> > #include "odb/transaction.h"
> > +#include "strvec.h"
>
> I see `struct strvec;` is already defined below
> odb_source_type_to_name(). Can we not include this and put the new
> struct below the strvec definition?
Good point, will do.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* Re: [PATCH 6/8] odb/source: support writing alternates when creating the database
2026-08-25 14:11 ` [PATCH 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
2026-08-28 14:53 ` Toon Claes
@ 2026-08-28 19:13 ` Junio C Hamano
2026-08-31 8:14 ` Patrick Steinhardt
1 sibling, 1 reply; 84+ messages in thread
From: Junio C Hamano @ 2026-08-28 19:13 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git
Patrick Steinhardt <ps@pks.im> writes:
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> diff --git a/odb/source-files.c b/odb/source-files.c
> index b7b3a297bb..5e77b21d9f 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> ...
> @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
> + if (opts->alternates && opts->alternates->nr) {
> + strbuf_reset(&path);
> + strbuf_addf(&path, "%s/info/alternates", source->path);
> +
> + /*
> + * The alternates file may already exist, e.g. when it has been
> + * seeded from a template directory. Read any preexisting
> + * entries so that we don't end up writing duplicates.
> + */
> + f = fopen(path.buf, "r");
> + if (f) {
> + while (strbuf_getline(&line, f) != EOF)
> + strset_add(&seen, line.buf);
> +
> + if (ferror(f)) {
> + ret = error_errno(_("unable to read alternates file"));
> + goto out;
> + }
> +
> + fclose(f);
> + } else if (errno != ENOENT) {
> + ret = error_errno(_("unable to read alternates file"));
> + goto out;
> + }
> +
> + f = fopen(path.buf, "a");
> + if (!f) {
> + ret = error_errno(_("unable to open alternates file for writing"));
> + goto out;
> + }
I understand that using 'a' instead of 'w' is an attempt to deal
with the potential TOCTOU problem, but shouldn't we be using the
standard lockfile API, which atomically adds (or fails to add) to
avoid leaving a partially written file? Or does it not matter,
since this is done only once upon repository creation when nobody
should be looking at the files on the filesystem?
Thanks.
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH 6/8] odb/source: support writing alternates when creating the database
2026-08-28 19:13 ` Junio C Hamano
@ 2026-08-31 8:14 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 8:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Fri, Aug 28, 2026 at 12:13:04PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > Add the ability to write alternates when creating the object database.
> > This change allows us to remove the `write_alternates()` callback in a
> > subsequent patch.
> >
> > Signed-off-by: Patrick Steinhardt <ps@pks.im>
> > ---
>
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index b7b3a297bb..5e77b21d9f 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > ...
> > @@ -64,8 +70,71 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
> > + if (opts->alternates && opts->alternates->nr) {
> > + strbuf_reset(&path);
> > + strbuf_addf(&path, "%s/info/alternates", source->path);
> > +
> > + /*
> > + * The alternates file may already exist, e.g. when it has been
> > + * seeded from a template directory. Read any preexisting
> > + * entries so that we don't end up writing duplicates.
> > + */
> > + f = fopen(path.buf, "r");
> > + if (f) {
> > + while (strbuf_getline(&line, f) != EOF)
> > + strset_add(&seen, line.buf);
> > +
> > + if (ferror(f)) {
> > + ret = error_errno(_("unable to read alternates file"));
> > + goto out;
> > + }
> > +
> > + fclose(f);
> > + } else if (errno != ENOENT) {
> > + ret = error_errno(_("unable to read alternates file"));
> > + goto out;
> > + }
> > +
> > + f = fopen(path.buf, "a");
> > + if (!f) {
> > + ret = error_errno(_("unable to open alternates file for writing"));
> > + goto out;
> > + }
>
> I understand that using 'a' instead of 'w' is an attempt to deal
> with the potential TOCTOU problem, but shouldn't we be using the
> standard lockfile API, which atomically adds (or fails to add) to
> avoid leaving a partially written file? Or does it not matter,
> since this is done only once upon repository creation when nobody
> should be looking at the files on the filesystem?
Toon also asked the same, and to be honest I never even considered doing
this. It's the repository creation anyway, so it just didn't come to my
mind that it could be racing. In theory though it can, even though it
feels quite contrived.
In any case, I don't see a strong reason why we shouldn't use a lockfile
here.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()`
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (5 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-25 14:11 ` [PATCH 8/8] odb/source: remove the ability to write alternates Patrick Steinhardt
` (4 subsequent siblings)
11 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
When creating a repository with alternates we first initialize the
object database and then write alternates to it in a separate step. This
is unfortunate due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
unfortunate as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so having this
callback is suboptimal there.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
We have thus introduced the ability to write alternates right at
creation time in the preceding commits, and we have unified setup of
alternates into a single location. All that's left to do for us now is
to wire up alternates as an option for the database creation.
Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 5 +----
setup.c | 9 ++++++---
setup.h | 2 +-
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 2e3473fddf..48ac379b1d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
- create_object_database(the_repository);
collect_alternates(&alternates, path, is_local);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+ create_object_database(the_repository, &alternates);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/setup.c b/setup.c
index 426cc7dff8..cfa286ff59 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+ const struct strvec *alternates)
{
- struct odb_create_on_disk_options opts = { 0 };
+ struct odb_create_on_disk_options opts = {
+ .alternates = alternates,
+ };
/*
* Create the "objects" directory in the common directory. This is done
@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,
if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
if (!(flags & INIT_DB_SKIP_ODB))
- create_object_database(repo);
+ create_object_database(repo, NULL);
startup_info->have_repository = 1;
diff --git a/setup.h b/setup.h
index 570ebcd150..34e86dad37 100644
--- a/setup.h
+++ b/setup.h
@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
/*
* NOTE NOTE NOTE!!
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH 8/8] odb/source: remove the ability to write alternates
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (6 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-08-25 14:11 ` Patrick Steinhardt
2026-08-28 14:53 ` Toon Claes
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (3 subsequent siblings)
11 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-25 14:11 UTC (permalink / raw)
To: git
There are no users of `odb_source_write_alternates()` in our tree
anymore. Remove that function and its supporting infrastructure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb.c | 9 ---------
odb.h | 7 -------
odb/source-files.c | 55 ---------------------------------------------------
odb/source-inmemory.c | 7 -------
odb/source-loose.c | 7 -------
odb/source-packed.c | 7 -------
odb/source.h | 26 ------------------------
7 files changed, 118 deletions(-)
diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
return alternate;
}
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir)
-{
- int ret = odb_source_write_alternate(odb->sources, dir);
- if (ret < 0)
- die(NULL);
- odb_add_alternate_recursively(odb, dir, 0);
-}
-
struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
const char *dir)
{
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
*/
int odb_has_alternates(struct object_database *odb);
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir);
-
/*
* Add the directory to the in-memory list of alternate sources (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 5e77b21d9f..feef9e169a 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -4,7 +4,6 @@
#include "chdir-notify.h"
#include "config.h"
#include "gettext.h"
-#include "lockfile.h"
#include "object-file.h"
#include "odb.h"
#include "odb/source.h"
@@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
-static int odb_source_files_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- struct lock_file lock = LOCK_INIT;
- char *path = xstrfmt("%s/%s", source->path, "info/alternates");
- FILE *in, *out;
- int found = 0;
- int ret;
-
- repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
- LOCK_DIE_ON_ERROR);
- out = fdopen_lock_file(&lock, "w");
- if (!out) {
- ret = error_errno(_("unable to fdopen alternates lockfile"));
- goto out;
- }
-
- in = fopen(path, "r");
- if (in) {
- struct strbuf line = STRBUF_INIT;
-
- while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(alternate, line.buf)) {
- found = 1;
- break;
- }
- fprintf_or_die(out, "%s\n", line.buf);
- }
-
- strbuf_release(&line);
- fclose(in);
- } else if (errno != ENOENT) {
- ret = error_errno(_("unable to read alternates file"));
- goto out;
- }
-
- if (found) {
- rollback_lock_file(&lock);
- } else {
- fprintf_or_die(out, "%s\n", alternate);
- if (commit_lock_file(&lock)) {
- ret = error_errno(_("unable to move new alternates file into place"));
- goto out;
- }
- }
-
- ret = 0;
-
-out:
- free(path);
- return ret;
-}
-
static int too_many_loose_objects(struct odb_source_files *files, int limit)
{
unsigned long loose_count;
@@ -839,7 +785,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.write_object_stream = odb_source_files_write_object_stream;
files->base.begin_transaction = odb_source_files_begin_transaction;
files->base.read_alternates = odb_source_files_read_alternates;
- files->base.write_alternate = odb_source_files_write_alternate;
files->base.optimize = odb_source_files_optimize;
files->base.optimize_required = odb_source_files_optimize_required;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("in-memory source does not support alternates");
-}
-
static void odb_source_inmemory_close(struct odb_source *source UNUSED)
{
}
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.freshen_object = odb_source_inmemory_freshen_object;
source->base.begin_transaction = odb_source_inmemory_begin_transaction;
source->base.read_alternates = odb_source_inmemory_read_alternates;
- source->base.write_alternate = odb_source_inmemory_write_alternate;
return source;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("loose source does not support alternates");
-}
-
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_object_stream = odb_source_loose_write_object_stream;
loose->base.begin_transaction = odb_source_loose_begin_transaction;
loose->base.read_alternates = odb_source_loose_read_alternates;
- loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("packed backend cannot write alternates");
-}
-
void (*report_garbage)(unsigned seen_bits, const char *path);
static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_object_stream = odb_source_packed_write_object_stream;
packed->base.begin_transaction = odb_source_packed_begin_transaction;
packed->base.read_alternates = odb_source_packed_read_alternates;
- packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 86b82fab3b..5ee769e839 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -287,19 +287,6 @@ struct odb_source {
int (*read_alternates)(struct odb_source *source,
struct strvec *out);
- /*
- * This callback is expected to persist the singular alternate passed
- * to it into its list of alternates. Any pre-existing alternates are
- * expected to remain active. Subsequent calls to `read_alternates` are
- * thus expected to yield the pre-existing list of alternates plus the
- * newly added alternate appended to its end.
- *
- * The callback is expected to return 0 on success, a negative error
- * code otherwise.
- */
- int (*write_alternate)(struct odb_source *source,
- const char *alternate);
-
/*
* This callback is expected to optimize the object database source.
* Returns 0 on success, a negative error code otherwise.
@@ -519,19 +506,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
return source->read_alternates(source, out);
}
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- return source->write_alternate(source, alternate);
-}
-
/*
* Create a new transaction that can be used to write objects into a temporary
* staging area. The objects will only be persisted when the transaction is
--
2.55.0.822.g20453c30eb.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH 8/8] odb/source: remove the ability to write alternates
2026-08-25 14:11 ` [PATCH 8/8] odb/source: remove the ability to write alternates Patrick Steinhardt
@ 2026-08-28 14:53 ` Toon Claes
2026-08-31 8:14 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Toon Claes @ 2026-08-28 14:53 UTC (permalink / raw)
To: Patrick Steinhardt, git
Patrick Steinhardt <ps@pks.im> writes:
> There are no users of `odb_source_write_alternates()` in our tree
> anymore. Remove that function and its supporting infrastructure.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> odb.c | 9 ---------
> odb.h | 7 -------
> odb/source-files.c | 55 ---------------------------------------------------
> odb/source-inmemory.c | 7 -------
> odb/source-loose.c | 7 -------
> odb/source-packed.c | 7 -------
> odb/source.h | 26 ------------------------
> 7 files changed, 118 deletions(-)
>
> diff --git a/odb.c b/odb.c
> index 67d98d64fc..b531cf8fb3 100644
> --- a/odb.c
> +++ b/odb.c
> @@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
> return alternate;
> }
>
> -void odb_add_to_alternates_file(struct object_database *odb,
> - const char *dir)
> -{
> - int ret = odb_source_write_alternate(odb->sources, dir);
> - if (ret < 0)
> - die(NULL);
> - odb_add_alternate_recursively(odb, dir, 0);
> -}
> -
> struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
> const char *dir)
> {
> diff --git a/odb.h b/odb.h
> index b9e0db56ec..2d002461f8 100644
> --- a/odb.h
> +++ b/odb.h
> @@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
> */
> int odb_has_alternates(struct object_database *odb);
>
> -/*
> - * Add the directory to the on-disk alternates file; the new entry will also
> - * take effect in the current process.
> - */
> -void odb_add_to_alternates_file(struct object_database *odb,
> - const char *dir);
> -
> /*
> * Add the directory to the in-memory list of alternate sources (along with any
> * recursive alternates it points to), but do not modify the on-disk alternates
> diff --git a/odb/source-files.c b/odb/source-files.c
> index 5e77b21d9f..feef9e169a 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -4,7 +4,6 @@
> #include "chdir-notify.h"
> #include "config.h"
> #include "gettext.h"
> -#include "lockfile.h"
> #include "object-file.h"
> #include "odb.h"
> #include "odb/source.h"
> @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
> return 0;
> }
>
> -static int odb_source_files_write_alternate(struct odb_source *source,
> - const char *alternate)
> -{
> - struct lock_file lock = LOCK_INIT;
> - char *path = xstrfmt("%s/%s", source->path, "info/alternates");
> - FILE *in, *out;
> - int found = 0;
> - int ret;
> -
> - repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
> - LOCK_DIE_ON_ERROR);
Why is the new implementation not using a lockfile?
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH 8/8] odb/source: remove the ability to write alternates
2026-08-28 14:53 ` Toon Claes
@ 2026-08-31 8:14 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 8:14 UTC (permalink / raw)
To: Toon Claes; +Cc: git
On Fri, Aug 28, 2026 at 04:53:47PM +0200, Toon Claes wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index 5e77b21d9f..feef9e169a 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > @@ -303,59 +302,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
> > return 0;
> > }
> >
> > -static int odb_source_files_write_alternate(struct odb_source *source,
> > - const char *alternate)
> > -{
> > - struct lock_file lock = LOCK_INIT;
> > - char *path = xstrfmt("%s/%s", source->path, "info/alternates");
> > - FILE *in, *out;
> > - int found = 0;
> > - int ret;
> > -
> > - repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
> > - LOCK_DIE_ON_ERROR);
>
> Why is the new implementation not using a lockfile?
Junio asked the same, and the only reason is that I simply didn't
think about using a lockfile at all. Will fix, thanks.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v2 0/8] odb: write alternates at creation time
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (7 preceding siblings ...)
2026-08-25 14:11 ` [PATCH 8/8] odb/source: remove the ability to write alternates Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
` (8 more replies)
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (2 subsequent siblings)
11 siblings, 9 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
Hi,
writing alternates into the object database currently happens via
`odb_source_write_alternate()`. But while that creates the ability to
create alternates at arbitrary points of a source's lifetime, we don't
use that functionality in the first place. Instead, we only ever write
alternates when creating a new repository.
This design is suboptimal due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
overblown as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so alternate
implementations may not even support them.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
This patch series thus refactors how we handle alternates so that we
don't write them ad-hoc anymore. Instead, the series introduces a new
option for `odb_source_create_on_disk()` that makes it handle those
alternates at creation time.
This is part of the bigger goal of moving handling of alternates into
the "files" backend.
This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
`alternates_db` field, 2026-08-17) merged into it.
Changes in v2:
- Use a lockfile to write "info/alternates" during creation time.
- Remove useless "strvec.h" include by reordering declarations a bit.
- Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (8):
builtin/clone: defer setup of the object database
builtin/clone: move around `setup_reference()`
builtin/clone: refactor handling of "--reference{,-if-able}"
builtin/clone: move setup of alternates for shared local clones
builtin/clone: move setup of alternates for non-shared local clones
odb/source: support writing alternates when creating the database
builtin/clone: write alternates via `odb_create_on_disk()`
odb/source: remove the ability to write alternates
builtin/clone.c | 108 +++++++++++++++++++++++--------------
odb.c | 9 ----
odb.h | 7 ---
odb/source-files.c | 130 ++++++++++++++++++++++++++-------------------
odb/source-inmemory.c | 7 ---
odb/source-loose.c | 7 ---
odb/source-packed.c | 7 ---
odb/source.h | 43 +++++----------
setup.c | 15 ++++--
setup.h | 15 ++++--
t/t5604-clone-reference.sh | 25 +++++++++
11 files changed, 203 insertions(+), 170 deletions(-)
Range-diff versus v1:
1: 85b01b6b1b = 1: ca4f283e20 builtin/clone: defer setup of the object database
2: 1e131d0a84 = 2: 4849b826ed builtin/clone: move around `setup_reference()`
3: a6d33bf077 = 3: 870b994a9d builtin/clone: refactor handling of "--reference{,-if-able}"
4: fd88a949f5 = 4: 6268e14ecb builtin/clone: move setup of alternates for shared local clones
5: e18f135daa = 5: c148c65508 builtin/clone: move setup of alternates for non-shared local clones
6: e0733d5d63 ! 6: 360b8e83db odb/source: support writing alternates when creating the database
@@ odb/source-files.c: static void odb_source_files_close(struct odb_source *source
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
++ struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
-+ FILE *f = NULL;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
++ FILE *alternates, *orig;
++
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
++ repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
++ path.buf, LOCK_DIE_ON_ERROR);
++
++ alternates = fdopen_lock_file(&alternates_lock, "w");
++ if (!alternates) {
++ ret = error_errno(_("unable to fdopen alternates lockfile"));
++ goto out;
++ }
++
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
-+ f = fopen(path.buf, "r");
-+ if (f) {
-+ while (strbuf_getline(&line, f) != EOF)
++ orig = fopen(path.buf, "r");
++ if (orig) {
++ while (strbuf_getline(&line, orig) != EOF) {
+ strset_add(&seen, line.buf);
++ fprintf(alternates, "%s\n", line.buf);
++ }
+
-+ if (ferror(f)) {
++ if (ferror(orig)) {
+ ret = error_errno(_("unable to read alternates file"));
++ fclose(orig);
+ goto out;
+ }
+
-+ fclose(f);
++ fclose(orig);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
-+ f = fopen(path.buf, "a");
-+ if (!f) {
-+ ret = error_errno(_("unable to open alternates file for writing"));
-+ goto out;
-+ }
-+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
-+
+ if (!strset_add(&seen, alternate))
+ continue;
-+
-+ fprintf(f, "%s\n", alternate);
++ fprintf(alternates, "%s\n", alternate);
+ }
+
-+ if (ferror(f)) {
++ if (ferror(alternates)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
-+ ret = fclose(f);
-+ f = NULL;
-+ if (ret) {
-+ ret = error_errno(_("unable to write alternates file"));
++ if (commit_lock_file(&alternates_lock)) {
++ ret = error_errno(_("unable to commit alternates file"));
+ goto out;
+ }
+ }
@@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
+ ret = 0;
+
+out:
-+ if (f)
-+ fclose(f);
++ rollback_lock_file(&alternates_lock);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
@@ odb/source-files.c: static int odb_source_files_create_on_disk(struct odb_source
static void odb_source_files_prepare(struct odb_source *source,
## odb/source.h ##
-@@
- #include "object.h"
- #include "odb.h"
- #include "odb/transaction.h"
-+#include "strvec.h"
-
- enum odb_source_type {
- /*
-@@ odb/source.h: enum odb_source_type {
- ODB_SOURCE_INMEMORY,
- };
+@@ odb/source.h: struct object_id;
+ struct odb_stream;
+ struct strvec;
+struct odb_create_on_disk_options {
+ /*
@@ odb/source.h: enum odb_source_type {
+};
+
/*
- * Convert between the enum and its name. Returns the equivalent of "unknown"
- * for unknown types.
+ * The source is the part of the object database that stores the actual
+ * objects. It thus encapsulates the logic to read and write the specific
@@ odb/source.h: struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
7: 24ae5658ea = 7: 2809430e28 builtin/clone: write alternates via `odb_create_on_disk()`
8: 3bda635fb8 ! 8: 2abcd3b53c odb/source: remove the ability to write alternates
@@ odb.h: int odb_mkstemp(struct object_database *odb,
* recursive alternates it points to), but do not modify the on-disk alternates
## odb/source-files.c ##
-@@
- #include "chdir-notify.h"
- #include "config.h"
- #include "gettext.h"
--#include "lockfile.h"
- #include "object-file.h"
- #include "odb.h"
- #include "odb/source.h"
@@ odb/source-files.c: static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
---
base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7
change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0
^ permalink raw reply [flat|nested] 84+ messages in thread* [PATCH v2 1/8] builtin/clone: defer setup of the object database
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-09-06 16:42 ` Justin Tobler
2026-08-31 10:02 ` [PATCH v2 2/8] builtin/clone: move around `setup_reference()` Patrick Steinhardt
` (7 subsequent siblings)
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
When cloning a repository we defer initialization of the reference
database. This is because we don't yet know all details required for us
to initialize the refdb in the first place. Most importantly, what we
are missing is information about the object hash.
We don't do the same thing for the object database yet, but here we
essentially have the same problem. While the "files" database does not
need any information about the object format at creation time, alternate
backends are likely to require that information so that they can
properly set up their data structures.
Besides this forward-looking future proofing though, we also have a
second use case for deferring initialization of the object database,
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
the user has passed "--refernce{,-if-able}", then we will end up writing
alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
already have access to the user-provided URI, that URI can be very well
rewritten via "url.<base>.insteadOf". We can of course read the global-
and system-level configuration to resolve it. But we explicitly resolve
the URI a second time after we have initialized the repository because
it can happen that we copy a ".git/config" over from our templates, and
that file may cause us to rewrite the path.
In a subsequent commit though we'll start to write alternates as part of
the repository initialization, so we'll need to have the URI properly
resolved before we can initialize the object database. This is ugly, but
as mentioned above it makes sense for us to defer its initialization
anyway so that we also know about the object hash already.
Introduce a new flag that makes `init_db()` skip initializing the object
database. Expose `create_object_database()` and make use of it after we
have resolved the URI.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 12 ++++++++----
setup.c | 8 +++++---
setup.h | 15 ++++++++++-----
3 files changed, 23 insertions(+), 12 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..0a67492ebd 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
* database. We do not yet know about the object format of the
* repository, and reference backends may persist that information into
* their on-disk data structures.
+ *
+ * Furthermore, we skip initializing the object database so that we can
+ * first resolve potential alternates before creating it.
*/
init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
GIT_HASH_UNKNOWN, ref_storage_format, NULL,
do_not_override_repo_unix_permissions,
- INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+ INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
if (real_git_dir) {
free((char *)git_dir);
@@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
strbuf_reset(&key);
}
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
-
remote = remote_get_early(remote_name);
if (!option_rev)
@@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
+ create_object_database(the_repository);
+ if (option_required_reference.nr || option_optional_reference.nr)
+ setup_reference();
+
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
diff --git a/setup.c b/setup.c
index d90654f584..e654e27d05 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
{
/*
* Create the "objects" directory in the common directory. This is done
@@ -2829,7 +2829,8 @@ int init_db(struct repository *repo,
const char *template_dir, int hash,
enum ref_storage_format ref_storage_format,
const char *initial_branch,
- int init_shared_repository, unsigned int flags)
+ int init_shared_repository,
+ enum init_db_flags flags)
{
int reinit;
int exist_ok = flags & INIT_DB_EXIST_OK;
@@ -2903,7 +2904,8 @@ int init_db(struct repository *repo,
if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- create_object_database(repo);
+ if (!(flags & INIT_DB_SKIP_ODB))
+ create_object_database(repo);
startup_info->have_repository = 1;
diff --git a/setup.h b/setup.h
index 763fd384e8..570ebcd150 100644
--- a/setup.h
+++ b/setup.h
@@ -256,9 +256,12 @@ int apply_repository_format(struct repository *repo,
const char *get_template_dir(const char *option_template);
-#define INIT_DB_QUIET (1 << 0)
-#define INIT_DB_EXIST_OK (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
+enum init_db_flags {
+ INIT_DB_QUIET = (1 << 0),
+ INIT_DB_EXIST_OK = (1 << 1),
+ INIT_DB_SKIP_REFDB = (1 << 2),
+ INIT_DB_SKIP_ODB = (1 << 3),
+};
int init_db(struct repository *repo,
const char *git_dir,
@@ -266,13 +269,15 @@ int init_db(struct repository *repo,
const char *worktree,
const char *template_dir, int hash_algo,
enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
+ const char *initial_branch,
+ int init_shared_repository,
+ enum init_db_flags flags);
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
/*
* NOTE NOTE NOTE!!
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database
2026-08-31 10:02 ` [PATCH v2 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-06 16:42 ` Justin Tobler
2026-09-07 7:23 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Justin Tobler @ 2026-09-06 16:42 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
>
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.
Ok, so if we want to move the writing of alternates to happen at ODB
creation time, this information will be needed ahead of time.
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
>
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
>
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
Ok.
> Introduce a new flag that makes `init_db()` skip initializing the object
> database. Expose `create_object_database()` and make use of it after we
> have resolved the URI.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 12 ++++++++----
> setup.c | 8 +++++---
> setup.h | 15 ++++++++++-----
> 3 files changed, 23 insertions(+), 12 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5b25cca510..0a67492ebd 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
> * database. We do not yet know about the object format of the
> * repository, and reference backends may persist that information into
> * their on-disk data structures.
> + *
> + * Furthermore, we skip initializing the object database so that we can
> + * first resolve potential alternates before creating it.
> */
> init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
> GIT_HASH_UNKNOWN, ref_storage_format, NULL,
> do_not_override_repo_unix_permissions,
> - INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> + INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
Ok, now we skip the initializing the ODB during init_db in favor of
delaying it to after we have the required config info. This makes sense
to me, but IMO the `init_db()` interface has grown quite awkward with
these "skip" flags. It appears that there are only two callers of
`init_db()` which makes me wonder if it would be simpler to just require
them to explicitly set up the ref DB and ODB.
> if (real_git_dir) {
> free((char *)git_dir);
> @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
> strbuf_reset(&key);
> }
>
> - if (option_required_reference.nr || option_optional_reference.nr)
> - setup_reference();
> -
> remote = remote_get_early(remote_name);
>
> if (!option_rev)
> @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
> if (option_local > 0 && !is_local)
> warning(_("--local is ignored"));
>
> + create_object_database(the_repository);
We now explicitly create the object database here.
> + if (option_required_reference.nr || option_optional_reference.nr)
> + setup_reference();
Any reason the reference setup is also further deferred here?
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v2 1/8] builtin/clone: defer setup of the object database
2026-09-06 16:42 ` Justin Tobler
@ 2026-09-07 7:23 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 7:23 UTC (permalink / raw)
To: Justin Tobler; +Cc: git, Toon Claes, Junio C Hamano
On Sun, Sep 06, 2026 at 11:42:48AM -0500, Justin Tobler wrote:
> On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index 5b25cca510..0a67492ebd 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -1184,11 +1184,14 @@ int cmd_clone(int argc,
> > * database. We do not yet know about the object format of the
> > * repository, and reference backends may persist that information into
> > * their on-disk data structures.
> > + *
> > + * Furthermore, we skip initializing the object database so that we can
> > + * first resolve potential alternates before creating it.
> > */
> > init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
> > GIT_HASH_UNKNOWN, ref_storage_format, NULL,
> > do_not_override_repo_unix_permissions,
> > - INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> > + INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB);
>
> Ok, now we skip the initializing the ODB during init_db in favor of
> delaying it to after we have the required config info. This makes sense
> to me, but IMO the `init_db()` interface has grown quite awkward with
> these "skip" flags. It appears that there are only two callers of
> `init_db()` which makes me wonder if it would be simpler to just require
> them to explicitly set up the ref DB and ODB.
We can, but there are some nuances here that make this a bit more
complicated. Most importantly, we'd start printing the message that the
repository was (re)initialized _before_ we create the actual object and
reference databases.
But thinking about this a bit more we can solve this, and we can even
get rid of the flags completely here:
- git-clone(1) always passes the QUIET flag, so we really only want to
print the message for git-init(1) anyway. So we can lift the logic
out of `init_db()` and then drop the flag.
- Same for `EXIST_OK`, we never allow preexisting repositories when
performing a clone.
The name `init_db()` would become very misleading in that case though,
so we should probably rename it to e.g. `create_repository()`. But
overall the change makes sense, as it moves the command-specific logic
to the commands themselves instead of making use of flags to control it.
I like it.
> > @@ -1311,9 +1314,6 @@ int cmd_clone(int argc,
> > strbuf_reset(&key);
> > }
> >
> > - if (option_required_reference.nr || option_optional_reference.nr)
> > - setup_reference();
> > -
> > remote = remote_get_early(remote_name);
> >
> > if (!option_rev)
> > @@ -1342,6 +1342,10 @@ int cmd_clone(int argc,
> > if (option_local > 0 && !is_local)
> > warning(_("--local is ignored"));
> >
> > + create_object_database(the_repository);
>
> We now explicitly create the object database here.
>
> > + if (option_required_reference.nr || option_optional_reference.nr)
> > + setup_reference();
>
> Any reason the reference setup is also further deferred here?
You might think that this has something to do with refs ("refs/*"), but
that's not the case. This setup here sets up alternates, and we can only
set those up after we have created the object database. I'll note this
in the commit message as it's quite non-obvious.
Thanks!
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v2 2/8] builtin/clone: move around `setup_reference()`
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
` (6 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
In a subsequent commit, `setup_reference()` will start to call
`copy_alternates()`. Prepare for this by moving the function further
down so that we can avoid adding a declaration.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 0a67492ebd..8c990ce0cc 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
return 0;
}
-static void setup_reference(void)
-{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
-}
-
static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
+static void setup_reference(void)
+{
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
+}
+
static void mkdir_if_missing(const char *pathname, mode_t mode)
{
struct stat st;
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v2 3/8] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 1/8] builtin/clone: defer setup of the object database Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 2/8] builtin/clone: move around `setup_reference()` Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 4/8] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
` (5 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
to set up alternates for the newly created repository. This allows it to
reuse objects from the source repository so that in the best case we
don't have to clone all objects over.
Those options are handled by the confusingly named `setup_reference()`
function -- without the above context, one might rightfully believe that
it was about refs, not about alternates. The function itself is rather
simple: we loop through all provided alternate paths and then, if such
an alternate is valid, we write it to the object database.
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
the alterantes into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8c990ce0cc..8eae3ac7d9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+ struct strvec *alternates;
+ int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
{
+ struct add_one_alternate_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
if (!ref_git) {
- if (*required)
+ if (data->required)
die("%s", err.buf);
else
fprintf(stderr,
_("info: Could not add alternate for '%s': %s\n"),
item->string, err.buf);
} else {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%s/objects", ref_git);
- odb_add_to_alternates_file(the_repository->objects, sb.buf);
- strbuf_release(&sb);
+ strvec_pushf(data->alternates, "%s/objects", ref_git);
}
strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
+ struct add_one_alternate_data data = {
+ .alternates = alternates,
+ .required = 1,
+ };
+
+ for_each_string_list(&option_required_reference,
+ add_one_alternate, &data);
+ data.required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_alternate, &data);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
};
+ struct strvec alternates = STRVEC_INIT;
const char * const builtin_clone_usage[] = {
N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1343,8 +1352,10 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
+ collect_alternates(&alternates);
+
+ for (size_t i = 0; i < alternates.nr; i++)
+ odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1641,6 +1652,7 @@ int cmd_clone(int argc,
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);
+ strvec_clear(&alternates);
free(remote_name);
strbuf_release(&reflog_msg);
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v2 4/8] builtin/clone: move setup of alternates for shared local clones
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (2 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 5/8] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
` (4 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
When cloning a local repository with "--shared" we add that repository
to the new repository's alternates. This is done in `clone_local()`,
which is responsible for performing local clones.
Move the logic into `collect_alternates()` to unify our setup of
alternates. Furthermore, this will allow us to set up alternates right
at creation time of the object database.
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8eae3ac7d9..08c8f5a94f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+ const char *src_repo, bool is_local)
{
if (option_required_reference.nr || option_optional_reference.nr) {
struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
add_one_alternate, &data);
}
+
+ if (is_local) {
+ struct strbuf commondir = STRBUF_INIT;
+
+ get_common_dir(&commondir, src_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+ strbuf_release(&commondir);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
static void clone_local(const char *src_repo, const char *dest_repo)
{
- if (option_shared) {
- struct strbuf alt = STRBUF_INIT;
- get_common_dir(&alt, src_repo);
- strbuf_addstr(&alt, "/objects");
- odb_add_to_alternates_file(the_repository->objects, alt.buf);
- strbuf_release(&alt);
- } else {
+ if (!option_shared) {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
get_common_dir(&src, src_repo);
@@ -1352,7 +1357,7 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- collect_alternates(&alternates);
+ collect_alternates(&alternates, path, is_local);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v2 5/8] builtin/clone: move setup of alternates for non-shared local clones
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (3 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 4/8] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-08-31 10:02 ` [PATCH v2 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
` (3 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
Similar as in the preceding commit, move the setup of alternates for
local clones with "--no-shared" into `collect_alternates()`. With this
step, the complete setup of alternates is now handled by that function.
Note that besides moving stuff around, it also fixes a bug: previously,
we did not know to resolve the referenced repository's common directory.
Consequently, when referencing a worktree we failed to resolve
alternates. But as `collect_alternates()` already knows to resolve the
commondir for "--local" we can simply reuse this resolved path for our
purpose.
Add two tests, the first one of which exercises this bug to avoid future
regressions. The second patch ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 34 +++++++++++++++++++++++-----------
t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 08c8f5a94f..2e3473fddf 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
- FILE *in = xfopen(src->buf, "r");
+ FILE *in;
+ struct strbuf path = STRBUF_INIT;
struct strbuf line = STRBUF_INIT;
+ strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+ in = fopen(path.buf, "r");
+ if (!in) {
+ if (errno == ENOENT)
+ goto out;
+ die_errno("could not read alternates file '%s'", path.buf);
+ }
+
while (strbuf_getline(&line, in) != EOF) {
char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
- odb_add_to_alternates_file(the_repository->objects,
- line.buf);
+ strvec_push(alternates, line.buf);
continue;
}
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
if (!normalize_path_copy(abs_path, abs_path))
- odb_add_to_alternates_file(the_repository->objects,
- abs_path);
+ strvec_push(alternates, abs_path);
else
warning("skipping invalid relative alternate: %s/%s",
src_repo, line.buf);
free(abs_path);
}
+
+out:
+ strbuf_release(&path);
strbuf_release(&line);
- fclose(in);
+ if (in)
+ fclose(in);
}
static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
get_common_dir(&commondir, src_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
+ read_alternates(alternates, commondir.buf);
strbuf_release(&commondir);
}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
continue;
}
- /* Files that cannot be copied bit-for-bit... */
- if (!fspathcmp(iter->relative_path, "info/alternates")) {
- copy_alternates(src, src_repo);
+ /* Alternates were already handled earlier. */
+ if (!fspathcmp(iter->relative_path, "info/alternates"))
continue;
- }
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
git clone --no-local --reference graph.git --dissociate orig clone
'
+test_expect_success 'local clone from linked worktree carries over alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
test_done
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v2 6/8] odb/source: support writing alternates when creating the database
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (4 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 5/8] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-09-06 17:02 ` Justin Tobler
2026-08-31 10:02 ` [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
` (2 subsequent siblings)
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
Add the ability to write alternates when creating the object database.
This change allows us to remove the `write_alternates()` callback in a
subsequent patch.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
odb/source.h | 17 +++++++++---
setup.c | 4 ++-
3 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
+#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
+ struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
+ FILE *alternates, *orig;
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
+ repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+ path.buf, LOCK_DIE_ON_ERROR);
+
+ alternates = fdopen_lock_file(&alternates_lock, "w");
+ if (!alternates) {
+ ret = error_errno(_("unable to fdopen alternates lockfile"));
+ goto out;
+ }
+
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
+ orig = fopen(path.buf, "r");
+ if (orig) {
+ while (strbuf_getline(&line, orig) != EOF) {
+ strset_add(&seen, line.buf);
+ fprintf(alternates, "%s\n", line.buf);
+ }
+
+ if (ferror(orig)) {
+ ret = error_errno(_("unable to read alternates file"));
+ fclose(orig);
+ goto out;
+ }
+
+ fclose(orig);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
+ if (!strset_add(&seen, alternate))
+ continue;
+ fprintf(alternates, "%s\n", alternate);
+ }
+
+ if (ferror(alternates)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
+ if (commit_lock_file(&alternates_lock)) {
+ ret = error_errno(_("unable to commit alternates file"));
+ goto out;
+ }
+ }
+
+ /* Reprepare the object database to activate alternates. */
+ odb_reprepare(source->odb);
+
+ ret = 0;
+
+out:
+ rollback_lock_file(&alternates_lock);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
+ strset_clear(&seen);
+ return ret;
}
static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
struct odb_stream;
struct strvec;
+struct odb_create_on_disk_options {
+ /*
+ * Alternates that shall be written into the newly created object
+ * database. Whether or not this option can be handled is specific to
+ * the backend.
+ */
+ const struct strvec *alternates;
+};
+
/*
* The source is the part of the object database that stores the actual
* objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
- int (*create_on_disk)(struct odb_source *source);
+ int (*create_on_disk)(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts);
/*
* This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
if (!source->create_on_disk)
return 0;
- return source->create_on_disk(source);
+ return source->create_on_disk(source, opts);
}
/*
diff --git a/setup.c b/setup.c
index e654e27d05..426cc7dff8 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
void create_object_database(struct repository *repo)
{
+ struct odb_create_on_disk_options opts = { 0 };
+
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
- if (odb_source_create_on_disk(repo->objects->sources) < 0)
+ if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
die(_("failed creating object database"));
}
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database
2026-08-31 10:02 ` [PATCH v2 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-09-06 17:02 ` Justin Tobler
2026-09-07 7:23 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Justin Tobler @ 2026-09-06 17:02 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> +struct odb_create_on_disk_options {
> + /*
> + * Alternates that shall be written into the newly created object
> + * database. Whether or not this option can be handled is specific to
> + * the backend.
> + */
> + const struct strvec *alternates;
> +};
So ODB interface here still exposes a way to signal alternates to the
backend, but an alternative backend may not actually use alternates at
all. Out of curiousity, does this mean git-clone(1) options such as
"--shared" should only be allowed to work with the "files" backend? Or
should the expected behavior of such options just be backend specific?
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v2 6/8] odb/source: support writing alternates when creating the database
2026-09-06 17:02 ` Justin Tobler
@ 2026-09-07 7:23 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 7:23 UTC (permalink / raw)
To: Justin Tobler; +Cc: git, Toon Claes, Junio C Hamano
On Sun, Sep 06, 2026 at 12:02:57PM -0500, Justin Tobler wrote:
> On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> > +struct odb_create_on_disk_options {
> > + /*
> > + * Alternates that shall be written into the newly created object
> > + * database. Whether or not this option can be handled is specific to
> > + * the backend.
> > + */
> > + const struct strvec *alternates;
> > +};
>
> So ODB interface here still exposes a way to signal alternates to the
> backend, but an alternative backend may not actually use alternates at
> all. Out of curiousity, does this mean git-clone(1) options such as
> "--shared" should only be allowed to work with the "files" backend? Or
> should the expected behavior of such options just be backend specific?
The latter. It's totally feasible that an alternative backend may
support alternates, but it's also feasible that it doesn't.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (5 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 6/8] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-09-06 17:07 ` Justin Tobler
2026-08-31 10:02 ` [PATCH v2 8/8] odb/source: remove the ability to write alternates Patrick Steinhardt
2026-09-01 6:09 ` [PATCH v2 0/8] odb: write alternates at creation time Toon Claes
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
When creating a repository with alternates we first initialize the
object database and then write alternates to it in a separate step. This
is unfortunate due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
unfortunate as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so having this
callback is suboptimal there.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
We have thus introduced the ability to write alternates right at
creation time in the preceding commits, and we have unified setup of
alternates into a single location. All that's left to do for us now is
to wire up alternates as an option for the database creation.
Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 5 +----
setup.c | 9 ++++++---
setup.h | 2 +-
3 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 2e3473fddf..48ac379b1d 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
- create_object_database(the_repository);
collect_alternates(&alternates, path, is_local);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+ create_object_database(the_repository, &alternates);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/setup.c b/setup.c
index 426cc7dff8..cfa286ff59 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+ const struct strvec *alternates)
{
- struct odb_create_on_disk_options opts = { 0 };
+ struct odb_create_on_disk_options opts = {
+ .alternates = alternates,
+ };
/*
* Create the "objects" directory in the common directory. This is done
@@ -2907,7 +2910,7 @@ int init_db(struct repository *repo,
if (!(flags & INIT_DB_SKIP_REFDB))
create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
if (!(flags & INIT_DB_SKIP_ODB))
- create_object_database(repo);
+ create_object_database(repo, NULL);
startup_info->have_repository = 1;
diff --git a/setup.h b/setup.h
index 570ebcd150..34e86dad37 100644
--- a/setup.h
+++ b/setup.h
@@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
/*
* NOTE NOTE NOTE!!
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()`
2026-08-31 10:02 ` [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-09-06 17:07 ` Justin Tobler
0 siblings, 0 replies; 84+ messages in thread
From: Justin Tobler @ 2026-09-06 17:07 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/08/31 12:02PM, Patrick Steinhardt wrote:
> When creating a repository with alternates we first initialize the
> object database and then write alternates to it in a separate step. This
> is unfortunate due to a couple of reasons:
>
> - It requires us to have a `write_alternates()` callback, which is
> unfortunate as we never even write alternates to an object database
> after it has been created.
Happy to see the list of callbacks shrink. :)
> - We're about to make alternates an implementation detail of the
> object database's backend in a future patch series, so having this
> callback is suboptimal there.
>
> - The backend has more flexibility with how exactly alternates are
> configured when it itself is in full control over their setup at the
> time where it creates the object database itself.
>
> We have thus introduced the ability to write alternates right at
> creation time in the preceding commits, and we have unified setup of
> alternates into a single location. All that's left to do for us now is
> to wire up alternates as an option for the database creation.
Makes sense.
> Do so.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 5 +----
> setup.c | 9 ++++++---
> setup.h | 2 +-
> 3 files changed, 8 insertions(+), 8 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 2e3473fddf..48ac379b1d 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1368,11 +1368,8 @@ int cmd_clone(int argc,
> if (option_local > 0 && !is_local)
> warning(_("--local is ignored"));
>
> - create_object_database(the_repository);
> collect_alternates(&alternates, path, is_local);
> -
> - for (size_t i = 0; i < alternates.nr; i++)
> - odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
> + create_object_database(the_repository, &alternates);
Alternates are no longer explicitly set up here and instead wired and
handled via `create_object_database()`. This patch looks good.
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v2 8/8] odb/source: remove the ability to write alternates
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (6 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 7/8] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-08-31 10:02 ` Patrick Steinhardt
2026-09-01 6:09 ` [PATCH v2 0/8] odb: write alternates at creation time Toon Claes
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-08-31 10:02 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano
There are no users of `odb_source_write_alternates()` in our tree
anymore. Remove that function and its supporting infrastructure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb.c | 9 ---------
odb.h | 7 -------
odb/source-files.c | 54 ---------------------------------------------------
odb/source-inmemory.c | 7 -------
odb/source-loose.c | 7 -------
odb/source-packed.c | 7 -------
odb/source.h | 26 -------------------------
7 files changed, 117 deletions(-)
diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
return alternate;
}
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir)
-{
- int ret = odb_source_write_alternate(odb->sources, dir);
- if (ret < 0)
- die(NULL);
- odb_add_alternate_recursively(odb, dir, 0);
-}
-
struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
const char *dir)
{
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
*/
int odb_has_alternates(struct object_database *odb);
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir);
-
/*
* Add the directory to the in-memory list of alternate sources (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
-static int odb_source_files_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- struct lock_file lock = LOCK_INIT;
- char *path = xstrfmt("%s/%s", source->path, "info/alternates");
- FILE *in, *out;
- int found = 0;
- int ret;
-
- repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
- LOCK_DIE_ON_ERROR);
- out = fdopen_lock_file(&lock, "w");
- if (!out) {
- ret = error_errno(_("unable to fdopen alternates lockfile"));
- goto out;
- }
-
- in = fopen(path, "r");
- if (in) {
- struct strbuf line = STRBUF_INIT;
-
- while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(alternate, line.buf)) {
- found = 1;
- break;
- }
- fprintf_or_die(out, "%s\n", line.buf);
- }
-
- strbuf_release(&line);
- fclose(in);
- } else if (errno != ENOENT) {
- ret = error_errno(_("unable to read alternates file"));
- goto out;
- }
-
- if (found) {
- rollback_lock_file(&lock);
- } else {
- fprintf_or_die(out, "%s\n", alternate);
- if (commit_lock_file(&lock)) {
- ret = error_errno(_("unable to move new alternates file into place"));
- goto out;
- }
- }
-
- ret = 0;
-
-out:
- free(path);
- return ret;
-}
-
static int too_many_loose_objects(struct odb_source_files *files, int limit)
{
unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.write_object_stream = odb_source_files_write_object_stream;
files->base.begin_transaction = odb_source_files_begin_transaction;
files->base.read_alternates = odb_source_files_read_alternates;
- files->base.write_alternate = odb_source_files_write_alternate;
files->base.optimize = odb_source_files_optimize;
files->base.optimize_required = odb_source_files_optimize_required;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("in-memory source does not support alternates");
-}
-
static void odb_source_inmemory_close(struct odb_source *source UNUSED)
{
}
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.freshen_object = odb_source_inmemory_freshen_object;
source->base.begin_transaction = odb_source_inmemory_begin_transaction;
source->base.read_alternates = odb_source_inmemory_read_alternates;
- source->base.write_alternate = odb_source_inmemory_write_alternate;
return source;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("loose source does not support alternates");
-}
-
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_object_stream = odb_source_loose_write_object_stream;
loose->base.begin_transaction = odb_source_loose_begin_transaction;
loose->base.read_alternates = odb_source_loose_read_alternates;
- loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("packed backend cannot write alternates");
-}
-
void (*report_garbage)(unsigned seen_bits, const char *path);
static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_object_stream = odb_source_packed_write_object_stream;
packed->base.begin_transaction = odb_source_packed_begin_transaction;
packed->base.read_alternates = odb_source_packed_read_alternates;
- packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
int (*read_alternates)(struct odb_source *source,
struct strvec *out);
- /*
- * This callback is expected to persist the singular alternate passed
- * to it into its list of alternates. Any pre-existing alternates are
- * expected to remain active. Subsequent calls to `read_alternates` are
- * thus expected to yield the pre-existing list of alternates plus the
- * newly added alternate appended to its end.
- *
- * The callback is expected to return 0 on success, a negative error
- * code otherwise.
- */
- int (*write_alternate)(struct odb_source *source,
- const char *alternate);
-
/*
* This callback is expected to optimize the object database source.
* Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
return source->read_alternates(source, out);
}
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- return source->write_alternate(source, alternate);
-}
-
/*
* Create a new transaction that can be used to write objects into a temporary
* staging area. The objects will only be persisted when the transaction is
--
2.55.0.979.g7e5102b832.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v2 0/8] odb: write alternates at creation time
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
` (7 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 8/8] odb/source: remove the ability to write alternates Patrick Steinhardt
@ 2026-09-01 6:09 ` Toon Claes
8 siblings, 0 replies; 84+ messages in thread
From: Toon Claes @ 2026-09-01 6:09 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Junio C Hamano
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> writing alternates into the object database currently happens via
> `odb_source_write_alternate()`. But while that creates the ability to
> create alternates at arbitrary points of a source's lifetime, we don't
> use that functionality in the first place. Instead, we only ever write
> alternates when creating a new repository.
>
> This design is suboptimal due to a couple of reasons:
>
> - It requires us to have a `write_alternates()` callback, which is
> overblown as we never even write alternates to an object database
> after it has been created.
>
> - We're about to make alternates an implementation detail of the
> object database's backend in a future patch series, so alternate
> implementations may not even support them.
>
> - The backend has more flexibility with how exactly alternates are
> configured when it itself is in full control over their setup at the
> time where it creates the object database itself.
>
> This patch series thus refactors how we handle alternates so that we
> don't write them ad-hoc anymore. Instead, the series introduces a new
> option for `odb_source_create_on_disk()` that makes it handle those
> alternates at creation time.
>
> This is part of the bigger goal of moving handling of alternates into
> the "files" backend.
>
> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> `alternates_db` field, 2026-08-17) merged into it.
>
> Changes in v2:
> - Use a lockfile to write "info/alternates" during creation time.
> - Remove useless "strvec.h" include by reordering declarations a bit.
> - Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
This version looks good to me!
--
Laters,
Toon
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v3 0/9] odb: write alternates at creation time
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (8 preceding siblings ...)
2026-08-31 10:02 ` [PATCH v2 0/8] odb: write alternates at creation time Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
` (8 more replies)
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
11 siblings, 9 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Hi,
writing alternates into the object database currently happens via
`odb_source_write_alternate()`. But while that creates the ability to
create alternates at arbitrary points of a source's lifetime, we don't
use that functionality in the first place. Instead, we only ever write
alternates when creating a new repository.
This design is suboptimal due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
overblown as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so alternate
implementations may not even support them.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
This patch series thus refactors how we handle alternates so that we
don't write them ad-hoc anymore. Instead, the series introduces a new
option for `odb_source_create_on_disk()` that makes it handle those
alternates at creation time.
This is part of the bigger goal of moving handling of alternates into
the "files" backend.
This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
`alternates_db` field, 2026-08-17) merged into it.
Changes in v3:
- Refactor `init_db()` to not create the reference and object database
at all anymore. Instead, it's now called `create_repository()` and
it is responsible for creating the initial repository skeleton,
only. This allows us to get rid of the flags and overall makes the
logic more straight-forward by moving the command-specific logic
into the respective commands.
- A couple of typo fixes.
- Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
- Use a lockfile to write "info/alternates" during creation time.
- Remove useless "strvec.h" include by reordering declarations a bit.
- Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
setup: split up concerns of `init_db()`
builtin/clone: defer setup of the object database
builtin/clone: move around `setup_reference()`
builtin/clone: refactor handling of "--reference{,-if-able}"
builtin/clone: move setup of alternates for shared local clones
builtin/clone: move setup of alternates for non-shared local clones
odb/source: support writing alternates when creating the database
builtin/clone: write alternates via `odb_create_on_disk()`
odb/source: remove the ability to write alternates
builtin/clone.c | 111 +++++++++++++++++++++++---------------
builtin/init-db.c | 32 ++++++++---
odb.c | 9 ----
odb.h | 7 ---
odb/source-files.c | 130 ++++++++++++++++++++++++++-------------------
odb/source-inmemory.c | 7 ---
odb/source-loose.c | 7 ---
odb/source-packed.c | 7 ---
odb/source.h | 43 +++++----------
setup.c | 61 ++++++++-------------
setup.h | 22 ++++----
t/t5604-clone-reference.sh | 25 +++++++++
12 files changed, 239 insertions(+), 222 deletions(-)
Range-diff versus v2:
1: 1a1580185a < -: ---------- builtin/clone: defer setup of the object database
-: ---------- > 1: 512e7d1ef2 setup: split up concerns of `init_db()`
-: ---------- > 2: a8808bcacf builtin/clone: defer setup of the object database
2: 7d0d4832ed = 3: 0193fb3ada builtin/clone: move around `setup_reference()`
3: d547e62b5b ! 4: 3c53ad13f2 builtin/clone: refactor handling of "--reference{,-if-able}"
@@ Commit message
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
- the alterantes into it one by one.
+ the alternates into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope.
4: ceb90593c8 = 5: 4fbcab0026 builtin/clone: move setup of alternates for shared local clones
5: d9587585f3 ! 6: b658ca0799 builtin/clone: move setup of alternates for non-shared local clones
@@ Commit message
purpose.
Add two tests, the first one of which exercises this bug to avoid future
- regressions. The second patch ensures that we properly handle relative
+ regressions. The second test ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
6: 7575379b17 = 7: bec8115803 odb/source: support writing alternates when creating the database
7: c07fd20d85 ! 8: 3deaa7ef64 builtin/clone: write alternates via `odb_create_on_disk()`
@@ builtin/clone.c: int cmd_clone(int argc,
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
+ ## builtin/init-db.c ##
+@@ builtin/init-db.c: int cmd_init_db(int argc,
+ template_dir, hash_algo, ref_storage_format,
+ init_shared_repository, &reinit);
+ create_reference_database(the_repository, initial_branch, quiet);
+- create_object_database(the_repository);
++ create_object_database(the_repository, NULL);
+
+ if (!quiet) {
+ int len = strlen(git_dir);
+
## setup.c ##
@@ setup.c: static int create_default_files(struct repository *repo,
return reinit;
@@ setup.c: static int create_default_files(struct repository *repo,
/*
* Create the "objects" directory in the common directory. This is done
-@@ setup.c: int init_db(struct repository *repo,
- if (!(flags & INIT_DB_SKIP_REFDB))
- create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- if (!(flags & INIT_DB_SKIP_ODB))
-- create_object_database(repo);
-+ create_object_database(repo, NULL);
-
- startup_info->have_repository = 1;
-
## setup.h ##
@@ setup.h: void initialize_repository_version(struct repository *repo,
8: a8b9f51897 = 9: d751b886b3 odb/source: remove the ability to write alternates
---
base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7
change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0
^ permalink raw reply [flat|nested] 84+ messages in thread* [PATCH v3 1/9] setup: split up concerns of `init_db()`
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-08 22:12 ` Justin Tobler
2026-09-07 8:25 ` [PATCH v3 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
` (7 subsequent siblings)
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
The function `init_db()` is responsible for creating the on-disk
directory structure required for a Git repository. It is used by both
git-init(1) and git-clone(1), and because their expected behaviour is
different we support a couple of flags:
- The `QUIET` flag controls whether the command is quiet or not. For
git-init(1) this is user-controllable, whereas for git-clone(1)
we're always quiet.
- The `EXIST_OK` flag controls whether a preexisting repository is
okay or not. For git-init(1) it is, for git-clone(1) it's not.
- The `SKIP_REFDB` flag controls whether the reference database should
already be created or not. For git-init(1) we do, but for
git-clone(1) we don't because it does not yet know about the default
branch and about the remote object hash.
Furthermore, we're about to add another divergence in behaviour, where
we have to also skip creation of the object database in git-clone(1).
This is becoming quite cumbersome though.
Instead of introducing another flag, start to split up concerns of the
function so that we never create the reference or object database. This
becomes the responsibility of the caller, which is thus free to defer
their creation to a later point in time. This lets us get rid of most of
the divergent behaviour:
- We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
anymore.
- We don't need the `QUIET` flag anymore, as nothing prints output
except for the final status message that tells the user that the
repository has been (re)initialized. But as this message is specific
to git-init(1), we can easily move it there.
The only piece of information we still have to convey is whether or not
reinitialization of a preexisting repository is okay. This is handled by
a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
to reinitialize the repository. Furthermore, the pointer will be written
to to indicate whether the repository was reinitialized or not, which we
need in git-init(1) to print the correct initialization message.
With these refactorings, `init_db()` is named quite misleadingly though,
as we don't create any of the reference or object databases anymore.
Rename it to `create_repository()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 9 +++++----
builtin/init-db.c | 32 ++++++++++++++++++++++++--------
setup.c | 54 +++++++++++++++++-------------------------------------
setup.h | 22 ++++++++++------------
4 files changed, 56 insertions(+), 61 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
* repository, and reference backends may persist that information into
* their on-disk data structures.
*/
- init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
- GIT_HASH_UNKNOWN, ref_storage_format, NULL,
- do_not_override_repo_unix_permissions,
- INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+ do_not_override_repo_unix_permissions, NULL);
+ create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
repo_set_hash_algo(the_repository, hash_algo);
create_reference_database(the_repository, NULL, 1);
+ startup_info->have_repository = 1;
/*
* Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
char *work_tree = NULL;
const char *template_dir = NULL;
char *template_dir_to_free = NULL;
- unsigned int flags = 0;
+ int quiet = 0;
int bare = startup_info->force_bare_repository ? 1 : -1;
const char *object_format = NULL;
const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
.callback = shared_callback
},
- OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+ OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
N_("specify the reference format to use")),
OPT_END()
};
- int ret;
+ int reinit;
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
die(_("--separate-git-dir incompatible with bare repository"));
}
- flags |= INIT_DB_EXIST_OK;
- ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
- template_dir, hash_algo, ref_storage_format, initial_branch,
- init_shared_repository, flags);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ template_dir, hash_algo, ref_storage_format,
+ init_shared_repository, &reinit);
+ create_reference_database(the_repository, initial_branch, quiet);
+ create_object_database(the_repository);
+
+ if (!quiet) {
+ int len = strlen(git_dir);
+
+ if (reinit)
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Reinitialized existing shared Git repository in %s%s\n")
+ : _("Reinitialized existing Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ else
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Initialized empty shared Git repository in %s%s\n")
+ : _("Initialized empty Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ }
free(template_dir_to_free);
free(real_git_dir_to_free);
free(work_tree);
free(git_dir);
- return ret;
+ return 0;
}
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
{
/*
* Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
}
}
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch,
- int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok)
{
- int reinit;
- int exist_ok = flags & INIT_DB_EXIST_OK;
+ int reinit_ignored;
char *original_git_dir = real_pathdup(git_dir, 1);
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
if (real_git_dir) {
struct stat st;
- if (!exist_ok && !stat(git_dir, &st))
+ if (!reinit_ok && !stat(git_dir, &st))
die(_("%s already exists"), git_dir);
- if (!exist_ok && !stat(real_git_dir, &st))
+ if (!reinit_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
safe_create_dir(repo, git_dir, 0);
- reinit = create_default_files(repo, template_dir, original_git_dir,
- &repo_fmt, init_shared_repository);
+ if (!reinit_ok)
+ reinit_ok = &reinit_ignored;
+ *reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+ &repo_fmt, init_shared_repository);
if (repo_settings_get_shared_repository(repo)) {
char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
repo_config_set(repo, "receive.denyNonFastforwards", "true");
}
- if (!(flags & INIT_DB_SKIP_REFDB))
- create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- create_object_database(repo);
-
- startup_info->have_repository = 1;
-
- if (!(flags & INIT_DB_QUIET)) {
- int len = strlen(git_dir);
-
- if (reinit)
- printf(repo_settings_get_shared_repository(repo)
- ? _("Reinitialized existing shared Git repository in %s%s\n")
- : _("Reinitialized existing Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- else
- printf(repo_settings_get_shared_repository(repo)
- ? _("Initialized empty shared Git repository in %s%s\n")
- : _("Initialized empty Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- }
-
clear_repository_format(&repo_fmt);
strbuf_release(&err);
free(original_git_dir);
- return 0;
}
diff --git a/setup.h b/setup.h
index 763fd384e8..c4aa464caa 100644
--- a/setup.h
+++ b/setup.h
@@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
const char *get_template_dir(const char *option_template);
-#define INIT_DB_QUIET (1 << 0)
-#define INIT_DB_EXIST_OK (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash_algo,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash_algo,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok);
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+void create_object_database(struct repository *repo);
/*
* NOTE NOTE NOTE!!
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`
2026-09-07 8:25 ` [PATCH v3 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
@ 2026-09-08 22:12 ` Justin Tobler
2026-09-09 5:48 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Justin Tobler @ 2026-09-08 22:12 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> The function `init_db()` is responsible for creating the on-disk
> directory structure required for a Git repository. It is used by both
> git-init(1) and git-clone(1), and because their expected behaviour is
> different we support a couple of flags:
>
> - The `QUIET` flag controls whether the command is quiet or not. For
> git-init(1) this is user-controllable, whereas for git-clone(1)
> we're always quiet.
>
> - The `EXIST_OK` flag controls whether a preexisting repository is
> okay or not. For git-init(1) it is, for git-clone(1) it's not.
>
> - The `SKIP_REFDB` flag controls whether the reference database should
> already be created or not. For git-init(1) we do, but for
> git-clone(1) we don't because it does not yet know about the default
> branch and about the remote object hash.
>
> Furthermore, we're about to add another divergence in behaviour, where
> we have to also skip creation of the object database in git-clone(1).
> This is becoming quite cumbersome though.
>
> Instead of introducing another flag, start to split up concerns of the
> function so that we never create the reference or object database. This
> becomes the responsibility of the caller, which is thus free to defer
> their creation to a later point in time. This lets us get rid of most of
> the divergent behaviour:
>
> - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
> anymore.
>
> - We don't need the `QUIET` flag anymore, as nothing prints output
> except for the final status message that tells the user that the
> repository has been (re)initialized. But as this message is specific
> to git-init(1), we can easily move it there.
>
> The only piece of information we still have to convey is whether or not
> reinitialization of a preexisting repository is okay. This is handled by
> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
> to reinitialize the repository. Furthermore, the pointer will be written
> to to indicate whether the repository was reinitialized or not, which we
> need in git-init(1) to print the correct initialization message.
I like the proposed direction here much more than the previous version.
:)
> With these refactorings, `init_db()` is named quite misleadingly though,
> as we don't create any of the reference or object databases anymore.
> Rename it to `create_repository()`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 9 +++++----
> builtin/init-db.c | 32 ++++++++++++++++++++++++--------
> setup.c | 54 +++++++++++++++++-------------------------------------
> setup.h | 22 ++++++++++------------
> 4 files changed, 56 insertions(+), 61 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 5b25cca510..904d2d859f 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
> * repository, and reference backends may persist that information into
> * their on-disk data structures.
> */
> - init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
> - GIT_HASH_UNKNOWN, ref_storage_format, NULL,
> - do_not_override_repo_unix_permissions,
> - INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
> + create_repository(the_repository, git_dir, real_git_dir, work_tree,
> + option_template, GIT_HASH_UNKNOWN, ref_storage_format,
> + do_not_override_repo_unix_permissions, NULL);
> + create_object_database(the_repository);
The implicit creation of the ref DB was already skipped so we only need
to explicitly add `create_object_database()`. Makes sense.
> if (real_git_dir) {
> free((char *)git_dir);
> @@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
> initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
> repo_set_hash_algo(the_repository, hash_algo);
> create_reference_database(the_repository, NULL, 1);
> + startup_info->have_repository = 1;
Ok IIUC, we now have to explicitly set "have_repository" because we
don't want to set it if the ODB has not been created.
> /*
> * Before fetching from the remote, download and install bundle
> diff --git a/builtin/init-db.c b/builtin/init-db.c
> index e96b1283b7..f2c7e3be6d 100644
> --- a/builtin/init-db.c
> +++ b/builtin/init-db.c
> @@ -80,7 +80,7 @@ int cmd_init_db(int argc,
> char *work_tree = NULL;
> const char *template_dir = NULL;
> char *template_dir_to_free = NULL;
> - unsigned int flags = 0;
> + int quiet = 0;
> int bare = startup_info->force_bare_repository ? 1 : -1;
> const char *object_format = NULL;
> const char *ref_format = NULL;
> @@ -102,7 +102,7 @@ int cmd_init_db(int argc,
> .flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
> .callback = shared_callback
> },
> - OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
> + OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
> OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
> N_("separate git dir from working tree")),
> OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
> @@ -113,7 +113,7 @@ int cmd_init_db(int argc,
> N_("specify the reference format to use")),
> OPT_END()
> };
> - int ret;
> + int reinit;
>
> argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
>
> @@ -247,14 +247,30 @@ int cmd_init_db(int argc,
> die(_("--separate-git-dir incompatible with bare repository"));
> }
>
> - flags |= INIT_DB_EXIST_OK;
> - ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
> - template_dir, hash_algo, ref_storage_format, initial_branch,
> - init_shared_repository, flags);
> + create_repository(the_repository, git_dir, real_git_dir, work_tree,
> + template_dir, hash_algo, ref_storage_format,
> + init_shared_repository, &reinit);
> + create_reference_database(the_repository, initial_branch, quiet);
> + create_object_database(the_repository);
> +
> + if (!quiet) {
> + int len = strlen(git_dir);
> +
> + if (reinit)
> + printf(repo_settings_get_shared_repository(the_repository)
> + ? _("Reinitialized existing shared Git repository in %s%s\n")
> + : _("Reinitialized existing Git repository in %s%s\n"),
> + git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> + else
> + printf(repo_settings_get_shared_repository(the_repository)
> + ? _("Initialized empty shared Git repository in %s%s\n")
> + : _("Initialized empty Git repository in %s%s\n"),
> + git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> + }
Previously all of this logic was specific to git-init(1), so now its
moved out of the generic function accordingly to furthur simplify the
interface. Nice.
[snip]
> diff --git a/setup.h b/setup.h
> index 763fd384e8..c4aa464caa 100644
> --- a/setup.h
> +++ b/setup.h
> @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
>
> const char *get_template_dir(const char *option_template);
>
> -#define INIT_DB_QUIET (1 << 0)
> -#define INIT_DB_EXIST_OK (1 << 1)
> -#define INIT_DB_SKIP_REFDB (1 << 2)
> -
> -int init_db(struct repository *repo,
> - const char *git_dir,
> - const char *real_git_dir,
> - const char *worktree,
> - const char *template_dir, int hash_algo,
> - enum ref_storage_format ref_storage_format,
> - const char *initial_branch, int init_shared_repository,
> - unsigned int flags);
> +void create_repository(struct repository *repo,
> + const char *git_dir,
> + const char *real_git_dir,
> + const char *worktree,
> + const char *template_dir,
> + int hash_algo,
> + enum ref_storage_format ref_storage_format,
> + int init_shared_repository,
> + int *reinit_ok);
While we are here, it might be nice to document these functions a little
bit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely
obvious to future readers.
> void initialize_repository_version(struct repository *repo,
> int hash_algo,
> enum ref_storage_format ref_storage_format,
> int reinit);
> void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
> +void create_object_database(struct repository *repo);
It might also be nice to document these functions to explain exactly
what we are "creating".
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v3 1/9] setup: split up concerns of `init_db()`
2026-09-08 22:12 ` Justin Tobler
@ 2026-09-09 5:48 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: Justin Tobler; +Cc: git, Toon Claes, Junio C Hamano
On Tue, Sep 08, 2026 at 05:12:21PM -0500, Justin Tobler wrote:
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > diff --git a/setup.h b/setup.h
> > index 763fd384e8..c4aa464caa 100644
> > --- a/setup.h
> > +++ b/setup.h
> > @@ -256,23 +256,21 @@ int apply_repository_format(struct repository *repo,
> >
> > const char *get_template_dir(const char *option_template);
> >
> > -#define INIT_DB_QUIET (1 << 0)
> > -#define INIT_DB_EXIST_OK (1 << 1)
> > -#define INIT_DB_SKIP_REFDB (1 << 2)
> > -
> > -int init_db(struct repository *repo,
> > - const char *git_dir,
> > - const char *real_git_dir,
> > - const char *worktree,
> > - const char *template_dir, int hash_algo,
> > - enum ref_storage_format ref_storage_format,
> > - const char *initial_branch, int init_shared_repository,
> > - unsigned int flags);
> > +void create_repository(struct repository *repo,
> > + const char *git_dir,
> > + const char *real_git_dir,
> > + const char *worktree,
> > + const char *template_dir,
> > + int hash_algo,
> > + enum ref_storage_format ref_storage_format,
> > + int init_shared_repository,
> > + int *reinit_ok);
>
> While we are here, it might be nice to document these functions a little
> bit. The NULL/non-NULL behavior of `reinit_ok` may not be entirely
> obvious to future readers.
>
> > void initialize_repository_version(struct repository *repo,
> > int hash_algo,
> > enum ref_storage_format ref_storage_format,
> > int reinit);
> > void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
> > +void create_object_database(struct repository *repo);
>
> It might also be nice to document these functions to explain exactly
> what we are "creating".
Yeah, fair. Will add some comments here.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v3 2/9] builtin/clone: defer setup of the object database
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-08 22:20 ` Justin Tobler
2026-09-07 8:25 ` [PATCH v3 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
` (6 subsequent siblings)
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When cloning a repository we defer initialization of the reference
database. This is because we don't yet know all details required for us
to initialize the refdb in the first place. Most importantly, what we
are missing is information about the object hash.
We don't do the same thing for the object database yet, but here we
essentially have the same problem. While the "files" database does not
need any information about the object format at creation time, alternate
backends are likely to require that information so that they can
properly set up their data structures.
Besides this forward-looking future proofing though, we also have a
second use case for deferring initialization of the object database,
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
the user has passed "--refernce{,-if-able}", then we will end up writing
alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
already have access to the user-provided URI, that URI can be very well
rewritten via "url.<base>.insteadOf". We can of course read the global-
and system-level configuration to resolve it. But we explicitly resolve
the URI a second time after we have initialized the repository because
it can happen that we copy a ".git/config" over from our templates, and
that file may cause us to rewrite the path.
In a subsequent commit though we'll start to write alternates as part of
the repository initialization, so we'll need to have the URI properly
resolved before we can initialize the object database. This is ugly, but
as mentioned above it makes sense for us to defer its initialization
anyway so that we also know about the object hash already.
Defer creation of the object database until after we have resolved the
URI.
Note that this also requires us to defer the call to `setup_reference()`
until after we have created the object database. While you might think
that this function has something to do with references ("refs/*"), it is
in fact responsible for setting up alternates. Consequently, we can only
call it after we have created the object database already.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
create_repository(the_repository, git_dir, real_git_dir, work_tree,
option_template, GIT_HASH_UNKNOWN, ref_storage_format,
do_not_override_repo_unix_permissions, NULL);
- create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
strbuf_reset(&key);
}
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
-
remote = remote_get_early(remote_name);
if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
+ create_object_database(the_repository);
+ if (option_required_reference.nr || option_optional_reference.nr)
+ setup_reference();
+
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database
2026-09-07 8:25 ` [PATCH v3 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-08 22:20 ` Justin Tobler
2026-09-09 5:48 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Justin Tobler @ 2026-09-08 22:20 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
>
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.
>
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
>
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
>
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
>
> Defer creation of the object database until after we have resolved the
> URI.
>
> Note that this also requires us to defer the call to `setup_reference()`
> until after we have created the object database. While you might think
> that this function has something to do with references ("refs/*"), it is
> in fact responsible for setting up alternates. Consequently, we can only
> call it after we have created the object database already.
Ah ok, so now that we are deferring ODB creation a bit further, we need
to also move ref DB creation to afterwards too. Makes sense.
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 904d2d859f..bdcbd7aa1b 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
> create_repository(the_repository, git_dir, real_git_dir, work_tree,
> option_template, GIT_HASH_UNKNOWN, ref_storage_format,
> do_not_override_repo_unix_permissions, NULL);
> - create_object_database(the_repository);
>
> if (real_git_dir) {
> free((char *)git_dir);
> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
> strbuf_reset(&key);
> }
>
> - if (option_required_reference.nr || option_optional_reference.nr)
> - setup_reference();
> -
> remote = remote_get_early(remote_name);
>
> if (!option_rev)
> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
> if (option_local > 0 && !is_local)
> warning(_("--local is ignored"));
>
> + create_object_database(the_repository);
> + if (option_required_reference.nr || option_optional_reference.nr)
> + setup_reference();
Ok so the move here is really just in preparation for the alternates
setup being handled as part of ODB creation in a subsequent patch and
purely just structural. Looks good.
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v3 2/9] builtin/clone: defer setup of the object database
2026-09-08 22:20 ` Justin Tobler
@ 2026-09-09 5:48 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: Justin Tobler; +Cc: git, Toon Claes, Junio C Hamano
On Tue, Sep 08, 2026 at 05:20:06PM -0500, Justin Tobler wrote:
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > When cloning a repository we defer initialization of the reference
> > database. This is because we don't yet know all details required for us
> > to initialize the refdb in the first place. Most importantly, what we
> > are missing is information about the object hash.
> >
> > We don't do the same thing for the object database yet, but here we
> > essentially have the same problem. While the "files" database does not
> > need any information about the object format at creation time, alternate
> > backends are likely to require that information so that they can
> > properly set up their data structures.
> >
> > Besides this forward-looking future proofing though, we also have a
> > second use case for deferring initialization of the object database,
> > namely alternates. When initializing the object database we do not yet
> > know whether we'll need alternates or not because this depends on the
> > repository we're about to clone from. If it is a local repository and
> > the user has passed "--refernce{,-if-able}", then we will end up writing
> > alternates into the object database.
> >
> > The ugly part though is that we cannot determine where the repository is
> > getting cloned from before it has been initialized. While we of course
> > already have access to the user-provided URI, that URI can be very well
> > rewritten via "url.<base>.insteadOf". We can of course read the global-
> > and system-level configuration to resolve it. But we explicitly resolve
> > the URI a second time after we have initialized the repository because
> > it can happen that we copy a ".git/config" over from our templates, and
> > that file may cause us to rewrite the path.
> >
> > In a subsequent commit though we'll start to write alternates as part of
> > the repository initialization, so we'll need to have the URI properly
> > resolved before we can initialize the object database. This is ugly, but
> > as mentioned above it makes sense for us to defer its initialization
> > anyway so that we also know about the object hash already.
> >
> > Defer creation of the object database until after we have resolved the
> > URI.
> >
> > Note that this also requires us to defer the call to `setup_reference()`
> > until after we have created the object database. While you might think
> > that this function has something to do with references ("refs/*"), it is
> > in fact responsible for setting up alternates. Consequently, we can only
> > call it after we have created the object database already.
>
> Ah ok, so now that we are deferring ODB creation a bit further, we need
> to also move ref DB creation to afterwards too. Makes sense.
This is not about the reference database, `setup_reference()` sets up
out alternates. But I think you just miswrote here, as you do mention
alternates further down in your reply.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v3 3/9] builtin/clone: move around `setup_reference()`
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
` (5 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
In a subsequent commit, `setup_reference()` will start to call
`copy_alternates()`. Prepare for this by moving the function further
down so that we can avoid adding a declaration.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
return 0;
}
-static void setup_reference(void)
-{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
-}
-
static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
+static void setup_reference(void)
+{
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
+}
+
static void mkdir_if_missing(const char *pathname, mode_t mode)
{
struct stat st;
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v3 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (2 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
` (4 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
to set up alternates for the newly created repository. This allows it to
reuse objects from the source repository so that in the best case we
don't have to clone all objects over.
Those options are handled by the confusingly named `setup_reference()`
function -- without the above context, one might rightfully believe that
it was about refs, not about alternates. The function itself is rather
simple: we loop through all provided alternate paths and then, if such
an alternate is valid, we write it to the object database.
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
the alternates into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..8786a49332 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+ struct strvec *alternates;
+ int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
{
+ struct add_one_alternate_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
if (!ref_git) {
- if (*required)
+ if (data->required)
die("%s", err.buf);
else
fprintf(stderr,
_("info: Could not add alternate for '%s': %s\n"),
item->string, err.buf);
} else {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%s/objects", ref_git);
- odb_add_to_alternates_file(the_repository->objects, sb.buf);
- strbuf_release(&sb);
+ strvec_pushf(data->alternates, "%s/objects", ref_git);
}
strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
+ struct add_one_alternate_data data = {
+ .alternates = alternates,
+ .required = 1,
+ };
+
+ for_each_string_list(&option_required_reference,
+ add_one_alternate, &data);
+ data.required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_alternate, &data);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
};
+ struct strvec alternates = STRVEC_INIT;
const char * const builtin_clone_usage[] = {
N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
+ collect_alternates(&alternates);
+
+ for (size_t i = 0; i < alternates.nr; i++)
+ odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);
+ strvec_clear(&alternates);
free(remote_name);
strbuf_release(&reflog_msg);
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (3 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-08 22:43 ` Justin Tobler
2026-09-07 8:25 ` [PATCH v3 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
` (3 subsequent siblings)
8 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When cloning a local repository with "--shared" we add that repository
to the new repository's alternates. This is done in `clone_local()`,
which is responsible for performing local clones.
Move the logic into `collect_alternates()` to unify our setup of
alternates. Furthermore, this will allow us to set up alternates right
at creation time of the object database.
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8786a49332..011fc867c8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+ const char *src_repo, bool is_local)
{
if (option_required_reference.nr || option_optional_reference.nr) {
struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
add_one_alternate, &data);
}
+
+ if (is_local) {
+ struct strbuf commondir = STRBUF_INIT;
+
+ get_common_dir(&commondir, src_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+ strbuf_release(&commondir);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
static void clone_local(const char *src_repo, const char *dest_repo)
{
- if (option_shared) {
- struct strbuf alt = STRBUF_INIT;
- get_common_dir(&alt, src_repo);
- strbuf_addstr(&alt, "/objects");
- odb_add_to_alternates_file(the_repository->objects, alt.buf);
- strbuf_release(&alt);
- } else {
+ if (!option_shared) {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- collect_alternates(&alternates);
+ collect_alternates(&alternates, path, is_local);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-07 8:25 ` [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-09-08 22:43 ` Justin Tobler
2026-09-08 22:48 ` Justin Tobler
0 siblings, 1 reply; 84+ messages in thread
From: Justin Tobler @ 2026-09-08 22:43 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> When cloning a local repository with "--shared" we add that repository
> to the new repository's alternates. This is done in `clone_local()`,
> which is responsible for performing local clones.
>
> Move the logic into `collect_alternates()` to unify our setup of
> alternates. Furthermore, this will allow us to set up alternates right
> at creation time of the object database.
>
> Note that the logic for cloning a local repository with "--no-shared" is
> not yet part of `collect_alternates()`. This will be handled in the next
> commit.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 23 ++++++++++++++---------
> 1 file changed, 14 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8786a49332..011fc867c8 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> fclose(in);
> }
>
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> + const char *src_repo, bool is_local)
> {
> if (option_required_reference.nr || option_optional_reference.nr) {
> struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
> for_each_string_list(&option_optional_reference,
> add_one_alternate, &data);
> }
> +
> + if (is_local) {
Could this condtional instead be:
if (is_local && option_shared)
> + struct strbuf commondir = STRBUF_INIT;
> +
> + get_common_dir(&commondir, src_repo);
> + if (option_shared)
> + strvec_pushf(alternates, "%s/objects", commondir.buf);
> +
> + strbuf_release(&commondir);
> + }
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-08 22:43 ` Justin Tobler
@ 2026-09-08 22:48 ` Justin Tobler
0 siblings, 0 replies; 84+ messages in thread
From: Justin Tobler @ 2026-09-08 22:48 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/09/08 05:43PM, Justin Tobler wrote:
> On 26/09/07 10:25AM, Patrick Steinhardt wrote:
> > + if (is_local) {
>
> Could this condtional instead be:
>
> if (is_local && option_shared)
Ahh nevermind, in the next patch we handle `!option_shared`.
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v3 6/9] builtin/clone: move setup of alternates for non-shared local clones
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (4 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
` (2 subsequent siblings)
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Similar as in the preceding commit, move the setup of alternates for
local clones with "--no-shared" into `collect_alternates()`. With this
step, the complete setup of alternates is now handled by that function.
Note that besides moving stuff around, it also fixes a bug: previously,
we did not know to resolve the referenced repository's common directory.
Consequently, when referencing a worktree we failed to resolve
alternates. But as `collect_alternates()` already knows to resolve the
commondir for "--local" we can simply reuse this resolved path for our
purpose.
Add two tests, the first one of which exercises this bug to avoid future
regressions. The second test ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 34 +++++++++++++++++++++++-----------
t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 011fc867c8..84c1317867 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
- FILE *in = xfopen(src->buf, "r");
+ FILE *in;
+ struct strbuf path = STRBUF_INIT;
struct strbuf line = STRBUF_INIT;
+ strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+ in = fopen(path.buf, "r");
+ if (!in) {
+ if (errno == ENOENT)
+ goto out;
+ die_errno("could not read alternates file '%s'", path.buf);
+ }
+
while (strbuf_getline(&line, in) != EOF) {
char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
- odb_add_to_alternates_file(the_repository->objects,
- line.buf);
+ strvec_push(alternates, line.buf);
continue;
}
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
if (!normalize_path_copy(abs_path, abs_path))
- odb_add_to_alternates_file(the_repository->objects,
- abs_path);
+ strvec_push(alternates, abs_path);
else
warning("skipping invalid relative alternate: %s/%s",
src_repo, line.buf);
free(abs_path);
}
+
+out:
+ strbuf_release(&path);
strbuf_release(&line);
- fclose(in);
+ if (in)
+ fclose(in);
}
static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
get_common_dir(&commondir, src_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
+ read_alternates(alternates, commondir.buf);
strbuf_release(&commondir);
}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
continue;
}
- /* Files that cannot be copied bit-for-bit... */
- if (!fspathcmp(iter->relative_path, "info/alternates")) {
- copy_alternates(src, src_repo);
+ /* Alternates were already handled earlier. */
+ if (!fspathcmp(iter->relative_path, "info/alternates"))
continue;
- }
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
git clone --no-local --reference graph.git --dissociate orig clone
'
+test_expect_success 'local clone from linked worktree carries over alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
test_done
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v3 7/9] odb/source: support writing alternates when creating the database
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (5 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Add the ability to write alternates when creating the object database.
This change allows us to remove the `write_alternates()` callback in a
subsequent patch.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
odb/source.h | 17 +++++++++---
setup.c | 4 ++-
3 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
+#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
+ struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
+ FILE *alternates, *orig;
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
+ repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+ path.buf, LOCK_DIE_ON_ERROR);
+
+ alternates = fdopen_lock_file(&alternates_lock, "w");
+ if (!alternates) {
+ ret = error_errno(_("unable to fdopen alternates lockfile"));
+ goto out;
+ }
+
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
+ orig = fopen(path.buf, "r");
+ if (orig) {
+ while (strbuf_getline(&line, orig) != EOF) {
+ strset_add(&seen, line.buf);
+ fprintf(alternates, "%s\n", line.buf);
+ }
+
+ if (ferror(orig)) {
+ ret = error_errno(_("unable to read alternates file"));
+ fclose(orig);
+ goto out;
+ }
+
+ fclose(orig);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
+ if (!strset_add(&seen, alternate))
+ continue;
+ fprintf(alternates, "%s\n", alternate);
+ }
+
+ if (ferror(alternates)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
+ if (commit_lock_file(&alternates_lock)) {
+ ret = error_errno(_("unable to commit alternates file"));
+ goto out;
+ }
+ }
+
+ /* Reprepare the object database to activate alternates. */
+ odb_reprepare(source->odb);
+
+ ret = 0;
+
+out:
+ rollback_lock_file(&alternates_lock);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
+ strset_clear(&seen);
+ return ret;
}
static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
struct odb_stream;
struct strvec;
+struct odb_create_on_disk_options {
+ /*
+ * Alternates that shall be written into the newly created object
+ * database. Whether or not this option can be handled is specific to
+ * the backend.
+ */
+ const struct strvec *alternates;
+};
+
/*
* The source is the part of the object database that stores the actual
* objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
- int (*create_on_disk)(struct odb_source *source);
+ int (*create_on_disk)(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts);
/*
* This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
if (!source->create_on_disk)
return 0;
- return source->create_on_disk(source);
+ return source->create_on_disk(source, opts);
}
/*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
void create_object_database(struct repository *repo)
{
+ struct odb_create_on_disk_options opts = { 0 };
+
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
- if (odb_source_create_on_disk(repo->objects->sources) < 0)
+ if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
die(_("failed creating object database"));
}
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v3 8/9] builtin/clone: write alternates via `odb_create_on_disk()`
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (6 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
2026-09-07 8:25 ` [PATCH v3 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When creating a repository with alternates we first initialize the
object database and then write alternates to it in a separate step. This
is unfortunate due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
unfortunate as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so having this
callback is suboptimal there.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
We have thus introduced the ability to write alternates right at
creation time in the preceding commits, and we have unified setup of
alternates into a single location. All that's left to do for us now is
to wire up alternates as an option for the database creation.
Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 5 +----
builtin/init-db.c | 2 +-
setup.c | 7 +++++--
setup.h | 2 +-
4 files changed, 8 insertions(+), 8 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 84c1317867..9e84646845 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
- create_object_database(the_repository);
collect_alternates(&alternates, path, is_local);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+ create_object_database(the_repository, &alternates);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
template_dir, hash_algo, ref_storage_format,
init_shared_repository, &reinit);
create_reference_database(the_repository, initial_branch, quiet);
- create_object_database(the_repository);
+ create_object_database(the_repository, NULL);
if (!quiet) {
int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+ const struct strvec *alternates)
{
- struct odb_create_on_disk_options opts = { 0 };
+ struct odb_create_on_disk_options opts = {
+ .alternates = alternates,
+ };
/*
* Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index c4aa464caa..e9941c6875 100644
--- a/setup.h
+++ b/setup.h
@@ -270,7 +270,7 @@ void initialize_repository_version(struct repository *repo,
enum ref_storage_format ref_storage_format,
int reinit);
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
/*
* NOTE NOTE NOTE!!
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v3 9/9] odb/source: remove the ability to write alternates
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
` (7 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-09-07 8:25 ` Patrick Steinhardt
8 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-07 8:25 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
There are no users of `odb_source_write_alternates()` in our tree
anymore. Remove that function and its supporting infrastructure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb.c | 9 ---------
odb.h | 7 -------
odb/source-files.c | 54 ---------------------------------------------------
odb/source-inmemory.c | 7 -------
odb/source-loose.c | 7 -------
odb/source-packed.c | 7 -------
odb/source.h | 26 -------------------------
7 files changed, 117 deletions(-)
diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
return alternate;
}
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir)
-{
- int ret = odb_source_write_alternate(odb->sources, dir);
- if (ret < 0)
- die(NULL);
- odb_add_alternate_recursively(odb, dir, 0);
-}
-
struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
const char *dir)
{
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
*/
int odb_has_alternates(struct object_database *odb);
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir);
-
/*
* Add the directory to the in-memory list of alternate sources (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
-static int odb_source_files_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- struct lock_file lock = LOCK_INIT;
- char *path = xstrfmt("%s/%s", source->path, "info/alternates");
- FILE *in, *out;
- int found = 0;
- int ret;
-
- repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
- LOCK_DIE_ON_ERROR);
- out = fdopen_lock_file(&lock, "w");
- if (!out) {
- ret = error_errno(_("unable to fdopen alternates lockfile"));
- goto out;
- }
-
- in = fopen(path, "r");
- if (in) {
- struct strbuf line = STRBUF_INIT;
-
- while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(alternate, line.buf)) {
- found = 1;
- break;
- }
- fprintf_or_die(out, "%s\n", line.buf);
- }
-
- strbuf_release(&line);
- fclose(in);
- } else if (errno != ENOENT) {
- ret = error_errno(_("unable to read alternates file"));
- goto out;
- }
-
- if (found) {
- rollback_lock_file(&lock);
- } else {
- fprintf_or_die(out, "%s\n", alternate);
- if (commit_lock_file(&lock)) {
- ret = error_errno(_("unable to move new alternates file into place"));
- goto out;
- }
- }
-
- ret = 0;
-
-out:
- free(path);
- return ret;
-}
-
static int too_many_loose_objects(struct odb_source_files *files, int limit)
{
unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.write_object_stream = odb_source_files_write_object_stream;
files->base.begin_transaction = odb_source_files_begin_transaction;
files->base.read_alternates = odb_source_files_read_alternates;
- files->base.write_alternate = odb_source_files_write_alternate;
files->base.optimize = odb_source_files_optimize;
files->base.optimize_required = odb_source_files_optimize_required;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("in-memory source does not support alternates");
-}
-
static void odb_source_inmemory_close(struct odb_source *source UNUSED)
{
}
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.freshen_object = odb_source_inmemory_freshen_object;
source->base.begin_transaction = odb_source_inmemory_begin_transaction;
source->base.read_alternates = odb_source_inmemory_read_alternates;
- source->base.write_alternate = odb_source_inmemory_write_alternate;
return source;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("loose source does not support alternates");
-}
-
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_object_stream = odb_source_loose_write_object_stream;
loose->base.begin_transaction = odb_source_loose_begin_transaction;
loose->base.read_alternates = odb_source_loose_read_alternates;
- loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("packed backend cannot write alternates");
-}
-
void (*report_garbage)(unsigned seen_bits, const char *path);
static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_object_stream = odb_source_packed_write_object_stream;
packed->base.begin_transaction = odb_source_packed_begin_transaction;
packed->base.read_alternates = odb_source_packed_read_alternates;
- packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
int (*read_alternates)(struct odb_source *source,
struct strvec *out);
- /*
- * This callback is expected to persist the singular alternate passed
- * to it into its list of alternates. Any pre-existing alternates are
- * expected to remain active. Subsequent calls to `read_alternates` are
- * thus expected to yield the pre-existing list of alternates plus the
- * newly added alternate appended to its end.
- *
- * The callback is expected to return 0 on success, a negative error
- * code otherwise.
- */
- int (*write_alternate)(struct odb_source *source,
- const char *alternate);
-
/*
* This callback is expected to optimize the object database source.
* Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
return source->read_alternates(source, out);
}
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- return source->write_alternate(source, alternate);
-}
-
/*
* Create a new transaction that can be used to write objects into a temporary
* staging area. The objects will only be persisted when the transaction is
--
2.55.0.1007.g17ff1f9808.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread
* [PATCH v4 0/9] odb: write alternates at creation time
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (9 preceding siblings ...)
2026-09-07 8:25 ` [PATCH v3 0/9] " Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-09 5:48 ` [PATCH v4 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
` (9 more replies)
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
11 siblings, 10 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Hi,
writing alternates into the object database currently happens via
`odb_source_write_alternate()`. But while that creates the ability to
create alternates at arbitrary points of a source's lifetime, we don't
use that functionality in the first place. Instead, we only ever write
alternates when creating a new repository.
This design is suboptimal due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
overblown as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so alternate
implementations may not even support them.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
This patch series thus refactors how we handle alternates so that we
don't write them ad-hoc anymore. Instead, the series introduces a new
option for `odb_source_create_on_disk()` that makes it handle those
alternates at creation time.
This is part of the bigger goal of moving handling of alternates into
the "files" backend.
This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
`alternates_db` field, 2026-08-17) merged into it.
Changes in v4:
- Add documentation for the different functions that play a role in
creating repositories.
- Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im
Changes in v3:
- Refactor `init_db()` to not create the reference and object database
at all anymore. Instead, it's now called `create_repository()` and
it is responsible for creating the initial repository skeleton,
only. This allows us to get rid of the flags and overall makes the
logic more straight-forward by moving the command-specific logic
into the respective commands.
- A couple of typo fixes.
- Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
- Use a lockfile to write "info/alternates" during creation time.
- Remove useless "strvec.h" include by reordering declarations a bit.
- Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
setup: split up concerns of `init_db()`
builtin/clone: defer setup of the object database
builtin/clone: move around `setup_reference()`
builtin/clone: refactor handling of "--reference{,-if-able}"
builtin/clone: move setup of alternates for shared local clones
builtin/clone: move setup of alternates for non-shared local clones
odb/source: support writing alternates when creating the database
builtin/clone: write alternates via `odb_create_on_disk()`
odb/source: remove the ability to write alternates
builtin/clone.c | 111 +++++++++++++++++++++++---------------
builtin/init-db.c | 32 ++++++++---
odb.c | 9 ----
odb.h | 7 ---
odb/source-files.c | 130 ++++++++++++++++++++++++++-------------------
odb/source-inmemory.c | 7 ---
odb/source-loose.c | 7 ---
odb/source-packed.c | 7 ---
odb/source.h | 43 +++++----------
setup.c | 61 ++++++++-------------
setup.h | 47 +++++++++++-----
t/t5604-clone-reference.sh | 25 +++++++++
12 files changed, 264 insertions(+), 222 deletions(-)
Range-diff versus v3:
1: b0e8d52235 ! 1: e6a17864eb setup: split up concerns of `init_db()`
@@ setup.h: int apply_repository_format(struct repository *repo,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
++/*
++ * Create the repository by creating the necessary directory structures,
++ * setting up the configuration and configuring the repository's format. If
++ * `template_dir` is set, copy over templates from that directory. Furthermore,
++ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
++ * reinitialize a preexisting repository. In that case, the pointer will be set
++ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
++ *
++ * Note that this function does not create the reference and object databases.
++ */
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
@@ setup.h: int apply_repository_format(struct repository *repo,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok);
++
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
++
++/*
++ * Create the reference database for the repository. The repository and its ref
++ * storage format must have already been configured properly before calling
++ * this function. When set, `initial_branch` overrides the default branch that
++ * HEAD will point to.
++ */
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
-+void create_object_database(struct repository *repo);
++/*
++ * Create the object database for the repository. The repository must have
++ * already been configured properly before calling this function.
++ */
++void create_object_database(struct repository *repo);
++
/*
* NOTE NOTE NOTE!!
+ *
2: 4fc3a45d2d = 2: 45ae2231ad builtin/clone: defer setup of the object database
3: fd875b60b9 = 3: 92f00f1905 builtin/clone: move around `setup_reference()`
4: 1ca710255b = 4: b6860d41fb builtin/clone: refactor handling of "--reference{,-if-able}"
5: 08a3a7a682 = 5: 070558b0b8 builtin/clone: move setup of alternates for shared local clones
6: afe84c063b = 6: 8e767a103a builtin/clone: move setup of alternates for non-shared local clones
7: 8f0d3b13d5 = 7: 39ee1b828d odb/source: support writing alternates when creating the database
8: 75d1e1e34c ! 8: 531152ee93 builtin/clone: write alternates via `odb_create_on_disk()`
@@ setup.c: static int create_default_files(struct repository *repo,
* Create the "objects" directory in the common directory. This is done
## setup.h ##
-@@ setup.h: void initialize_repository_version(struct repository *repo,
- enum ref_storage_format ref_storage_format,
- int reinit);
- void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+@@ setup.h: void create_reference_database(struct repository *repo, const char *initial_bran
+
+ /*
+ * Create the object database for the repository. The repository must have
+- * already been configured properly before calling this function.
++ * already been configured properly before calling this function. When set,
++ * `alternates` is the list of alternates that should be written into the
++ * object database.
+ */
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
9: a674c3af3c = 9: 25bd93544e odb/source: remove the ability to write alternates
---
base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7
change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0
^ permalink raw reply [flat|nested] 84+ messages in thread* [PATCH v4 1/9] setup: split up concerns of `init_db()`
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 9:24 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
` (8 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
The function `init_db()` is responsible for creating the on-disk
directory structure required for a Git repository. It is used by both
git-init(1) and git-clone(1), and because their expected behaviour is
different we support a couple of flags:
- The `QUIET` flag controls whether the command is quiet or not. For
git-init(1) this is user-controllable, whereas for git-clone(1)
we're always quiet.
- The `EXIST_OK` flag controls whether a preexisting repository is
okay or not. For git-init(1) it is, for git-clone(1) it's not.
- The `SKIP_REFDB` flag controls whether the reference database should
already be created or not. For git-init(1) we do, but for
git-clone(1) we don't because it does not yet know about the default
branch and about the remote object hash.
Furthermore, we're about to add another divergence in behaviour, where
we have to also skip creation of the object database in git-clone(1).
This is becoming quite cumbersome though.
Instead of introducing another flag, start to split up concerns of the
function so that we never create the reference or object database. This
becomes the responsibility of the caller, which is thus free to defer
their creation to a later point in time. This lets us get rid of most of
the divergent behaviour:
- We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
anymore.
- We don't need the `QUIET` flag anymore, as nothing prints output
except for the final status message that tells the user that the
repository has been (re)initialized. But as this message is specific
to git-init(1), we can easily move it there.
The only piece of information we still have to convey is whether or not
reinitialization of a preexisting repository is okay. This is handled by
a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
to reinitialize the repository. Furthermore, the pointer will be written
to to indicate whether the repository was reinitialized or not, which we
need in git-init(1) to print the correct initialization message.
With these refactorings, `init_db()` is named quite misleadingly though,
as we don't create any of the reference or object databases anymore.
Rename it to `create_repository()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 9 +++++----
builtin/init-db.c | 32 ++++++++++++++++++++++++--------
setup.c | 54 +++++++++++++++++-------------------------------------
setup.h | 45 +++++++++++++++++++++++++++++++++------------
4 files changed, 79 insertions(+), 61 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
* repository, and reference backends may persist that information into
* their on-disk data structures.
*/
- init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
- GIT_HASH_UNKNOWN, ref_storage_format, NULL,
- do_not_override_repo_unix_permissions,
- INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+ do_not_override_repo_unix_permissions, NULL);
+ create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
repo_set_hash_algo(the_repository, hash_algo);
create_reference_database(the_repository, NULL, 1);
+ startup_info->have_repository = 1;
/*
* Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
char *work_tree = NULL;
const char *template_dir = NULL;
char *template_dir_to_free = NULL;
- unsigned int flags = 0;
+ int quiet = 0;
int bare = startup_info->force_bare_repository ? 1 : -1;
const char *object_format = NULL;
const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
.callback = shared_callback
},
- OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+ OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
N_("specify the reference format to use")),
OPT_END()
};
- int ret;
+ int reinit;
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
die(_("--separate-git-dir incompatible with bare repository"));
}
- flags |= INIT_DB_EXIST_OK;
- ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
- template_dir, hash_algo, ref_storage_format, initial_branch,
- init_shared_repository, flags);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ template_dir, hash_algo, ref_storage_format,
+ init_shared_repository, &reinit);
+ create_reference_database(the_repository, initial_branch, quiet);
+ create_object_database(the_repository);
+
+ if (!quiet) {
+ int len = strlen(git_dir);
+
+ if (reinit)
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Reinitialized existing shared Git repository in %s%s\n")
+ : _("Reinitialized existing Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ else
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Initialized empty shared Git repository in %s%s\n")
+ : _("Initialized empty Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ }
free(template_dir_to_free);
free(real_git_dir_to_free);
free(work_tree);
free(git_dir);
- return ret;
+ return 0;
}
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
{
/*
* Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
}
}
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch,
- int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok)
{
- int reinit;
- int exist_ok = flags & INIT_DB_EXIST_OK;
+ int reinit_ignored;
char *original_git_dir = real_pathdup(git_dir, 1);
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
if (real_git_dir) {
struct stat st;
- if (!exist_ok && !stat(git_dir, &st))
+ if (!reinit_ok && !stat(git_dir, &st))
die(_("%s already exists"), git_dir);
- if (!exist_ok && !stat(real_git_dir, &st))
+ if (!reinit_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
safe_create_dir(repo, git_dir, 0);
- reinit = create_default_files(repo, template_dir, original_git_dir,
- &repo_fmt, init_shared_repository);
+ if (!reinit_ok)
+ reinit_ok = &reinit_ignored;
+ *reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+ &repo_fmt, init_shared_repository);
if (repo_settings_get_shared_repository(repo)) {
char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
repo_config_set(repo, "receive.denyNonFastforwards", "true");
}
- if (!(flags & INIT_DB_SKIP_REFDB))
- create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- create_object_database(repo);
-
- startup_info->have_repository = 1;
-
- if (!(flags & INIT_DB_QUIET)) {
- int len = strlen(git_dir);
-
- if (reinit)
- printf(repo_settings_get_shared_repository(repo)
- ? _("Reinitialized existing shared Git repository in %s%s\n")
- : _("Reinitialized existing Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- else
- printf(repo_settings_get_shared_repository(repo)
- ? _("Initialized empty shared Git repository in %s%s\n")
- : _("Initialized empty Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- }
-
clear_repository_format(&repo_fmt);
strbuf_release(&err);
free(original_git_dir);
- return 0;
}
diff --git a/setup.h b/setup.h
index 763fd384e8..f1c1ed65fb 100644
--- a/setup.h
+++ b/setup.h
@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,
const char *get_template_dir(const char *option_template);
-#define INIT_DB_QUIET (1 << 0)
-#define INIT_DB_EXIST_OK (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash_algo,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
+/*
+ * Create the repository by creating the necessary directory structures,
+ * setting up the configuration and configuring the repository's format. If
+ * `template_dir` is set, copy over templates from that directory. Furthermore,
+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
+ * reinitialize a preexisting repository. In that case, the pointer will be set
+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
+ *
+ * Note that this function does not create the reference and object databases.
+ */
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash_algo,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok);
+
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
+
+/*
+ * Create the reference database for the repository. The repository and its ref
+ * storage format must have already been configured properly before calling
+ * this function. When set, `initial_branch` overrides the default branch that
+ * HEAD will point to.
+ */
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+/*
+ * Create the object database for the repository. The repository must have
+ * already been configured properly before calling this function.
+ */
+void create_object_database(struct repository *repo);
+
/*
* NOTE NOTE NOTE!!
*
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 1/9] setup: split up concerns of `init_db()`
2026-09-09 5:48 ` [PATCH v4 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
@ 2026-09-10 9:24 ` Karthik Nayak
0 siblings, 0 replies; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 9:24 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 4305 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> The function `init_db()` is responsible for creating the on-disk
> directory structure required for a Git repository. It is used by both
> git-init(1) and git-clone(1), and because their expected behaviour is
> different we support a couple of flags:
>
> - The `QUIET` flag controls whether the command is quiet or not. For
> git-init(1) this is user-controllable, whereas for git-clone(1)
> we're always quiet.
>
> - The `EXIST_OK` flag controls whether a preexisting repository is
> okay or not. For git-init(1) it is, for git-clone(1) it's not.
>
> - The `SKIP_REFDB` flag controls whether the reference database should
> already be created or not. For git-init(1) we do, but for
> git-clone(1) we don't because it does not yet know about the default
> branch and about the remote object hash.
>
> Furthermore, we're about to add another divergence in behaviour, where
> we have to also skip creation of the object database in git-clone(1).
> This is becoming quite cumbersome though.
>
> Instead of introducing another flag, start to split up concerns of the
> function so that we never create the reference or object database. This
> becomes the responsibility of the caller, which is thus free to defer
> their creation to a later point in time. This lets us get rid of most of
> the divergent behaviour:
>
> - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
> anymore.
>
> - We don't need the `QUIET` flag anymore, as nothing prints output
> except for the final status message that tells the user that the
> repository has been (re)initialized. But as this message is specific
> to git-init(1), we can easily move it there.
>
> The only piece of information we still have to convey is whether or not
> reinitialization of a preexisting repository is okay. This is handled by
> a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
> to reinitialize the repository. Furthermore, the pointer will be written
> to to indicate whether the repository was reinitialized or not, which we
> need in git-init(1) to print the correct initialization message.
>
> With these refactorings, `init_db()` is named quite misleadingly though,
> as we don't create any of the reference or object databases anymore.
> Rename it to `create_repository()`.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 9 +++++----
> builtin/init-db.c | 32 ++++++++++++++++++++++++--------
> setup.c | 54 +++++++++++++++++-------------------------------------
> setup.h | 45 +++++++++++++++++++++++++++++++++------------
> 4 files changed, 79 insertions(+), 61 deletions(-)
>
[snip]
> @@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
>
> safe_create_dir(repo, git_dir, 0);
>
> - reinit = create_default_files(repo, template_dir, original_git_dir,
> - &repo_fmt, init_shared_repository);
> + if (!reinit_ok)
> + reinit_ok = &reinit_ignored;
> + *reinit_ok = create_default_files(repo, template_dir, original_git_dir,
> + &repo_fmt, init_shared_repository);
>
> if (repo_settings_get_shared_repository(repo)) {
> char buf[10];
> @@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
> repo_config_set(repo, "receive.denyNonFastforwards", "true");
> }
>
> - if (!(flags & INIT_DB_SKIP_REFDB))
> - create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
> - create_object_database(repo);
> -
> - startup_info->have_repository = 1;
> -
> - if (!(flags & INIT_DB_QUIET)) {
> - int len = strlen(git_dir);
> -
> - if (reinit)
> - printf(repo_settings_get_shared_repository(repo)
> - ? _("Reinitialized existing shared Git repository in %s%s\n")
> - : _("Reinitialized existing Git repository in %s%s\n"),
> - git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> - else
> - printf(repo_settings_get_shared_repository(repo)
> - ? _("Initialized empty shared Git repository in %s%s\n")
> - : _("Initialized empty Git repository in %s%s\n"),
> - git_dir, len && git_dir[len-1] != '/' ? "/" : "");
> - }
> -
I was wondering if the order of initialization changes with the move,
but it stays the same. So all the changes look good.
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 2/9] builtin/clone: defer setup of the object database
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
2026-09-09 5:48 ` [PATCH v4 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 9:28 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
` (7 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When cloning a repository we defer initialization of the reference
database. This is because we don't yet know all details required for us
to initialize the refdb in the first place. Most importantly, what we
are missing is information about the object hash.
We don't do the same thing for the object database yet, but here we
essentially have the same problem. While the "files" database does not
need any information about the object format at creation time, alternate
backends are likely to require that information so that they can
properly set up their data structures.
Besides this forward-looking future proofing though, we also have a
second use case for deferring initialization of the object database,
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
the user has passed "--refernce{,-if-able}", then we will end up writing
alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
already have access to the user-provided URI, that URI can be very well
rewritten via "url.<base>.insteadOf". We can of course read the global-
and system-level configuration to resolve it. But we explicitly resolve
the URI a second time after we have initialized the repository because
it can happen that we copy a ".git/config" over from our templates, and
that file may cause us to rewrite the path.
In a subsequent commit though we'll start to write alternates as part of
the repository initialization, so we'll need to have the URI properly
resolved before we can initialize the object database. This is ugly, but
as mentioned above it makes sense for us to defer its initialization
anyway so that we also know about the object hash already.
Defer creation of the object database until after we have resolved the
URI.
Note that this also requires us to defer the call to `setup_reference()`
until after we have created the object database. While you might think
that this function has something to do with references ("refs/*"), it is
in fact responsible for setting up alternates. Consequently, we can only
call it after we have created the object database already.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
create_repository(the_repository, git_dir, real_git_dir, work_tree,
option_template, GIT_HASH_UNKNOWN, ref_storage_format,
do_not_override_repo_unix_permissions, NULL);
- create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
strbuf_reset(&key);
}
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
-
remote = remote_get_early(remote_name);
if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
+ create_object_database(the_repository);
+ if (option_required_reference.nr || option_optional_reference.nr)
+ setup_reference();
+
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 2/9] builtin/clone: defer setup of the object database
2026-09-09 5:48 ` [PATCH v4 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-10 9:28 ` Karthik Nayak
0 siblings, 0 replies; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 9:28 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 4052 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> When cloning a repository we defer initialization of the reference
> database. This is because we don't yet know all details required for us
> to initialize the refdb in the first place. Most importantly, what we
> are missing is information about the object hash.
>
> We don't do the same thing for the object database yet, but here we
> essentially have the same problem. While the "files" database does not
> need any information about the object format at creation time, alternate
> backends are likely to require that information so that they can
> properly set up their data structures.
>
> Besides this forward-looking future proofing though, we also have a
> second use case for deferring initialization of the object database,
> namely alternates. When initializing the object database we do not yet
> know whether we'll need alternates or not because this depends on the
> repository we're about to clone from. If it is a local repository and
> the user has passed "--refernce{,-if-able}", then we will end up writing
> alternates into the object database.
>
Nit: s/refernce/reference
> The ugly part though is that we cannot determine where the repository is
> getting cloned from before it has been initialized. While we of course
> already have access to the user-provided URI, that URI can be very well
> rewritten via "url.<base>.insteadOf". We can of course read the global-
> and system-level configuration to resolve it. But we explicitly resolve
> the URI a second time after we have initialized the repository because
> it can happen that we copy a ".git/config" over from our templates, and
> that file may cause us to rewrite the path.
>
> In a subsequent commit though we'll start to write alternates as part of
> the repository initialization, so we'll need to have the URI properly
> resolved before we can initialize the object database. This is ugly, but
> as mentioned above it makes sense for us to defer its initialization
> anyway so that we also know about the object hash already.
>
> Defer creation of the object database until after we have resolved the
> URI.
>
> Note that this also requires us to defer the call to `setup_reference()`
> until after we have created the object database. While you might think
> that this function has something to do with references ("refs/*"), it is
> in fact responsible for setting up alternates. Consequently, we can only
> call it after we have created the object database already.
>
Haha. I like this last para, you kinda explained the thought I was
getting as I was getting it :)
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 8 ++++----
> 1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 904d2d859f..bdcbd7aa1b 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
> create_repository(the_repository, git_dir, real_git_dir, work_tree,
> option_template, GIT_HASH_UNKNOWN, ref_storage_format,
> do_not_override_repo_unix_permissions, NULL);
> - create_object_database(the_repository);
>
> if (real_git_dir) {
> free((char *)git_dir);
> @@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
> strbuf_reset(&key);
> }
>
> - if (option_required_reference.nr || option_optional_reference.nr)
> - setup_reference();
> -
So this is the alternate setup, which we move down to after the
`create_object_database()`. Looks good.
> remote = remote_get_early(remote_name);
>
> if (!option_rev)
> @@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
> if (option_local > 0 && !is_local)
> warning(_("--local is ignored"));
>
> + create_object_database(the_repository);
> + if (option_required_reference.nr || option_optional_reference.nr)
> + setup_reference();
> +
> transport = transport_get(remote, path ? path : remote->url.v[0]);
> transport_set_verbosity(transport, option_verbosity, option_progress);
> transport->family = family;
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 3/9] builtin/clone: move around `setup_reference()`
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
2026-09-09 5:48 ` [PATCH v4 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
2026-09-09 5:48 ` [PATCH v4 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 9:29 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
` (6 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
In a subsequent commit, `setup_reference()` will start to call
`copy_alternates()`. Prepare for this by moving the function further
down so that we can avoid adding a declaration.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
return 0;
}
-static void setup_reference(void)
-{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
-}
-
static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
+static void setup_reference(void)
+{
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
+}
+
static void mkdir_if_missing(const char *pathname, mode_t mode)
{
struct stat st;
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (2 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 9:37 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
` (5 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
to set up alternates for the newly created repository. This allows it to
reuse objects from the source repository so that in the best case we
don't have to clone all objects over.
Those options are handled by the confusingly named `setup_reference()`
function -- without the above context, one might rightfully believe that
it was about refs, not about alternates. The function itself is rather
simple: we loop through all provided alternate paths and then, if such
an alternate is valid, we write it to the object database.
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
the alternates into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..8786a49332 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct add_one_alternate_data {
+ struct strvec *alternates;
+ int required;
+};
+
+static int add_one_alternate(struct string_list_item *item, void *cb_data)
{
+ struct add_one_alternate_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
if (!ref_git) {
- if (*required)
+ if (data->required)
die("%s", err.buf);
else
fprintf(stderr,
_("info: Could not add alternate for '%s': %s\n"),
item->string, err.buf);
} else {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%s/objects", ref_git);
- odb_add_to_alternates_file(the_repository->objects, sb.buf);
- strbuf_release(&sb);
+ strvec_pushf(data->alternates, "%s/objects", ref_git);
}
strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
+ struct add_one_alternate_data data = {
+ .alternates = alternates,
+ .required = 1,
+ };
+
+ for_each_string_list(&option_required_reference,
+ add_one_alternate, &data);
+ data.required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_alternate, &data);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
};
+ struct strvec alternates = STRVEC_INIT;
const char * const builtin_clone_usage[] = {
N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
+ collect_alternates(&alternates);
+
+ for (size_t i = 0; i < alternates.nr; i++)
+ odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);
+ strvec_clear(&alternates);
free(remote_name);
strbuf_release(&reflog_msg);
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-09-09 5:48 ` [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-09-10 9:37 ` Karthik Nayak
2026-09-10 14:26 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 9:37 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 5126 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
> to set up alternates for the newly created repository. This allows it to
> reuse objects from the source repository so that in the best case we
> don't have to clone all objects over.
>
> Those options are handled by the confusingly named `setup_reference()`
> function -- without the above context, one might rightfully believe that
> it was about refs, not about alternates. The function itself is rather
> simple: we loop through all provided alternate paths and then, if such
> an alternate is valid, we write it to the object database.
>
> In subsequent commits we're about to consolidate the complete setup of
> alternates into this function, and furthermore we'll refactor the setup
> of the object database to handle doing this for us instead of writing
> the alternates into it one by one.
>
> Prepare for this refactoring by collecting the alternates into a strvec.
> Rename the function to `collect_alternates()` to clarify its scope.
>
Yay!
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
> 1 file changed, 28 insertions(+), 16 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index ac5843d7b9..8786a49332 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
> return canon;
> }
>
> -static int add_one_reference(struct string_list_item *item, void *cb_data)
> +struct add_one_alternate_data {
So this is `add_one_alternate()`'s data, was a bit confusing cause I
first read that this was information regarding a single alternate, but
then it also has a field called `alternates`.
> + struct strvec *alternates;
> + int required;
Question: here and other places, I see some of the boolean-ish fields
being declared as `int`. It doesn't matter, but is there a reason?
> +};
> +
> +static int add_one_alternate(struct string_list_item *item, void *cb_data)
> {
> + struct add_one_alternate_data *data = cb_data;
> struct strbuf err = STRBUF_INIT;
> - int *required = cb_data;
> char *ref_git = compute_alternate_path(item->string, &err);
>
> if (!ref_git) {
> - if (*required)
> + if (data->required)
> die("%s", err.buf);
> else
> fprintf(stderr,
> _("info: Could not add alternate for '%s': %s\n"),
> item->string, err.buf);
> } else {
> - struct strbuf sb = STRBUF_INIT;
> - strbuf_addf(&sb, "%s/objects", ref_git);
> - odb_add_to_alternates_file(the_repository->objects, sb.buf);
> - strbuf_release(&sb);
> + strvec_pushf(data->alternates, "%s/objects", ref_git);
> }
>
> strbuf_release(&err);
> @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> fclose(in);
> }
>
> -static void setup_reference(void)
> +static void collect_alternates(struct strvec *alternates)
> {
> - int required = 1;
> - for_each_string_list(&option_required_reference,
> - add_one_reference, &required);
> - required = 0;
> - for_each_string_list(&option_optional_reference,
> - add_one_reference, &required);
> + if (option_required_reference.nr || option_optional_reference.nr) {
> + struct add_one_alternate_data data = {
> + .alternates = alternates,
> + .required = 1,
> + };
> +
> + for_each_string_list(&option_required_reference,
> + add_one_alternate, &data);
> + data.required = 0;
> + for_each_string_list(&option_optional_reference,
> + add_one_alternate, &data);
> + }
Nit: might be nicer to read
if (!option_required_reference.nr && !option_optional_reference.nr)
return;
The rest of it doesn't have to be in the `if` block.
> }
>
> static void mkdir_if_missing(const char *pathname, mode_t mode)
> @@ -999,6 +1007,7 @@ int cmd_clone(int argc,
> N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
> OPT_END()
> };
> + struct strvec alternates = STRVEC_INIT;
>
> const char * const builtin_clone_usage[] = {
> N_("git clone [<options>] [--] <repo> [<dir>]"),
> @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
> warning(_("--local is ignored"));
>
> create_object_database(the_repository);
> - if (option_required_reference.nr || option_optional_reference.nr)
> - setup_reference();
> + collect_alternates(&alternates);
> +
> + for (size_t i = 0; i < alternates.nr; i++)
> + odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
>
So now adding the alternates is moved out of `collect_alternates()`.
Nit: might be nice to mention this in the commit message.
> transport = transport_get(remote, path ? path : remote->url.v[0]);
> transport_set_verbosity(transport, option_verbosity, option_progress);
> @@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
> string_list_clear(&option_not, 0);
> string_list_clear(&option_config, 0);
> string_list_clear(&server_options, 0);
> + strvec_clear(&alternates);
>
> free(remote_name);
> strbuf_release(&reflog_msg);
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-09-10 9:37 ` Karthik Nayak
@ 2026-09-10 14:26 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 14:26 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Toon Claes, Junio C Hamano, Justin Tobler
On Thu, Sep 10, 2026 at 02:37:31AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/builtin/clone.c b/builtin/clone.c
> > index ac5843d7b9..8786a49332 100644
> > --- a/builtin/clone.c
> > +++ b/builtin/clone.c
> > @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
> > return canon;
> > }
> >
> > -static int add_one_reference(struct string_list_item *item, void *cb_data)
> > +struct add_one_alternate_data {
>
> So this is `add_one_alternate()`'s data, was a bit confusing cause I
> first read that this was information regarding a single alternate, but
> then it also has a field called `alternates`.
We can also rename this to `collect_one_alternate()` and the structure
`collect_alternates_data`.
> > + struct strvec *alternates;
> > + int required;
>
> Question: here and other places, I see some of the boolean-ish fields
> being declared as `int`. It doesn't matter, but is there a reason?
No, there isn't. I'm probably just still getting used to new fancy
features like that.
> > @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> > fclose(in);
> > }
> >
> > -static void setup_reference(void)
> > +static void collect_alternates(struct strvec *alternates)
> > {
> > - int required = 1;
> > - for_each_string_list(&option_required_reference,
> > - add_one_reference, &required);
> > - required = 0;
> > - for_each_string_list(&option_optional_reference,
> > - add_one_reference, &required);
> > + if (option_required_reference.nr || option_optional_reference.nr) {
> > + struct add_one_alternate_data data = {
> > + .alternates = alternates,
> > + .required = 1,
> > + };
> > +
> > + for_each_string_list(&option_required_reference,
> > + add_one_alternate, &data);
> > + data.required = 0;
> > + for_each_string_list(&option_optional_reference,
> > + add_one_alternate, &data);
> > + }
>
> Nit: might be nicer to read
>
> if (!option_required_reference.nr && !option_optional_reference.nr)
> return;
>
> The rest of it doesn't have to be in the `if` block.
We'll extend this over the next couple patches, so I'll leave this
as-is.
> > @@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
> > warning(_("--local is ignored"));
> >
> > create_object_database(the_repository);
> > - if (option_required_reference.nr || option_optional_reference.nr)
> > - setup_reference();
> > + collect_alternates(&alternates);
> > +
> > + for (size_t i = 0; i < alternates.nr; i++)
> > + odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
> >
>
> So now adding the alternates is moved out of `collect_alternates()`.
> Nit: might be nice to mention this in the commit message.
Will do.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (3 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 10:52 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
` (4 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When cloning a local repository with "--shared" we add that repository
to the new repository's alternates. This is done in `clone_local()`,
which is responsible for performing local clones.
Move the logic into `collect_alternates()` to unify our setup of
alternates. Furthermore, this will allow us to set up alternates right
at creation time of the object database.
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
commit.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 8786a49332..011fc867c8 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+ const char *src_repo, bool is_local)
{
if (option_required_reference.nr || option_optional_reference.nr) {
struct add_one_alternate_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
add_one_alternate, &data);
}
+
+ if (is_local) {
+ struct strbuf commondir = STRBUF_INIT;
+
+ get_common_dir(&commondir, src_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+ strbuf_release(&commondir);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
static void clone_local(const char *src_repo, const char *dest_repo)
{
- if (option_shared) {
- struct strbuf alt = STRBUF_INIT;
- get_common_dir(&alt, src_repo);
- strbuf_addstr(&alt, "/objects");
- odb_add_to_alternates_file(the_repository->objects, alt.buf);
- strbuf_release(&alt);
- } else {
+ if (!option_shared) {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- collect_alternates(&alternates);
+ collect_alternates(&alternates, path, is_local);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-09 5:48 ` [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-09-10 10:52 ` Karthik Nayak
2026-09-10 10:54 ` Karthik Nayak
0 siblings, 1 reply; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 10:52 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 2784 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> When cloning a local repository with "--shared" we add that repository
> to the new repository's alternates. This is done in `clone_local()`,
> which is responsible for performing local clones.
>
> Move the logic into `collect_alternates()` to unify our setup of
> alternates. Furthermore, this will allow us to set up alternates right
> at creation time of the object database.
>
> Note that the logic for cloning a local repository with "--no-shared" is
> not yet part of `collect_alternates()`. This will be handled in the next
> commit.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> builtin/clone.c | 23 ++++++++++++++---------
> 1 file changed, 14 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/clone.c b/builtin/clone.c
> index 8786a49332..011fc867c8 100644
> --- a/builtin/clone.c
> +++ b/builtin/clone.c
> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
> fclose(in);
> }
>
> -static void collect_alternates(struct strvec *alternates)
> +static void collect_alternates(struct strvec *alternates,
> + const char *src_repo, bool is_local)
> {
> if (option_required_reference.nr || option_optional_reference.nr) {
> struct add_one_alternate_data data = {
> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
> for_each_string_list(&option_optional_reference,
> add_one_alternate, &data);
> }
> +
> + if (is_local) {
>
Shouldn't we also check for `option_shared` here?
> + struct strbuf commondir = STRBUF_INIT;
> +
> + get_common_dir(&commondir, src_repo);
> + if (option_shared)
> + strvec_pushf(alternates, "%s/objects", commondir.buf);
> +
> + strbuf_release(&commondir);
> + }
> }
>
> static void mkdir_if_missing(const char *pathname, mode_t mode)
> @@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
>
> static void clone_local(const char *src_repo, const char *dest_repo)
> {
> - if (option_shared) {
> - struct strbuf alt = STRBUF_INIT;
> - get_common_dir(&alt, src_repo);
> - strbuf_addstr(&alt, "/objects");
> - odb_add_to_alternates_file(the_repository->objects, alt.buf);
> - strbuf_release(&alt);
> - } else {
> + if (!option_shared) {
> struct strbuf src = STRBUF_INIT;
> struct strbuf dest = STRBUF_INIT;
> get_common_dir(&src, src_repo);
> @@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
> warning(_("--local is ignored"));
>
> create_object_database(the_repository);
> - collect_alternates(&alternates);
> + collect_alternates(&alternates, path, is_local);
>
> for (size_t i = 0; i < alternates.nr; i++)
> odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
>
> --
> 2.55.0.1074.ge7621b4bad.dirty
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-10 10:52 ` Karthik Nayak
@ 2026-09-10 10:54 ` Karthik Nayak
2026-09-10 14:26 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 10:54 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 1952 bytes --]
Karthik Nayak <karthik.188@gmail.com> writes:
> Patrick Steinhardt <ps@pks.im> writes:
>
>> When cloning a local repository with "--shared" we add that repository
>> to the new repository's alternates. This is done in `clone_local()`,
>> which is responsible for performing local clones.
>>
>> Move the logic into `collect_alternates()` to unify our setup of
>> alternates. Furthermore, this will allow us to set up alternates right
>> at creation time of the object database.
>>
>> Note that the logic for cloning a local repository with "--no-shared" is
>> not yet part of `collect_alternates()`. This will be handled in the next
>> commit.
>>
>> Signed-off-by: Patrick Steinhardt <ps@pks.im>
>> ---
>> builtin/clone.c | 23 ++++++++++++++---------
>> 1 file changed, 14 insertions(+), 9 deletions(-)
>>
>> diff --git a/builtin/clone.c b/builtin/clone.c
>> index 8786a49332..011fc867c8 100644
>> --- a/builtin/clone.c
>> +++ b/builtin/clone.c
>> @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
>> fclose(in);
>> }
>>
>> -static void collect_alternates(struct strvec *alternates)
>> +static void collect_alternates(struct strvec *alternates,
>> + const char *src_repo, bool is_local)
>> {
>> if (option_required_reference.nr || option_optional_reference.nr) {
>> struct add_one_alternate_data data = {
>> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
>> for_each_string_list(&option_optional_reference,
>> add_one_alternate, &data);
>> }
>> +
>> + if (is_local) {
>>
>
> Shouldn't we also check for `option_shared` here?
>
>> + struct strbuf commondir = STRBUF_INIT;
>> +
>> + get_common_dir(&commondir, src_repo);
>> + if (option_shared)
>> + strvec_pushf(alternates, "%s/objects", commondir.buf);
>> +
So we do it here, but then commondir is initiated but unused otherwise.
>> + strbuf_release(&commondir);
>> + }
>> }
>>
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-10 10:54 ` Karthik Nayak
@ 2026-09-10 14:26 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 14:26 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Toon Claes, Junio C Hamano, Justin Tobler
On Thu, Sep 10, 2026 at 05:54:50AM -0500, Karthik Nayak wrote:
> Karthik Nayak <karthik.188@gmail.com> writes:
> > Patrick Steinhardt <ps@pks.im> writes:
> >> diff --git a/builtin/clone.c b/builtin/clone.c
> >> index 8786a49332..011fc867c8 100644
> >> --- a/builtin/clone.c
> >> +++ b/builtin/clone.c
> >> @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
> >> for_each_string_list(&option_optional_reference,
> >> add_one_alternate, &data);
> >> }
> >> +
> >> + if (is_local) {
> >>
> >
> > Shouldn't we also check for `option_shared` here?
> >
> >> + struct strbuf commondir = STRBUF_INIT;
> >> +
> >> + get_common_dir(&commondir, src_repo);
> >> + if (option_shared)
> >> + strvec_pushf(alternates, "%s/objects", commondir.buf);
> >> +
>
> So we do it here, but then commondir is initiated but unused otherwise.
It is, yes. But this is done to prep for the next step, where the
`commondir` variable will be used regardless of whether or not we use
`option_shared`.
I'll adapt the commit message a bit.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (4 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 11:00 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
` (3 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Similar as in the preceding commit, move the setup of alternates for
local clones with "--no-shared" into `collect_alternates()`. With this
step, the complete setup of alternates is now handled by that function.
Note that besides moving stuff around, it also fixes a bug: previously,
we did not know to resolve the referenced repository's common directory.
Consequently, when referencing a worktree we failed to resolve
alternates. But as `collect_alternates()` already knows to resolve the
commondir for "--local" we can simply reuse this resolved path for our
purpose.
Add two tests, the first one of which exercises this bug to avoid future
regressions. The second test ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 34 +++++++++++++++++++++++-----------
t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 011fc867c8..84c1317867 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
- FILE *in = xfopen(src->buf, "r");
+ FILE *in;
+ struct strbuf path = STRBUF_INIT;
struct strbuf line = STRBUF_INIT;
+ strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+ in = fopen(path.buf, "r");
+ if (!in) {
+ if (errno == ENOENT)
+ goto out;
+ die_errno("could not read alternates file '%s'", path.buf);
+ }
+
while (strbuf_getline(&line, in) != EOF) {
char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
- odb_add_to_alternates_file(the_repository->objects,
- line.buf);
+ strvec_push(alternates, line.buf);
continue;
}
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
if (!normalize_path_copy(abs_path, abs_path))
- odb_add_to_alternates_file(the_repository->objects,
- abs_path);
+ strvec_push(alternates, abs_path);
else
warning("skipping invalid relative alternate: %s/%s",
src_repo, line.buf);
free(abs_path);
}
+
+out:
+ strbuf_release(&path);
strbuf_release(&line);
- fclose(in);
+ if (in)
+ fclose(in);
}
static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
get_common_dir(&commondir, src_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
+ read_alternates(alternates, commondir.buf);
strbuf_release(&commondir);
}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
continue;
}
- /* Files that cannot be copied bit-for-bit... */
- if (!fspathcmp(iter->relative_path, "info/alternates")) {
- copy_alternates(src, src_repo);
+ /* Alternates were already handled earlier. */
+ if (!fspathcmp(iter->relative_path, "info/alternates"))
continue;
- }
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
git clone --no-local --reference graph.git --dissociate orig clone
'
+test_expect_success 'local clone from linked worktree carries over alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
test_done
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared local clones
2026-09-09 5:48 ` [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-09-10 11:00 ` Karthik Nayak
0 siblings, 0 replies; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 11:00 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 1279 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Similar as in the preceding commit, move the setup of alternates for
> local clones with "--no-shared" into `collect_alternates()`. With this
> step, the complete setup of alternates is now handled by that function.
>
> Note that besides moving stuff around, it also fixes a bug: previously,
> we did not know to resolve the referenced repository's common directory.
> Consequently, when referencing a worktree we failed to resolve
> alternates. But as `collect_alternates()` already knows to resolve the
> commondir for "--local" we can simply reuse this resolved path for our
> purpose.
>
> Add two tests, the first one of which exercises this bug to avoid future
> regressions. The second test ensures that we properly handle relative
> alternates for a referenced worktree.
[snip]
> static void collect_alternates(struct strvec *alternates,
> @@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
> get_common_dir(&commondir, src_repo);
> if (option_shared)
> strvec_pushf(alternates, "%s/objects", commondir.buf);
> + else
> + read_alternates(alternates, commondir.buf);
>
Okay so this is why we did what we did in the prev patch. Makes sense.
> strbuf_release(&commondir);
> }
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 7/9] odb/source: support writing alternates when creating the database
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (5 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-10 11:10 ` Karthik Nayak
2026-09-09 5:48 ` [PATCH v4 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
` (2 subsequent siblings)
9 siblings, 1 reply; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
Add the ability to write alternates when creating the object database.
This change allows us to remove the `write_alternates()` callback in a
subsequent patch.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
odb/source.h | 17 +++++++++---
setup.c | 4 ++-
3 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
+#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
+ struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
+ FILE *alternates, *orig;
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
+ repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+ path.buf, LOCK_DIE_ON_ERROR);
+
+ alternates = fdopen_lock_file(&alternates_lock, "w");
+ if (!alternates) {
+ ret = error_errno(_("unable to fdopen alternates lockfile"));
+ goto out;
+ }
+
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
+ orig = fopen(path.buf, "r");
+ if (orig) {
+ while (strbuf_getline(&line, orig) != EOF) {
+ strset_add(&seen, line.buf);
+ fprintf(alternates, "%s\n", line.buf);
+ }
+
+ if (ferror(orig)) {
+ ret = error_errno(_("unable to read alternates file"));
+ fclose(orig);
+ goto out;
+ }
+
+ fclose(orig);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
+ if (!strset_add(&seen, alternate))
+ continue;
+ fprintf(alternates, "%s\n", alternate);
+ }
+
+ if (ferror(alternates)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
+ if (commit_lock_file(&alternates_lock)) {
+ ret = error_errno(_("unable to commit alternates file"));
+ goto out;
+ }
+ }
+
+ /* Reprepare the object database to activate alternates. */
+ odb_reprepare(source->odb);
+
+ ret = 0;
+
+out:
+ rollback_lock_file(&alternates_lock);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
+ strset_clear(&seen);
+ return ret;
}
static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
struct odb_stream;
struct strvec;
+struct odb_create_on_disk_options {
+ /*
+ * Alternates that shall be written into the newly created object
+ * database. Whether or not this option can be handled is specific to
+ * the backend.
+ */
+ const struct strvec *alternates;
+};
+
/*
* The source is the part of the object database that stores the actual
* objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
- int (*create_on_disk)(struct odb_source *source);
+ int (*create_on_disk)(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts);
/*
* This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
if (!source->create_on_disk)
return 0;
- return source->create_on_disk(source);
+ return source->create_on_disk(source, opts);
}
/*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
void create_object_database(struct repository *repo)
{
+ struct odb_create_on_disk_options opts = { 0 };
+
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
- if (odb_source_create_on_disk(repo->objects->sources) < 0)
+ if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
die(_("failed creating object database"));
}
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database
2026-09-09 5:48 ` [PATCH v4 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-09-10 11:10 ` Karthik Nayak
2026-09-11 5:15 ` Patrick Steinhardt
0 siblings, 1 reply; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 11:10 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 4370 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Add the ability to write alternates when creating the object database.
> This change allows us to remove the `write_alternates()` callback in a
> subsequent patch.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
> odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
> odb/source.h | 17 +++++++++---
> setup.c | 4 ++-
> 3 files changed, 91 insertions(+), 6 deletions(-)
>
> diff --git a/odb/source-files.c b/odb/source-files.c
> index b7b3a297bb..8fe65d91f8 100644
> --- a/odb/source-files.c
> +++ b/odb/source-files.c
> @@ -18,6 +18,7 @@
> #include "run-command.h"
> #include "strbuf.h"
> #include "string-list.h"
> +#include "strmap.h"
> #include "strvec.h"
> #include "tree.h"
> #include "write-or-die.h"
> @@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
> odb_source_close(&files->packed->base);
> }
>
> -static int odb_source_files_create_on_disk(struct odb_source *source)
> +static int odb_source_files_create_on_disk(struct odb_source *source,
> + const struct odb_create_on_disk_options *opts)
> {
> + struct lock_file alternates_lock = LOCK_INIT;
> struct strbuf path = STRBUF_INIT;
> + struct strset seen = STRSET_INIT;
> + struct strbuf line = STRBUF_INIT;
> + int ret;
>
> safe_create_dir(source->odb->repo, source->path, 1);
>
> @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
> strbuf_addf(&path, "%s/info", source->path);
> safe_create_dir(source->odb->repo, path.buf, 1);
>
> + if (opts->alternates && opts->alternates->nr) {
> + FILE *alternates, *orig;
> +
So this is similar to what we already do in
`odb_source_files_write_alternate()`.
> + strbuf_reset(&path);
> + strbuf_addf(&path, "%s/info/alternates", source->path);
> +
> + repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
> + path.buf, LOCK_DIE_ON_ERROR);
> +
> + alternates = fdopen_lock_file(&alternates_lock, "w");
> + if (!alternates) {
> + ret = error_errno(_("unable to fdopen alternates lockfile"));
> + goto out;
> + }
> +
> + /*
> + * The alternates file may already exist, e.g. when it has been
> + * seeded from a template directory. Read any preexisting
> + * entries so that we don't end up writing duplicates.
> + */
> + orig = fopen(path.buf, "r");
> + if (orig) {
> + while (strbuf_getline(&line, orig) != EOF) {
> + strset_add(&seen, line.buf);
> + fprintf(alternates, "%s\n", line.buf);
> + }
> +
> + if (ferror(orig)) {
> + ret = error_errno(_("unable to read alternates file"));
> + fclose(orig);
> + goto out;
> + }
Shouldn't this be checked inside the for loop with every `fprintf` call?
> +
> + fclose(orig);
> + } else if (errno != ENOENT) {
> + ret = error_errno(_("unable to read alternates file"));
> + goto out;
> + }
> +
> + for (size_t i = 0; i < opts->alternates->nr; i++) {
> + const char *alternate = opts->alternates->v[i];
> + if (!strset_add(&seen, alternate))
> + continue;
> + fprintf(alternates, "%s\n", alternate);
> + }
> +
> + if (ferror(alternates)) {
> + ret = error_errno(_("unable to write alternates file"));
> + goto out;
> + }
> +
same here.
> + if (commit_lock_file(&alternates_lock)) {
> + ret = error_errno(_("unable to commit alternates file"));
> + goto out;
> + }
> + }
> +
> + /* Reprepare the object database to activate alternates. */
> + odb_reprepare(source->odb);
> +
> + ret = 0;
> +
> +out:
> + rollback_lock_file(&alternates_lock);
> + strbuf_release(&line);
> strbuf_release(&path);
> - return 0;
> + strset_clear(&seen);
> + return ret;
> }
>
> static void odb_source_files_prepare(struct odb_source *source,
> diff --git a/odb/source.h b/odb/source.h
> index ea8675247e..63f1c0c531 100644
> --- a/odb/source.h
> +++ b/odb/source.h
> @@ -36,6 +36,15 @@ struct object_id;
> struct odb_stream;
> struct strvec;
>
> +struct odb_create_on_disk_options {
> + /*
> + * Alternates that shall be written into the newly created object
> + * database. Whether or not this option can be handled is specific to
> + * the backend.
> + */
Would it make sense to formalize errors thrown by backends, so we know
when a backend specifically cannot handle alternates?
> + const struct strvec *alternates;
> +};
> +
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v4 7/9] odb/source: support writing alternates when creating the database
2026-09-10 11:10 ` Karthik Nayak
@ 2026-09-11 5:15 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-11 5:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Toon Claes, Junio C Hamano, Justin Tobler
On Thu, Sep 10, 2026 at 04:10:31AM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> > diff --git a/odb/source-files.c b/odb/source-files.c
> > index b7b3a297bb..8fe65d91f8 100644
> > --- a/odb/source-files.c
> > +++ b/odb/source-files.c
> > @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
[snip]
> > + /*
> > + * The alternates file may already exist, e.g. when it has been
> > + * seeded from a template directory. Read any preexisting
> > + * entries so that we don't end up writing duplicates.
> > + */
> > + orig = fopen(path.buf, "r");
> > + if (orig) {
> > + while (strbuf_getline(&line, orig) != EOF) {
> > + strset_add(&seen, line.buf);
> > + fprintf(alternates, "%s\n", line.buf);
> > + }
> > +
> > + if (ferror(orig)) {
> > + ret = error_errno(_("unable to read alternates file"));
> > + fclose(orig);
> > + goto out;
> > + }
>
> Shouldn't this be checked inside the for loop with every `fprintf` call?
There isn't really any need to, as the error indicator on file streams
is sticky. Sure, it would allow us to potentially abort earlier. But
it's unlikely that this really matters in practice.
> > diff --git a/odb/source.h b/odb/source.h
> > index ea8675247e..63f1c0c531 100644
> > --- a/odb/source.h
> > +++ b/odb/source.h
> > @@ -36,6 +36,15 @@ struct object_id;
> > struct odb_stream;
> > struct strvec;
> >
> > +struct odb_create_on_disk_options {
> > + /*
> > + * Alternates that shall be written into the newly created object
> > + * database. Whether or not this option can be handled is specific to
> > + * the backend.
> > + */
>
> Would it make sense to formalize errors thrown by backends, so we know
> when a backend specifically cannot handle alternates?
Maybe, but there's nothing that'd use it. So I'm a bit hesitant to
introduce that right now without us having a way to verify the logic at
all. We may want to eventually introduce such logic though.
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v4 8/9] builtin/clone: write alternates via `odb_create_on_disk()`
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (6 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-09 5:48 ` [PATCH v4 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
2026-09-09 18:30 ` [PATCH v4 0/9] odb: write alternates at creation time Justin Tobler
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
When creating a repository with alternates we first initialize the
object database and then write alternates to it in a separate step. This
is unfortunate due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
unfortunate as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so having this
callback is suboptimal there.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
We have thus introduced the ability to write alternates right at
creation time in the preceding commits, and we have unified setup of
alternates into a single location. All that's left to do for us now is
to wire up alternates as an option for the database creation.
Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 5 +----
builtin/init-db.c | 2 +-
setup.c | 7 +++++--
setup.h | 6 ++++--
4 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 84c1317867..9e84646845 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
- create_object_database(the_repository);
collect_alternates(&alternates, path, is_local);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+ create_object_database(the_repository, &alternates);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
template_dir, hash_algo, ref_storage_format,
init_shared_repository, &reinit);
create_reference_database(the_repository, initial_branch, quiet);
- create_object_database(the_repository);
+ create_object_database(the_repository, NULL);
if (!quiet) {
int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+ const struct strvec *alternates)
{
- struct odb_create_on_disk_options opts = { 0 };
+ struct odb_create_on_disk_options opts = {
+ .alternates = alternates,
+ };
/*
* Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index f1c1ed65fb..27b2492373 100644
--- a/setup.h
+++ b/setup.h
@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran
/*
* Create the object database for the repository. The repository must have
- * already been configured properly before calling this function.
+ * already been configured properly before calling this function. When set,
+ * `alternates` is the list of alternates that should be written into the
+ * object database.
*/
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
/*
* NOTE NOTE NOTE!!
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v4 9/9] odb/source: remove the ability to write alternates
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (7 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-09-09 5:48 ` Patrick Steinhardt
2026-09-09 18:30 ` [PATCH v4 0/9] odb: write alternates at creation time Justin Tobler
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-09 5:48 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
There are no users of `odb_source_write_alternates()` in our tree
anymore. Remove that function and its supporting infrastructure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb.c | 9 ---------
odb.h | 7 -------
odb/source-files.c | 54 ---------------------------------------------------
odb/source-inmemory.c | 7 -------
odb/source-loose.c | 7 -------
odb/source-packed.c | 7 -------
odb/source.h | 26 -------------------------
7 files changed, 117 deletions(-)
diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
return alternate;
}
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir)
-{
- int ret = odb_source_write_alternate(odb->sources, dir);
- if (ret < 0)
- die(NULL);
- odb_add_alternate_recursively(odb, dir, 0);
-}
-
struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
const char *dir)
{
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
*/
int odb_has_alternates(struct object_database *odb);
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir);
-
/*
* Add the directory to the in-memory list of alternate sources (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
-static int odb_source_files_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- struct lock_file lock = LOCK_INIT;
- char *path = xstrfmt("%s/%s", source->path, "info/alternates");
- FILE *in, *out;
- int found = 0;
- int ret;
-
- repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
- LOCK_DIE_ON_ERROR);
- out = fdopen_lock_file(&lock, "w");
- if (!out) {
- ret = error_errno(_("unable to fdopen alternates lockfile"));
- goto out;
- }
-
- in = fopen(path, "r");
- if (in) {
- struct strbuf line = STRBUF_INIT;
-
- while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(alternate, line.buf)) {
- found = 1;
- break;
- }
- fprintf_or_die(out, "%s\n", line.buf);
- }
-
- strbuf_release(&line);
- fclose(in);
- } else if (errno != ENOENT) {
- ret = error_errno(_("unable to read alternates file"));
- goto out;
- }
-
- if (found) {
- rollback_lock_file(&lock);
- } else {
- fprintf_or_die(out, "%s\n", alternate);
- if (commit_lock_file(&lock)) {
- ret = error_errno(_("unable to move new alternates file into place"));
- goto out;
- }
- }
-
- ret = 0;
-
-out:
- free(path);
- return ret;
-}
-
static int too_many_loose_objects(struct odb_source_files *files, int limit)
{
unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.write_object_stream = odb_source_files_write_object_stream;
files->base.begin_transaction = odb_source_files_begin_transaction;
files->base.read_alternates = odb_source_files_read_alternates;
- files->base.write_alternate = odb_source_files_write_alternate;
files->base.optimize = odb_source_files_optimize;
files->base.optimize_required = odb_source_files_optimize_required;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("in-memory source does not support alternates");
-}
-
static void odb_source_inmemory_close(struct odb_source *source UNUSED)
{
}
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.freshen_object = odb_source_inmemory_freshen_object;
source->base.begin_transaction = odb_source_inmemory_begin_transaction;
source->base.read_alternates = odb_source_inmemory_read_alternates;
- source->base.write_alternate = odb_source_inmemory_write_alternate;
return source;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("loose source does not support alternates");
-}
-
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_object_stream = odb_source_loose_write_object_stream;
loose->base.begin_transaction = odb_source_loose_begin_transaction;
loose->base.read_alternates = odb_source_loose_read_alternates;
- loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("packed backend cannot write alternates");
-}
-
void (*report_garbage)(unsigned seen_bits, const char *path);
static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_object_stream = odb_source_packed_write_object_stream;
packed->base.begin_transaction = odb_source_packed_begin_transaction;
packed->base.read_alternates = odb_source_packed_read_alternates;
- packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
int (*read_alternates)(struct odb_source *source,
struct strvec *out);
- /*
- * This callback is expected to persist the singular alternate passed
- * to it into its list of alternates. Any pre-existing alternates are
- * expected to remain active. Subsequent calls to `read_alternates` are
- * thus expected to yield the pre-existing list of alternates plus the
- * newly added alternate appended to its end.
- *
- * The callback is expected to return 0 on success, a negative error
- * code otherwise.
- */
- int (*write_alternate)(struct odb_source *source,
- const char *alternate);
-
/*
* This callback is expected to optimize the object database source.
* Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
return source->read_alternates(source, out);
}
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- return source->write_alternate(source, alternate);
-}
-
/*
* Create a new transaction that can be used to write objects into a temporary
* staging area. The objects will only be persisted when the transaction is
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v4 0/9] odb: write alternates at creation time
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
` (8 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
@ 2026-09-09 18:30 ` Justin Tobler
9 siblings, 0 replies; 84+ messages in thread
From: Justin Tobler @ 2026-09-09 18:30 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: git, Toon Claes, Junio C Hamano
On 26/09/09 07:48AM, Patrick Steinhardt wrote:
> Changes in v4:
> - Add documentation for the different functions that play a role in
> creating repositories.
Thanks. This version looks good to me.
-Justin
^ permalink raw reply [flat|nested] 84+ messages in thread
* [PATCH v5 0/9] odb: write alternates at creation time
2026-08-25 14:11 [PATCH 0/8] odb: write alternates at creation time Patrick Steinhardt
` (10 preceding siblings ...)
2026-09-09 5:48 ` [PATCH v4 0/9] odb: write alternates at creation time Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
` (9 more replies)
11 siblings, 10 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
Hi,
writing alternates into the object database currently happens via
`odb_source_write_alternate()`. But while that creates the ability to
create alternates at arbitrary points of a source's lifetime, we don't
use that functionality in the first place. Instead, we only ever write
alternates when creating a new repository.
This design is suboptimal due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
overblown as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so alternate
implementations may not even support them.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
This patch series thus refactors how we handle alternates so that we
don't write them ad-hoc anymore. Instead, the series introduces a new
option for `odb_source_create_on_disk()` that makes it handle those
alternates at creation time.
This is part of the bigger goal of moving handling of alternates into
the "files" backend.
This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
`alternates_db` field, 2026-08-17) merged into it.
Changes in v5:
- Rename `add_one_alternate` and `add_one_alternate_data` to
`collect_one_alternate` and `collect_alternates_data` to clarify
their intent a bit.
- Drop extra parameter in `collect_alternates()`.
- Clarify why we compute `commondir` even though it's unused.
- Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
Changes in v4:
- Add documentation for the different functions that play a role in
creating repositories.
- Link to v3: https://patch.msgid.link/20260907-pks-odb-write-alternates-at-creation-time-v3-0-735d0b5b3e00@pks.im
Changes in v3:
- Refactor `init_db()` to not create the reference and object database
at all anymore. Instead, it's now called `create_repository()` and
it is responsible for creating the initial repository skeleton,
only. This allows us to get rid of the flags and overall makes the
logic more straight-forward by moving the command-specific logic
into the respective commands.
- A couple of typo fixes.
- Link to v2: https://patch.msgid.link/20260831-pks-odb-write-alternates-at-creation-time-v2-0-aecd2382ba1c@pks.im
Changes in v2:
- Use a lockfile to write "info/alternates" during creation time.
- Remove useless "strvec.h" include by reordering declarations a bit.
- Link to v1: https://patch.msgid.link/20260825-pks-odb-write-alternates-at-creation-time-v1-0-911513ba95c3@pks.im
Thanks!
Patrick
---
Patrick Steinhardt (9):
setup: split up concerns of `init_db()`
builtin/clone: defer setup of the object database
builtin/clone: move around `setup_reference()`
builtin/clone: refactor handling of "--reference{,-if-able}"
builtin/clone: move setup of alternates for shared local clones
builtin/clone: move setup of alternates for non-shared local clones
odb/source: support writing alternates when creating the database
builtin/clone: write alternates via `odb_create_on_disk()`
odb/source: remove the ability to write alternates
builtin/clone.c | 111 +++++++++++++++++++++++---------------
builtin/init-db.c | 32 ++++++++---
odb.c | 9 ----
odb.h | 7 ---
odb/source-files.c | 130 ++++++++++++++++++++++++++-------------------
odb/source-inmemory.c | 7 ---
odb/source-loose.c | 7 ---
odb/source-packed.c | 7 ---
odb/source.h | 43 +++++----------
setup.c | 61 ++++++++-------------
setup.h | 47 +++++++++++-----
t/t5604-clone-reference.sh | 25 +++++++++
12 files changed, 264 insertions(+), 222 deletions(-)
Range-diff versus v4:
1: 30a1faa807 = 1: e5d789fdfa setup: split up concerns of `init_db()`
2: 7a646ebe92 ! 2: 94a19390b4 builtin/clone: defer setup of the object database
@@ Commit message
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
- the user has passed "--refernce{,-if-able}", then we will end up writing
- alternates into the object database.
+ the user has passed "--reference{,-if-able}", then we will end up
+ writing alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
3: af77d0e9e3 = 3: e9ccb5b1bd builtin/clone: move around `setup_reference()`
4: 2ca937bce1 ! 4: 30c4933fb0 builtin/clone: refactor handling of "--reference{,-if-able}"
@@ Commit message
the alternates into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
- Rename the function to `collect_alternates()` to clarify its scope.
+ Rename the function to `collect_alternates()` to clarify its scope, as
+ it does not set up the references itself anymore.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
@@ builtin/clone.c: static char *get_repo_path(const char *repo, int *is_bundle)
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
-+struct add_one_alternate_data {
++struct collect_alternates_data {
+ struct strvec *alternates;
-+ int required;
++ bool required;
+};
+
-+static int add_one_alternate(struct string_list_item *item, void *cb_data)
++static int collect_one_alternate(struct string_list_item *item, void *cb_data)
{
-+ struct add_one_alternate_data *data = cb_data;
++ struct collect_alternates_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
@@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
-+ struct add_one_alternate_data data = {
++ struct collect_alternates_data data = {
+ .alternates = alternates,
-+ .required = 1,
++ .required = true,
+ };
+
+ for_each_string_list(&option_required_reference,
-+ add_one_alternate, &data);
-+ data.required = 0;
++ collect_one_alternate, &data);
++ data.required = false;
+ for_each_string_list(&option_optional_reference,
-+ add_one_alternate, &data);
++ collect_one_alternate, &data);
+ }
}
5: 4ba60fc24b ! 5: 5141c179e2 builtin/clone: move setup of alternates for shared local clones
@@ Commit message
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
- commit.
+ commit, but means that at this step, we may compute `commondir` without
+ it being used. It will become used in the next step though.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
@@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
-+ const char *src_repo, bool is_local)
++ const char *local_source_repo)
{
if (option_required_reference.nr || option_optional_reference.nr) {
- struct add_one_alternate_data data = {
+ struct collect_alternates_data data = {
@@ builtin/clone.c: static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
- add_one_alternate, &data);
+ collect_one_alternate, &data);
}
+
-+ if (is_local) {
++ if (local_source_repo) {
+ struct strbuf commondir = STRBUF_INIT;
+
-+ get_common_dir(&commondir, src_repo);
++ get_common_dir(&commondir, local_source_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
@@ builtin/clone.c: int cmd_clone(int argc,
create_object_database(the_repository);
- collect_alternates(&alternates);
-+ collect_alternates(&alternates, path, is_local);
++ collect_alternates(&alternates, is_local ? path : NULL);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
6: 4f3c8fb936 ! 6: b0ed987d1c builtin/clone: move setup of alternates for non-shared local clones
@@ Commit message
Signed-off-by: Patrick Steinhardt <ps@pks.im>
## builtin/clone.c ##
-@@ builtin/clone.c: static int add_one_alternate(struct string_list_item *item, void *cb_data)
+@@ builtin/clone.c: static int collect_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
@@ builtin/clone.c: static void copy_alternates(struct strbuf *src, const char *src
static void collect_alternates(struct strvec *alternates,
@@ builtin/clone.c: static void collect_alternates(struct strvec *alternates,
- get_common_dir(&commondir, src_repo);
+ get_common_dir(&commondir, local_source_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
7: 47548ac464 = 7: 4cc70ca1dd odb/source: support writing alternates when creating the database
8: 4e3be623dd ! 8: 2cea0312bd builtin/clone: write alternates via `odb_create_on_disk()`
@@ builtin/clone.c: int cmd_clone(int argc,
warning(_("--local is ignored"));
- create_object_database(the_repository);
- collect_alternates(&alternates, path, is_local);
+ collect_alternates(&alternates, is_local ? path : NULL);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
9: 7207b7e2ed = 9: e1f8f728a6 odb/source: remove the ability to write alternates
---
base-commit: afa255aeb620346d56a2c01fb5ae9163513c56d7
change-id: 20260813-pks-odb-write-alternates-at-creation-time-64010deb94a0
^ permalink raw reply [flat|nested] 84+ messages in thread* [PATCH v5 1/9] setup: split up concerns of `init_db()`
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
` (8 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
The function `init_db()` is responsible for creating the on-disk
directory structure required for a Git repository. It is used by both
git-init(1) and git-clone(1), and because their expected behaviour is
different we support a couple of flags:
- The `QUIET` flag controls whether the command is quiet or not. For
git-init(1) this is user-controllable, whereas for git-clone(1)
we're always quiet.
- The `EXIST_OK` flag controls whether a preexisting repository is
okay or not. For git-init(1) it is, for git-clone(1) it's not.
- The `SKIP_REFDB` flag controls whether the reference database should
already be created or not. For git-init(1) we do, but for
git-clone(1) we don't because it does not yet know about the default
branch and about the remote object hash.
Furthermore, we're about to add another divergence in behaviour, where
we have to also skip creation of the object database in git-clone(1).
This is becoming quite cumbersome though.
Instead of introducing another flag, start to split up concerns of the
function so that we never create the reference or object database. This
becomes the responsibility of the caller, which is thus free to defer
their creation to a later point in time. This lets us get rid of most of
the divergent behaviour:
- We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags
anymore.
- We don't need the `QUIET` flag anymore, as nothing prints output
except for the final status message that tells the user that the
repository has been (re)initialized. But as this message is specific
to git-init(1), we can easily move it there.
The only piece of information we still have to convey is whether or not
reinitialization of a preexisting repository is okay. This is handled by
a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay
to reinitialize the repository. Furthermore, the pointer will be written
to to indicate whether the repository was reinitialized or not, which we
need in git-init(1) to print the correct initialization message.
With these refactorings, `init_db()` is named quite misleadingly though,
as we don't create any of the reference or object databases anymore.
Rename it to `create_repository()`.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 9 +++++----
builtin/init-db.c | 32 ++++++++++++++++++++++++--------
setup.c | 54 +++++++++++++++++-------------------------------------
setup.h | 45 +++++++++++++++++++++++++++++++++------------
4 files changed, 79 insertions(+), 61 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 5b25cca510..904d2d859f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1185,10 +1185,10 @@ int cmd_clone(int argc,
* repository, and reference backends may persist that information into
* their on-disk data structures.
*/
- init_db(the_repository, git_dir, real_git_dir, work_tree, option_template,
- GIT_HASH_UNKNOWN, ref_storage_format, NULL,
- do_not_override_repo_unix_permissions,
- INIT_DB_QUIET | INIT_DB_SKIP_REFDB);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ option_template, GIT_HASH_UNKNOWN, ref_storage_format,
+ do_not_override_repo_unix_permissions, NULL);
+ create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1445,6 +1445,7 @@ int cmd_clone(int argc,
initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1);
repo_set_hash_algo(the_repository, hash_algo);
create_reference_database(the_repository, NULL, 1);
+ startup_info->have_repository = 1;
/*
* Before fetching from the remote, download and install bundle
diff --git a/builtin/init-db.c b/builtin/init-db.c
index e96b1283b7..f2c7e3be6d 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -80,7 +80,7 @@ int cmd_init_db(int argc,
char *work_tree = NULL;
const char *template_dir = NULL;
char *template_dir_to_free = NULL;
- unsigned int flags = 0;
+ int quiet = 0;
int bare = startup_info->force_bare_repository ? 1 : -1;
const char *object_format = NULL;
const char *ref_format = NULL;
@@ -102,7 +102,7 @@ int cmd_init_db(int argc,
.flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
.callback = shared_callback
},
- OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
+ OPT_BOOL('q', "quiet", &quiet, N_("be quiet")),
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
N_("separate git dir from working tree")),
OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
@@ -113,7 +113,7 @@ int cmd_init_db(int argc,
N_("specify the reference format to use")),
OPT_END()
};
- int ret;
+ int reinit;
argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
@@ -247,14 +247,30 @@ int cmd_init_db(int argc,
die(_("--separate-git-dir incompatible with bare repository"));
}
- flags |= INIT_DB_EXIST_OK;
- ret = init_db(the_repository, git_dir, real_git_dir, work_tree,
- template_dir, hash_algo, ref_storage_format, initial_branch,
- init_shared_repository, flags);
+ create_repository(the_repository, git_dir, real_git_dir, work_tree,
+ template_dir, hash_algo, ref_storage_format,
+ init_shared_repository, &reinit);
+ create_reference_database(the_repository, initial_branch, quiet);
+ create_object_database(the_repository);
+
+ if (!quiet) {
+ int len = strlen(git_dir);
+
+ if (reinit)
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Reinitialized existing shared Git repository in %s%s\n")
+ : _("Reinitialized existing Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ else
+ printf(repo_settings_get_shared_repository(the_repository)
+ ? _("Initialized empty shared Git repository in %s%s\n")
+ : _("Initialized empty Git repository in %s%s\n"),
+ git_dir, len && git_dir[len-1] != '/' ? "/" : "");
+ }
free(template_dir_to_free);
free(real_git_dir_to_free);
free(work_tree);
free(git_dir);
- return ret;
+ return 0;
}
diff --git a/setup.c b/setup.c
index d90654f584..8c7b97f92e 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,7 +2647,7 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-static void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo)
{
/*
* Create the "objects" directory in the common directory. This is done
@@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt,
}
}
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch,
- int init_shared_repository, unsigned int flags)
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok)
{
- int reinit;
- int exist_ok = flags & INIT_DB_EXIST_OK;
+ int reinit_ignored;
char *original_git_dir = real_pathdup(git_dir, 1);
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
struct strbuf err = STRBUF_INIT;
@@ -2840,10 +2840,10 @@ int init_db(struct repository *repo,
if (real_git_dir) {
struct stat st;
- if (!exist_ok && !stat(git_dir, &st))
+ if (!reinit_ok && !stat(git_dir, &st))
die(_("%s already exists"), git_dir);
- if (!exist_ok && !stat(real_git_dir, &st))
+ if (!reinit_ok && !stat(real_git_dir, &st))
die(_("%s already exists"), real_git_dir);
apply_and_export_relative_gitdir(repo, real_git_dir, 1);
@@ -2877,8 +2877,10 @@ int init_db(struct repository *repo,
safe_create_dir(repo, git_dir, 0);
- reinit = create_default_files(repo, template_dir, original_git_dir,
- &repo_fmt, init_shared_repository);
+ if (!reinit_ok)
+ reinit_ok = &reinit_ignored;
+ *reinit_ok = create_default_files(repo, template_dir, original_git_dir,
+ &repo_fmt, init_shared_repository);
if (repo_settings_get_shared_repository(repo)) {
char buf[10];
@@ -2901,29 +2903,7 @@ int init_db(struct repository *repo,
repo_config_set(repo, "receive.denyNonFastforwards", "true");
}
- if (!(flags & INIT_DB_SKIP_REFDB))
- create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET);
- create_object_database(repo);
-
- startup_info->have_repository = 1;
-
- if (!(flags & INIT_DB_QUIET)) {
- int len = strlen(git_dir);
-
- if (reinit)
- printf(repo_settings_get_shared_repository(repo)
- ? _("Reinitialized existing shared Git repository in %s%s\n")
- : _("Reinitialized existing Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- else
- printf(repo_settings_get_shared_repository(repo)
- ? _("Initialized empty shared Git repository in %s%s\n")
- : _("Initialized empty Git repository in %s%s\n"),
- git_dir, len && git_dir[len-1] != '/' ? "/" : "");
- }
-
clear_repository_format(&repo_fmt);
strbuf_release(&err);
free(original_git_dir);
- return 0;
}
diff --git a/setup.h b/setup.h
index 763fd384e8..f1c1ed65fb 100644
--- a/setup.h
+++ b/setup.h
@@ -256,24 +256,45 @@ int apply_repository_format(struct repository *repo,
const char *get_template_dir(const char *option_template);
-#define INIT_DB_QUIET (1 << 0)
-#define INIT_DB_EXIST_OK (1 << 1)
-#define INIT_DB_SKIP_REFDB (1 << 2)
-
-int init_db(struct repository *repo,
- const char *git_dir,
- const char *real_git_dir,
- const char *worktree,
- const char *template_dir, int hash_algo,
- enum ref_storage_format ref_storage_format,
- const char *initial_branch, int init_shared_repository,
- unsigned int flags);
+/*
+ * Create the repository by creating the necessary directory structures,
+ * setting up the configuration and configuring the repository's format. If
+ * `template_dir` is set, copy over templates from that directory. Furthermore,
+ * if and only if `reinit_ok` is a non-NULL pointer, then the function may
+ * reinitialize a preexisting repository. In that case, the pointer will be set
+ * to `1` in case the repo was reinitialized and `0` if it didn't exist yet.
+ *
+ * Note that this function does not create the reference and object databases.
+ */
+void create_repository(struct repository *repo,
+ const char *git_dir,
+ const char *real_git_dir,
+ const char *worktree,
+ const char *template_dir,
+ int hash_algo,
+ enum ref_storage_format ref_storage_format,
+ int init_shared_repository,
+ int *reinit_ok);
+
void initialize_repository_version(struct repository *repo,
int hash_algo,
enum ref_storage_format ref_storage_format,
int reinit);
+
+/*
+ * Create the reference database for the repository. The repository and its ref
+ * storage format must have already been configured properly before calling
+ * this function. When set, `initial_branch` overrides the default branch that
+ * HEAD will point to.
+ */
void create_reference_database(struct repository *repo, const char *initial_branch, int quiet);
+/*
+ * Create the object database for the repository. The repository must have
+ * already been configured properly before calling this function.
+ */
+void create_object_database(struct repository *repo);
+
/*
* NOTE NOTE NOTE!!
*
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 2/9] builtin/clone: defer setup of the object database
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
` (7 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
When cloning a repository we defer initialization of the reference
database. This is because we don't yet know all details required for us
to initialize the refdb in the first place. Most importantly, what we
are missing is information about the object hash.
We don't do the same thing for the object database yet, but here we
essentially have the same problem. While the "files" database does not
need any information about the object format at creation time, alternate
backends are likely to require that information so that they can
properly set up their data structures.
Besides this forward-looking future proofing though, we also have a
second use case for deferring initialization of the object database,
namely alternates. When initializing the object database we do not yet
know whether we'll need alternates or not because this depends on the
repository we're about to clone from. If it is a local repository and
the user has passed "--reference{,-if-able}", then we will end up
writing alternates into the object database.
The ugly part though is that we cannot determine where the repository is
getting cloned from before it has been initialized. While we of course
already have access to the user-provided URI, that URI can be very well
rewritten via "url.<base>.insteadOf". We can of course read the global-
and system-level configuration to resolve it. But we explicitly resolve
the URI a second time after we have initialized the repository because
it can happen that we copy a ".git/config" over from our templates, and
that file may cause us to rewrite the path.
In a subsequent commit though we'll start to write alternates as part of
the repository initialization, so we'll need to have the URI properly
resolved before we can initialize the object database. This is ugly, but
as mentioned above it makes sense for us to defer its initialization
anyway so that we also know about the object hash already.
Defer creation of the object database until after we have resolved the
URI.
Note that this also requires us to defer the call to `setup_reference()`
until after we have created the object database. While you might think
that this function has something to do with references ("refs/*"), it is
in fact responsible for setting up alternates. Consequently, we can only
call it after we have created the object database already.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 904d2d859f..bdcbd7aa1b 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1188,7 +1188,6 @@ int cmd_clone(int argc,
create_repository(the_repository, git_dir, real_git_dir, work_tree,
option_template, GIT_HASH_UNKNOWN, ref_storage_format,
do_not_override_repo_unix_permissions, NULL);
- create_object_database(the_repository);
if (real_git_dir) {
free((char *)git_dir);
@@ -1311,9 +1310,6 @@ int cmd_clone(int argc,
strbuf_reset(&key);
}
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
-
remote = remote_get_early(remote_name);
if (!option_rev)
@@ -1342,6 +1338,10 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
+ create_object_database(the_repository);
+ if (option_required_reference.nr || option_optional_reference.nr)
+ setup_reference();
+
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
transport->family = family;
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 3/9] builtin/clone: move around `setup_reference()`
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 1/9] setup: split up concerns of `init_db()` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 2/9] builtin/clone: defer setup of the object database Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
` (6 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
In a subsequent commit, `setup_reference()` will start to call
`copy_alternates()`. Prepare for this by moving the function further
down so that we can avoid adding a declaration.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index bdcbd7aa1b..ac5843d7b9 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -179,16 +179,6 @@ static int add_one_reference(struct string_list_item *item, void *cb_data)
return 0;
}
-static void setup_reference(void)
-{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
-}
-
static void copy_alternates(struct strbuf *src, const char *src_repo)
{
/*
@@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
+static void setup_reference(void)
+{
+ int required = 1;
+ for_each_string_list(&option_required_reference,
+ add_one_reference, &required);
+ required = 0;
+ for_each_string_list(&option_optional_reference,
+ add_one_reference, &required);
+}
+
static void mkdir_if_missing(const char *pathname, mode_t mode)
{
struct stat st;
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 4/9] builtin/clone: refactor handling of "--reference{,-if-able}"
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (2 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 3/9] builtin/clone: move around `setup_reference()` Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
` (5 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it
to set up alternates for the newly created repository. This allows it to
reuse objects from the source repository so that in the best case we
don't have to clone all objects over.
Those options are handled by the confusingly named `setup_reference()`
function -- without the above context, one might rightfully believe that
it was about refs, not about alternates. The function itself is rather
simple: we loop through all provided alternate paths and then, if such
an alternate is valid, we write it to the object database.
In subsequent commits we're about to consolidate the complete setup of
alternates into this function, and furthermore we'll refactor the setup
of the object database to handle doing this for us instead of writing
the alternates into it one by one.
Prepare for this refactoring by collecting the alternates into a strvec.
Rename the function to `collect_alternates()` to clarify its scope, as
it does not set up the references itself anymore.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 44 ++++++++++++++++++++++++++++----------------
1 file changed, 28 insertions(+), 16 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index ac5843d7b9..08d913d306 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle)
return canon;
}
-static int add_one_reference(struct string_list_item *item, void *cb_data)
+struct collect_alternates_data {
+ struct strvec *alternates;
+ bool required;
+};
+
+static int collect_one_alternate(struct string_list_item *item, void *cb_data)
{
+ struct collect_alternates_data *data = cb_data;
struct strbuf err = STRBUF_INIT;
- int *required = cb_data;
char *ref_git = compute_alternate_path(item->string, &err);
if (!ref_git) {
- if (*required)
+ if (data->required)
die("%s", err.buf);
else
fprintf(stderr,
_("info: Could not add alternate for '%s': %s\n"),
item->string, err.buf);
} else {
- struct strbuf sb = STRBUF_INIT;
- strbuf_addf(&sb, "%s/objects", ref_git);
- odb_add_to_alternates_file(the_repository->objects, sb.buf);
- strbuf_release(&sb);
+ strvec_pushf(data->alternates, "%s/objects", ref_git);
}
strbuf_release(&err);
@@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void setup_reference(void)
+static void collect_alternates(struct strvec *alternates)
{
- int required = 1;
- for_each_string_list(&option_required_reference,
- add_one_reference, &required);
- required = 0;
- for_each_string_list(&option_optional_reference,
- add_one_reference, &required);
+ if (option_required_reference.nr || option_optional_reference.nr) {
+ struct collect_alternates_data data = {
+ .alternates = alternates,
+ .required = true,
+ };
+
+ for_each_string_list(&option_required_reference,
+ collect_one_alternate, &data);
+ data.required = false;
+ for_each_string_list(&option_optional_reference,
+ collect_one_alternate, &data);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -999,6 +1007,7 @@ int cmd_clone(int argc,
N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")),
OPT_END()
};
+ struct strvec alternates = STRVEC_INIT;
const char * const builtin_clone_usage[] = {
N_("git clone [<options>] [--] <repo> [<dir>]"),
@@ -1339,8 +1348,10 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- if (option_required_reference.nr || option_optional_reference.nr)
- setup_reference();
+ collect_alternates(&alternates);
+
+ for (size_t i = 0; i < alternates.nr; i++)
+ odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
@@ -1638,6 +1649,7 @@ int cmd_clone(int argc,
string_list_clear(&option_not, 0);
string_list_clear(&option_config, 0);
string_list_clear(&server_options, 0);
+ strvec_clear(&alternates);
free(remote_name);
strbuf_release(&reflog_msg);
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 5/9] builtin/clone: move setup of alternates for shared local clones
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (3 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 4/9] builtin/clone: refactor handling of "--reference{,-if-able}" Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
` (4 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
When cloning a local repository with "--shared" we add that repository
to the new repository's alternates. This is done in `clone_local()`,
which is responsible for performing local clones.
Move the logic into `collect_alternates()` to unify our setup of
alternates. Furthermore, this will allow us to set up alternates right
at creation time of the object database.
Note that the logic for cloning a local repository with "--no-shared" is
not yet part of `collect_alternates()`. This will be handled in the next
commit, but means that at this step, we may compute `commondir` without
it being used. It will become used in the next step though.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 08d913d306..d397fd36b2 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
fclose(in);
}
-static void collect_alternates(struct strvec *alternates)
+static void collect_alternates(struct strvec *alternates,
+ const char *local_source_repo)
{
if (option_required_reference.nr || option_optional_reference.nr) {
struct collect_alternates_data data = {
@@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates)
for_each_string_list(&option_optional_reference,
collect_one_alternate, &data);
}
+
+ if (local_source_repo) {
+ struct strbuf commondir = STRBUF_INIT;
+
+ get_common_dir(&commondir, local_source_repo);
+ if (option_shared)
+ strvec_pushf(alternates, "%s/objects", commondir.buf);
+
+ strbuf_release(&commondir);
+ }
}
static void mkdir_if_missing(const char *pathname, mode_t mode)
@@ -357,13 +368,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
static void clone_local(const char *src_repo, const char *dest_repo)
{
- if (option_shared) {
- struct strbuf alt = STRBUF_INIT;
- get_common_dir(&alt, src_repo);
- strbuf_addstr(&alt, "/objects");
- odb_add_to_alternates_file(the_repository->objects, alt.buf);
- strbuf_release(&alt);
- } else {
+ if (!option_shared) {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
get_common_dir(&src, src_repo);
@@ -1348,7 +1353,7 @@ int cmd_clone(int argc,
warning(_("--local is ignored"));
create_object_database(the_repository);
- collect_alternates(&alternates);
+ collect_alternates(&alternates, is_local ? path : NULL);
for (size_t i = 0; i < alternates.nr; i++)
odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 6/9] builtin/clone: move setup of alternates for non-shared local clones
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (4 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 5/9] builtin/clone: move setup of alternates for shared local clones Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
` (3 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
Similar as in the preceding commit, move the setup of alternates for
local clones with "--no-shared" into `collect_alternates()`. With this
step, the complete setup of alternates is now handled by that function.
Note that besides moving stuff around, it also fixes a bug: previously,
we did not know to resolve the referenced repository's common directory.
Consequently, when referencing a worktree we failed to resolve
alternates. But as `collect_alternates()` already knows to resolve the
commondir for "--local" we can simply reuse this resolved path for our
purpose.
Add two tests, the first one of which exercises this bug to avoid future
regressions. The second test ensures that we properly handle relative
alternates for a referenced worktree.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 34 +++++++++++++++++++++++-----------
t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++
2 files changed, 48 insertions(+), 11 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index d397fd36b2..17353a8e1f 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -181,7 +181,7 @@ static int collect_one_alternate(struct string_list_item *item, void *cb_data)
return 0;
}
-static void copy_alternates(struct strbuf *src, const char *src_repo)
+static void read_alternates(struct strvec *alternates, const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
@@ -195,29 +195,41 @@ static void copy_alternates(struct strbuf *src, const char *src_repo)
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
- FILE *in = xfopen(src->buf, "r");
+ FILE *in;
+ struct strbuf path = STRBUF_INIT;
struct strbuf line = STRBUF_INIT;
+ strbuf_addf(&path, "%s/objects/info/alternates", src_repo);
+
+ in = fopen(path.buf, "r");
+ if (!in) {
+ if (errno == ENOENT)
+ goto out;
+ die_errno("could not read alternates file '%s'", path.buf);
+ }
+
while (strbuf_getline(&line, in) != EOF) {
char *abs_path;
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
- odb_add_to_alternates_file(the_repository->objects,
- line.buf);
+ strvec_push(alternates, line.buf);
continue;
}
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
if (!normalize_path_copy(abs_path, abs_path))
- odb_add_to_alternates_file(the_repository->objects,
- abs_path);
+ strvec_push(alternates, abs_path);
else
warning("skipping invalid relative alternate: %s/%s",
src_repo, line.buf);
free(abs_path);
}
+
+out:
+ strbuf_release(&path);
strbuf_release(&line);
- fclose(in);
+ if (in)
+ fclose(in);
}
static void collect_alternates(struct strvec *alternates,
@@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates,
get_common_dir(&commondir, local_source_repo);
if (option_shared)
strvec_pushf(alternates, "%s/objects", commondir.buf);
+ else
+ read_alternates(alternates, commondir.buf);
strbuf_release(&commondir);
}
@@ -320,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
continue;
}
- /* Files that cannot be copied bit-for-bit... */
- if (!fspathcmp(iter->relative_path, "info/alternates")) {
- copy_alternates(src, src_repo);
+ /* Alternates were already handled earlier. */
+ if (!fspathcmp(iter->relative_path, "info/alternates"))
continue;
- }
if (unlink(dest->buf) && errno != ENOENT)
die_errno(_("failed to unlink '%s'"), dest->buf);
diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh
index 39a0c318df..9e4b98fdb8 100755
--- a/t/t5604-clone-reference.sh
+++ b/t/t5604-clone-reference.sh
@@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' '
git clone --no-local --reference graph.git --dissociate orig clone
'
+test_expect_success 'local clone from linked worktree carries over alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
+test_expect_success 'local clone from linked worktree resolves relative alternates' '
+ rm -fr base derived derived-wt dst expect &&
+ git init base &&
+ test_commit -C base one &&
+ git clone --shared base derived &&
+ echo "../../../base/.git/objects" >derived/.git/objects/info/alternates &&
+ git -C derived worktree add ../derived-wt &&
+ git clone derived-wt dst &&
+ echo "$(pwd)/base/.git/objects" >expect &&
+ test_cmp expect dst/.git/objects/info/alternates &&
+ git -C dst fsck
+'
+
test_done
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 7/9] odb/source: support writing alternates when creating the database
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (5 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 6/9] builtin/clone: move setup of alternates for non-shared " Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
` (2 subsequent siblings)
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
Add the ability to write alternates when creating the object database.
This change allows us to remove the `write_alternates()` callback in a
subsequent patch.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--
odb/source.h | 17 +++++++++---
setup.c | 4 ++-
3 files changed, 91 insertions(+), 6 deletions(-)
diff --git a/odb/source-files.c b/odb/source-files.c
index b7b3a297bb..8fe65d91f8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -18,6 +18,7 @@
#include "run-command.h"
#include "strbuf.h"
#include "string-list.h"
+#include "strmap.h"
#include "strvec.h"
#include "tree.h"
#include "write-or-die.h"
@@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source)
odb_source_close(&files->packed->base);
}
-static int odb_source_files_create_on_disk(struct odb_source *source)
+static int odb_source_files_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
+ struct lock_file alternates_lock = LOCK_INIT;
struct strbuf path = STRBUF_INIT;
+ struct strset seen = STRSET_INIT;
+ struct strbuf line = STRBUF_INIT;
+ int ret;
safe_create_dir(source->odb->repo, source->path, 1);
@@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source)
strbuf_addf(&path, "%s/info", source->path);
safe_create_dir(source->odb->repo, path.buf, 1);
+ if (opts->alternates && opts->alternates->nr) {
+ FILE *alternates, *orig;
+
+ strbuf_reset(&path);
+ strbuf_addf(&path, "%s/info/alternates", source->path);
+
+ repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock,
+ path.buf, LOCK_DIE_ON_ERROR);
+
+ alternates = fdopen_lock_file(&alternates_lock, "w");
+ if (!alternates) {
+ ret = error_errno(_("unable to fdopen alternates lockfile"));
+ goto out;
+ }
+
+ /*
+ * The alternates file may already exist, e.g. when it has been
+ * seeded from a template directory. Read any preexisting
+ * entries so that we don't end up writing duplicates.
+ */
+ orig = fopen(path.buf, "r");
+ if (orig) {
+ while (strbuf_getline(&line, orig) != EOF) {
+ strset_add(&seen, line.buf);
+ fprintf(alternates, "%s\n", line.buf);
+ }
+
+ if (ferror(orig)) {
+ ret = error_errno(_("unable to read alternates file"));
+ fclose(orig);
+ goto out;
+ }
+
+ fclose(orig);
+ } else if (errno != ENOENT) {
+ ret = error_errno(_("unable to read alternates file"));
+ goto out;
+ }
+
+ for (size_t i = 0; i < opts->alternates->nr; i++) {
+ const char *alternate = opts->alternates->v[i];
+ if (!strset_add(&seen, alternate))
+ continue;
+ fprintf(alternates, "%s\n", alternate);
+ }
+
+ if (ferror(alternates)) {
+ ret = error_errno(_("unable to write alternates file"));
+ goto out;
+ }
+
+ if (commit_lock_file(&alternates_lock)) {
+ ret = error_errno(_("unable to commit alternates file"));
+ goto out;
+ }
+ }
+
+ /* Reprepare the object database to activate alternates. */
+ odb_reprepare(source->odb);
+
+ ret = 0;
+
+out:
+ rollback_lock_file(&alternates_lock);
+ strbuf_release(&line);
strbuf_release(&path);
- return 0;
+ strset_clear(&seen);
+ return ret;
}
static void odb_source_files_prepare(struct odb_source *source,
diff --git a/odb/source.h b/odb/source.h
index ea8675247e..63f1c0c531 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -36,6 +36,15 @@ struct object_id;
struct odb_stream;
struct strvec;
+struct odb_create_on_disk_options {
+ /*
+ * Alternates that shall be written into the newly created object
+ * database. Whether or not this option can be handled is specific to
+ * the backend.
+ */
+ const struct strvec *alternates;
+};
+
/*
* The source is the part of the object database that stores the actual
* objects. It thus encapsulates the logic to read and write the specific
@@ -106,7 +115,8 @@ struct odb_source {
* This callback may be NULL in case the source does not need any
* on-disk setup.
*/
- int (*create_on_disk)(struct odb_source *source);
+ int (*create_on_disk)(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts);
/*
* This callback is expected to prepare the source so that it becomes
@@ -356,11 +366,12 @@ static inline void odb_source_close(struct odb_source *source)
* Create on-disk data structures that are required for this source to operate
* correctly. Returns 0 on success, a negative error code otherwise.
*/
-static inline int odb_source_create_on_disk(struct odb_source *source)
+static inline int odb_source_create_on_disk(struct odb_source *source,
+ const struct odb_create_on_disk_options *opts)
{
if (!source->create_on_disk)
return 0;
- return source->create_on_disk(source);
+ return source->create_on_disk(source, opts);
}
/*
diff --git a/setup.c b/setup.c
index 8c7b97f92e..37a8e6f124 100644
--- a/setup.c
+++ b/setup.c
@@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo,
void create_object_database(struct repository *repo)
{
+ struct odb_create_on_disk_options opts = { 0 };
+
/*
* Create the "objects" directory in the common directory. This is done
* so that the repository can be discovered regardless of the backend
@@ -2668,7 +2670,7 @@ void create_object_database(struct repository *repo)
repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV);
- if (odb_source_create_on_disk(repo->objects->sources) < 0)
+ if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0)
die(_("failed creating object database"));
}
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 8/9] builtin/clone: write alternates via `odb_create_on_disk()`
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (6 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 7/9] odb/source: support writing alternates when creating the database Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 15:09 ` [PATCH v5 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
2026-09-10 19:53 ` [PATCH v5 0/9] odb: write alternates at creation time Karthik Nayak
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
When creating a repository with alternates we first initialize the
object database and then write alternates to it in a separate step. This
is unfortunate due to a couple of reasons:
- It requires us to have a `write_alternates()` callback, which is
unfortunate as we never even write alternates to an object database
after it has been created.
- We're about to make alternates an implementation detail of the
object database's backend in a future patch series, so having this
callback is suboptimal there.
- The backend has more flexibility with how exactly alternates are
configured when it itself is in full control over their setup at the
time where it creates the object database itself.
We have thus introduced the ability to write alternates right at
creation time in the preceding commits, and we have unified setup of
alternates into a single location. All that's left to do for us now is
to wire up alternates as an option for the database creation.
Do so.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
builtin/clone.c | 5 +----
builtin/init-db.c | 2 +-
setup.c | 7 +++++--
setup.h | 6 ++++--
4 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/builtin/clone.c b/builtin/clone.c
index 17353a8e1f..b14264c33a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -1364,11 +1364,8 @@ int cmd_clone(int argc,
if (option_local > 0 && !is_local)
warning(_("--local is ignored"));
- create_object_database(the_repository);
collect_alternates(&alternates, is_local ? path : NULL);
-
- for (size_t i = 0; i < alternates.nr; i++)
- odb_add_to_alternates_file(the_repository->objects, alternates.v[i]);
+ create_object_database(the_repository, &alternates);
transport = transport_get(remote, path ? path : remote->url.v[0]);
transport_set_verbosity(transport, option_verbosity, option_progress);
diff --git a/builtin/init-db.c b/builtin/init-db.c
index f2c7e3be6d..5c22eae2f3 100644
--- a/builtin/init-db.c
+++ b/builtin/init-db.c
@@ -251,7 +251,7 @@ int cmd_init_db(int argc,
template_dir, hash_algo, ref_storage_format,
init_shared_repository, &reinit);
create_reference_database(the_repository, initial_branch, quiet);
- create_object_database(the_repository);
+ create_object_database(the_repository, NULL);
if (!quiet) {
int len = strlen(git_dir);
diff --git a/setup.c b/setup.c
index 37a8e6f124..17d0d25973 100644
--- a/setup.c
+++ b/setup.c
@@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo,
return reinit;
}
-void create_object_database(struct repository *repo)
+void create_object_database(struct repository *repo,
+ const struct strvec *alternates)
{
- struct odb_create_on_disk_options opts = { 0 };
+ struct odb_create_on_disk_options opts = {
+ .alternates = alternates,
+ };
/*
* Create the "objects" directory in the common directory. This is done
diff --git a/setup.h b/setup.h
index f1c1ed65fb..27b2492373 100644
--- a/setup.h
+++ b/setup.h
@@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran
/*
* Create the object database for the repository. The repository must have
- * already been configured properly before calling this function.
+ * already been configured properly before calling this function. When set,
+ * `alternates` is the list of alternates that should be written into the
+ * object database.
*/
-void create_object_database(struct repository *repo);
+void create_object_database(struct repository *repo, const struct strvec *alternates);
/*
* NOTE NOTE NOTE!!
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* [PATCH v5 9/9] odb/source: remove the ability to write alternates
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (7 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 8/9] builtin/clone: write alternates via `odb_create_on_disk()` Patrick Steinhardt
@ 2026-09-10 15:09 ` Patrick Steinhardt
2026-09-10 19:53 ` [PATCH v5 0/9] odb: write alternates at creation time Karthik Nayak
9 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-10 15:09 UTC (permalink / raw)
To: git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler, Karthik Nayak
There are no users of `odb_source_write_alternates()` in our tree
anymore. Remove that function and its supporting infrastructure.
Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
odb.c | 9 ---------
odb.h | 7 -------
odb/source-files.c | 54 ---------------------------------------------------
odb/source-inmemory.c | 7 -------
odb/source-loose.c | 7 -------
odb/source-packed.c | 7 -------
odb/source.h | 26 -------------------------
7 files changed, 117 deletions(-)
diff --git a/odb.c b/odb.c
index 67d98d64fc..b531cf8fb3 100644
--- a/odb.c
+++ b/odb.c
@@ -239,15 +239,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database *
return alternate;
}
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir)
-{
- int ret = odb_source_write_alternate(odb->sources, dir);
- if (ret < 0)
- die(NULL);
- odb_add_alternate_recursively(odb, dir, 0);
-}
-
struct odb_source *odb_add_to_alternates_memory(struct object_database *odb,
const char *dir)
{
diff --git a/odb.h b/odb.h
index b9e0db56ec..2d002461f8 100644
--- a/odb.h
+++ b/odb.h
@@ -270,13 +270,6 @@ int odb_mkstemp(struct object_database *odb,
*/
int odb_has_alternates(struct object_database *odb);
-/*
- * Add the directory to the on-disk alternates file; the new entry will also
- * take effect in the current process.
- */
-void odb_add_to_alternates_file(struct object_database *odb,
- const char *dir);
-
/*
* Add the directory to the in-memory list of alternate sources (along with any
* recursive alternates it points to), but do not modify the on-disk alternates
diff --git a/odb/source-files.c b/odb/source-files.c
index 8fe65d91f8..b3f340dff8 100644
--- a/odb/source-files.c
+++ b/odb/source-files.c
@@ -306,59 +306,6 @@ static int odb_source_files_read_alternates(struct odb_source *source,
return 0;
}
-static int odb_source_files_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- struct lock_file lock = LOCK_INIT;
- char *path = xstrfmt("%s/%s", source->path, "info/alternates");
- FILE *in, *out;
- int found = 0;
- int ret;
-
- repo_hold_lock_file_for_update(source->odb->repo, &lock, path,
- LOCK_DIE_ON_ERROR);
- out = fdopen_lock_file(&lock, "w");
- if (!out) {
- ret = error_errno(_("unable to fdopen alternates lockfile"));
- goto out;
- }
-
- in = fopen(path, "r");
- if (in) {
- struct strbuf line = STRBUF_INIT;
-
- while (strbuf_getline(&line, in) != EOF) {
- if (!strcmp(alternate, line.buf)) {
- found = 1;
- break;
- }
- fprintf_or_die(out, "%s\n", line.buf);
- }
-
- strbuf_release(&line);
- fclose(in);
- } else if (errno != ENOENT) {
- ret = error_errno(_("unable to read alternates file"));
- goto out;
- }
-
- if (found) {
- rollback_lock_file(&lock);
- } else {
- fprintf_or_die(out, "%s\n", alternate);
- if (commit_lock_file(&lock)) {
- ret = error_errno(_("unable to move new alternates file into place"));
- goto out;
- }
- }
-
- ret = 0;
-
-out:
- free(path);
- return ret;
-}
-
static int too_many_loose_objects(struct odb_source_files *files, int limit)
{
unsigned long loose_count;
@@ -842,7 +789,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb,
files->base.write_object_stream = odb_source_files_write_object_stream;
files->base.begin_transaction = odb_source_files_begin_transaction;
files->base.read_alternates = odb_source_files_read_alternates;
- files->base.write_alternate = odb_source_files_write_alternate;
files->base.optimize = odb_source_files_optimize;
files->base.optimize_required = odb_source_files_optimize_required;
diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c
index 795672adf2..b00248dfb2 100644
--- a/odb/source-inmemory.c
+++ b/odb/source-inmemory.c
@@ -326,12 +326,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("in-memory source does not support alternates");
-}
-
static void odb_source_inmemory_close(struct odb_source *source UNUSED)
{
}
@@ -388,7 +382,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb)
source->base.freshen_object = odb_source_inmemory_freshen_object;
source->base.begin_transaction = odb_source_inmemory_begin_transaction;
source->base.read_alternates = odb_source_inmemory_read_alternates;
- source->base.write_alternate = odb_source_inmemory_write_alternate;
return source;
}
diff --git a/odb/source-loose.c b/odb/source-loose.c
index bb3455dfbd..0f9b30bac1 100644
--- a/odb/source-loose.c
+++ b/odb/source-loose.c
@@ -982,12 +982,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_loose_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("loose source does not support alternates");
-}
-
static void odb_source_loose_clear_cache(struct odb_source_loose *loose)
{
oidtree_clear(loose->cache);
@@ -1053,7 +1047,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb,
loose->base.write_object_stream = odb_source_loose_write_object_stream;
loose->base.begin_transaction = odb_source_loose_begin_transaction;
loose->base.read_alternates = odb_source_loose_read_alternates;
- loose->base.write_alternate = odb_source_loose_write_alternate;
if (!is_absolute_path(loose->base.path))
chdir_notify_register(NULL, odb_source_loose_reparent, loose);
diff --git a/odb/source-packed.c b/odb/source-packed.c
index 630d955585..c2d253759c 100644
--- a/odb/source-packed.c
+++ b/odb/source-packed.c
@@ -628,12 +628,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED,
return 0;
}
-static int odb_source_packed_write_alternate(struct odb_source *source UNUSED,
- const char *alternate UNUSED)
-{
- return error("packed backend cannot write alternates");
-}
-
void (*report_garbage)(unsigned seen_bits, const char *path);
static void report_helper(const struct string_list *list,
@@ -849,7 +843,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb,
packed->base.write_object_stream = odb_source_packed_write_object_stream;
packed->base.begin_transaction = odb_source_packed_begin_transaction;
packed->base.read_alternates = odb_source_packed_read_alternates;
- packed->base.write_alternate = odb_source_packed_write_alternate;
if (!is_absolute_path(path))
chdir_notify_register(NULL, odb_source_packed_reparent, packed);
diff --git a/odb/source.h b/odb/source.h
index 63f1c0c531..693a9fc604 100644
--- a/odb/source.h
+++ b/odb/source.h
@@ -286,19 +286,6 @@ struct odb_source {
int (*read_alternates)(struct odb_source *source,
struct strvec *out);
- /*
- * This callback is expected to persist the singular alternate passed
- * to it into its list of alternates. Any pre-existing alternates are
- * expected to remain active. Subsequent calls to `read_alternates` are
- * thus expected to yield the pre-existing list of alternates plus the
- * newly added alternate appended to its end.
- *
- * The callback is expected to return 0 on success, a negative error
- * code otherwise.
- */
- int (*write_alternate)(struct odb_source *source,
- const char *alternate);
-
/*
* This callback is expected to optimize the object database source.
* Returns 0 on success, a negative error code otherwise.
@@ -518,19 +505,6 @@ static inline int odb_source_read_alternates(struct odb_source *source,
return source->read_alternates(source, out);
}
-/*
- * Write and persist a new alternate object database source for the given
- * source. Any preexisting alternates are expected to stay valid, and the new
- * alternate shall be appended to the end of the list.
- *
- * Returns 0 on success, a negative error code otherwise.
- */
-static inline int odb_source_write_alternate(struct odb_source *source,
- const char *alternate)
-{
- return source->write_alternate(source, alternate);
-}
-
/*
* Create a new transaction that can be used to write objects into a temporary
* staging area. The objects will only be persisted when the transaction is
--
2.55.0.1074.ge7621b4bad.dirty
^ permalink raw reply related [flat|nested] 84+ messages in thread* Re: [PATCH v5 0/9] odb: write alternates at creation time
2026-09-10 15:09 ` [PATCH v5 " Patrick Steinhardt
` (8 preceding siblings ...)
2026-09-10 15:09 ` [PATCH v5 9/9] odb/source: remove the ability to write alternates Patrick Steinhardt
@ 2026-09-10 19:53 ` Karthik Nayak
2026-09-11 5:15 ` Patrick Steinhardt
9 siblings, 1 reply; 84+ messages in thread
From: Karthik Nayak @ 2026-09-10 19:53 UTC (permalink / raw)
To: Patrick Steinhardt, git; +Cc: Toon Claes, Junio C Hamano, Justin Tobler
[-- Attachment #1: Type: text/plain, Size: 2116 bytes --]
Patrick Steinhardt <ps@pks.im> writes:
> Hi,
>
> writing alternates into the object database currently happens via
> `odb_source_write_alternate()`. But while that creates the ability to
> create alternates at arbitrary points of a source's lifetime, we don't
> use that functionality in the first place. Instead, we only ever write
> alternates when creating a new repository.
>
> This design is suboptimal due to a couple of reasons:
>
> - It requires us to have a `write_alternates()` callback, which is
> overblown as we never even write alternates to an object database
> after it has been created.
>
> - We're about to make alternates an implementation detail of the
> object database's backend in a future patch series, so alternate
> implementations may not even support them.
>
> - The backend has more flexibility with how exactly alternates are
> configured when it itself is in full control over their setup at the
> time where it creates the object database itself.
>
> This patch series thus refactors how we handle alternates so that we
> don't write them ad-hoc anymore. Instead, the series introduces a new
> option for `odb_source_create_on_disk()` that makes it handle those
> alternates at creation time.
>
> This is part of the bigger goal of moving handling of alternates into
> the "files" backend.
>
> This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> `alternates_db` field, 2026-08-17) merged into it.
>
> Changes in v5:
> - Rename `add_one_alternate` and `add_one_alternate_data` to
> `collect_one_alternate` and `collect_alternates_data` to clarify
> their intent a bit.
> - Drop extra parameter in `collect_alternates()`.
> - Clarify why we compute `commondir` even though it's unused.
> - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
>
Looks like you missed my comment/question in
CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but
otherwise looks good :)
[snip]
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 690 bytes --]
^ permalink raw reply [flat|nested] 84+ messages in thread* Re: [PATCH v5 0/9] odb: write alternates at creation time
2026-09-10 19:53 ` [PATCH v5 0/9] odb: write alternates at creation time Karthik Nayak
@ 2026-09-11 5:15 ` Patrick Steinhardt
0 siblings, 0 replies; 84+ messages in thread
From: Patrick Steinhardt @ 2026-09-11 5:15 UTC (permalink / raw)
To: Karthik Nayak; +Cc: git, Toon Claes, Junio C Hamano, Justin Tobler
On Thu, Sep 10, 2026 at 12:53:21PM -0700, Karthik Nayak wrote:
> Patrick Steinhardt <ps@pks.im> writes:
>
> > Hi,
> >
> > writing alternates into the object database currently happens via
> > `odb_source_write_alternate()`. But while that creates the ability to
> > create alternates at arbitrary points of a source's lifetime, we don't
> > use that functionality in the first place. Instead, we only ever write
> > alternates when creating a new repository.
> >
> > This design is suboptimal due to a couple of reasons:
> >
> > - It requires us to have a `write_alternates()` callback, which is
> > overblown as we never even write alternates to an object database
> > after it has been created.
> >
> > - We're about to make alternates an implementation detail of the
> > object database's backend in a future patch series, so alternate
> > implementations may not even support them.
> >
> > - The backend has more flexibility with how exactly alternates are
> > configured when it itself is in full control over their setup at the
> > time where it creates the object database itself.
> >
> > This patch series thus refactors how we handle alternates so that we
> > don't write them ad-hoc anymore. Instead, the series introduces a new
> > option for `odb_source_create_on_disk()` that makes it handle those
> > alternates at creation time.
> >
> > This is part of the bigger goal of moving handling of alternates into
> > the "files" backend.
> >
> > This series is built on top of 2c3adbb2c4 (The 18th batch, 2026-08-24)
> > with ps/odb-eagerly-load-alternates at 0076dc9f81 (odb: drop
> > `alternates_db` field, 2026-08-17) merged into it.
> >
> > Changes in v5:
> > - Rename `add_one_alternate` and `add_one_alternate_data` to
> > `collect_one_alternate` and `collect_alternates_data` to clarify
> > their intent a bit.
> > - Drop extra parameter in `collect_alternates()`.
> > - Clarify why we compute `commondir` even though it's unused.
> > - Link to v4: https://patch.msgid.link/20260909-pks-odb-write-alternates-at-creation-time-v4-0-d8a78ffc32e4@pks.im
> >
>
> Looks like you missed my comment/question in
> CAOLa=ZQaPstiQmXm9=TyWPUxL6X2=Lcqeg6y2XeXzSJDpq-GBA@mail.gmail.com, but
> otherwise looks good :)
Oh, indeed, I somehow overlooked that mail. Replied to it now, but I
don't think it requires further changes. Thanks!
Patrick
^ permalink raw reply [flat|nested] 84+ messages in thread