gitview: Add some useful keybindings.
[gitweb.git] / sha1_file.c
index 58edec0bb677e480552b9464e905ea983747c183..aea0f40d57a134a2a853136cd0815e5c743a115d 100644 (file)
@@ -9,6 +9,10 @@
 #include "cache.h"
 #include "delta.h"
 #include "pack.h"
+#include "blob.h"
+#include "commit.h"
+#include "tag.h"
+#include "tree.h"
 
 #ifndef O_NOATIME
 #if defined(__linux__) && (defined(__i386__) || defined(__PPC__))
@@ -104,9 +108,10 @@ int safe_create_leading_directories(char *path)
 
 char * sha1_to_hex(const unsigned char *sha1)
 {
-       static char buffer[50];
+       static int bufno;
+       static char hexbuffer[4][50];
        static const char hex[] = "0123456789abcdef";
-       char *buf = buffer;
+       char *buffer = hexbuffer[3 & ++bufno], *buf = buffer;
        int i;
 
        for (i = 0; i < 20; i++) {
@@ -212,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.
  *
@@ -227,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;
@@ -244,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)
@@ -306,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;
@@ -335,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);
@@ -578,6 +617,12 @@ static void prepare_packed_git_one(char *objdir, int local)
 
                /* we have .idx.  Is it a file we can map? */
                strcpy(path + len, de->d_name);
+               for (p = packed_git; p; p = p->next) {
+                       if (!memcmp(path, p->pack_name, len + namelen - 4))
+                               break;
+               }
+               if (p)
+                       continue;
                p = add_packed_git(path, len + namelen, local);
                if (!p)
                        continue;
@@ -587,12 +632,12 @@ static void prepare_packed_git_one(char *objdir, int local)
        closedir(dir);
 }
 
+static int prepare_packed_git_run_once = 0;
 void prepare_packed_git(void)
 {
-       static int run_once = 0;
        struct alternate_object_database *alt;
 
-       if (run_once)
+       if (prepare_packed_git_run_once)
                return;
        prepare_packed_git_one(get_object_directory(), 1);
        prepare_alt_odb();
@@ -601,7 +646,13 @@ void prepare_packed_git(void)
                prepare_packed_git_one(alt->base, 0);
                alt->name[-1] = '/';
        }
-       run_once = 1;
+       prepare_packed_git_run_once = 1;
+}
+
+static void reprepare_packed_git(void)
+{
+       prepare_packed_git_run_once = 0;
+       prepare_packed_git();
 }
 
 int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
@@ -804,10 +855,12 @@ static int packed_delta_info(unsigned char *base_sha1,
                 * the result size.
                 */
                data = delta_head;
-               get_delta_hdr_size(&data); /* ignore base size */
+
+               /* ignore base size */
+               get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
 
                /* Read the result size */
-               result_size = get_delta_hdr_size(&data);
+               result_size = get_delta_hdr_size(&data, delta_head+sizeof(delta_head));
                *sizep = result_size;
        }
        return 0;
