From: Jonathan Tan <jonathantanmy@xxxxxxxxxx> Teach gc to stop traversal at promisor objects, and to leave promisor packfiles alone. This has the effect of only repacking non-promisor packfiles, and preserves the distinction between promisor packfiles and non-promisor packfiles. Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx> Signed-off-by: Jeff Hostetler <jeffhost@xxxxxxxxxxxxx> --- Documentation/git-pack-objects.txt | 4 +++ builtin/gc.c | 4 +++ builtin/pack-objects.c | 14 ++++++++++ builtin/prune.c | 7 +++++ builtin/repack.c | 12 +++++++-- t/t0410-partial-clone.sh | 54 ++++++++++++++++++++++++++++++++++++-- 6 files changed, 91 insertions(+), 4 deletions(-) diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 6786351..ee462c6 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -246,6 +246,10 @@ So does `git bundle` (see linkgit:git-bundle[1]) when it creates a bundle. Ignore missing objects without error. This may be used with or without and of the above filtering. +--exclude-promisor-objects:: + Silently omit referenced but missing objects from the packfile. + This is used with partial clone. + SEE ALSO -------- linkgit:git-rev-list[1] diff --git a/builtin/gc.c b/builtin/gc.c index 3c5eae0..a17806a 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -20,6 +20,7 @@ #include "argv-array.h" #include "commit.h" #include "packfile.h" +#include "partial-clone-utils.h" #define FAILED_RUN "failed to run %s" @@ -458,6 +459,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix) argv_array_push(&prune, prune_expire); if (quiet) argv_array_push(&prune, "--no-progress"); + if (is_partial_clone_registered()) + argv_array_push(&prune, + "--exclude-promisor-objects"); if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) return error(FAILED_RUN, prune.argv[0]); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index e16722f..957e459 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -83,6 +83,7 @@ static unsigned long window_memory_limit = 0; static struct list_objects_filter_options filter_options; static int arg_ignore_missing; +static int arg_exclude_promisor_objects; /* * stats @@ -2561,6 +2562,11 @@ static void show_object(struct object *obj, const char *name, void *data) if (arg_ignore_missing && !has_object_file(&obj->oid)) return; + if (arg_exclude_promisor_objects && + !has_object_file(&obj->oid) && + is_promisor_object(&obj->oid)) + return; + add_preferred_base_object(name); add_object_entry(obj->oid.hash, obj->type, name, 0); obj->flags |= OBJECT_ADDED; @@ -2972,6 +2978,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_BOOL(0, "filter-ignore-missing", &arg_ignore_missing, N_("ignore and omit missing objects from packfile")), + OPT_BOOL(0, "exclude-promisor-objects", &arg_exclude_promisor_objects, + N_("do not pack objects in promisor packfiles")), OPT_END(), }; @@ -3017,6 +3025,12 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) argv_array_push(&rp, "--unpacked"); } + if (arg_exclude_promisor_objects) { + use_internal_rev_list = 1; + fetch_if_missing = 0; + argv_array_push(&rp, "--exclude-promisor-objects"); + } + if (!reuse_object) reuse_delta = 0; if (pack_compression_level == -1) diff --git a/builtin/prune.c b/builtin/prune.c index cddabf2..be34645 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -101,12 +101,15 @@ int cmd_prune(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct progress *progress = NULL; + int exclude_promisor_objects = 0; const struct option options[] = { OPT__DRY_RUN(&show_only, N_("do not remove, show only")), OPT__VERBOSE(&verbose, N_("report pruned objects")), OPT_BOOL(0, "progress", &show_progress, N_("show progress")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("expire objects older than <time>")), + OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, + N_("limit traversal to objects outside promisor packfiles")), OPT_END() }; char *s; @@ -139,6 +142,10 @@ int cmd_prune(int argc, const char **argv, const char *prefix) show_progress = isatty(2); if (show_progress) progress = start_delayed_progress(_("Checking connectivity"), 0); + if (exclude_promisor_objects) { + fetch_if_missing = 0; + revs.exclude_promisor_objects = 1; + } mark_reachable_objects(&revs, 1, expire, progress); stop_progress(&progress); diff --git a/builtin/repack.c b/builtin/repack.c index f17a68a..a5a7977 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -83,7 +83,8 @@ static void remove_pack_on_signal(int signo) /* * Adds all packs hex strings to the fname list, which do not - * have a corresponding .keep file. + * have a corresponding .keep or .promisor 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) { @@ -101,7 +102,8 @@ 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))) + if (!file_exists(mkpath("%s/%s.keep", packdir, fname)) && + !file_exists(mkpath("%s/%s.promisor", packdir, fname))) string_list_append_nodup(fname_list, fname); else free(fname); @@ -232,6 +234,12 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_push(&cmd.args, "--all"); argv_array_push(&cmd.args, "--reflog"); argv_array_push(&cmd.args, "--indexed-objects"); + + /* + * TODO Should this be if (is_partial_clone_registered()) ... + */ + argv_array_push(&cmd.args, "--exclude-promisor-objects"); + if (window) argv_array_pushf(&cmd.args, "--window=%s", window); if (window_memory) diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 59de768..7ddcb4c 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -11,13 +11,15 @@ delete_object () { pack_as_from_promisor () { HASH=$(git -C repo pack-objects .git/objects/pack/pack) && >repo/.git/objects/pack/pack-$HASH.promisor + echo $HASH } promise_and_delete () { HASH=$(git -C repo rev-parse "$1") && git -C repo tag -a -m message my_annotated_tag "$HASH" && git -C repo rev-parse my_annotated_tag | pack_as_from_promisor && - git -C repo tag -d my_annotated_tag && + # tag -d prints a message to stdout, so redirect it + git -C repo tag -d my_annotated_tag >/dev/null && delete_object repo "$HASH" } @@ -261,6 +263,54 @@ 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' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && + HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialcloneremote "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 + 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 +' + +test_expect_success 'gc stops traversal when a missing but promised object is reached' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo my_commit && + + TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && + HASH=$(promise_and_delete $TREE_HASH) && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialcloneremote "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 + 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 +' + LIB_HTTPD_PORT=12345 # default port, 410, cannot be used as non-root . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd @@ -277,7 +327,7 @@ test_expect_success 'fetching of missing objects from an HTTP server' ' rm -rf repo/.git/objects/* && git -C repo config core.repositoryformatversion 1 && - git -C repo config extensions.partialcloneremoteremote "origin" && + git -C repo config extensions.partialcloneremote "origin" && git -C repo cat-file -p "$HASH" && # Ensure that the .promisor file is written, and check that its -- 2.9.3