sha1_file.c: learn about index version 2
[gitweb.git] / sha1_file.c
index 6d0a72ed093d353a672129f7e460d0c1015212d7..0be9737bd12948acd40c5c66af7e80f08da063b3 100644 (file)
@@ -349,6 +349,7 @@ static void link_alt_odb_entries(const char *alt, const char *ep, int sep,
 static void read_info_alternates(const char * relative_base, int depth)
 {
        char *map;
+       size_t mapsz;
        struct stat st;
        char path[PATH_MAX];
        int fd;
@@ -361,12 +362,13 @@ static void read_info_alternates(const char * relative_base, int depth)
                close(fd);
                return;
        }
-       map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       mapsz = xsize_t(st.st_size);
+       map = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
 
-       link_alt_odb_entries(map, map + st.st_size, '\n', relative_base, depth);
+       link_alt_odb_entries(map, map + mapsz, '\n', relative_base, depth);
 
-       munmap(map, st.st_size);
+       munmap(map, mapsz);
 }
 
 void prepare_alt_odb(void)
@@ -430,61 +432,100 @@ void pack_report()
                pack_mapped, peak_pack_mapped);
 }
 
-static int check_packed_git_idx(const char *path, unsigned long *idx_size_,
-                               void **idx_map_)
+static int check_packed_git_idx(const char *path,  struct packed_git *p)
 {
        void *idx_map;
-       uint32_t *index;
-       unsigned long idx_size;
-       int nr, i;
+       struct pack_idx_header *hdr;
+       size_t idx_size;
+       uint32_t version, nr, i, *index;
        int fd = open(path, O_RDONLY);
        struct stat st;
+
        if (fd < 0)
                return -1;
        if (fstat(fd, &st)) {
                close(fd);
                return -1;
        }
-       idx_size = st.st_size;
+       idx_size = xsize_t(st.st_size);
+       if (idx_size < 4 * 256 + 20 + 20) {
+               close(fd);
+               return error("index file %s is too small", path);
+       }
        idx_map = xmmap(NULL, idx_size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
 
-       index = idx_map;
-       *idx_map_ = idx_map;
-       *idx_size_ = idx_size;
-
-       /* check index map */
-       if (idx_size < 4*256 + 20 + 20)
-               return error("index file %s is too small", path);
-
-       /* a future index format would start with this, as older git
-        * binaries would fail the non-monotonic index check below.
-        * give a nicer warning to the user if we can.
-        */
-       if (index[0] == htonl(PACK_IDX_SIGNATURE))
-               return error("index file %s is a newer version"
-                       " and is not supported by this binary"
-                       " (try upgrading GIT to a newer version)",
-                       path);
+       hdr = idx_map;
+       if (hdr->idx_signature == htonl(PACK_IDX_SIGNATURE)) {
+               version = ntohl(hdr->idx_version);
+               if (version < 2 || version > 2) {
+                       munmap(idx_map, idx_size);
+                       return error("index file %s is version %d"
+                                    " and is not supported by this binary"
+                                    " (try upgrading GIT to a newer version)",
+                                    path, version);
+               }
+       } else
+               version = 1;
 
        nr = 0;
+       index = idx_map;
+       if (version > 1)
+               index += 2;  /* skip index header */
        for (i = 0; i < 256; i++) {
-               unsigned int n = ntohl(index[i]);
-               if (n < nr)
+               uint32_t n = ntohl(index[i]);
+               if (n < nr) {
+                       munmap(idx_map, idx_size);
                        return error("non-monotonic index %s", path);
+               }
                nr = n;
        }
 
-       /*
-        * Total size:
-        *  - 256 index entries 4 bytes each
-        *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
-        *  - 20-byte SHA1 of the packfile
-        *  - 20-byte SHA1 file checksum
-        */
-       if (idx_size != 4*256 + nr * 24 + 20 + 20)
-               return error("wrong index file size in %s", path);
+       if (version == 1) {
+               /*
+                * Total size:
+                *  - 256 index entries 4 bytes each
+                *  - 24-byte entries * nr (20-byte sha1 + 4-byte offset)
+                *  - 20-byte SHA1 of the packfile
+                *  - 20-byte SHA1 file checksum
+                */
+               if (idx_size != 4*256 + nr * 24 + 20 + 20) {
+                       munmap(idx_map, idx_size);
+                       return error("wrong index file size in %s", path);
+               }
+       } else if (version == 2) {
+               /*
+                * Minimum size:
+                *  - 8 bytes of header
+                *  - 256 index entries 4 bytes each
+                *  - 20-byte sha1 entry * nr
+                *  - 4-byte crc entry * nr
+                *  - 4-byte offset entry * nr
+                *  - 20-byte SHA1 of the packfile
+                *  - 20-byte SHA1 file checksum
+                * And after the 4-byte offset table might be a
+                * variable sized table containing 8-byte entries
+                * for offsets larger than 2^31.
+                */
+               unsigned long min_size = 8 + 4*256 + nr*(20 + 4 + 4) + 20 + 20;
+               if (idx_size < min_size || idx_size > min_size + (nr - 1)*8) {
+                       munmap(idx_map, idx_size);
+                       return error("wrong index file size in %s", path);
+               }
+               if (idx_size != min_size) {
+                       /* make sure we can deal with large pack offsets */
+                       off_t x = 0x7fffffffUL, y = 0xffffffffUL;
+                       if (x > (x + 1) || y > (y + 1)) {
+                               munmap(idx_map, idx_size);
+                               return error("pack too large for current definition of off_t in %s", path);
+                       }
+               }
+       }
 
+       p->index_version = version;
+       p->index_data = idx_map;
+       p->index_size = idx_size;
+       p->num_objects = nr;
        return 0;
 }
 
@@ -596,16 +637,16 @@ static int open_packed_git_1(struct packed_git *p)
                        p->pack_name, ntohl(hdr.hdr_version));
 
        /* Verify the pack matches its index. */
