Skip to content

Commit

Permalink
clone: clone from a repository with relative alternates
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
gitster committed Aug 23, 2011
1 parent dbc92b0 commit e6baf4a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 25 deletions.
91 changes: 68 additions & 23 deletions builtin/clone.c
Original file line number Diff line number Diff line change
Expand Up @@ -209,34 +209,34 @@ static void strip_trailing_slashes(char *dir)

static int add_one_reference(struct string_list_item *item, void *cb_data)
{
const char *ref_git;
char *ref_git_copy;

char *ref_git;
struct strbuf alternate = STRBUF_INIT;
struct remote *remote;
struct transport *transport;
const struct ref *extra;

ref_git = real_path(item->string);

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

ref_git_copy = xstrdup(ref_git);

add_to_alternates_file(ref_git_copy);
strbuf_addf(&alternate, "%s/objects", ref_git);
add_to_alternates_file(alternate.buf);
strbuf_release(&alternate);

remote = remote_get(ref_git_copy);
transport = transport_get(remote, ref_git_copy);
remote = remote_get(ref_git);
transport = transport_get(remote, ref_git);
for (extra = transport_get_remote_refs(transport); extra;
extra = extra->next)
add_extra_ref(extra->name, extra->old_sha1, 0);

transport_disconnect(transport);

free(ref_git_copy);
free(ref_git);
return 0;
}

Expand All @@ -245,7 +245,42 @@ static void setup_reference(void)
for_each_string_list(&option_reference, add_one_reference, NULL);
}

static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
static void copy_alternates(struct strbuf *src, struct strbuf *dst,
const char *src_repo)
{
/*
* Read from the source objects/info/alternates file
* and copy the entries to corresponding file in the
* destination repository with add_to_alternates_file().
* Both src and dst have "$path/objects/info/alternates".
*
* Instead of copying bit-for-bit from the original,
* we need to append to existing one so that the already
* created entry via "clone -s" is not lost, and also
* to turn entries with paths relative to the original
* absolute, so that they can be used in the new repository.
*/
FILE *in = fopen(src->buf, "r");
struct strbuf line = STRBUF_INIT;

while (strbuf_getline(&line, in, '\n') != EOF) {
char *abs_path, abs_buf[PATH_MAX];
if (!line.len || line.buf[0] == '#')
continue;
if (is_absolute_path(line.buf)) {
add_to_alternates_file(line.buf);
continue;
}
abs_path = mkpath("%s/objects/%s", src_repo, line.buf);
normalize_path_copy(abs_buf, abs_path);
add_to_alternates_file(abs_buf);
}
strbuf_release(&line);
fclose(in);
}

static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
const char *src_repo, int src_baselen)
{
struct dirent *de;
struct stat buf;
Expand Down Expand Up @@ -281,7 +316,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest)
}
if (S_ISDIR(buf.st_mode)) {
if (de->d_name[0] != '.')
copy_or_link_directory(src, dest);
copy_or_link_directory(src, dest,
src_repo, src_baselen);
continue;
}

/* Files that cannot be copied bit-for-bit... */
if (!strcmp(src->buf + src_baselen, "/info/alternates")) {
copy_alternates(src, dest, src_repo);
continue;
}

Expand All @@ -304,17 +346,20 @@ static const struct ref *clone_local(const char *src_repo,
const char *dest_repo)
{
const struct ref *ret;
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
struct remote *remote;
struct transport *transport;

if (option_shared)
add_to_alternates_file(src_repo);
else {
if (option_shared) {
struct strbuf alt = STRBUF_INIT;
strbuf_addf(&alt, "%s/objects", src_repo);
add_to_alternates_file(alt.buf);
strbuf_release(&alt);
} else {
struct strbuf src = STRBUF_INIT;
struct strbuf dest = STRBUF_INIT;
strbuf_addf(&src, "%s/objects", src_repo);
strbuf_addf(&dest, "%s/objects", dest_repo);
copy_or_link_directory(&src, &dest);
copy_or_link_directory(&src, &dest, src_repo, src.len);
strbuf_release(&src);
strbuf_release(&dest);
}
Expand Down
2 changes: 1 addition & 1 deletion sha1_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ void add_to_alternates_file(const char *reference)
{
struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), LOCK_DIE_ON_ERROR);
char *alt = mkpath("%s/objects\n", reference);
char *alt = mkpath("%s\n", reference);
write_or_die(fd, alt, strlen(alt));
if (commit_lock_file(lock))
die("could not close alternates file");
Expand Down
10 changes: 9 additions & 1 deletion t/t5601-clone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ test_expect_success 'clone separate gitdir where target already exists' '
test_must_fail git clone --separate-git-dir realgitdir src dst
'

test_expect_failure 'clone --reference from original' '
test_expect_success 'clone --reference from original' '
git clone --shared --bare src src-1 &&
git clone --bare src src-2 &&
git clone --reference=src-2 --bare src-1 target-8 &&
Expand All @@ -222,4 +222,12 @@ test_expect_success 'clone with more than one --reference' '
grep /src-4/ target-9/.git/objects/info/alternates
'

test_expect_success 'clone from original with relative alternate' '
mkdir nest &&
git clone --bare src nest/src-5 &&
echo ../../../src/.git/objects >nest/src-5/objects/info/alternates &&
git clone --bare nest/src-5 target-10 &&
grep /src/\\.git/objects target-10/objects/info/alternates
'

test_done

0 comments on commit e6baf4a

Please sign in to comment.