Transitively read alternatives
[gitweb.git] / sha1_file.c
index 54648282592d85cea0b1d42e02f358c240991ce2..b62d0e3dcd0c0142a025d202eec2f233932aed68 100644 (file)
@@ -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,14 +234,85 @@ static struct alternate_object_database **alt_odb_tail;
  * SHA1, an extra slash for the first level indirection, and the
  * terminating NUL.
  */
-static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
-                                const char *relative_base)
+static int link_alt_odb_entry(const char * entry, int len, const char * relative_base, int depth)
 {
-       const char *cp, *last;
-       struct alternate_object_database *ent;
+       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, int depth)
+{
+       const char *cp, *last;
+
+       if (depth > 5) {
+               error("%s: ignoring alternate object stores, nesting too deep.",
+                               relative_base);
+               return;
+       }
+
        last = alt;
        while (last < ep) {
                cp = last;
@@ -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;
-
-       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);
+       char path[PATH_MAX];
+       int fd;
 
-       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);