@@ -868,17 +921,19 @@ void packed_object_info_detail(struct pack_entry *e,
                               unsigned char *base_sha1)
 {
        struct packed_git *p = e->p;
-       unsigned long offset, left;
+       unsigned long offset;
        unsigned char *pack;
        enum object_type kind;
 
        offset = unpack_object_header(p, e->offset, &kind, size);
        pack = p->pack_base + offset;
-       left = p->pack_size - offset;
        if (kind != OBJ_DELTA)
                *delta_chain_length = 0;
        else {
                unsigned int chain_length = 0;
+               if (p->pack_size <= offset + 20)
+                       die("pack file %s records an incomplete delta base",
+                           p->pack_name);
                memcpy(base_sha1, pack, 20);
                do {
                        struct pack_entry base_ent;
@@ -894,16 +949,16 @@ void packed_object_info_detail(struct pack_entry *e,
        }
        switch (kind) {
        case OBJ_COMMIT:
-               strcpy(type, "commit");
+               strcpy(type, commit_type);
                break;
        case OBJ_TREE:
-               strcpy(type, "tree");
+               strcpy(type, tree_type);
                break;
        case OBJ_BLOB:
-               strcpy(type, "blob");
+               strcpy(type, blob_type);
                break;
        case OBJ_TAG:
-               strcpy(type, "tag");
+               strcpy(type, tag_type);
                break;
        default:
                die("corrupted pack file %s containing object of kind %d",
@@ -934,16 +989,16 @@ static int packed_object_info(struct pack_entry *entry,
                unuse_packed_git(p);
                return retval;
        case OBJ_COMMIT:
-               strcpy(type, "commit");
+               strcpy(type, commit_type);
                break;
        case OBJ_TREE:
-               strcpy(type, "tree");
+               strcpy(type, tree_type);
                break;
        case OBJ_BLOB:
-               strcpy(type, "blob");
+               strcpy(type, blob_type);
                break;
        case OBJ_TAG:
-               strcpy(type, "tag");
+               strcpy(type, tag_type);
                break;
        default:
                die("corrupted pack file %s containing object of kind %d",
@@ -1071,16 +1126,16 @@ void *unpack_entry_gently(struct pack_entry *entry,
                retval = unpack_delta_entry(pack, size, left, type, sizep, p);
                return retval;
        case OBJ_COMMIT:
-               strcpy(type, "commit");
+               strcpy(type, commit_type);
                break;
        case OBJ_TREE:
-               strcpy(type, "tree");
+               strcpy(type, tree_type);
                break;
        case OBJ_BLOB:
-               strcpy(type, "blob");
+               strcpy(type, blob_type);
                break;
        case OBJ_TAG:
-               strcpy(type, "tag");
+               strcpy(type, tag_type);
                break;
        default:
                return NULL;
@@ -1118,7 +1173,7 @@ int find_pack_entry_one(const unsigned char *sha1,
                int mi = (lo + hi) / 2;
                int cmp = memcmp(index + 24 * mi + 4, sha1, 20);
                if (!cmp) {
-                       e->offset = ntohl(*((int*)(index + 24 * mi)));
+                       e->offset = ntohl(*((unsigned int *)(index + 24 * mi)));
                        memcpy(e->sha1, sha1, 20);
                        e->p = p;
                        return 1;
@@ -1169,9 +1224,12 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep
        if (!map) {
                struct pack_entry e;
 
-               if (!find_pack_entry(sha1, &e))
-                       return error("unable to find %s", sha1_to_hex(sha1));
-               return packed_object_info(&e, type, sizep);
+               if (find_pack_entry(sha1, &e))
+                       return packed_object_info(&e, type, sizep);
+               reprepare_packed_git();
+               if (find_pack_entry(sha1, &e))
+                       return packed_object_info(&e, type, sizep);
+               return error("unable to find %s", sha1_to_hex(sha1));
        }
        if (unpack_sha1_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
                status = error("unable to unpack %s header",
@@ -1213,6 +1271,9 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size
                munmap(map, mapsize);
                return buf;
        }
+       reprepare_packed_git();
+       if (find_pack_entry(sha1, &e))
+               return read_packed_sha1(sha1, type, size);
        return NULL;
 }
 
@@ -1241,9 +1302,9 @@ void *read_object_with_reference(const unsigned char *sha1,
                        return buffer;
                }
                /* Handle references */
-               else if (!strcmp(type, "commit"))
+               else if (!strcmp(type, commit_type))
                        ref_type = "tree ";
-               else if (!strcmp(type, "tag"))
+               else if (!strcmp(type, tag_type))
                        ref_type = "object ";
                else {
                        free(buffer);
@@ -1356,6 +1417,25 @@ int move_temp_to_file(const char *tmpfile, char *filename)
        return 0;
 }
 
+static int write_buffer(int fd, const void *buf, size_t len)
+{
+       while (len) {
+               ssize_t size;
+
+               size = write(fd, buf, len);
+               if (!size)
+                       return error("file write: disk full");
+               if (size < 0) {
+                       if (errno == EINTR || errno == EAGAIN)
+                               continue;
+                       return error("file write error (%s)", strerror(errno));
+               }
+               len -= size;
+               buf += size;
+       }
+       return 0;
+}
+
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
        int size;
@@ -1422,8 +1502,8 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        deflateEnd(&stream);
        size = stream.total_out;
 
-       if (write(fd, compressed, size) != size)
-               die("unable to write file");
+       if (write_buffer(fd, compressed, size) < 0)
+               die("unable to write sha1 file");
        fchmod(fd, 0444);
        close(fd);
        free(compressed);
@@ -1431,73 +1511,70 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        return move_temp_to_file(tmpfile, filename);
 }
 
-int write_sha1_to_fd(int fd, const unsigned char *sha1)
+/*
+ * We need to unpack and recompress the object for writing
+ * it out to a different file.
+ */
+static void *repack_object(const unsigned char *sha1, unsigned long *objsize)
 {
-       ssize_t size;
-       unsigned long objsize;
-       int posn = 0;
-       void *map = map_sha1_file_internal(sha1, &objsize);
-       void *buf = map;
-       void *temp_obj = NULL;
+       size_t size;
        z_stream stream;
+       unsigned char *unpacked;
+       unsigned long len;
+       char type[20];
+       char hdr[50];
+       int hdrlen;
+       void *buf;
 
-       if (!buf) {
-               unsigned char *unpacked;
-               unsigned long len;
-               char type[20];
-               char hdr[50];
-               int hdrlen;
-               // need to unpack and recompress it by itself
-               unpacked = read_packed_sha1(sha1, type, &len);
+       // need to unpack and recompress it by itself
+       unpacked = read_packed_sha1(sha1, type, &len);
 
-               hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
+       hdrlen = sprintf(hdr, "%s %lu", type, len) + 1;
 
-               /* Set it up */
-               memset(&stream, 0, sizeof(stream));
-               deflateInit(&stream, Z_BEST_COMPRESSION);
-               size = deflateBound(&stream, len + hdrlen);
-               temp_obj = buf = xmalloc(size);
-
-               /* Compress it */
-               stream.next_out = buf;
-               stream.avail_out = size;
-               
-               /* First header.. */
-               stream.next_in = (void *)hdr;
-               stream.avail_in = hdrlen;
-               while (deflate(&stream, 0) == Z_OK)
-                       /* nothing */;
+       /* Set it up */
+       memset(&stream, 0, sizeof(stream));
+       deflateInit(&stream, Z_BEST_COMPRESSION);
+       size = deflateBound(&stream, len + hdrlen);
+       buf = xmalloc(size);
 
-               /* Then the data itself.. */
-               stream.next_in = unpacked;
-               stream.avail_in = len;
-               while (deflate(&stream, Z_FINISH) == Z_OK)
-                       /* nothing */;
-               deflateEnd(&stream);
-               free(unpacked);
-               
-               objsize = stream.total_out;
-       }
+       /* Compress it */
+       stream.next_out = buf;
+       stream.avail_out = size;
 
-       do {
-               size = write(fd, buf + posn, objsize - posn);
-               if (size <= 0) {
-                       if (!size) {
-                               fprintf(stderr, "write closed\n");
-                       } else {
-                               perror("write ");
-                       }
-                       return -1;
-               }
-               posn += size;
-       } while (posn < objsize);
+       /* First header.. */
+       stream.next_in = (void *)hdr;
+       stream.avail_in = hdrlen;
+       while (deflate(&stream, 0) == Z_OK)
+               /* nothing */;
 
-       if (map)
-               munmap(map, objsize);
-       if (temp_obj)
-               free(temp_obj);
+       /* Then the data itself.. */
+       stream.next_in = unpacked;
+       stream.avail_in = len;
+       while (deflate(&stream, Z_FINISH) == Z_OK)
+               /* nothing */;
+       deflateEnd(&stream);
+       free(unpacked);
 
-       return 0;
+       *objsize = stream.total_out;
+       return buf;
+}
+
+int write_sha1_to_fd(int fd, const unsigned char *sha1)
+{
+       int retval;
+       unsigned long objsize;
+       void *buf = map_sha1_file_internal(sha1, &objsize);
+
+       if (buf) {
+               retval = write_buffer(fd, buf, objsize);
+               munmap(buf, objsize);
+               return retval;
+       }
+
+       buf = repack_object(sha1, &objsize);
+       retval = write_buffer(fd, buf, objsize);
+       free(buf);
+       return retval;
 }
 
 int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
@@ -1536,7 +1613,8 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
                                SHA1_Update(&c, discard, sizeof(discard) -
                                            stream.avail_out);
                        } while (stream.avail_in && ret == Z_OK);
-                       write(local, buffer, *bufposn - stream.avail_in);
+                       if (write_buffer(local, buffer, *bufposn - stream.avail_in) < 0)
+                               die("unable to write sha1 file");
                        memmove(buffer, buffer + *bufposn - stream.avail_in,
                                stream.avail_in);
                        *bufposn = stream.avail_in;
@@ -1602,16 +1680,24 @@ int has_sha1_file(const unsigned char *sha1)
        return find_sha1_file(sha1, &st) ? 1 : 0;
 }
 
-int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
+/*
+ * reads from fd as long as possible into a supplied buffer of size bytes.
+ * If neccessary the buffer's size is increased using realloc()
+ *
+ * returns 0 if anything went fine and -1 otherwise
+ *
+ * NOTE: both buf and size may change, but even when -1 is returned
+ * you still have to free() it yourself.
+ */
+int read_pipe(int fd, char** return_buf, unsigned long* return_size)
 {
-       unsigned long size = 4096;
-       char *buf = malloc(size);
-       int iret, ret;
+       char* buf = *return_buf;
+       unsigned long size = *return_size;
+       int iret;
        unsigned long off = 0;
-       unsigned char hdr[50];
-       int hdrlen;
+
        do {
-               iret = read(fd, buf + off, size - off);
+               iret = xread(fd, buf + off, size - off);
                if (iret > 0) {
                        off += iret;
                        if (off == size) {
@@ -1620,16 +1706,34 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
                        }
                }
        } while (iret > 0);
-       if (iret < 0) {
+
+       *return_buf = buf;
+       *return_size = off;
+
+       if (iret < 0)
+               return -1;
+       return 0;
+}
+
+int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
+{
+       unsigned long size = 4096;
+       char *buf = malloc(size);
+       int ret;
+       unsigned char hdr[50];
+       int hdrlen;
+
+       if (read_pipe(fd, &buf, &size)) {
                free(buf);
                return -1;
        }
+
        if (!type)
-               type = "blob";
+               type = blob_type;
        if (write_object)
-               ret = write_sha1_file(buf, off, type, sha1);
+               ret = write_sha1_file(buf, size, type, sha1);
        else {
-               write_sha1_file_prepare(buf, off, type, sha1, hdr, &hdrlen);
+               write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
                ret = 0;
        }
        free(buf);
@@ -1652,7 +1756,7 @@ int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, con
                return -1;
 
        if (!type)
-               type = "blob";
+               type = blob_type;
        if (write_object)
                ret = write_sha1_file(buf, size, type, sha1);
        else {
@@ -1690,9 +1794,9 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                if (!write_object) {
                        unsigned char hdr[50];
                        int hdrlen;
-                       write_sha1_file_prepare(target, st->st_size, "blob",
+                       write_sha1_file_prepare(target, st->st_size, blob_type,
                                                sha1, hdr, &hdrlen);
-               } else if (write_sha1_file(target, st->st_size, "blob", sha1))
+               } else if (write_sha1_file(target, st->st_size, blob_type, sha1))
                        return error("%s: failed to insert into database",
                                     path);
                free(target);