builtin/clone: move setup of alternates for non-shared local clones

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>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
next
Patrick Steinhardt 2026-09-10 17:09:16 +02:00 committed by Junio C Hamano
parent 87e10a91e2
commit 002a7cfc73
2 changed files with 48 additions and 11 deletions

View File

@ -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);

View File

@ -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