diff --git a/git-clone.sh b/git-clone.sh index b78524767c0..227245c865e 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -261,11 +261,7 @@ yes,yes) ;; yes) mkdir -p "$GIT_DIR/objects/info" - { - test -f "$repo/objects/info/alternates" && - cat "$repo/objects/info/alternates"; - echo "$repo/objects" - } >>"$GIT_DIR/objects/info/alternates" + echo "$repo/objects" >> "$GIT_DIR/objects/info/alternates" ;; esac git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" diff --git a/sha1_file.c b/sha1_file.c index 54648282592..b62d0e3dcd0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -217,6 +217,8 @@ char *sha1_pack_index_name(const unsigned char *sha1) struct alternate_object_database *alt_odb_list; static struct alternate_object_database **alt_odb_tail; +static void read_info_alternates(const char * alternates, int depth); + /* * Prepare alternate object database registry. * @@ -232,13 +234,84 @@ static struct alternate_object_database **alt_odb_tail; * SHA1, an extra slash for the first level indirection, and the * terminating NUL. */ +static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth) +{ + struct stat st; + const char *objdir = get_object_directory(); + struct alternate_object_database *ent; + struct alternate_object_database *alt; + /* 43 = 40-byte + 2 '/' + terminating NUL */ + int pfxlen = len; + int entlen = pfxlen + 43; + int base_len = -1; + + if (*entry != '/' && relative_base) { + /* Relative alt-odb */ + if (base_len < 0) + base_len = strlen(relative_base) + 1; + entlen += base_len; + pfxlen += base_len; + } + ent = xmalloc(sizeof(*ent) + entlen); + + if (*entry != '/' && relative_base) { + memcpy(ent->base, relative_base, base_len - 1); + ent->base[base_len - 1] = '/'; + memcpy(ent->base + base_len, entry, len); + } + else + memcpy(ent->base, entry, pfxlen); + + ent->name = ent->base + pfxlen + 1; + ent->base[pfxlen + 3] = '/'; + ent->base[pfxlen] = ent->base[entlen-1] = 0; + + /* Detect cases where alternate disappeared */ + if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) { + error("object directory %s does not exist; " + "check .git/objects/info/alternates.", + ent->base); + free(ent); + return -1; + } + + /* Prevent the common mistake of listing the same + * thing twice, or object directory itself. + */ + for (alt = alt_odb_list; alt; alt = alt->next) { + if (!memcmp(ent->base, alt->base, pfxlen)) { + free(ent); + return -1; + } + } + if (!memcmp(ent->base, objdir, pfxlen)) { + free(ent); + return -1; + } + + /* add the alternate entry */ + *alt_odb_tail = ent; + alt_odb_tail = &(ent->next); + ent->next = NULL; + + /* recursively add alternates */ + read_info_alternates(ent->base, depth + 1); + + ent->base[pfxlen] = '/'; + + return 0; +} + static void link_alt_odb_entries(const char *alt, const char *ep, int sep, - const char *relative_base) + const char *relative_base, int depth) { const char *cp, *last; - struct alternate_object_database *ent; - const char *objdir = get_object_directory(); - int base_len = -1; + + if (depth > 5) { + error("%s: ignoring alternate object stores, nesting too deep.", + relative_base); + return; + } last = alt; while (last < ep) { @@ -249,60 +322,15 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep, last = cp + 1; continue; } - for ( ; cp < ep && *cp != sep; cp++) - ; + while (cp < ep && *cp != sep) + cp++; if (last != cp) { - struct stat st; - struct alternate_object_database *alt; - /* 43 = 40-byte + 2 '/' + terminating NUL */ - int pfxlen = cp - last; - int entlen = pfxlen + 43; - - if (*last != '/' && relative_base) { - /* Relative alt-odb */ - if (base_len < 0) - base_len = strlen(relative_base) + 1; - entlen += base_len; - pfxlen += base_len; - } - ent = xmalloc(sizeof(*ent) + entlen); - - if (*last != '/' && relative_base) { - memcpy(ent->base, relative_base, base_len - 1); - ent->base[base_len - 1] = '/'; - memcpy(ent->base + base_len, - last, cp - last); - } - else - memcpy(ent->base, last, pfxlen); - - ent->name = ent->base + pfxlen + 1; - ent->base[pfxlen + 3] = '/'; - ent->base[pfxlen] = ent->base[entlen-1] = 0; - - /* Detect cases where alternate disappeared */ - if (stat(ent->base, &st) || !S_ISDIR(st.st_mode)) { - error("object directory %s does not exist; " - "check .git/objects/info/alternates.", - ent->base); - goto bad; - } - ent->base[pfxlen] = '/'; - - /* Prevent the common mistake of listing the same - * thing twice, or object directory itself. - */ - for (alt = alt_odb_list; alt; alt = alt->next) - if (!memcmp(ent->base, alt->base, pfxlen)) - goto bad; - if (!memcmp(ent->base, objdir, pfxlen)) { - bad: - free(ent); - } - else { - *alt_odb_tail = ent; - alt_odb_tail = &(ent->next); - ent->next = NULL; + if ((*last != '/') && depth) { + error("%s: ignoring relative alternate object store %s", + relative_base, last); + } else { + link_alt_odb_entry(last, cp - last, + relative_base, depth); } } while (cp < ep && *cp == sep) @@ -311,23 +339,14 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep, } } -void prepare_alt_odb(void) +static void read_info_alternates(const char * relative_base, int depth) { - char path[PATH_MAX]; char *map; - int fd; struct stat st; - char *alt; + char path[PATH_MAX]; + int fd; - alt = getenv(ALTERNATE_DB_ENVIRONMENT); - if (!alt) alt = ""; - - if (alt_odb_tail) - return; - alt_odb_tail = &alt_odb_list; - link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL); - - sprintf(path, "%s/info/alternates", get_object_directory()); + sprintf(path, "%s/info/alternates", relative_base); fd = open(path, O_RDONLY); if (fd < 0) return; @@ -340,11 +359,26 @@ void prepare_alt_odb(void) if (map == MAP_FAILED) return; - link_alt_odb_entries(map, map + st.st_size, '\n', - get_object_directory()); + link_alt_odb_entries(map, map + st.st_size, '\n', relative_base, depth); + munmap(map, st.st_size); } +void prepare_alt_odb(void) +{ + char *alt; + + alt = getenv(ALTERNATE_DB_ENVIRONMENT); + if (!alt) alt = ""; + + if (alt_odb_tail) + return; + alt_odb_tail = &alt_odb_list; + link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0); + + read_info_alternates(get_object_directory(), 0); +} + static char *find_sha1_file(const unsigned char *sha1, struct stat *st) { char *name = sha1_file_name(sha1); diff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh new file mode 100755 index 00000000000..097d037f5da --- /dev/null +++ b/t/t5710-info-alternate.sh @@ -0,0 +1,105 @@ +#!/bin/sh +# +# Copyright (C) 2006 Martin Waitz +# + +test_description='test transitive info/alternate entries' +. ./test-lib.sh + +# test that a file is not reachable in the current repository +# but that it is after creating a info/alternate entry +reachable_via() { + alternate="$1" + file="$2" + if git cat-file -e "HEAD:$file"; then return 1; fi + echo "$alternate" >> .git/objects/info/alternate + git cat-file -e "HEAD:$file" +} + +test_valid_repo() { + git fsck-objects --full > fsck.log && + test `wc -l < fsck.log` = 0 +} + +base_dir=`pwd` + +test_expect_success 'preparing first repository' \ +'test_create_repo A && cd A && +echo "Hello World" > file1 && +git add file1 && +git commit -m "Initial commit" file1 && +git repack -a -d && +git prune' + +cd "$base_dir" + +test_expect_success 'preparing second repository' \ +'git clone -l -s A B && cd B && +echo "foo bar" > file2 && +git add file2 && +git commit -m "next commit" file2 && +git repack -a -d -l && +git prune' + +cd "$base_dir" + +test_expect_success 'preparing third repository' \ +'git clone -l -s B C && cd C && +echo "Goodbye, cruel world" > file3 && +git add file3 && +git commit -m "one more" file3 && +git repack -a -d -l && +git prune' + +cd "$base_dir" + +test_expect_failure 'creating too deep nesting' \ +'git clone -l -s C D && +git clone -l -s D E && +git clone -l -s E F && +git clone -l -s F G && +test_valid_repo' + +cd "$base_dir" + +test_expect_success 'validity of third repository' \ +'cd C && +test_valid_repo' + +cd "$base_dir" + +test_expect_success 'validity of fourth repository' \ +'cd D && +test_valid_repo' + +cd "$base_dir" + +test_expect_success 'breaking of loops' \ +"echo '$base_dir/B/.git/objects' >> '$base_dir'/A/.git/objects/info/alternates&& +cd C && +test_valid_repo" + +cd "$base_dir" + +test_expect_failure 'that info/alternates is neccessary' \ +'cd C && +rm .git/objects/info/alternates && +test_valid_repo' + +cd "$base_dir" + +test_expect_success 'that relative alternate is possible for current dir' \ +'cd C && +echo "../../../B/.git/objects" > .git/objects/info/alternates && +test_valid_repo' + +cd "$base_dir" + +test_expect_failure 'that relative alternate is only possible for current dir' \ +'cd D && +test_valid_repo' + +cd "$base_dir" + +test_done +