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 <gitster@pobox.com>

Junio C Hamano committed Aug 22, 2011 at 18:05 UTC e6baf4a1ae1bb75d59967066ade1290cf105dcd8
3 files changed +78 -25
builtin/clone.c
+68 -23
@@ -209,34 +209,34 @@ static void strip_trailing_slashes(char *dir)
209
210 static int add_one_reference(struct string_list_item *item, void *cb_data)
211 {
212 - const char *ref_git;
213 - char *ref_git_copy;
214 -
212 + char *ref_git;
213 + struct strbuf alternate = STRBUF_INIT;
214 struct remote *remote;
215 struct transport *transport;
216 const struct ref *extra;
217
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)))
225 die(_("reference repository '%s' is not a local directory."),
226 item->string);
227
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);
231
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);
234 for (extra = transport_get_remote_refs(transport); extra;
235 extra = extra->next)
236 add_extra_ref(extra->name, extra->old_sha1, 0);
237
238 transport_disconnect(transport);
238 -
239 - free(ref_git_copy);
239 + free(ref_git);
240 return 0;
241 }
242
@@ -245,7 +245,42 @@ static void setup_reference(void)
245 for_each_string_list(&option_reference, add_one_reference, NULL);
246 }
247
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)
284 {
285 struct dirent *de;
286 struct stat buf;
@@ -281,7 +316,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
316 }
317 if (S_ISDIR(buf.st_mode)) {
318 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);
327 continue;
328 }
329
@@ -304,17 +346,20 @@ static const struct ref *clone_local(const char *src_repo,
346 const char *dest_repo)
347 {
348 const struct ref *ret;
307 - struct strbuf src = STRBUF_INIT;
308 - struct strbuf dest = STRBUF_INIT;
349 struct remote *remote;
350 struct transport *transport;
351
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;
360 strbuf_addf(&src, "%s/objects", src_repo);
361 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);
363 strbuf_release(&src);
364 strbuf_release(&dest);
365 }
sha1_file.c
+1 -1
@@ -380,7 +380,7 @@ void add_to_alternates_file(const char *reference)
380 {
381 struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
382 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);
384 write_or_die(fd, alt, strlen(alt));
385 if (commit_lock_file(lock))
386 die("could not close alternates file");
t/t5601-clone.sh
+9 -1
@@ -207,7 +207,7 @@ test_expect_success 'clone separate gitdir where target already exists' '
207 test_must_fail git clone --separate-git-dir realgitdir src dst
208 '
209
210 -test_expect_failure 'clone --reference from original' '
210 +test_expect_success 'clone --reference from original' '
211 git clone --shared --bare src src-1 &&
212 git clone --bare src src-2 &&
213 git clone --reference=src-2 --bare src-1 target-8 &&
@@ -222,4 +222,12 @@ test_expect_success 'clone with more than one --reference' '
222 grep /src-4/ target-9/.git/objects/info/alternates
223 '
224
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 +
233 test_done