t9700: remove useless check
[gitweb.git] / builtin-unpack-objects.c
index f0ae5c987af79bc5254df7cd3587ed1852e63209..a8918666655bb91f952ccdac18715bd9ba4a09f2 100644 (file)
@@ -7,36 +7,64 @@
 #include "commit.h"
 #include "tag.h"
 #include "tree.h"
+#include "tree-walk.h"
+#include "progress.h"
+#include "decorate.h"
+#include "fsck.h"
 
-#include <sys/time.h>
-
-static int dry_run, quiet;
-static const char unpack_usage[] = "git-unpack-objects [-n] [-q] < pack-file";
+static int dry_run, quiet, recover, has_errors, strict;
+static const char unpack_usage[] = "git-unpack-objects [-n] [-q] [-r] [--strict] < pack-file";
 
 /* We always read in 4kB chunks. */
 static unsigned char buffer[4096];
-static unsigned long offset, len, eof;
+static unsigned int offset, len;
+static off_t consumed_bytes;
 static SHA_CTX ctx;
 
+/*
+ * When running under --strict mode, objects whose reachability are
+ * suspect are kept in core without getting written in the object
+ * store.
+ */
+struct obj_buffer {
+       char *buffer;
+       unsigned long size;
+};
+
+static struct decoration obj_decorate;
+
+static struct obj_buffer *lookup_object_buffer(struct object *base)
+{
+       return lookup_decoration(&obj_decorate, base);
+}
+
+static void add_object_buffer(struct object *object, char *buffer, unsigned long size)
+{
+       struct obj_buffer *obj;
+       obj = xcalloc(1, sizeof(struct obj_buffer));
+       obj->buffer = buffer;
+       obj->size = size;
+       if (add_decoration(&obj_decorate, object, obj))
+               die("object %s tried to add buffer twice!", sha1_to_hex(object->sha1));
+}
+
 /*
  * Make sure at least "min" bytes are available in the buffer, and
  * return the pointer to the buffer.
  */
