pack-objects: use mru list when iterating over packs
[gitweb.git] / builtin / pack-objects.c
index 8ad11f2110f7f4496e38e908b46e9869af5de4fc..977d25f4955b4346bc20471ed35186a2a4b2195e 100644 (file)
@@ -23,6 +23,7 @@
 #include "reachable.h"
 #include "sha1-array.h"
 #include "argv-array.h"
+#include "mru.h"
 
 static const char *pack_usage[] = {
        N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"),
@@ -46,6 +47,7 @@ static int keep_unreachable, unpack_unreachable, include_tag;
 static unsigned long unpack_unreachable_expiration;
 static int pack_loose_unreachable;
 static int local;
+static int have_non_local_packs;
 static int incremental;
 static int ignore_packed_keep;
 static int allow_ofs_delta;
@@ -956,7 +958,7 @@ static int want_object_in_pack(const unsigned char *sha1,
                               struct packed_git **found_pack,
                               off_t *found_offset)
 {
-       struct packed_git *p;
+       struct mru_entry *entry;
 
        if (!exclude && local && has_loose_object_nonlocal(sha1))
                return 0;
@@ -964,7 +966,8 @@ static int want_object_in_pack(const unsigned char *sha1,
        *found_pack = NULL;
        *found_offset = 0;
 
-       for (p = packed_git; p; p = p->next) {
+       for (entry = packed_git_mru->head; entry; entry = entry->next) {
+               struct packed_git *p = entry->item;
                off_t offset = find_pack_entry_one(sha1, p);
                if (offset) {
                        if (!*found_pack) {
@@ -990,8 +993,11 @@ static int want_object_in_pack(const unsigned char *sha1,
                         * we just found is going to be packed, so break
                         * out of the loop to return 1 now.
                         */
-                       if (!ignore_packed_keep && !local)
+                       if (!ignore_packed_keep &&
+                           (!local || !have_non_local_packs)) {
+                               mru_mark(packed_git_mru, entry);
                                break;
+                       }
 
                        if (local && !p->pack_local)
                                return 0;
@@ -1492,6 +1498,83 @@ static int pack_offset_sort(const void *_a, const void *_b)
                        (a->in_pack_offset > b->in_pack_offset);
 }
 
+/*
+ * Drop an on-disk delta we were planning to reuse. Naively, this would
+ * just involve blanking out the "delta" field, but we have to deal
+ * with some extra book-keeping:
+ *
+ *   1. Removing ourselves from the delta_sibling linked list.
+ *
+ *   2. Updating our size/type to the non-delta representation. These were
+ *      either not recorded initially (size) or overwritten with the delta type
+ *      (type) when check_object() decided to reuse the delta.
+ */
+static void drop_reused_delta(struct object_entry *entry)
+{
+       struct object_entry **p = &entry->delta->delta_child;
+       struct object_info oi = OBJECT_INFO_INIT;
+
+       while (*p) {
+               if (*p == entry)
+                       *p = (*p)->delta_sibling;
+               else
+                       p = &(*p)->delta_sibling;
+       }
+       entry->delta = NULL;
+
+       oi.sizep = &entry->size;
+       oi.typep = &entry->type;
+       if (packed_object_info(entry->in_pack, entry->in_pack_offset, &oi) < 0) {
+               /*
+                * We failed to get the info from this pack for some reason;
+                * fall back to sha1_object_info, which may find another copy.
+                * And if that fails, the error will be recorded in entry->type
+                * and dealt with in prepare_pack().
+                */
+               entry->type = sha1_object_info(entry->idx.sha1, &entry->size);
+       }
+}
+
+/*
+ * Follow the chain of deltas from this entry onward, throwing away any links
+ * that cause us to hit a cycle (as determined by the DFS state flags in
+ * the entries).
+ */
+static void break_delta_chains(struct object_entry *entry)
+{
+       /* If it's not a delta, it can't be part of a cycle. */
+       if (!entry->delta) {
+               entry->dfs_state = DFS_DONE;
+               return;
+       }
+
+       switch (entry->dfs_state) {
+       case DFS_NONE:
+               /*
+                * This is the first time we've seen the object. We mark it as
+                * part of the active potential cycle and recurse.
+                */
+               entry->dfs_state = DFS_ACTIVE;
+               break_delta_chains(entry->delta);
+               entry->dfs_state = DFS_DONE;
+               break;
+
+       case DFS_DONE:
+               /* object already examined, and not part of a cycle */
+               break;
+
+       case DFS_ACTIVE:
+               /*
+                * We found a cycle that needs broken. It would be correct to
+                * break any link in the chain, but it's convenient to
+                * break this one.
+                */
+               drop_reused_delta(entry);
+               entry->dfs_state = DFS_DONE;
+               break;
+       }
+}
+
 static void get_object_details(void)
 {
        uint32_t i;
@@ -1509,6 +1592,13 @@ static void get_object_details(void)
                        entry->no_try_delta = 1;
        }
 
+       /*
+        * This must happen in a second pass, since we rely on the delta
+        * information for the whole list being completed.
+        */
+       for (i = 0; i < to_pack.nr_objects; i++)
+               break_delta_chains(&to_pack.objects[i]);
+
        free(sorted_by_offset);
 }
 
@@ -2799,6 +2889,28 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                progress = 2;
 
        prepare_packed_git();
+       if (ignore_packed_keep) {
+               struct packed_git *p;
+               for (p = packed_git; p; p = p->next)
+                       if (p->pack_local && p->pack_keep)
+                               break;
+               if (!p) /* no keep-able packs found */
+                       ignore_packed_keep = 0;
+       }
+       if (local) {
+               /*
+                * unlike ignore_packed_keep above, we do not want to
+                * unset "local" based on looking at packs, as it
+                * also covers non-local objects
+                */
+               struct packed_git *p;
+               for (p = packed_git; p; p = p->next) {
+                       if (!p->pack_local) {
+                               have_non_local_packs = 1;
+                               break;
+                       }
+               }
+       }
 
        if (progress)
                progress_state = start_progress(_("Counting objects"), 0);