git-gc --auto: move threshold check to need_to_gc() function.
[gitweb.git] / builtin-pack-objects.c
index 8b9740c27703dbbe71eb607b35520c5967279c0d..ba7c8da5bf0b91c5ddab3fa692ca49d025d85e29 100644 (file)
 #include "progress.h"
 
 static const char pack_usage[] = "\
-git-pack-objects [{ -q | --progress | --all-progress }] [--max-pack-size=N] \n\
-       [--local] [--incremental] [--window=N] [--depth=N] \n\
+git-pack-objects [{ -q | --progress | --all-progress }] \n\
+       [--max-pack-size=N] [--local] [--incremental] \n\
+       [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
        [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
-       [--stdout | base-name] [<ref-list | <object-list]";
+       [--stdout | base-name] [--keep-unreachable] [<ref-list | <object-list]";
 
 struct object_entry {
        struct pack_idx_entry idx;
        unsigned long size;     /* uncompressed size */
-
-       unsigned int hash;      /* name hint hash */
-       unsigned int depth;     /* delta depth */
        struct packed_git *in_pack;     /* already in pack */
        off_t in_pack_offset;
        struct object_entry *delta;     /* delta base object */
@@ -37,6 +35,7 @@ struct object_entry {
                                             */
        void *delta_data;       /* cached delta (uncompressed) */
        unsigned long delta_size;       /* delta data size (uncompressed) */
+       unsigned int hash;      /* name hint hash */
        enum object_type type;
        enum object_type in_pack_type;  /* could be delta */
        unsigned char in_pack_header_size;
@@ -58,7 +57,7 @@ static struct object_entry **written_list;
 static uint32_t nr_objects, nr_alloc, nr_result, nr_written;
 
 static int non_empty;
-static int no_reuse_delta, no_reuse_object;
+static int no_reuse_delta, no_reuse_object, keep_unreachable;
 static int local;
 static int incremental;
 static int allow_ofs_delta;
@@ -79,6 +78,9 @@ static unsigned long delta_cache_size = 0;
 static unsigned long max_delta_cache_size = 0;
 static unsigned long cache_max_small_delta_size = 1000;
 
+static unsigned long window_memory_usage = 0;
+static unsigned long window_memory_limit = 0;
+
 /*
  * The object names in objects array are hashed with this hashtable,
  * to help looking up the entry by object name.
@@ -252,7 +254,7 @@ static void *delta_against(void *buf, unsigned long size, struct object_entry *e
         delta_buf = diff_delta(otherbuf, othersize,
                               buf, size, &delta_size, 0);
         if (!delta_buf || delta_size != entry->delta_size)
-               die("delta size changed");
+               die("delta size changed");
         free(buf);
         free(otherbuf);
        return delta_buf;
@@ -584,7 +586,7 @@ static off_t write_one(struct sha1file *f,
 static int open_object_dir_tmp(const char *path)
 {
     snprintf(tmpname, sizeof(tmpname), "%s/%s", get_object_directory(), path);
-    return mkstemp(tmpname);
+    return xmkstemp(tmpname);
 }
 
 /* forward declaration for write_pack_file */
@@ -610,8 +612,6 @@ static void write_pack_file(void)
                        f = sha1fd(1, "<stdout>");
                } else {
                        int fd = open_object_dir_tmp("tmp_pack_XXXXXX");
-                       if (fd < 0)
-                               die("unable to create %s: %s\n", tmpname, strerror(errno));
                        pack_tmp_name = xstrdup(tmpname);
                        f = sha1fd(fd, pack_tmp_name);
                }
@@ -979,6 +979,8 @@ static void add_pbase_object(struct tree_desc *tree,
        int cmp;
 
        while (tree_entry(tree,&entry)) {
+               if (S_ISGITLINK(entry.mode))
+                       continue;
                cmp = tree_entry_len(entry.path, entry.sha1) != cmplen ? 1 :
                      memcmp(name, entry.path, cmplen);
                if (cmp > 0)
@@ -1270,11 +1272,11 @@ struct unpacked {
        struct object_entry *entry;
        void *data;
        struct delta_index *index;
+       unsigned depth;
 };
 
-static int delta_cacheable(struct unpacked *trg, struct unpacked *src,
-                           unsigned long src_size, unsigned long trg_size,
-                           unsigned long delta_size)
+static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
+                          unsigned long delta_size)
 {
        if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size)
                return 0;
@@ -1303,6 +1305,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        struct object_entry *trg_entry = trg->entry;
        struct object_entry *src_entry = src->entry;
        unsigned long trg_size, src_size, delta_size, sizediff, max_size, sz;
+       unsigned ref_depth;
        enum object_type type;
        void *delta_buf;
 
@@ -1327,34 +1330,49 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                return 0;
 
        /* Let's not bust the allowed depth. */
-       if (src_entry->depth >= max_depth)
+       if (src->depth >= max_depth)
                return 0;
 
        /* Now some size filtering heuristics. */
        trg_size = trg_entry->size;
-       max_size = trg_size/2 - 20;
-       max_size = max_size * (max_depth - src_entry->depth) / max_depth;
+       if (!trg_entry->delta) {
+               max_size = trg_size/2 - 20;
+               ref_depth = 1;
+       } else {
+               max_size = trg_entry->delta_size;
+               ref_depth = trg->depth;
+       }
+       max_size = max_size * (max_depth - src->depth) /
+                                               (max_depth - ref_depth + 1);
        if (max_size == 0)
                return 0;
-       if (trg_entry->delta && trg_entry->delta_size <= max_size)
-               max_size = trg_entry->delta_size-1;
        src_size = src_entry->size;
        sizediff = src_size < trg_size ? trg_size - src_size : 0;
        if (sizediff >= max_size)
                return 0;
+       if (trg_size < src_size / 32)
+               return 0;
 
        /* Load data if not already done */
        if (!trg->data) {
                trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
+               if (!trg->data)
+                       die("object %s cannot be read",
+                           sha1_to_hex(trg_entry->idx.sha1));
                if (sz != trg_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
+               window_memory_usage += sz;
        }
        if (!src->data) {
                src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
+               if (!src->data)
+                       die("object %s cannot be read",
+                           sha1_to_hex(src_entry->idx.sha1));
                if (sz != src_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(src_entry->idx.sha1), sz, src_size);
+               window_memory_usage += sz;
        }
        if (!src->index) {
                src->index = create_delta_index(src->data, src_size);
@@ -1364,22 +1382,33 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                                warning("suboptimal pack - out of memory");
                        return 0;
                }
+               window_memory_usage += sizeof_delta_index(src->index);
        }
 
        delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
        if (!delta_buf)
                return 0;
 
+       if (trg_entry->delta) {
+               /* Prefer only shallower same-sized deltas. */
+               if (delta_size == trg_entry->delta_size &&
+                   src->depth + 1 >= trg->depth) {
+                       free(delta_buf);
+                       return 0;
+               }
+       }
+
+       trg_entry->delta = src_entry;
+       trg_entry->delta_size = delta_size;
+       trg->depth = src->depth + 1;
+
        if (trg_entry->delta_data) {
                delta_cache_size -= trg_entry->delta_size;
                free(trg_entry->delta_data);
+               trg_entry->delta_data = NULL;
        }
-       trg_entry->delta_data = 0;
-       trg_entry->delta = src_entry;
-       trg_entry->delta_size = delta_size;
-       trg_entry->depth = src_entry->depth + 1;
 
-       if (delta_cacheable(src, trg, src_size, trg_size, delta_size)) {
+       if (delta_cacheable(src_size, trg_size, delta_size)) {
                trg_entry->delta_data = xrealloc(delta_buf, delta_size);
                delta_cache_size += trg_entry->delta_size;
        } else
@@ -1400,9 +1429,23 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
        return m;
 }
 
+static void free_unpacked(struct unpacked *n)
+{
+       window_memory_usage -= sizeof_delta_index(n->index);
+       free_delta_index(n->index);
+       n->index = NULL;
+       if (n->data) {
+               free(n->data);
+               n->data = NULL;
+               window_memory_usage -= n->entry->size;
+       }
+       n->entry = NULL;
+       n->depth = 0;
+}
+
 static void find_deltas(struct object_entry **list, int window, int depth)
 {
-       uint32_t i = nr_objects, idx = 0, processed = 0;
+       uint32_t i = nr_objects, idx = 0, count = 0, processed = 0;
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array;
        int max_depth;
@@ -1437,12 +1480,17 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (entry->no_try_delta)
                        continue;
 
-               free_delta_index(n->index);
-               n->index = NULL;
-               free(n->data);
-               n->data = NULL;
+               free_unpacked(n);
                n->entry = entry;
 
+               while (window_memory_limit &&
+                      window_memory_usage > window_memory_limit &&
+                      count > 1) {
+                       uint32_t tail = (idx + window - count) % window;
+                       free_unpacked(array + tail);
+                       count--;
+               }
+
                /*
                 * If the current object is at pack edge, take the depth the
                 * objects that depend on the current object into account
@@ -1472,11 +1520,13 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                 * depth, leaving it in the window is pointless.  we
                 * should evict it first.
                 */
-               if (entry->delta && depth <= entry->depth)
+               if (entry->delta && depth <= n->depth)
                        continue;
 
                next:
                idx++;
+               if (count + 1 < window)
+                       count++;
                if (idx >= window)
                        idx = 0;
        } while (i > 0);
@@ -1515,7 +1565,11 @@ static int git_pack_config(const char *k, const char *v)
                window = git_config_int(k, v);
                return 0;
        }
-       if(!strcmp(k, "pack.depth")) {
+       if (!strcmp(k, "pack.windowmemory")) {
+               window_memory_limit = git_config_ulong(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "pack.depth")) {
                depth = git_config_int(k, v);
                return 0;
        }
@@ -1571,15 +1625,19 @@ static void read_object_list_from_stdin(void)
        }
 }
 
+#define OBJECT_ADDED (1u<<20)
+
 static void show_commit(struct commit *commit)
 {
        add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
+       commit->object.flags |= OBJECT_ADDED;
 }
 
 static void show_object(struct object_array_entry *p)
 {
        add_preferred_base_object(p->name);
        add_object_entry(p->item->sha1, p->item->type, p->name, 0);
+       p->item->flags |= OBJECT_ADDED;
 }
 
 static void show_edge(struct commit *commit)
@@ -1587,6 +1645,86 @@ static void show_edge(struct commit *commit)
        add_preferred_base(commit->object.sha1);
 }
 
