]> git.ipfire.org Git - thirdparty/git.git/commitdiff
repack: repack promisor objects if -a or -A is set
authorJonathan Tan <jonathantanmy@google.com>
Wed, 8 Aug 2018 22:34:06 +0000 (15:34 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 9 Aug 2018 16:17:39 +0000 (09:17 -0700)
Currently, repack does not touch promisor packfiles at all, potentially
causing the performance of repositories that have many such packfiles to
drop. Therefore, repack all promisor objects if invoked with -a or -A.

This is done by an additional invocation of pack-objects on all promisor
objects individually given, which takes care of deduplication and allows
the resulting packfiles to respect flags such as --max-pack-size.

Signed-off-by: Jonathan Tan <jonathantanmy@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Documentation/git-repack.txt
builtin/repack.c
t/t0410-partial-clone.sh

index d90e7907f4843a048caf11a2fae42a973d893d9a..d056250968e13953bd8bc7dae71e745336788e38 100644 (file)
@@ -40,6 +40,11 @@ OPTIONS
 Note that users fetching over dumb protocols will have to fetch the
 whole new pack in order to get any contained object, no matter how many
 other objects in that pack they already have locally.
++
+Promisor packfiles are repacked separately: if there are packfiles that
+have an associated ".promisor" file, these packfiles will be repacked
+into another separate pack, and an empty ".promisor" file corresponding
+to the new separate pack will be written.
 
 -A::
        Same as `-a`, unless `-d` is used.  Then any unreachable
index f8cae7d665e5fa3d3df8e9e2b4c98e74c0a99ede..5c97dec3dbb7691436f19d0772313cadaf7c018d 100644 (file)
@@ -8,6 +8,7 @@
 #include "strbuf.h"
 #include "string-list.h"
 #include "argv-array.h"
+#include "packfile.h"
 
 static int delta_base_offset = 1;
 static int pack_kept_objects = -1;
@@ -83,7 +84,7 @@ static void remove_pack_on_signal(int signo)
 
 /*
  * Adds all packs hex strings to the fname list, which do not
- * have a corresponding .keep or .promisor file. These packs are not to
+ * have a corresponding .keep file. These packs are not to
  * be kept if we are going to pack everything into one file.
  */
 static void get_non_kept_pack_filenames(struct string_list *fname_list,
@@ -111,8 +112,7 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
 
                fname = xmemdupz(e->d_name, len);
 
-               if (!file_exists(mkpath("%s/%s.keep", packdir, fname)) &&
-                   !file_exists(mkpath("%s/%s.promisor", packdir, fname)))
+               if (!file_exists(mkpath("%s/%s.keep", packdir, fname)))
                        string_list_append_nodup(fname_list, fname);
                else
                        free(fname);
@@ -122,7 +122,7 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list,
 
 static void remove_redundant_pack(const char *dir_name, const char *base_name)
 {
-       const char *exts[] = {".pack", ".idx", ".keep", ".bitmap"};
+       const char *exts[] = {".pack", ".idx", ".keep", ".bitmap", ".promisor"};
        int i;
        struct strbuf buf = STRBUF_INIT;
        size_t plen;
@@ -179,6 +179,76 @@ static void prepare_pack_objects(struct child_process *cmd,
        cmd->out = -1;
 }
 
+/*
+ * Write oid to the given struct child_process's stdin, starting it first if
+ * necessary.
+ */
+static int write_oid(const struct object_id *oid, struct packed_git *pack,
+                    uint32_t pos, void *data)
+{
+       struct child_process *cmd = data;
+
+       if (cmd->in == -1) {
+               if (start_command(cmd))
+                       die("Could not start pack-objects to repack promisor objects");
+       }
+
+       xwrite(cmd->in, oid_to_hex(oid), GIT_SHA1_HEXSZ);
+       xwrite(cmd->in, "\n", 1);
+       return 0;
+}
+
+static void repack_promisor_objects(const struct pack_objects_args *args,
+                                   struct string_list *names)
+{
+       struct child_process cmd = CHILD_PROCESS_INIT;
+       FILE *out;
+       struct strbuf line = STRBUF_INIT;
+
+       prepare_pack_objects(&cmd, args);
+       cmd.in = -1;
+
+       /*
+        * NEEDSWORK: Giving pack-objects only the OIDs without any ordering
+        * hints may result in suboptimal deltas in the resulting pack. See if
+        * the OIDs can be sent with fake paths such that pack-objects can use a
+        * {type -> existing pack order} ordering when computing deltas instead
+        * of a {type -> size} ordering, which may produce better deltas.
+        */
+       for_each_packed_object(write_oid, &cmd,
+                              FOR_EACH_OBJECT_PROMISOR_ONLY);
+
+       if (cmd.in == -1)
+               /* No packed objects; cmd was never started */
+               return;
+
+       close(cmd.in);
+
+       out = xfdopen(cmd.out, "r");
+       while (strbuf_getline_lf(&line, out) != EOF) {
+               char *promisor_name;
+               int fd;
+               if (line.len != 40)
+                       die("repack: Expecting 40 character sha1 lines only from pack-objects.");
+               string_list_append(names, line.buf);
+
+               /*
+                * pack-objects creates the .pack and .idx files, but not the
+                * .promisor file. Create the .promisor file, which is empty.
+                */
+               promisor_name = mkpathdup("%s-%s.promisor", packtmp,
+                                         line.buf);
+               fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600);
+               if (fd < 0)
+                       die_errno("unable to create '%s'", promisor_name);
+               close(fd);
+               free(promisor_name);
+       }
+       fclose(out);
+       if (finish_command(&cmd))
+               die("Could not finish pack-objects to repack promisor objects");
+}
+
 #define ALL_INTO_ONE 1
 #define LOOSEN_UNREACHABLE 2
 
@@ -191,6 +261,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
                {".pack"},
                {".idx"},
                {".bitmap", 1},
+               {".promisor", 1},
        };
        struct child_process cmd = CHILD_PROCESS_INIT;
        struct string_list_item *item;
@@ -293,6 +364,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
        if (pack_everything & ALL_INTO_ONE) {
                get_non_kept_pack_filenames(&existing_packs, &keep_pack_list);
 
+               repack_promisor_objects(&po_args, &names);
+
                if (existing_packs.nr && delete_redundant) {
                        if (unpack_unreachable) {
                                argv_array_pushf(&cmd.args,
@@ -440,6 +513,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix)
 
        /* End of pack replacement. */
 
+       reprepare_packed_git(the_repository);
+
        if (delete_redundant) {
                int opts = 0;
                string_list_sort(&names);
index 4984ca583d03a7cc0a6fb9323799e776eee4565b..128130066499feb5bdad705b6fb0ef03bf446fe9 100755 (executable)
@@ -271,28 +271,91 @@ test_expect_success 'rev-list accepts missing and promised objects on command li
        git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB"
 '
 
-test_expect_success 'gc does not repack promisor objects' '
+test_expect_success 'gc repacks promisor objects separately from non-promisor objects' '
        rm -rf repo &&
        test_create_repo repo &&
-       test_commit -C repo my_commit &&
+       test_commit -C repo one &&
+       test_commit -C repo two &&
 
-       TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) &&
-       HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) &&
+       TREE_ONE=$(git -C repo rev-parse one^{tree}) &&
+       printf "$TREE_ONE\n" | pack_as_from_promisor &&
+       TREE_TWO=$(git -C repo rev-parse two^{tree}) &&
+       printf "$TREE_TWO\n" | pack_as_from_promisor &&
 
        git -C repo config core.repositoryformatversion 1 &&
        git -C repo config extensions.partialclone "arbitrary string" &&
        git -C repo gc &&
 
-       # Ensure that the promisor packfile still exists, and remove it
-       test -e repo/.git/objects/pack/pack-$HASH.pack &&
-       rm repo/.git/objects/pack/pack-$HASH.* &&
-
-       # Ensure that the single other pack contains the commit, but not the tree
+       # Ensure that exactly one promisor packfile exists, and that it
+       # contains the trees but not the commits
+       ls repo/.git/objects/pack/pack-*.promisor >promisorlist &&
+       test_line_count = 1 promisorlist &&
+       PROMISOR_PACKFILE=$(sed "s/.promisor/.pack/" <promisorlist) &&
+       git verify-pack $PROMISOR_PACKFILE -v >out &&
+       grep "$TREE_ONE" out &&
+       grep "$TREE_TWO" out &&
+       ! grep "$(git -C repo rev-parse one)" out &&
+       ! grep "$(git -C repo rev-parse two)" out &&
+
+       # Remove the promisor packfile and associated files
+       rm $(sed "s/.promisor//" <promisorlist).* &&
+
+       # Ensure that the single other pack contains the commits, but not the
+       # trees
        ls repo/.git/objects/pack/pack-*.pack >packlist &&
        test_line_count = 1 packlist &&
        git verify-pack repo/.git/objects/pack/pack-*.pack -v >out &&
-       grep "$(git -C repo rev-parse HEAD)" out &&
-       ! grep "$TREE_HASH" out
+       grep "$(git -C repo rev-parse one)" out &&
+       grep "$(git -C repo rev-parse two)" out &&
+       ! grep "$TREE_ONE" out &&
+       ! grep "$TREE_TWO" out
+'
+
+test_expect_success 'gc does not repack promisor objects if there are none' '
+       rm -rf repo &&
+       test_create_repo repo &&
+       test_commit -C repo one &&
+
+       git -C repo config core.repositoryformatversion 1 &&
+       git -C repo config extensions.partialclone "arbitrary string" &&
+       git -C repo gc &&
+
+       # Ensure that only one pack exists
+       ls repo/.git/objects/pack/pack-*.pack >packlist &&
+       test_line_count = 1 packlist
+'
+
+repack_and_check () {
+       rm -rf repo2 &&
+       cp -r repo repo2 &&
+       git -C repo2 repack $1 -d &&
+       git -C repo2 fsck &&
+
+       git -C repo2 cat-file -e $2 &&
+       git -C repo2 cat-file -e $3
+}
+
+test_expect_success 'repack -d does not irreversibly delete promisor objects' '
+       rm -rf repo &&
+       test_create_repo repo &&
+       git -C repo config core.repositoryformatversion 1 &&
+       git -C repo config extensions.partialclone "arbitrary string" &&
+
+       git -C repo commit --allow-empty -m one &&
+       git -C repo commit --allow-empty -m two &&
+       git -C repo commit --allow-empty -m three &&
+       git -C repo commit --allow-empty -m four &&
+       ONE=$(git -C repo rev-parse HEAD^^^) &&
+       TWO=$(git -C repo rev-parse HEAD^^) &&
+       THREE=$(git -C repo rev-parse HEAD^) &&
+
+       printf "$TWO\n" | pack_as_from_promisor &&
+       printf "$THREE\n" | pack_as_from_promisor &&
+       delete_object repo "$ONE" &&
+
+       repack_and_check -a "$TWO" "$THREE" &&
+       repack_and_check -A "$TWO" "$THREE" &&
+       repack_and_check -l "$TWO" "$THREE"
 '
 
 test_expect_success 'gc stops traversal when a missing but promised object is reached' '