-static void * fill(int min)
+static void *fill(int min)
 {
        if (min <= len)
                return buffer + offset;
-       if (eof)
-               die("unable to fill input");
        if (min > sizeof(buffer))
                die("cannot fill %d bytes", min);
        if (offset) {
                SHA1_Update(&ctx, buffer, offset);
-               memcpy(buffer, buffer + offset, len);
+               memmove(buffer, buffer + offset, len);
                offset = 0;
        }
        do {
-               int ret = xread(0, buffer + len, sizeof(buffer) - len);
+               ssize_t ret = xread(0, buffer + len, sizeof(buffer) - len);
                if (ret <= 0) {
                        if (!ret)
                                die("early EOF");
@@ -53,6 +81,11 @@ static void use(int bytes)
                die("used more bytes than were available");
        len -= bytes;
        offset += bytes;
+
+       /* make sure off_t is sufficiently large not to wrap */
+       if (consumed_bytes > consumed_bytes + bytes)
+               die("pack too large for current definition of off_t");
+       consumed_bytes += bytes;
 }
 
 static void *get_data(unsigned long size)
@@ -73,8 +106,15 @@ static void *get_data(unsigned long size)
                use(len - stream.avail_in);
                if (stream.total_out == size && ret == Z_STREAM_END)
                        break;
-               if (ret != Z_OK)
-                       die("inflate returned %d\n", ret);
+               if (ret != Z_OK) {
+                       error("inflate returned %d\n", ret);
+                       free(buf);
+                       buf = NULL;
+                       if (!recover)
+                               exit(1);
+                       has_errors = 1;
+                       break;
+               }
                stream.next_in = fill(1);
                stream.avail_in = len;
        }
@@ -84,6 +124,8 @@ static void *get_data(unsigned long size)
 
 struct delta_info {
        unsigned char base_sha1[20];
+       unsigned nr;
+       off_t base_offset;
        unsigned long size;
        void *delta;
        struct delta_info *next;
@@ -91,30 +133,133 @@ struct delta_info {
 
 static struct delta_info *delta_list;
 
-static void add_delta_to_list(unsigned char *base_sha1, void *delta, unsigned long size)
+static void add_delta_to_list(unsigned nr, unsigned const char *base_sha1,
+                             off_t base_offset,
+                             void *delta, unsigned long size)
 {
        struct delta_info *info = xmalloc(sizeof(*info));
 
-       memcpy(info->base_sha1, base_sha1, 20);
+       hashcpy(info->base_sha1, base_sha1);
+       info->base_offset = base_offset;
        info->size = size;
        info->delta = delta;
+       info->nr = nr;
        info->next = delta_list;
        delta_list = info;
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size);
+struct obj_info {
+       off_t offset;
+       unsigned char sha1[20];
+       struct object *obj;
+};
+
+#define FLAG_OPEN (1u<<20)
+#define FLAG_WRITTEN (1u<<21)
+
+static struct obj_info *obj_list;
+unsigned nr_objects;
 
-static void write_object(void *buf, unsigned long size, const char *type)
+/*
+ * Called only from check_object() after it verified this object
+ * is Ok.
+ */
+static void write_cached_object(struct object *obj)
 {
        unsigned char sha1[20];
-       if (write_sha1_file(buf, size, type, sha1) < 0)
-               die("failed to write object");
-       added_object(sha1, type, buf, size);
+       struct obj_buffer *obj_buf = lookup_object_buffer(obj);
+       if (write_sha1_file(obj_buf->buffer, obj_buf->size, typename(obj->type), sha1) < 0)
+               die("failed to write object %s", sha1_to_hex(obj->sha1));
+       obj->flags |= FLAG_WRITTEN;
+}
+
+/*
+ * At the very end of the processing, write_rest() scans the objects
+ * that have reachability requirements and calls this function.
+ * Verify its reachability and validity recursively and write it out.
+ */
+static int check_object(struct object *obj, int type, void *data)
+{
+       if (!obj)
+               return 0;
+
+       if (obj->flags & FLAG_WRITTEN)
+               return 1;
+
+       if (type != OBJ_ANY && obj->type != type)
+               die("object type mismatch");
+
+       if (!(obj->flags & FLAG_OPEN)) {
+               unsigned long size;
+               int type = sha1_object_info(obj->sha1, &size);
+               if (type != obj->type || type <= 0)
+                       die("object of unexpected type");
+               obj->flags |= FLAG_WRITTEN;
+               return 1;
+       }
+
+       if (fsck_object(obj, 1, fsck_error_function))
+               die("Error in object");
+       if (!fsck_walk(obj, check_object, 0))
+               die("Error on reachable objects of %s", sha1_to_hex(obj->sha1));
+       write_cached_object(obj);
+       return 1;
+}
+
+static void write_rest(void)
+{
+       unsigned i;
+       for (i = 0; i < nr_objects; i++)
+               check_object(obj_list[i].obj, OBJ_ANY, 0);
+}
+
+static void added_object(unsigned nr, enum object_type type,
+                        void *data, unsigned long size);
+
+/*
+ * Write out nr-th object from the list, now we know the contents
+ * of it.  Under --strict, this buffers structured objects in-core,
+ * to be checked at the end.
+ */
+static void write_object(unsigned nr, enum object_type type,
+                        void *buf, unsigned long size)
+{
+       if (!strict) {
+               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+                       die("failed to write object");
+               added_object(nr, type, buf, size);
+               free(buf);
+               obj_list[nr].obj = NULL;
+       } else if (type == OBJ_BLOB) {
+               struct blob *blob;
+               if (write_sha1_file(buf, size, typename(type), obj_list[nr].sha1) < 0)
+                       die("failed to write object");
+               added_object(nr, type, buf, size);
+               free(buf);
+
+               blob = lookup_blob(obj_list[nr].sha1);
+               if (blob)
+                       blob->object.flags |= FLAG_WRITTEN;
+               else
+                       die("invalid blob object");
+               obj_list[nr].obj = NULL;
+       } else {
+               struct object *obj;
+               int eaten;
+               hash_sha1_file(buf, size, typename(type), obj_list[nr].sha1);
+               added_object(nr, type, buf, size);
+               obj = parse_object_buffer(obj_list[nr].sha1, type, size, buf, &eaten);
+               if (!obj)
+                       die("invalid %s", typename(type));
+               add_object_buffer(obj, buf, size);
+               obj->flags |= FLAG_OPEN;
+               obj_list[nr].obj = obj;
+       }
 }
 
-static int resolve_delta(const char *type,
-       void *base, unsigned long base_size,
-       void *delta, unsigned long delta_size)
+static void resolve_delta(unsigned nr, enum object_type type,
+                         void *base, unsigned long base_size,
+                         void *delta, unsigned long delta_size)
 {
        void *result;
        unsigned long result_size;
@@ -125,21 +270,26 @@ static int resolve_delta(const char *type,
        if (!result)
                die("failed to apply delta");
        free(delta);
-       write_object(result, result_size, type);
-       free(result);
-       return 0;
+       write_object(nr, type, result, result_size);
 }
 
-static void added_object(unsigned char *sha1, const char *type, void *data, unsigned long size)
+/*
+ * We now know the contents of an object (which is nr-th in the pack);
+ * resolve all the deltified objects that are based on it.
+ */
+static void added_object(unsigned nr, enum object_type type,
+                        void *data, unsigned long size)
 {
        struct delta_info **p = &delta_list;
        struct delta_info *info;
 
        while ((info = *p) != NULL) {
-               if (!hashcmp(info->base_sha1, sha1)) {
+               if (!hashcmp(info->base_sha1, obj_list[nr].sha1) ||
+                   info->base_offset == obj_list[nr].offset) {
                        *p = info->next;
                        p = &delta_list;
-                       resolve_delta(type, data, size, info->delta, info->size);
+                       resolve_delta(info->nr, type, data, size,
+                                     info->delta, info->size);
                        free(info);
                        continue;
                }
@@ -147,60 +297,135 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi
        }
 }
 
-static int unpack_non_delta_entry(enum object_type kind, unsigned long size)
+static void unpack_non_delta_entry(enum object_type type, unsigned long size,
+                                  unsigned nr)
 {
        void *buf = get_data(size);
-       const char *type;
-
-       switch (kind) {
-       case OBJ_COMMIT: type = commit_type; break;
-       case OBJ_TREE:   type = tree_type; break;
-       case OBJ_BLOB:   type = blob_type; break;
-       case OBJ_TAG:    type = tag_type; break;
-       default: die("bad type %d", kind);
-       }
-       if (!dry_run)
-               write_object(buf, size, type);
-       free(buf);
-       return 0;
+
+       if (!dry_run && buf)
+               write_object(nr, type, buf, size);
+       else
+               free(buf);
 }
 
-static int unpack_delta_entry(unsigned long delta_size)
+static int resolve_against_held(unsigned nr, const unsigned char *base,
+                               void *delta_data, unsigned long delta_size)
+{
+       struct object *obj;
+       struct obj_buffer *obj_buffer;
+       obj = lookup_object(base);
+       if (!obj)
+               return 0;
+       obj_buffer = lookup_object_buffer(obj);
+       if (!obj_buffer)
+               return 0;
+       resolve_delta(nr, obj->type, obj_buffer->buffer,
+                     obj_buffer->size, delta_data, delta_size);
+       return 1;
+}
+
+static void unpack_delta_entry(enum object_type type, unsigned long delta_size,
+                              unsigned nr)
 {
        void *delta_data, *base;
        unsigned long base_size;
-       char type[20];
        unsigned char base_sha1[20];
-       int result;
 
-       memcpy(base_sha1, fill(20), 20);
-       use(20);
+       if (type == OBJ_REF_DELTA) {
+               hashcpy(base_sha1, fill(20));
+               use(20);
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               if (has_sha1_file(base_sha1))
+                       ; /* Ok we have this one */
+               else if (resolve_against_held(nr, base_sha1,
+                                             delta_data, delta_size))
+                       return; /* we are done */
+               else {
+                       /* cannot resolve yet --- queue it */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, base_sha1, 0, delta_data, delta_size);
+                       return;
+               }
+       } else {
+               unsigned base_found = 0;
+               unsigned char *pack, c;
+               off_t base_offset;
+               unsigned lo, mid, hi;
 
-       delta_data = get_data(delta_size);
-       if (dry_run) {
-               free(delta_data);
-               return 0;
+               pack = fill(1);
+               c = *pack;
+               use(1);
+               base_offset = c & 127;
+               while (c & 128) {
+                       base_offset += 1;
+                       if (!base_offset || MSB(base_offset, 7))
+                               die("offset value overflow for delta base object");
+                       pack = fill(1);
+                       c = *pack;
+                       use(1);
+                       base_offset = (base_offset << 7) + (c & 127);
+               }
+               base_offset = obj_list[nr].offset - base_offset;
+
+               delta_data = get_data(delta_size);
+               if (dry_run || !delta_data) {
+                       free(delta_data);
+                       return;
+               }
+               lo = 0;
+               hi = nr;
+               while (lo < hi) {
+                       mid = (lo + hi)/2;
+                       if (base_offset < obj_list[mid].offset) {
+                               hi = mid;
+                       } else if (base_offset > obj_list[mid].offset) {
+                               lo = mid + 1;
+                       } else {
+                               hashcpy(base_sha1, obj_list[mid].sha1);
+                               base_found = !is_null_sha1(base_sha1);
+                               break;
+                       }
+               }
+               if (!base_found) {
+                       /*
+                        * The delta base object is itself a delta that
+                        * has not been resolved yet.
+                        */
+                       hashcpy(obj_list[nr].sha1, null_sha1);
+                       add_delta_to_list(nr, null_sha1, base_offset, delta_data, delta_size);
+                       return;
+               }
        }
 
-       if (!has_sha1_file(base_sha1)) {
-               add_delta_to_list(base_sha1, delta_data, delta_size);
-               return 0;
+       if (resolve_against_held(nr, base_sha1, delta_data, delta_size))
+               return;
+
+       base = read_sha1_file(base_sha1, &type, &base_size);
+       if (!base) {
+               error("failed to read delta-pack base object %s",
+                     sha1_to_hex(base_sha1));
+               if (!recover)
+                       exit(1);
+               has_errors = 1;
+               return;
        }
-       base = read_sha1_file(base_sha1, type, &base_size);
-       if (!base)
-               die("failed to read delta-pack base object %s", sha1_to_hex(base_sha1));
-       result = resolve_delta(type, base, base_size, delta_data, delta_size);
+       resolve_delta(nr, type, base, base_size, delta_data, delta_size);
        free(base);
-       return result;
 }
 
-static void unpack_one(unsigned nr, unsigned total)
+static void unpack_one(unsigned nr)
 {
        unsigned shift;
        unsigned char *pack, c;
        unsigned long size;
        enum object_type type;
 
+       obj_list[nr].offset = consumed_bytes;
+
        pack = fill(1);
        c = *pack;
        use(1);
@@ -209,54 +434,57 @@ static void unpack_one(unsigned nr, unsigned total)
        shift = 4;
        while (c & 0x80) {
                pack = fill(1);
-               c = *pack++;
+               c = *pack;
                use(1);
                size += (c & 0x7f) << shift;
                shift += 7;
        }
-       if (!quiet) {
-               static unsigned long last_sec;
-               static unsigned last_percent;
-               struct timeval now;
-               unsigned percentage = (nr * 100) / total;
-
-               gettimeofday(&now, NULL);
-               if (percentage != last_percent || now.tv_sec != last_sec) {
-                       last_sec = now.tv_sec;
-                       last_percent = percentage;
-                       fprintf(stderr, "%4u%% (%u/%u) done\r", percentage, nr, total);
-               }
-       }
+
        switch (type) {
        case OBJ_COMMIT:
        case OBJ_TREE:
        case OBJ_BLOB:
        case OBJ_TAG:
-               unpack_non_delta_entry(type, size);
+               unpack_non_delta_entry(type, size, nr);
                return;
-       case OBJ_DELTA:
-               unpack_delta_entry(size);
+       case OBJ_REF_DELTA:
+       case OBJ_OFS_DELTA:
+               unpack_delta_entry(type, size, nr);
                return;
        default:
-               die("bad object type %d", type);
+               error("bad object type %d", type);
+               has_errors = 1;
+               if (recover)
+                       return;
+               exit(1);
        }
 }
 
 static void unpack_all(void)
 {
        int i;
+       struct progress *progress = NULL;
        struct pack_header *hdr = fill(sizeof(struct pack_header));
-       unsigned nr_objects = ntohl(hdr->hdr_entries);
+
+       nr_objects = ntohl(hdr->hdr_entries);
 
        if (ntohl(hdr->hdr_signature) != PACK_SIGNATURE)
                die("bad pack file");
        if (!pack_version_ok(hdr->hdr_version))
-               die("unknown pack file version %d", ntohl(hdr->hdr_version));
-       fprintf(stderr, "Unpacking %d objects\n", nr_objects);
-
+               die("unknown pack file version %"PRIu32,
+                       ntohl(hdr->hdr_version));
        use(sizeof(struct pack_header));
-       for (i = 0; i < nr_objects; i++)
-               unpack_one(i+1, nr_objects);
+
+       if (!quiet)
+               progress = start_progress("Unpacking objects", nr_objects);
+       obj_list = xmalloc(nr_objects * sizeof(*obj_list));
+       memset(obj_list, 0, nr_objects * sizeof(*obj_list));
+       for (i = 0; i < nr_objects; i++) {
+               unpack_one(i);
+               display_progress(progress, i + 1);
+       }
+       stop_progress(&progress);
+
        if (delta_list)
                die("unresolved deltas left after unpacking");
 }
@@ -266,7 +494,7 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        int i;
        unsigned char sha1[20];
 
-       git_config(git_default_config);
+       git_config(git_default_config, NULL);
 
        quiet = !isatty(2);
 
@@ -282,6 +510,29 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
                                quiet = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "-r")) {
+                               recover = 1;
+                               continue;
+                       }
+                       if (!strcmp(arg, "--strict")) {
+                               strict = 1;
+                               continue;
+                       }
+                       if (!prefixcmp(arg, "--pack_header=")) {
+                               struct pack_header *hdr;
+                               char *c;
+
+                               hdr = (struct pack_header *)buffer;
+                               hdr->hdr_signature = htonl(PACK_SIGNATURE);
+                               hdr->hdr_version = htonl(strtoul(arg + 14, &c, 10));
+                               if (*c != ',')
+                                       die("bad %s", arg);
+                               hdr->hdr_entries = htonl(strtoul(c + 1, &c, 10));
+                               if (*c)
+                                       die("bad %s", arg);
+                               len = sizeof(*hdr);
+                               continue;
+                       }
                        usage(unpack_usage);
                }
 
@@ -292,6 +543,8 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        unpack_all();
        SHA1_Update(&ctx, buffer, offset);
        SHA1_Final(sha1, &ctx);
+       if (strict)
+               write_rest();
        if (hashcmp(fill(20), sha1))
                die("final sha1 did not match");
        use(20);
@@ -306,7 +559,5 @@ int cmd_unpack_objects(int argc, const char **argv, const char *prefix)
        }
 
        /* All done */
-       if (!quiet)
-               fprintf(stderr, "\n");
-       return 0;
+       return has_errors;
 }