We allow to keep existing packs by having companion .keep files. This is helpful when a pack is permanently kept. In the next patch, git-gc just wants to keep a pack temporarily, for one pack-objects run. git-gc can use --keep-pack for this use case. A note about why the pack_keep field cannot be reused and pack_keep_in_core has to be added. This is about the case when --keep-pack is specified together with either --keep-unreachable or --unpack-unreachable, but --honor-pack-keep is NOT specified. In this case, we want to exclude objects from the packs specified on command line, not from ones with .keep files. If only one bit flag is used, we have to clear pack_keep on pack files with the .keep file. But we can't make any assumption about unreachable objects in .keep packs. If "pack_keep" field is false for .keep packs, we could potentially pull lots of unreachable objects into the new pack, or unpack them loose. The safer approach is ignore all packs with either .keep file or --keep-pack. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@xxxxxxxxx> --- Documentation/git-pack-objects.txt | 9 +++++- Documentation/git-repack.txt | 9 +++++- builtin/pack-objects.c | 48 ++++++++++++++++++++++++++---- builtin/repack.c | 21 +++++++++++-- cache.h | 1 + t/t7700-repack.sh | 25 ++++++++++++++++ 6 files changed, 102 insertions(+), 11 deletions(-) diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt index 81bc490ac5..403524652a 100644 --- a/Documentation/git-pack-objects.txt +++ b/Documentation/git-pack-objects.txt @@ -12,7 +12,7 @@ SYNOPSIS 'git pack-objects' [-q | --progress | --all-progress] [--all-progress-implied] [--no-reuse-delta] [--delta-base-offset] [--non-empty] [--local] [--incremental] [--window=<n>] [--depth=<n>] - [--revs [--unpacked | --all]] + [--revs [--unpacked | --all]] [--keep-pack=<pack-name>] [--stdout [--filter=<filter-spec>] | base-name] [--shallow] [--keep-true-parents] < object-list @@ -126,6 +126,13 @@ base-name:: has a .keep file to be ignored, even if it would have otherwise been packed. +--keep-pack=<pack-name>:: + This flag causes an object already in the given pack to be + ignored, even if it would have otherwise been + packed. `<pack-name>` is the the pack file name without + leading directory (e.g. `pack-123.pack`). The option could be + specified multiple times to keep multiple packs. + --incremental:: This flag causes an object already in a pack to be ignored even if it would have otherwise been packed. diff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt index ae750e9e11..ce497d9d12 100644 --- a/Documentation/git-repack.txt +++ b/Documentation/git-repack.txt @@ -9,7 +9,7 @@ git-repack - Pack unpacked objects in a repository SYNOPSIS -------- [verse] -'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] +'git repack' [-a] [-A] [-d] [-f] [-F] [-l] [-n] [-q] [-b] [--window=<n>] [--depth=<n>] [--threads=<n>] [--keep-pack=<pack-name>] DESCRIPTION ----------- @@ -133,6 +133,13 @@ other objects in that pack they already have locally. with `-b` or `repack.writeBitmaps`, as it ensures that the bitmapped packfile has the necessary objects. +--keep-pack=<pack-name>:: + Exclude the given pack from repacking. This is the equivalent + of having `.keep` file on the pack. `<pack-name>` is the the + pack file name without leading directory (e.g. `pack-123.pack`). + The option could be specified multiple times to keep multiple + packs. + --unpack-unreachable=<when>:: When loosening unreachable objects, do not bother loosening any objects older than `<when>`. This can be used to optimize out diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5c674b2843..7b9fe6c89f 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -28,6 +28,7 @@ #include "argv-array.h" #include "list.h" #include "packfile.h" +#include "dir.h" static const char *pack_usage[] = { N_("git pack-objects --stdout [<options>...] [< <ref-list> | < <object-list>]"), @@ -53,7 +54,7 @@ static int pack_loose_unreachable; static int local; static int have_non_local_packs; static int incremental; -static int ignore_packed_keep; +static int ignore_packed_keep, ignore_packed_keep_in_core; static int allow_ofs_delta; static struct pack_idx_option pack_idx_opts; static const char *base_name; @@ -982,13 +983,15 @@ static int want_found_object(int exclude, struct packed_git *p) * Otherwise, we signal "-1" at the end to tell the caller that we do * not know either way, and it needs to check more packs. */ - if (!ignore_packed_keep && + if (!ignore_packed_keep && !ignore_packed_keep_in_core && (!local || !have_non_local_packs)) return 1; if (local && !p->pack_local) return 0; - if (ignore_packed_keep && p->pack_local && p->pack_keep) + if (p->pack_local && + ((ignore_packed_keep && p->pack_keep) || + (ignore_packed_keep_in_core && p->pack_keep_in_core))) return 0; /* we don't know yet; keep looking for more packs */ @@ -2677,7 +2680,7 @@ static void add_objects_in_unpacked_packs(struct rev_info *revs) struct object_id oid; struct object *o; - if (!p->pack_local || p->pack_keep) + if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) continue; if (open_pack_index(p)) die("cannot open pack index"); @@ -2739,7 +2742,8 @@ static int has_sha1_pack_kept_or_nonlocal(const struct object_id *oid) p = (last_found != (void *)1) ? last_found : packed_git; while (p) { - if ((!p->pack_local || p->pack_keep) && + if ((!p->pack_local || p->pack_keep || + p->pack_keep_in_core) && find_pack_entry_one(oid->hash, p)) { last_found = p; return 1; @@ -2782,7 +2786,7 @@ static void loosen_unused_packed_objects(struct rev_info *revs) struct object_id oid; for (p = packed_git; p; p = p->next) { - if (!p->pack_local || p->pack_keep) + if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) continue; if (open_pack_index(p)) @@ -2809,6 +2813,7 @@ static int pack_options_allow_reuse(void) return pack_to_stdout && allow_ofs_delta && !ignore_packed_keep && + !ignore_packed_keep_in_core && (!local || !have_non_local_packs) && !incremental; } @@ -2917,6 +2922,33 @@ static void get_object_list(int ac, const char **av) oid_array_clear(&recent_objects); } +static void add_extra_kept_packs(const struct string_list *names) +{ + struct packed_git *p; + + if (!names->nr) + return; + + prepare_packed_git(); + for (p = packed_git; p; p = p->next) { + const char *name = basename(p->pack_name); + int i; + + if (!p->pack_local) + continue; + + for (i = 0; i < names->nr; i++) + if (!fspathcmp(name, names->items[i].string)) + break; + + if (i < names->nr) { + p->pack_keep_in_core = 1; + ignore_packed_keep_in_core = 1; + continue; + } + } +} + static int option_parse_index_version(const struct option *opt, const char *arg, int unset) { @@ -2956,6 +2988,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) struct argv_array rp = ARGV_ARRAY_INIT; int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; int rev_list_index = 0; + struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -3022,6 +3055,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("create packs suitable for shallow fetches")), OPT_BOOL(0, "honor-pack-keep", &ignore_packed_keep, N_("ignore packs that have companion .keep file")), + OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), + N_("ignore this pack")), OPT_INTEGER(0, "compression", &pack_compression_level, N_("pack compression level")), OPT_SET_INT(0, "keep-true-parents", &grafts_replace_parents, @@ -3150,6 +3185,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) progress = 2; prepare_packed_git(); + add_extra_kept_packs(&keep_pack_list); if (ignore_packed_keep) { struct packed_git *p; for (p = packed_git; p; p = p->next) diff --git a/builtin/repack.c b/builtin/repack.c index 7bdb40142f..6c636e159e 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -86,7 +86,8 @@ static void remove_pack_on_signal(int signo) * 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) +static void get_non_kept_pack_filenames(struct string_list *fname_list, + const struct string_list *extra_keep) { DIR *dir; struct dirent *e; @@ -97,6 +98,14 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list) while ((e = readdir(dir)) != NULL) { size_t len; + int i; + + for (i = 0; i < extra_keep->nr; i++) + if (!fspathcmp(e->d_name, extra_keep->items[i].string)) + break; + if (extra_keep->nr > 0 && i < extra_keep->nr) + continue; + if (!strip_suffix(e->d_name, ".pack", &len)) continue; @@ -148,7 +157,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; struct strbuf line = STRBUF_INIT; - int ext, ret, failed; + int i, ext, ret, failed; FILE *out; /* variables to be filled by option parsing */ @@ -160,6 +169,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) const char *depth = NULL; const char *threads = NULL; const char *max_pack_size = NULL; + struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; int no_reuse_delta = 0, no_reuse_object = 0; int no_update_server_info = 0; int quiet = 0; @@ -200,6 +210,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) N_("maximum size of each packfile")), OPT_BOOL(0, "pack-kept-objects", &pack_kept_objects, N_("repack objects in packs marked with .keep")), + OPT_STRING_LIST(0, "keep-pack", &keep_pack_list, N_("name"), + N_("do not repack this pack")), OPT_END() }; @@ -230,6 +242,9 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_push(&cmd.args, "--keep-true-parents"); if (!pack_kept_objects) argv_array_push(&cmd.args, "--honor-pack-keep"); + for (i = 0; i < keep_pack_list.nr; i++) + argv_array_pushf(&cmd.args, "--keep-pack=%s", + keep_pack_list.items[i].string); argv_array_push(&cmd.args, "--non-empty"); argv_array_push(&cmd.args, "--all"); argv_array_push(&cmd.args, "--reflog"); @@ -254,7 +269,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) argv_array_push(&cmd.args, "--write-bitmap-index"); if (pack_everything & ALL_INTO_ONE) { - get_non_kept_pack_filenames(&existing_packs); + get_non_kept_pack_filenames(&existing_packs, &keep_pack_list); if (existing_packs.nr && delete_redundant) { if (unpack_unreachable) { diff --git a/cache.h b/cache.h index 21fbcc2414..42d700f3d8 100644 --- a/cache.h +++ b/cache.h @@ -1635,6 +1635,7 @@ extern struct packed_git { int pack_fd; unsigned pack_local:1, pack_keep:1, + pack_keep_in_core:1, freshened:1, do_not_close:1, pack_promisor:1; diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 38247afbec..05ae0de3aa 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -196,5 +196,30 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' git cat-file -t $H1 ' +test_expect_success 'repack --keep-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + test_commit one && + git repack -d && + test_commit two && + git repack -d && + test_commit three && + git repack -d && + test_commit four && + git repack -d && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 4 pack-list && + KEEP1=$(head -n1 pack-list) && + KEEP4=$(tail -n1 pack-list) && + git repack -a -d --keep-pack $KEEP1 --keep-pack $KEEP4 && + ls .git/objects/pack/*.pack >new-counts && + grep -q $KEEP1 new-counts && + grep -q $KEEP4 new-counts && + test_line_count = 3 new-counts && + git fsck + ) +' + test_done -- 2.16.2.903.gd04caf5039