Skip to content

Commit e6baf4a

Browse files
committed
clone: clone from a repository with relative alternates
Cloning from a local repository blindly copies or hardlinks all the files under objects/ hierarchy. This results in two issues: - If the repository cloned has an "objects/info/alternates" file, and the command line of clone specifies --reference, the ones specified on the command line get overwritten by the copy from the original repository. - An entry in a "objects/info/alternates" file can specify the object stores it borrows objects from as a path relative to the "objects/" directory. When cloning a repository with such an alternates file, if the new repository is not sitting next to the original repository, such relative paths needs to be adjusted so that they can be used in the new repository. This updates add_to_alternates_file() to take the path to the alternate object store, including the "/objects" part at the end (earlier, it was taking the path to $GIT_DIR and was adding "/objects" itself), as it is technically possible to specify in objects/info/alternates file the path of a directory whose name does not end with "/objects". Signed-off-by: Junio C Hamano <[email protected]>
1 parent dbc92b0 commit e6baf4a

File tree

3 files changed

+78
-25
lines changed

3 files changed

+78
-25
lines changed

builtin/clone.c

Lines changed: 68 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -209,34 +209,34 @@ static void strip_trailing_slashes(char *dir)
209209

210210
static int add_one_reference(struct string_list_item *item, void *cb_data)
211211
{
212-
const char *ref_git;
213-
char *ref_git_copy;
214-
212+
char *ref_git;
213+
struct strbuf alternate = STRBUF_INIT;
215214
struct remote *remote;
216215
struct transport *transport;
217216
const struct ref *extra;
218217

219-
ref_git = real_path(item->string);
220-
221-
if (is_directory(mkpath("%s/.git/objects", ref_git)))
222-
ref_git = mkpath("%s/.git", ref_git);
223-
else if (!is_directory(mkpath("%s/objects", ref_git)))
218+
/* Beware: real_path() and mkpath() return static buffer */
219+
ref_git = xstrdup(real_path(item->string));
220+
if (is_directory(mkpath("%s/.git/objects", ref_git))) {
221+
char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git));
222+
free(ref_git);
223+
ref_git = ref_git_git;
224+
} else if (!is_directory(mkpath("%s/objects", ref_git)))
224225
die(_("reference repository '%s' is not a local directory."),
225226
item->string);
226227

227-
ref_git_copy = xstrdup(ref_git);
228-
229-
add_to_alternates_file(ref_git_copy);
228+
strbuf_addf(&alternate, "%s/objects", ref_git);
229+
add_to_alternates_file(alternate.buf);
230+
strbuf_release(&alternate);
230231

231-
remote = remote_get(ref_git_copy);
232-
transport = transport_get(remote, ref_git_copy);
232+
remote = remote_get(ref_git);
233+
transport = transport_get(remote, ref_git);
233234
for (extra = transport_get_remote_refs(transport); extra;
234235
extra = extra->next)
235236
add_extra_ref(extra->name, extra->old_sha1, 0);
236237

237238
transport_disconnect(transport);
238-
239-
free(ref_git_copy);
239+
free(ref_git);
240240
return 0;
241241
}
242242

@@ -245,7 +245,42 @@ static void setup_reference(void)
245245
for_each_string_list(&option_reference, add_one_reference, NULL);
246246
}
247247