-       if (num_packed_objects(p) != ntohl(hdr.hdr_entries))
+       if (p->num_objects != ntohl(hdr.hdr_entries))
                return error("packfile %s claims to have %u objects"
-                       " while index size indicates %u objects",
-                       p->pack_name, ntohl(hdr.hdr_entries),
-                       num_packed_objects(p));
+                            " while index indicates %u objects",
+                            p->pack_name, ntohl(hdr.hdr_entries),
+                            p->num_objects);
        if (lseek(p->pack_fd, p->pack_size - sizeof(sha1), SEEK_SET) == -1)
                return error("end of packfile %s is unavailable", p->pack_name);
        if (read_in_full(p->pack_fd, sha1, sizeof(sha1)) != sizeof(sha1))
                return error("packfile %s signature is unavailable", p->pack_name);
-       idx_sha1 = ((unsigned char *)p->index_base) + p->index_size - 40;
+       idx_sha1 = ((unsigned char *)p->index_data) + p->index_size - 40;
        if (hashcmp(sha1, idx_sha1))
                return error("packfile %s does not match index", p->pack_name);
        return 0;
@@ -622,7 +663,7 @@ static int open_packed_git(struct packed_git *p)
        return -1;
 }
 
-static int in_window(struct pack_window *win, unsigned long offset)
+static int in_window(struct pack_window *win, off_t offset)
 {
        /* We must promise at least 20 bytes (one hash) after the
         * offset is available from this window, otherwise the offset
@@ -637,7 +678,7 @@ static int in_window(struct pack_window *win, unsigned long offset)
 
 unsigned char* use_pack(struct packed_git *p,
                struct pack_window **w_cursor,
-               unsigned long offset,
+               off_t offset,
                unsigned int *left)
 {
        struct pack_window *win = *w_cursor;
@@ -662,11 +703,13 @@ unsigned char* use_pack(struct packed_git *p,
                }
                if (!win) {
                        size_t window_align = packed_git_window_size / 2;
+                       off_t len;
                        win = xcalloc(1, sizeof(*win));
                        win->offset = (offset / window_align) * window_align;
-                       win->len = p->pack_size - win->offset;
-                       if (win->len > packed_git_window_size)
-                               win->len = packed_git_window_size;
+                       len = p->pack_size - win->offset;
+                       if (len > packed_git_window_size)
+                               len = packed_git_window_size;
+                       win->len = (size_t)len;
                        pack_mapped += win->len;
                        while (packed_git_limit < pack_mapped
                                && unuse_one_window(p))
@@ -695,41 +738,41 @@ unsigned char* use_pack(struct packed_git *p,
        }
        offset -= win->offset;
        if (left)
-               *left = win->len - offset;
+               *left = win->len - xsize_t(offset);
        return win->base + offset;
 }
 
-struct packed_git *add_packed_git(char *path, int path_len, int local)
+struct packed_git *add_packed_git(const char *path, int path_len, int local)
 {
        struct stat st;
-       struct packed_git *p;
-       unsigned long idx_size;
-       void *idx_map;
-       unsigned char sha1[20];
+       struct packed_git *p = xmalloc(sizeof(*p) + path_len + 2);
 
-       if (check_packed_git_idx(path, &idx_size, &idx_map))
+       /*
+        * Make sure a corresponding .pack file exists and that
+        * the index looks sane.
+        */
+       path_len -= strlen(".idx");
+       if (path_len < 1)
                return NULL;
-
-       /* do we have a corresponding .pack file? */
-       strcpy(path + path_len - 4, ".pack");
-       if (stat(path, &st) || !S_ISREG(st.st_mode)) {
-               munmap(idx_map, idx_size);
+       memcpy(p->pack_name, path, path_len);
+       strcpy(p->pack_name + path_len, ".pack");
+       if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) ||
+           check_packed_git_idx(path, p)) {
+               free(p);
                return NULL;
        }