+struct in_pack_object {
+       off_t offset;
+       struct object *object;
+};
+
+struct in_pack {
+       int alloc;
+       int nr;
+       struct in_pack_object *array;
+};
+
+static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack)
+{
+       in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->sha1, p);
+       in_pack->array[in_pack->nr].object = object;
+       in_pack->nr++;
+}
+
+/*
+ * Compare the objects in the offset order, in order to emulate the
+ * "git-rev-list --objects" output that produced the pack originally.
+ */
+static int ofscmp(const void *a_, const void *b_)
+{
+       struct in_pack_object *a = (struct in_pack_object *)a_;
+       struct in_pack_object *b = (struct in_pack_object *)b_;
+
+       if (a->offset < b->offset)
+               return -1;
+       else if (a->offset > b->offset)
+               return 1;
+       else
+               return hashcmp(a->object->sha1, b->object->sha1);
+}
+
+static void add_objects_in_unpacked_packs(struct rev_info *revs)
+{
+       struct packed_git *p;
+       struct in_pack in_pack;
+       uint32_t i;
+
+       memset(&in_pack, 0, sizeof(in_pack));
+
+       for (p = packed_git; p; p = p->next) {
+               const unsigned char *sha1;
+               struct object *o;
+
+               for (i = 0; i < revs->num_ignore_packed; i++) {
+                       if (matches_pack_name(p, revs->ignore_packed[i]))
+                               break;
+               }
+               if (revs->num_ignore_packed <= i)
+                       continue;
+               if (open_pack_index(p))
+                       die("cannot open pack index");
+
+               ALLOC_GROW(in_pack.array,
+                          in_pack.nr + p->num_objects,
+                          in_pack.alloc);
+
+               for (i = 0; i < p->num_objects; i++) {
+                       sha1 = nth_packed_object_sha1(p, i);
+                       o = lookup_unknown_object(sha1);
+                       if (!(o->flags & OBJECT_ADDED))
+                               mark_in_pack_object(o, p, &in_pack);
+                       o->flags |= OBJECT_ADDED;
+               }
+       }
+
+       if (in_pack.nr) {
+               qsort(in_pack.array, in_pack.nr, sizeof(in_pack.array[0]),
+                     ofscmp);
+               for (i = 0; i < in_pack.nr; i++) {
+                       struct object *o = in_pack.array[i].object;
+                       add_object_entry(o->sha1, o->type, "", 0);
+               }
+       }
+       free(in_pack.array);
+}
+
 static void get_object_list(int ac, const char **av)
 {
        struct rev_info revs;
@@ -1618,6 +1756,9 @@ static void get_object_list(int ac, const char **av)
        prepare_revision_walk(&revs);
        mark_edges_uninteresting(revs.commits, &revs, show_edge);
        traverse_commit_list(&revs, show_commit, show_object);
+
+       if (keep_unreachable)
+               add_objects_in_unpacked_packs(&revs);
 }
 
 static int adjust_perm(const char *path, mode_t mode)
@@ -1691,6 +1832,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                                usage(pack_usage);
                        continue;
                }
+               if (!prefixcmp(arg, "--window-memory=")) {
+                       if (!git_parse_ulong(arg+16, &window_memory_limit))
+                               usage(pack_usage);
+                       continue;
+               }
                if (!prefixcmp(arg, "--depth=")) {
                        char *end;
                        depth = strtoul(arg+8, &end, 0);
@@ -1730,6 +1876,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                        use_internal_rev_list = 1;
                        continue;
                }
+               if (!strcmp("--keep-unreachable", arg)) {
+                       keep_unreachable = 1;
+                       continue;
+               }
                if (!strcmp("--unpacked", arg) ||
                    !prefixcmp(arg, "--unpacked=") ||
                    !strcmp("--reflog", arg) ||