248-
static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
248+
static void copy_alternates(struct strbuf *src, struct strbuf *dst,
249+
const char *src_repo)
250+
{
251+
/*
252+
* Read from the source objects/info/alternates file
253+
* and copy the entries to corresponding file in the
254+
* destination repository with add_to_alternates_file().
255+
* Both src and dst have "$path/objects/info/alternates".
256+
*
257+
* Instead of copying bit-for-bit from the original,
258+
* we need to append to existing one so that the already
259+
* created entry via "clone -s" is not lost, and also
260+
* to turn entries with paths relative to the original
261+
* absolute, so that they can be used in the new repository.
262+
*/
263+
FILE *in = fopen(src->buf, "r");
264+
struct strbuf line = STRBUF_INIT;
265+
266+
while (strbuf_getline(&line, in, '\n') != EOF) {
267+
char *abs_path, abs_buf[PATH_MAX];
268+
if (!line.len || line.buf[0] == '#')
269+
continue;
270+
if (is_absolute_path(line.buf)) {
271+
add_to_alternates_file(line.buf);
272+
continue;
273+
}
274+
abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
275+
normalize_path_copy(abs_buf, abs_path);
276+
add_to_alternates_file(abs_buf);
277+
}
278+
strbuf_release(&line);
279+
fclose(in);
280+
}
281+
282+
static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
283+
const char *src_repo, int src_baselen)
249284
{
250285
struct dirent *de;
251286
struct stat buf;
@@ -281,7 +316,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
281316
}
282317
if (S_ISDIR(buf.st_mode)) {
283318
if (de->d_name[0] != '.')
284-
copy_or_link_directory(src, dest);
319+
copy_or_link_directory(src, dest,
320+
src_repo, src_baselen);
321+
continue;
322+
}
323+
324+
/* Files that cannot be copied bit-for-bit... */
325+
if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
326+
copy_alternates(src, dest, src_repo);
285327
continue;
286328
}
287329

@@ -304,17 +346,20 @@ static const struct ref *clone_local(const char *src_repo,
304346
const char *dest_repo)
305347
{
306348
const struct ref *ret;
307-
struct strbuf src = STRBUF_INIT;
308-
struct strbuf dest = STRBUF_INIT;
309349
struct remote *remote;
310350
struct transport *transport;
311351

312-
if (option_shared)
313-
add_to_alternates_file(src_repo);
314-
else {
352+
if (option_shared) {
353+
struct strbuf alt = STRBUF_INIT;
354+
strbuf_addf(&alt, "%s/objects", src_repo);
355+
add_to_alternates_file(alt.buf);
356+
strbuf_release(&alt);
357+
} else {
358+
struct strbuf src = STRBUF_INIT;
359+
struct strbuf dest = STRBUF_INIT;
315360
strbuf_addf(&src, "%s/objects", src_repo);
316361
strbuf_addf(&dest, "%s/objects", dest_repo);
317-
copy_or_link_directory(&src, &dest);
362+
copy_or_link_directory(&src, &dest, src_repo, src.len);
318363
strbuf_release(&src);
319364
strbuf_release(&dest);
320365
}

sha1_file.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ void add_to_alternates_file(const char *reference)
380380
{
381381
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
382382
int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
383-
char *alt = mkpath("%s/objects\n", reference);
383+
char *alt = mkpath("%s\n", reference);
384384
write_or_die(fd, alt, strlen(alt));
385385
if (commit_lock_file(lock))
386386
die("could not close alternates file");

t/t5601-clone.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ test_expect_success 'clone separate gitdir where target already exists' '
207207
test_must_fail git clone --separate-git-dir realgitdir src dst
208208
'
209209

210-
test_expect_failure 'clone --reference from original' '
210+
test_expect_success 'clone --reference from original' '
211211
git clone --shared --bare src src-1 &&
212212
git clone --bare src src-2 &&
213213
git clone --reference=src-2 --bare src-1 target-8 &&
@@ -222,4 +222,12 @@ test_expect_success 'clone with more than one --reference' '
222222
grep /src-4/ target-9/.git/objects/info/alternates
223223
'
224224

225+
test_expect_success 'clone from original with relative alternate' '
226+
mkdir nest &&
227+
git clone --bare src nest/src-5 &&
228+
echo ../../../src/.git/objects >nest/src-5/objects/info/alternates &&
229+
git clone --bare nest/src-5 target-10 &&
230+
grep /src/\\.git/objects target-10/objects/info/alternates
231+
'
232+
225233
test_done

0 commit comments

Comments
 (0)