git-daemon: produce output when ready
[gitweb.git] / builtin / index-pack.c
index b4cf8c53e0ebbee65a0e4bc0ac1afd1173d1b8e8..af7dc37a441d12bc4283ac730524e07a582d014f 100644 (file)
@@ -1,4 +1,4 @@
-#include "cache.h"
+#include "builtin.h"
 #include "delta.h"
 #include "pack.h"
 #include "csum-file.h"
 #include "exec_cmd.h"
 
 static const char index_pack_usage[] =
-"git index-pack [-v] [-o <index-file>] [{ ---keep | --keep=<msg> }] [--strict] { <pack-file> | --stdin [--fix-thin] [<pack-file>] }";
+"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])";
 
-struct object_entry
-{
+struct object_entry {
        struct pack_idx_entry idx;
        unsigned long size;
        unsigned int hdr_size;
        enum object_type type;
        enum object_type real_type;
+       unsigned delta_depth;
+       int base_object_no;
 };
 
 union delta_base {
@@ -44,8 +45,7 @@ struct base_data {
 #define FLAG_LINK (1u<<20)
 #define FLAG_CHECKED (1u<<21)
 
-struct delta_entry
-{
+struct delta_entry {
        union delta_base base;
        int obj_no;
 };
@@ -68,6 +68,7 @@ static struct progress *progress;
 static unsigned char input_buffer[4096];
 static unsigned int input_offset, input_len;
 static off_t consumed_bytes;
+static unsigned deepest_delta;
 static git_SHA_CTX input_ctx;
 static uint32_t input_crc32;
 static int input_fd, output_fd, pack_fd;
@@ -161,7 +162,7 @@ static void use(int bytes)
        input_offset += bytes;
 
        /* make sure off_t is sufficiently large not to wrap */
-       if (consumed_bytes > consumed_bytes + bytes)
+       if (signed_add_overflows(consumed_bytes, bytes))
                die("pack too large for current definition of off_t");
        consumed_bytes += bytes;
 }
@@ -171,10 +172,10 @@ static const char *open_pack_file(const char *pack_name)
        if (from_stdin) {
                input_fd = 0;
                if (!pack_name) {
-                       static char tmpfile[PATH_MAX];
-                       output_fd = odb_mkstemp(tmpfile, sizeof(tmpfile),
+                       static char tmp_file[PATH_MAX];
+                       output_fd = odb_mkstemp(tmp_file, sizeof(tmp_file),
                                                "pack/tmp_pack_XXXXXX");
-                       pack_name = xstrdup(tmpfile);
+                       pack_name = xstrdup(tmp_file);
                } else
                        output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600);
                if (output_fd < 0)
@@ -209,7 +210,7 @@ static void parse_pack_header(void)
 static NORETURN void bad_object(unsigned long offset, const char *format,
                       ...) __attribute__((format (printf, 2, 3)));
 
-static void bad_object(unsigned long offset, const char *format, ...)
+static NORETURN void bad_object(unsigned long offset, const char *format, ...)
 {
        va_list params;
        char buf[1024];
@@ -266,26 +267,23 @@ static void unlink_base_data(struct base_data *c)
 
 static void *unpack_entry_data(unsigned long offset, unsigned long size)
 {
-       z_stream stream;
+       int status;
+       git_zstream stream;
        void *buf = xmalloc(size);
 
        memset(&stream, 0, sizeof(stream));
+       git_inflate_init(&stream);
        stream.next_out = buf;
        stream.avail_out = size;
-       stream.next_in = fill(1);
-       stream.avail_in = input_len;
-       git_inflate_init(&stream);
 
-       for (;;) {
-               int ret = git_inflate(&stream, 0);
-               use(input_len - stream.avail_in);
-               if (stream.total_out == size && ret == Z_STREAM_END)
-                       break;
-               if (ret != Z_OK)
-                       bad_object(offset, "inflate returned %d", ret);
+       do {
                stream.next_in = fill(1);
                stream.avail_in = input_len;
-       }
+               status = git_inflate(&stream, 0);
+               use(input_len - stream.avail_in);
+       } while (status == Z_OK);
+       if (stream.total_out != size || status != Z_STREAM_END)
+               bad_object(offset, "inflate returned %d", status);
        git_inflate_end(&stream);
        return buf;
 }
@@ -299,7 +297,7 @@ static void *unpack_raw_entry(struct object_entry *obj, union delta_base *delta_
        void *data;
 
        obj->idx.offset = consumed_bytes;
-       input_crc32 = crc32(0, Z_NULL, 0);
+       input_crc32 = crc32(0, NULL, 0);
 
        p = fill(1);
        c = *p;
@@ -359,38 +357,53 @@ static void *get_data_from_pack(struct object_entry *obj)
 {
        off_t from = obj[0].idx.offset + obj[0].hdr_size;
        unsigned long len = obj[1].idx.offset - from;
-       unsigned long rdy = 0;
-       unsigned char *src, *data;
-       z_stream stream;
-       int st;
+       unsigned char *data, *inbuf;
+       git_zstream stream;
+       int status;
 
-       src = xmalloc(len);
-       data = src;
-       do {
-               ssize_t n = pread(pack_fd, data + rdy, len - rdy, from + rdy);
-               if (n < 0)
-                       die_errno("cannot pread pack file");
-               if (!n)
-                       die("premature end of pack file, %lu bytes missing",
-                           len - rdy);
-               rdy += n;
-       } while (rdy < len);
        data = xmalloc(obj->size);
+       inbuf = xmalloc((len < 64*1024) ? len : 64*1024);
+
        memset(&stream, 0, sizeof(stream));
+       git_inflate_init(&stream);
        stream.next_out = data;
        stream.avail_out = obj->size;
-       stream.next_in = src;
-       stream.avail_in = len;
-       git_inflate_init(&stream);
-       while ((st = git_inflate(&stream, Z_FINISH)) == Z_OK);
-       git_inflate_end(&stream);
-       if (st != Z_STREAM_END || stream.total_out != obj->size)
+
+       do {
+               ssize_t n = (len < 64*1024) ? len : 64*1024;
+               n = pread(pack_fd, inbuf, n, from);
+               if (n < 0)
+                       die_errno("cannot pread pack file");
+               if (!n)
+                       die("premature end of pack file, %lu bytes missing", len);
+               from += n;
+               len -= n;
+               stream.next_in = inbuf;
+               stream.avail_in = n;
+               status = git_inflate(&stream, 0);
+       } while (len && status == Z_OK && !stream.avail_in);
+
+       /* This has been inflated OK when first encountered, so... */
+       if (status != Z_STREAM_END || stream.total_out != obj->size)
                die("serious inflate inconsistency");
-       free(src);
+
+       git_inflate_end(&stream);
+       free(inbuf);
        return data;
 }
 
-static int find_delta(const union delta_base *base)
+static int compare_delta_bases(const union delta_base *base1,
+                              const union delta_base *base2,
+                              enum object_type type1,
+                              enum object_type type2)
+{
+       int cmp = type1 - type2;
+       if (cmp)
+               return cmp;
+       return memcmp(base1, base2, UNION_BASE_SZ);
+}
+
+static int find_delta(const union delta_base *base, enum object_type type)
 {
        int first = 0, last = nr_deltas;
 
@@ -399,7 +412,8 @@ static int find_delta(const union delta_base *base)
                 struct delta_entry *delta = &deltas[next];
                 int cmp;
 
-                cmp = memcmp(base, &delta->base, UNION_BASE_SZ);
+               cmp = compare_delta_bases(base, &delta->base,
+                                         type, objects[delta->obj_no].type);
                 if (!cmp)
                         return next;
                 if (cmp < 0) {
@@ -412,9 +426,10 @@ static int find_delta(const union delta_base *base)
 }
 
 static void find_delta_children(const union delta_base *base,
-                               int *first_index, int *last_index)
+                               int *first_index, int *last_index,
+                               enum object_type type)
 {
-       int first = find_delta(base);
+       int first = find_delta(base, type);
        int last = first;
        int end = nr_deltas - 1;
 
@@ -484,12 +499,17 @@ static void sha1_object(const void *data, unsigned long size,
        }
 }
 
+static int is_delta_type(enum object_type type)
+{
+       return (type == OBJ_REF_DELTA || type == OBJ_OFS_DELTA);
+}
+
 static void *get_base_data(struct base_data *c)
 {
        if (!c->data) {
                struct object_entry *obj = c->obj;
 
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+               if (is_delta_type(obj->type)) {
                        void *base = get_base_data(c->base);
                        void *raw = get_data_from_pack(obj);
                        c->data = patch_delta(
@@ -516,6 +536,10 @@ static void resolve_delta(struct object_entry *delta_obj,
        void *base_data, *delta_data;
 
        delta_obj->real_type = base->obj->real_type;
+       delta_obj->delta_depth = base->obj->delta_depth + 1;
+       if (deepest_delta < delta_obj->delta_depth)
+               deepest_delta = delta_obj->delta_depth;
+       delta_obj->base_object_no = base->obj - objects;
        delta_data = get_data_from_pack(delta_obj);
        base_data = get_base_data(base);
        result->obj = delta_obj;
@@ -542,11 +566,13 @@ static void find_unresolved_deltas(struct base_data *base,
                union delta_base base_spec;
 
                hashcpy(base_spec.sha1, base->obj->idx.sha1);
-               find_delta_children(&base_spec, &ref_first, &ref_last);
+               find_delta_children(&base_spec,
+                                   &ref_first, &ref_last, OBJ_REF_DELTA);
 
                memset(&base_spec, 0, sizeof(base_spec));
                base_spec.offset = base->obj->idx.offset;
-               find_delta_children(&base_spec, &ofs_first, &ofs_last);
+               find_delta_children(&base_spec,
+                                   &ofs_first, &ofs_last, OBJ_OFS_DELTA);
        }
 
        if (ref_last == -1 && ofs_last == -1) {
@@ -558,24 +584,24 @@ static void find_unresolved_deltas(struct base_data *base,
 
        for (i = ref_first; i <= ref_last; i++) {
                struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_REF_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ref_last && ofs_last == -1)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
+               struct base_data result;
+
+               assert(child->real_type == OBJ_REF_DELTA);
+               resolve_delta(child, base, &result);
+               if (i == ref_last && ofs_last == -1)
+                       free_base_data(base);
+               find_unresolved_deltas(&result, base);
        }
 
        for (i = ofs_first; i <= ofs_last; i++) {
                struct object_entry *child = objects + deltas[i].obj_no;
-               if (child->real_type == OBJ_OFS_DELTA) {
-                       struct base_data result;
-                       resolve_delta(child, base, &result);
-                       if (i == ofs_last)
-                               free_base_data(base);
-                       find_unresolved_deltas(&result, base);
-               }
+               struct base_data result;
+
+               assert(child->real_type == OBJ_OFS_DELTA);
+               resolve_delta(child, base, &result);
+               if (i == ofs_last)
+                       free_base_data(base);
+               find_unresolved_deltas(&result, base);
        }
 
        unlink_base_data(base);
@@ -585,7 +611,11 @@ static int compare_delta_entry(const void *a, const void *b)
 {
        const struct delta_entry *delta_a = a;
        const struct delta_entry *delta_b = b;
-       return memcmp(&delta_a->base, &delta_b->base, UNION_BASE_SZ);
+
+       /* group by type (ref vs ofs) and then by value (sha-1 or offset) */
+       return compare_delta_bases(&delta_a->base, &delta_b->base,
+                                  objects[delta_a->obj_no].type,
+                                  objects[delta_b->obj_no].type);
 }
 
 /* Parse all objects and return the pack content SHA1 hash */
@@ -609,7 +639,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                void *data = unpack_raw_entry(obj, &delta->base);
                obj->real_type = obj->type;
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA) {
+               if (is_delta_type(obj->type)) {
                        nr_deltas++;
                        delta->obj_no = i;
                        delta++;
@@ -656,7 +686,7 @@ static void parse_pack_objects(unsigned char *sha1)
                struct object_entry *obj = &objects[i];
                struct base_data base_obj;
 
-               if (obj->type == OBJ_REF_DELTA || obj->type == OBJ_OFS_DELTA)
+               if (is_delta_type(obj->type))
                        continue;
                base_obj.obj = obj;
                base_obj.data = NULL;
@@ -667,26 +697,26 @@ static void parse_pack_objects(unsigned char *sha1)
 
 static int write_compressed(struct sha1file *f, void *in, unsigned int size)
 {
-       z_stream stream;
-       unsigned long maxsize;
-       void *out;
+       git_zstream stream;
+       int status;
+       unsigned char outbuf[4096];
 
        memset(&stream, 0, sizeof(stream));
-       deflateInit(&stream, zlib_compression_level);
-       maxsize = deflateBound(&stream, size);
-       out = xmalloc(maxsize);
-
-       /* Compress it */
+       git_deflate_init(&stream, zlib_compression_level);
        stream.next_in = in;
        stream.avail_in = size;
-       stream.next_out = out;
-       stream.avail_out = maxsize;
-       while (deflate(&stream, Z_FINISH) == Z_OK);
-       deflateEnd(&stream);
 
+       do {
+               stream.next_out = outbuf;
+               stream.avail_out = sizeof(outbuf);
+               status = git_deflate(&stream, Z_FINISH);
+               sha1write(f, outbuf, sizeof(outbuf) - stream.avail_out);
+       } while (status == Z_OK);
+
+       if (status != Z_STREAM_END)
+               die("unable to deflate appended object (%d)", status);
        size = stream.total_out;
-       sha1write(f, out, size);
-       free(out);
+       git_deflate_end(&stream);
        return size;
 }
 
@@ -860,48 +890,148 @@ static void final(const char *final_pack_name, const char *curr_pack_name,
 
 static int git_index_pack_config(const char *k, const char *v, void *cb)
 {
+       struct pack_idx_option *opts = cb;
+
        if (!strcmp(k, "pack.indexversion")) {
-               pack_idx_default_version = git_config_int(k, v);
-               if (pack_idx_default_version > 2)
-                       die("bad pack.indexversion=%"PRIu32,
-                               pack_idx_default_version);
+               opts->version = git_config_int(k, v);
+               if (opts->version > 2)
+                       die("bad pack.indexversion=%"PRIu32, opts->version);
                return 0;
        }
        return git_default_config(k, v, cb);
 }
 
+static int cmp_uint32(const void *a_, const void *b_)
+{
+       uint32_t a = *((uint32_t *)a_);
+       uint32_t b = *((uint32_t *)b_);
+
+       return (a < b) ? -1 : (a != b);
+}
+
+static void read_v2_anomalous_offsets(struct packed_git *p,
+                                     struct pack_idx_option *opts)
+{
+       const uint32_t *idx1, *idx2;
+       uint32_t i;
+
+       /* The address of the 4-byte offset table */
+       idx1 = (((const uint32_t *)p->index_data)
+               + 2 /* 8-byte header */
+               + 256 /* fan out */
+               + 5 * p->num_objects /* 20-byte SHA-1 table */
+               + p->num_objects /* CRC32 table */
+               );
+
+       /* The address of the 8-byte offset table */
+       idx2 = idx1 + p->num_objects;
+
+       for (i = 0; i < p->num_objects; i++) {
+               uint32_t off = ntohl(idx1[i]);
+               if (!(off & 0x80000000))
+                       continue;
+               off = off & 0x7fffffff;
+               if (idx2[off * 2])
+                       continue;
+               /*
+                * The real offset is ntohl(idx2[off * 2]) in high 4
+                * octets, and ntohl(idx2[off * 2 + 1]) in low 4
+                * octets.  But idx2[off * 2] is Zero!!!
+                */
+               ALLOC_GROW(opts->anomaly, opts->anomaly_nr + 1, opts->anomaly_alloc);
+               opts->anomaly[opts->anomaly_nr++] = ntohl(idx2[off * 2 + 1]);
+       }
+
+       if (1 < opts->anomaly_nr)
+               qsort(opts->anomaly, opts->anomaly_nr, sizeof(uint32_t), cmp_uint32);
+}
+
+static void read_idx_option(struct pack_idx_option *opts, const char *pack_name)
+{
+       struct packed_git *p = add_packed_git(pack_name, strlen(pack_name), 1);
+
+       if (!p)
+               die("Cannot open existing pack file '%s'", pack_name);
+       if (open_pack_index(p))
+               die("Cannot open existing pack idx file for '%s'", pack_name);
+
+       /* Read the attributes from the existing idx file */
+       opts->version = p->index_version;
+
+       if (opts->version == 2)
+               read_v2_anomalous_offsets(p, opts);
+
+       /*
+        * Get rid of the idx file as we do not need it anymore.
+        * NEEDSWORK: extract this bit from free_pack_by_name() in
+        * sha1_file.c, perhaps?  It shouldn't matter very much as we
+        * know we haven't installed this pack (hence we never have
+        * read anything from it).
+        */
+       close_pack_index(p);
+       free(p);
+}
+
+static void show_pack_info(int stat_only)
+{
+       int i, baseobjects = nr_objects - nr_deltas;
+       unsigned long *chain_histogram = NULL;
+
+       if (deepest_delta)
+               chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long));
+
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *obj = &objects[i];
+
+               if (is_delta_type(obj->type))
+                       chain_histogram[obj->delta_depth - 1]++;
+               if (stat_only)
+                       continue;
+               printf("%s %-6s %lu %lu %"PRIuMAX,
+                      sha1_to_hex(obj->idx.sha1),
+                      typename(obj->real_type), obj->size,
+                      (unsigned long)(obj[1].idx.offset - obj->idx.offset),
+                      (uintmax_t)obj->idx.offset);
+               if (is_delta_type(obj->type)) {
+                       struct object_entry *bobj = &objects[obj->base_object_no];
+                       printf(" %u %s", obj->delta_depth, sha1_to_hex(bobj->idx.sha1));
+               }
+               putchar('\n');
+       }
+
+       if (baseobjects)
+               printf("non delta: %d object%s\n",
+                      baseobjects, baseobjects > 1 ? "s" : "");
+       for (i = 0; i < deepest_delta; i++) {
+               if (!chain_histogram[i])
+                       continue;
+               printf("chain length = %d: %lu object%s\n",
+                      i + 1,
+                      chain_histogram[i],
+                      chain_histogram[i] > 1 ? "s" : "");
+       }
+}
+
 int cmd_index_pack(int argc, const char **argv, const char *prefix)
 {
-       int i, fix_thin_pack = 0;
+       int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
        const char *curr_pack, *curr_index;
        const char *index_name = NULL, *pack_name = NULL;
        const char *keep_name = NULL, *keep_msg = NULL;
        char *index_name_buf = NULL, *keep_name_buf = NULL;
        struct pack_idx_entry **idx_objects;
+       struct pack_idx_option opts;
        unsigned char pack_sha1[20];
 
        if (argc == 2 && !strcmp(argv[1], "-h"))
                usage(index_pack_usage);
 
-       /*
-        * We wish to read the repository's config file if any, and
-        * for that it is necessary to call setup_git_directory_gently().
-        * However if the cwd was inside .git/objects/pack/ then we need
-        * to go back there or all the pack name arguments will be wrong.
-        * And in that case we cannot rely on any prefix returned by
-        * setup_git_directory_gently() either.
-        */
-       {
-               char cwd[PATH_MAX+1];
-               int nongit;
-
-               if (!getcwd(cwd, sizeof(cwd)-1))
-                       die("Unable to get current working directory");
-               setup_git_directory_gently(&nongit);
-               git_config(git_index_pack_config, NULL);
-               if (chdir(cwd))
-                       die("Cannot come back to cwd");
-       }
+       read_replace_refs = 0;
+
+       reset_pack_idx_option(&opts);
+       git_config(git_index_pack_config, &opts);
+       if (prefix && chdir(prefix))
+               die("Cannot come back to cwd");
 
        for (i = 1; i < argc; i++) {
                const char *arg = argv[i];
@@ -913,6 +1043,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                fix_thin_pack = 1;
                        } else if (!strcmp(arg, "--strict")) {
                                strict = 1;
+                       } else if (!strcmp(arg, "--verify")) {
+                               verify = 1;
+                       } else if (!strcmp(arg, "--verify-stat")) {
+                               verify = 1;
+                               stat = 1;
+                       } else if (!strcmp(arg, "--verify-stat-only")) {
+                               verify = 1;
+                               stat = 1;
+                               stat_only = 1;
                        } else if (!strcmp(arg, "--keep")) {
                                keep_msg = "";
                        } else if (!prefixcmp(arg, "--keep=")) {
@@ -938,12 +1077,12 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                                index_name = argv[++i];
                        } else if (!prefixcmp(arg, "--index-version=")) {
                                char *c;
-                               pack_idx_default_version = strtoul(arg + 16, &c, 10);
-                               if (pack_idx_default_version > 2)
+                               opts.version = strtoul(arg + 16, &c, 10);
+                               if (opts.version > 2)
                                        die("bad %s", arg);
                                if (*c == ',')
-                                       pack_idx_off32_limit = strtoul(c+1, &c, 0);
-                               if (*c || pack_idx_off32_limit & 0x80000000)
+                                       opts.off32_limit = strtoul(c+1, &c, 0);
+                               if (*c || opts.off32_limit & 0x80000000)
                                        die("bad %s", arg);
                        } else
                                usage(index_pack_usage);
@@ -979,11 +1118,19 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
                strcpy(keep_name_buf + len - 5, ".keep");
                keep_name = keep_name_buf;
        }
+       if (verify) {
+               if (!index_name)
+                       die("--verify with no packfile name given");
+               read_idx_option(&opts, index_name);
+               opts.flags |= WRITE_IDX_VERIFY | WRITE_IDX_STRICT;
+       }
+       if (strict)
+               opts.flags |= WRITE_IDX_STRICT;
 
        curr_pack = open_pack_file(pack_name);
        parse_pack_header();
-       objects = xmalloc((nr_objects + 1) * sizeof(struct object_entry));
-       deltas = xmalloc(nr_objects * sizeof(struct delta_entry));
+       objects = xcalloc(nr_objects + 1, sizeof(struct object_entry));
+       deltas = xcalloc(nr_objects, sizeof(struct delta_entry));
        parse_pack_objects(pack_sha1);
        if (nr_deltas == nr_resolved_deltas) {
                stop_progress(&progress);
@@ -1023,16 +1170,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix)
        if (strict)
                check_objects();
 
+       if (stat)
+               show_pack_info(stat_only);
+
        idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
        for (i = 0; i < nr_objects; i++)
                idx_objects[i] = &objects[i].idx;
-       curr_index = write_idx_file(index_name, idx_objects, nr_objects, pack_sha1);
+       curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_sha1);
        free(idx_objects);
 
-       final(pack_name, curr_pack,
-               index_name, curr_index,
-               keep_name, keep_msg,
-               pack_sha1);
+       if (!verify)
+               final(pack_name, curr_pack,
+                     index_name, curr_index,
+                     keep_name, keep_msg,
+                     pack_sha1);
+       else
+               close(input_fd);
        free(objects);
        free(index_name_buf);
        free(keep_name_buf);