+
        /* ok, it looks sane as far as we can check without
         * actually mapping the pack file.
         */
-       p = xmalloc(sizeof(*p) + path_len + 2);
-       strcpy(p->pack_name, path);
-       p->index_size = idx_size;
        p->pack_size = st.st_size;
-       p->index_base = idx_map;
        p->next = NULL;
        p->windows = NULL;
        p->pack_fd = -1;
        p->pack_local = local;
-       if ((path_len > 44) && !get_sha1_hex(path + path_len - 44, sha1))
-               hashcpy(p->sha1, sha1);
+       p->mtime = st.st_mtime;
+       if (path_len < 40 || get_sha1_hex(path + path_len - 40, p->sha1))
+               hashclr(p->sha1);
        return p;
 }
 
@@ -739,23 +782,19 @@ struct packed_git *parse_pack_index(unsigned char *sha1)
        return parse_pack_index_file(sha1, path);
 }
 
-struct packed_git *parse_pack_index_file(const unsigned char *sha1, char *idx_path)
+struct packed_git *parse_pack_index_file(const unsigned char *sha1,
+                                        const char *idx_path)
 {
-       struct packed_git *p;
-       unsigned long idx_size;
-       void *idx_map;
-       char *path;
+       const char *path = sha1_pack_name(sha1);
+       struct packed_git *p = xmalloc(sizeof(*p) + strlen(path) + 2);
 
-       if (check_packed_git_idx(idx_path, &idx_size, &idx_map))
+       if (check_packed_git_idx(idx_path, p)) {
+               free(p);
                return NULL;
+       }
 
-       path = sha1_pack_name(sha1);
-
-       p = xmalloc(sizeof(*p) + strlen(path) + 2);
        strcpy(p->pack_name, path);
-       p->index_size = idx_size;
        p->pack_size = 0;
-       p->index_base = idx_map;
        p->next = NULL;
        p->windows = NULL;
        p->pack_fd = -1;
@@ -812,6 +851,60 @@ static void prepare_packed_git_one(char *objdir, int local)
        closedir(dir);
 }
 
+static int sort_pack(const void *a_, const void *b_)
+{
+       struct packed_git *a = *((struct packed_git **)a_);
+       struct packed_git *b = *((struct packed_git **)b_);
+       int st;
+
+       /*
+        * Local packs tend to contain objects specific to our
+        * variant of the project than remote ones.  In addition,
+        * remote ones could be on a network mounted filesystem.
+        * Favor local ones for these reasons.
+        */
+       st = a->pack_local - b->pack_local;
+       if (st)
+               return -st;
+
+       /*
+        * Younger packs tend to contain more recent objects,
+        * and more recent objects tend to get accessed more
+        * often.
+        */
+       if (a->mtime < b->mtime)
+               return 1;
+       else if (a->mtime == b->mtime)
+               return 0;
+       return -1;
+}
+
+static void rearrange_packed_git(void)
+{
+       struct packed_git **ary, *p;
+       int i, n;
+
+       for (n = 0, p = packed_git; p; p = p->next)
+               n++;
+       if (n < 2)
+               return;
+
+       /* prepare an array of packed_git for easier sorting */
+       ary = xcalloc(n, sizeof(struct packed_git *));
+       for (n = 0, p = packed_git; p; p = p->next)
+               ary[n++] = p;
+
+       qsort(ary, n, sizeof(struct packed_git *), sort_pack);
+
+       /* link them back again */
+       for (i = 0; i < n - 1; i++)
+               ary[i]->next = ary[i + 1];
+       ary[n - 1]->next = NULL;
+       packed_git = ary[0];
+
+       free(ary);
+}
+
 static int prepare_packed_git_run_once = 0;
 void prepare_packed_git(void)
 {
@@ -826,6 +919,7 @@ void prepare_packed_git(void)
                prepare_packed_git_one(alt->base, 0);
                alt->name[-1] = '/';
        }
+       rearrange_packed_git();
        prepare_packed_git_run_once = 1;
 }
 
@@ -871,9 +965,9 @@ void *map_sha1_file(const unsigned char *sha1, unsigned long *size)
                 */
                sha1_file_open_flag = 0;
        }
-       map = xmmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+       *size = xsize_t(st.st_size);
+       map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
-       *size = st.st_size;
        return map;
 }
 
@@ -956,26 +1050,50 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon
        return 0;
 }
 
-static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size)
+static void *unpack_sha1_rest(z_stream *stream, void *buffer, unsigned long size, const unsigned char *sha1)
 {
        int bytes = strlen(buffer) + 1;
        unsigned char *buf = xmalloc(1+size);
        unsigned long n;
+       int status = Z_OK;
 
        n = stream->total_out - bytes;
        if (n > size)
                n = size;
        memcpy(buf, (char *) buffer + bytes, n);
        bytes = n;
-       if (bytes < size) {
+       if (bytes <= size) {
+               /*
+                * The above condition must be (bytes <= size), not
+                * (bytes < size).  In other words, even though we
+                * expect no more output and set avail_out to zer0,
+                * the input zlib stream may have bytes that express
+                * "this concludes the stream", and we *do* want to
+                * eat that input.
+                *
+                * Otherwise we would not be able to test that we
+                * consumed all the input to reach the expected size;
+                * we also want to check that zlib tells us that all
+                * went well with status == Z_STREAM_END at the end.
+                */
                stream->next_out = buf + bytes;
                stream->avail_out = size - bytes;
-               while (inflate(stream, Z_FINISH) == Z_OK)
-                       /* nothing */;
+               while (status == Z_OK)
+                       status = inflate(stream, Z_FINISH);
        }
        buf[size] = 0;
-       inflateEnd(stream);
-       return buf;
+       if (status == Z_STREAM_END && !stream->avail_in) {
+               inflateEnd(stream);
+               return buf;
+       }
+
+       if (status < 0)
+               error("corrupt loose object '%s'", sha1_to_hex(sha1));
+       else if (stream->avail_in)
+               error("garbage at end of loose object '%s'",
+                     sha1_to_hex(sha1));
+       free(buf);
+       return NULL;
 }
 
 /*
@@ -1029,7 +1147,7 @@ static int parse_sha1_header(const char *hdr, unsigned long *sizep)
        return *hdr ? -1 : type_from_string(type);
 }
 
-void * unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size)
+static void *unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type, unsigned long *size, const unsigned char *sha1)
 {
        int ret;
        z_stream stream;
@@ -1039,17 +1157,17 @@ void * unpack_sha1_file(void *map, unsigned long mapsize, enum object_type *type
        if (ret < Z_OK || (*type = parse_sha1_header(hdr, size)) < 0)
                return NULL;
 
-       return unpack_sha1_rest(&stream, hdr, *size);
+       return unpack_sha1_rest(&stream, hdr, *size, sha1);
 }
 
-static unsigned long get_delta_base(struct packed_git *p,
+static off_t get_delta_base(struct packed_git *p,
                                    struct pack_window **w_curs,
-                                   unsigned long *curpos,
+                                   off_t *curpos,
                                    enum object_type type,
-                                   unsigned long delta_obj_offset)
+                                   off_t delta_obj_offset)
 {
        unsigned char *base_info = use_pack(p, w_curs, *curpos, NULL);
-       unsigned long base_offset;
+       off_t base_offset;
 
        /* use_pack() assured us we have [base_info, base_info + 20)
         * as a range that we can look at without walking off the
@@ -1063,7 +1181,7 @@ static unsigned long get_delta_base(struct packed_git *p,
                base_offset = c & 127;
                while (c & 128) {
                        base_offset += 1;
-                       if (!base_offset || base_offset & ~(~0UL >> 7))
+                       if (!base_offset || MSB(base_offset, 7))
                                die("offset value overflow for delta base object");
                        c = base_info[used++];
                        base_offset = (base_offset << 7) + (c & 127);
@@ -1085,17 +1203,17 @@ static unsigned long get_delta_base(struct packed_git *p,
 }
 
 /* forward declaration for a mutually recursive function */
-static int packed_object_info(struct packed_git *p, unsigned long offset,
+static int packed_object_info(struct packed_git *p, off_t offset,
                              unsigned long *sizep);
 
 static int packed_delta_info(struct packed_git *p,
                             struct pack_window **w_curs,
-                            unsigned long curpos,
+                            off_t curpos,
                             enum object_type type,
-                            unsigned long obj_offset,
+                            off_t obj_offset,
                             unsigned long *sizep)
 {
-       unsigned long base_offset;
+       off_t base_offset;
 
        base_offset = get_delta_base(p, w_curs, &curpos, type, obj_offset);
        type = packed_object_info(p, base_offset, NULL);
@@ -1145,7 +1263,7 @@ static int packed_delta_info(struct packed_git *p,
 
 static int unpack_object_header(struct packed_git *p,
                                struct pack_window **w_curs,
-                               unsigned long *curpos,
+                               off_t *curpos,
                                unsigned long *sizep)
 {
        unsigned char *base;
@@ -1169,14 +1287,15 @@ static int unpack_object_header(struct packed_git *p,
 }
 
 const char *packed_object_info_detail(struct packed_git *p,
-                                     unsigned long obj_offset,
+                                     off_t obj_offset,
                                      unsigned long *size,
                                      unsigned long *store_size,
                                      unsigned int *delta_chain_length,
                                      unsigned char *base_sha1)
 {
        struct pack_window *w_curs = NULL;
-       unsigned long curpos, dummy;
+       off_t curpos;
+       unsigned long dummy;
        unsigned char *next_sha1;
        enum object_type type;
 
@@ -1200,6 +1319,7 @@ const char *packed_object_info_detail(struct packed_git *p,
                        obj_offset = get_delta_base(p, &w_curs, &curpos, type, obj_offset);
                        if (*delta_chain_length == 0) {
                                /* TODO: find base_sha1 as pointed by curpos */
+                               hashclr(base_sha1);
                        }
                        break;
                case OBJ_REF_DELTA:
@@ -1215,11 +1335,12 @@ const char *packed_object_info_detail(struct packed_git *p,
        }
 }
 
-static int packed_object_info(struct packed_git *p, unsigned long obj_offset,
+static int packed_object_info(struct packed_git *p, off_t obj_offset,
                              unsigned long *sizep)
 {
        struct pack_window *w_curs = NULL;
-       unsigned long size, curpos = obj_offset;
+       unsigned long size;
+       off_t curpos = obj_offset;
        enum object_type type;
 
        type = unpack_object_header(p, &w_curs, &curpos, &size);
@@ -1247,7 +1368,7 @@ static int packed_object_info(struct packed_git *p, unsigned long obj_offset,
 
 static void *unpack_compressed_entry(struct packed_git *p,
                                    struct pack_window **w_curs,
-                                   unsigned long curpos,
+                                   off_t curpos,
                                    unsigned long size)
 {
        int st;
@@ -1276,22 +1397,128 @@ static void *unpack_compressed_entry(struct packed_git *p,
        return buffer;
 }
 
+#define MAX_DELTA_CACHE (256)
+
+static size_t delta_base_cached;
+
+static struct delta_base_cache_lru_list {
+       struct delta_base_cache_lru_list *prev;
+       struct delta_base_cache_lru_list *next;
+} delta_base_cache_lru = { &delta_base_cache_lru, &delta_base_cache_lru };
+
+static struct delta_base_cache_entry {
+       struct delta_base_cache_lru_list lru;
+       void *data;
+       struct packed_git *p;
+       off_t base_offset;
+       unsigned long size;
+       enum object_type type;
+} delta_base_cache[MAX_DELTA_CACHE];
+
+static unsigned long pack_entry_hash(struct packed_git *p, off_t base_offset)
+{
+       unsigned long hash;
+
+       hash = (unsigned long)p + (unsigned long)base_offset;
+       hash += (hash >> 8) + (hash >> 16);
+       return hash % MAX_DELTA_CACHE;
+}
+
+static void *cache_or_unpack_entry(struct packed_git *p, off_t base_offset,
+       unsigned long *base_size, enum object_type *type, int keep_cache)
+{
+       void *ret;
+       unsigned long hash = pack_entry_hash(p, base_offset);
+       struct delta_base_cache_entry *ent = delta_base_cache + hash;
+
+       ret = ent->data;
+       if (ret && ent->p == p && ent->base_offset == base_offset)
+               goto found_cache_entry;
+       return unpack_entry(p, base_offset, type, base_size);
+
+found_cache_entry:
+       if (!keep_cache) {
+               ent->data = NULL;
+               ent->lru.next->prev = ent->lru.prev;
+               ent->lru.prev->next = ent->lru.next;
+               delta_base_cached -= ent->size;
+       }
+       else {
+               ret = xmalloc(ent->size + 1);
+               memcpy(ret, ent->data, ent->size);
+               ((char *)ret)[ent->size] = 0;
+       }
+       *type = ent->type;
+       *base_size = ent->size;
+       return ret;
+}
+
+static inline void release_delta_base_cache(struct delta_base_cache_entry *ent)
+{
+       if (ent->data) {
+               free(ent->data);
+               ent->data = NULL;
+               ent->lru.next->prev = ent->lru.prev;
+               ent->lru.prev->next = ent->lru.next;
+               delta_base_cached -= ent->size;
+       }
+}
+
+static void add_delta_base_cache(struct packed_git *p, off_t base_offset,
+       void *base, unsigned long base_size, enum object_type type)
+{
+       unsigned long hash = pack_entry_hash(p, base_offset);
+       struct delta_base_cache_entry *ent = delta_base_cache + hash;
+       struct delta_base_cache_lru_list *lru;
+
+       release_delta_base_cache(ent);
+       delta_base_cached += base_size;
+
+       for (lru = delta_base_cache_lru.next;
+            delta_base_cached > delta_base_cache_limit
+            && lru != &delta_base_cache_lru;
+            lru = lru->next) {
+               struct delta_base_cache_entry *f = (void *)lru;
+               if (f->type == OBJ_BLOB)
+                       release_delta_base_cache(f);
+       }
+       for (lru = delta_base_cache_lru.next;
+            delta_base_cached > delta_base_cache_limit
+            && lru != &delta_base_cache_lru;
+            lru = lru->next) {
+               struct delta_base_cache_entry *f = (void *)lru;
+               release_delta_base_cache(f);
+       }
+
+       ent->p = p;
+       ent->base_offset = base_offset;
+       ent->type = type;
+       ent->data = base;
+       ent->size = base_size;
+       ent->lru.next = &delta_base_cache_lru;
+       ent->lru.prev = delta_base_cache_lru.prev;
+       delta_base_cache_lru.prev->next = &ent->lru;
+       delta_base_cache_lru.prev = &ent->lru;
+}
+
 static void *unpack_delta_entry(struct packed_git *p,
                                struct pack_window **w_curs,
-                               unsigned long curpos,
+                               off_t curpos,
                                unsigned long delta_size,
-                               unsigned long obj_offset,
+                               off_t obj_offset,
                                enum object_type *type,
                                unsigned long *sizep)
 {
        void *delta_data, *result, *base;
-       unsigned long base_size, base_offset;
+       unsigned long base_size;
+       off_t base_offset;
 
        base_offset = get_delta_base(p, w_curs, &curpos, *type, obj_offset);
-       base = unpack_entry(p, base_offset, type, &base_size);
+       base = cache_or_unpack_entry(p, base_offset, &base_size, type, 0);
        if (!base)
-               die("failed to read delta base object at %lu from %s",
-                   base_offset, p->pack_name);
+               die("failed to read delta base object"
+                   " at %"PRIuMAX" from %s",
+                   (uintmax_t)base_offset, p->pack_name);
 
        delta_data = unpack_compressed_entry(p, w_curs, curpos, delta_size);
        result = patch_delta(base, base_size,
@@ -1300,15 +1527,15 @@ static void *unpack_delta_entry(struct packed_git *p,
        if (!result)
                die("failed to apply delta");
        free(delta_data);
-       free(base);
+       add_delta_base_cache(p, base_offset, base, base_size, *type);
        return result;
 }
 
-void *unpack_entry(struct packed_git *p, unsigned long obj_offset,
+void *unpack_entry(struct packed_git *p, off_t obj_offset,
                   enum object_type *type, unsigned long *sizep)
 {
        struct pack_window *w_curs = NULL;
-       unsigned long curpos = obj_offset;
+       off_t curpos = obj_offset;
        void *data;
 
        *type = unpack_object_header(p, &w_curs, &curpos, sizep);
@@ -1331,35 +1558,60 @@ void *unpack_entry(struct packed_git *p, unsigned long obj_offset,
        return data;
 }
 
-int num_packed_objects(const struct packed_git *p)
+const unsigned char *nth_packed_object_sha1(const struct packed_git *p,
+                                           uint32_t n)
 {
-       /* See check_packed_git_idx() */
-       return (p->index_size - 20 - 20 - 4*256) / 24;
+       const unsigned char *index = p->index_data;
+       if (n >= p->num_objects)
+               return NULL;
+       index += 4 * 256;
+       if (p->index_version == 1) {
+               return index + 24 * n + 4;
+       } else {
+               index += 8;
+               return index + 20 * n;
+       }
 }
 
-int nth_packed_object_sha1(const struct packed_git *p, int n,
-                          unsigned char* sha1)
+static off_t nth_packed_object_offset(const struct packed_git *p, uint32_t n)
 {
-       void *index = p->index_base + 256;
-       if (n < 0 || num_packed_objects(p) <= n)
-               return -1;
-       hashcpy(sha1, (unsigned char *) index + (24 * n) + 4);
-       return 0;
+       const unsigned char *index = p->index_data;
+       index += 4 * 256;
+       if (p->index_version == 1) {
+               return ntohl(*((uint32_t *)(index + 24 * n)));
+       } else {
+               uint32_t off;
+               index += 8 + p->num_objects * (20 + 4);
+               off = ntohl(*((uint32_t *)(index + 4 * n)));
+               if (!(off & 0x80000000))
+                       return off;
+               index += p->num_objects * 4 + (off & 0x7fffffff) * 8;
+               return (((uint64_t)ntohl(*((uint32_t *)(index + 0)))) << 32) |
+                                  ntohl(*((uint32_t *)(index + 4)));
+       }
 }
 
-unsigned long find_pack_entry_one(const unsigned char *sha1,
+off_t find_pack_entry_one(const unsigned char *sha1,
                                  struct packed_git *p)
 {
-       uint32_t *level1_ofs = p->index_base;
-       int hi = ntohl(level1_ofs[*sha1]);
-       int lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
-       void *index = p->index_base + 256;
+       const uint32_t *level1_ofs = p->index_data;
+       const unsigned char *index = p->index_data;
+       unsigned hi, lo;
+
+       if (p->index_version > 1) {
+               level1_ofs += 2;
+               index += 8;
+       }
+       index += 4 * 256;
+       hi = ntohl(level1_ofs[*sha1]);
+       lo = ((*sha1 == 0x0) ? 0 : ntohl(level1_ofs[*sha1 - 1]));
 
        do {
-               int mi = (lo + hi) / 2;
-               int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1);
+               unsigned mi = (lo + hi) / 2;
+               unsigned x = (p->index_version > 1) ? (mi * 20) : (mi * 24 + 4);
+               int cmp = hashcmp(index + x, sha1);
                if (!cmp)
-                       return ntohl(*((uint32_t *)((char *)index + (24 * mi))));
+                       return nth_packed_object_offset(p, mi);
                if (cmp > 0)
                        hi = mi;
                else
@@ -1389,7 +1641,7 @@ static int matches_pack_name(struct packed_git *p, const char *ig)
 static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed)
 {
        struct packed_git *p;
-       unsigned long offset;
+       off_t offset;
 
        prepare_packed_git();
 
@@ -1481,7 +1733,7 @@ static void *read_packed_sha1(const unsigned char *sha1,
        if (!find_pack_entry(sha1, &e, NULL))
                return NULL;
        else
-               return unpack_entry(e.p, e.offset, type, size);
+               return cache_or_unpack_entry(e.p, e.offset, size, type, 1);
 }
 
 /*
@@ -1555,7 +1807,7 @@ void *read_sha1_file(const unsigned char *sha1, enum object_type *type,
                return buf;
        map = map_sha1_file(sha1, &mapsize);
        if (map) {
-               buf = unpack_sha1_file(map, mapsize, type, size);
+               buf = unpack_sha1_file(map, mapsize, type, size, sha1);
                munmap(map, mapsize);
                return buf;
        }
@@ -1610,7 +1862,7 @@ void *read_object_with_reference(const unsigned char *sha1,
        }
 }
 
-static void write_sha1_file_prepare(void *buf, unsigned long len,
+static void write_sha1_file_prepare(const void *buf, unsigned long len,
                                     const char *type, unsigned char *sha1,
                                     char *hdr, int *hdrlen)
 {
@@ -1738,7 +1990,7 @@ static void setup_object_header(z_stream *stream, const char *type, unsigned lon
        stream->avail_out -= hdrlen;
 }
 
-int hash_sha1_file(void *buf, unsigned long len, const char *type,
+int hash_sha1_file(const void *buf, unsigned long len, const char *type,
                    unsigned char *sha1)
 {
        char hdr[32];
@@ -1749,7 +2001,7 @@ int hash_sha1_file(void *buf, unsigned long len, const char *type,
 
 int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
 {
-       int size;
+       int size, ret;
        unsigned char *compressed;
        z_stream stream;
        unsigned char sha1[20];
@@ -1781,7 +2033,7 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
                return error("sha1 file %s: %s\n", filename, strerror(errno));
        }
 
-       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+       snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_obj_XXXXXX", get_object_directory());
 
        fd = mkstemp(tmpfile);
        if (fd < 0) {
@@ -1809,15 +2061,21 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha
        /* Then the data itself.. */
        stream.next_in = buf;
        stream.avail_in = len;
-       while (deflate(&stream, Z_FINISH) == Z_OK)
-               /* nothing */;
-       deflateEnd(&stream);
+       ret = deflate(&stream, Z_FINISH);
+       if (ret != Z_STREAM_END)
+               die("unable to deflate new object %s (%d)", sha1_to_hex(sha1), ret);
+
+       ret = deflateEnd(&stream);
+       if (ret != Z_OK)
+               die("deflateEnd on object %s failed (%d)", sha1_to_hex(sha1), ret);
+
        size = stream.total_out;
 
        if (write_buffer(fd, compressed, size) < 0)
                die("unable to write sha1 file");
        fchmod(fd, 0444);
-       close(fd);
+       if (close(fd))
+               die("unable to write sha1 file");
        free(compressed);
 
        return move_temp_to_file(tmpfile, filename);
@@ -1902,7 +2160,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        int ret;
        SHA_CTX c;
 
-       snprintf(tmpfile, sizeof(tmpfile), "%s/obj_XXXXXX", get_object_directory());
+       snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_obj_XXXXXX", get_object_directory());
 
        local = mkstemp(tmpfile);
        if (local < 0) {
@@ -1951,7 +2209,9 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer,
        } while (1);
        inflateEnd(&stream);
 
-       close(local);
+       fchmod(local, 0444);
+       if (close(local) != 0)
+               die("unable to write sha1 file");
        SHA1_Final(real_sha1, &c);
        if (ret != Z_STREAM_END) {
                unlink(tmpfile);
@@ -2056,11 +2316,10 @@ int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object)
 int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object,
             enum object_type type, const char *path)
 {
-       unsigned long size = st->st_size;
-       void *buf;
+       size_t size = xsize_t(st->st_size);
+       void *buf = NULL;
        int ret, re_allocated = 0;
 
-       buf = "";
        if (size)
                buf = xmmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
        close(fd);
@@ -2100,6 +2359,7 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
 {
        int fd;
        char *target;
+       size_t len;
 
        switch (st->st_mode & S_IFMT) {
        case S_IFREG:
@@ -2112,16 +2372,17 @@ int index_path(unsigned char *sha1, const char *path, struct stat *st, int write
                                     path);
                break;
        case S_IFLNK:
-               target = xmalloc(st->st_size+1);
-               if (readlink(path, target, st->st_size+1) != st->st_size) {
+               len = xsize_t(st->st_size);
+               target = xmalloc(len + 1);
+               if (readlink(path, target, len + 1) != st->st_size) {
                        char *errstr = strerror(errno);
                        free(target);
                        return error("readlink(\"%s\"): %s", path,
                                     errstr);
                }
                if (!write_object)
-                       hash_sha1_file(target, st->st_size, blob_type, sha1);
-               else if (write_sha1_file(target, st->st_size, blob_type, sha1))
+                       hash_sha1_file(target, len, blob_type, sha1);
+               else if (write_sha1_file(target, len, blob_type, sha1))
                        return error("%s: failed to insert into database",
                                     path);
                free(target);