builtin/gc: introduce object database optimization options

Introduce `struct odb_optimize_options` to decouple the options that are specific to optimizing the object database from `struct gc_config`. This structure will be moved into the object database layer in a subsequent commit. Note that there are a small set of backend-specific options in this structure. In an ideal world those of course wouldn't exist, but as we're introducing the object database abstractions retroactively we are somewhat forced to keep them. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Jul 13, 2026 at 07:52 UTC cc78c5029e62e6e1283c7d05bf60d845922aac9b
1 file changed +120 -61
builtin/gc.c
+120 -61
@@ -593,7 +593,39 @@ static int keep_one_pack(struct string_list_item *item, void *data)
593 return 0;
594 }
595
596 -static void add_repack_all_option(struct gc_config *cfg,
596 +enum odb_optimize_flags {
597 + /* Enable verbose logging and progress reporting. */
598 + ODB_OPTIMIZE_VERBOSE = (1 << 0),
599 +
600 + /* Perform auto-maintenance, only optimizing objects as required. */
601 + ODB_OPTIMIZE_AUTO = (1 << 1),
602 +
603 + /* Recompute existing deltas. */
604 + ODB_OPTIMIZE_NO_REUSE_DELTAS = (1 << 2),
605 +};
606 +
607 +struct odb_optimize_options {
608 + enum odb_optimize_flags flags;
609 + const char *prune_expire;
610 + const char *expire_to;
611 + int depth;
612 + int window;
613 +
614 + /* Backend-specific options. */
615 + int keep_largest_pack;
616 + int cruft_packs;
617 + unsigned long max_cruft_size;
618 +};
619 +
620 +#define OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, aggressive) \
621 + .prune_expire = (cfg)->prune_expire, \
622 + .expire_to = (cfg)->repack_expire_to, \
623 + .cruft_packs = (cfg)->cruft_packs, \
624 + .max_cruft_size = (cfg)->max_cruft_size, \
625 + .window = (aggressive) ? (cfg)->aggressive_window : 0, \
626 + .depth = (aggressive) ? (cfg)->aggressive_depth : 0
627 +
628 +static void add_repack_all_option(const struct odb_optimize_options *opts,
629 struct string_list *keep_pack,
630 struct strvec *args)
631 {
@@ -603,22 +635,22 @@ static void add_repack_all_option(struct gc_config *cfg,
635 repo_config_get_string(the_repository, "gc.repackfilter", &repack_filter);
636 repo_config_get_string(the_repository, "gc.repackfilterto", &repack_filter_to);
637
606 - if (cfg->prune_expire && !strcmp(cfg->prune_expire, "now")
607 - && !(cfg->cruft_packs && cfg->repack_expire_to))
638 + if (opts->prune_expire && !strcmp(opts->prune_expire, "now") &&
639 + !(opts->cruft_packs && opts->expire_to))
640 strvec_push(args, "-a");
609 - else if (cfg->cruft_packs) {
641 + else if (opts->cruft_packs) {
642 strvec_push(args, "--cruft");
611 - if (cfg->prune_expire)
612 - strvec_pushf(args, "--cruft-expiration=%s", cfg->prune_expire);
613 - if (cfg->max_cruft_size)
643 + if (opts->prune_expire)
644 + strvec_pushf(args, "--cruft-expiration=%s", opts->prune_expire);
645 + if (opts->max_cruft_size)
646 strvec_pushf(args, "--max-cruft-size=%lu",
615 - cfg->max_cruft_size);
616 - if (cfg->repack_expire_to)
617 - strvec_pushf(args, "--expire-to=%s", cfg->repack_expire_to);
647 + opts->max_cruft_size);
648 + if (opts->expire_to)
649 + strvec_pushf(args, "--expire-to=%s", opts->expire_to);
650 } else {
651 strvec_push(args, "-A");
620 - if (cfg->prune_expire)
621 - strvec_pushf(args, "--unpack-unreachable=%s", cfg->prune_expire);
652 + if (opts->prune_expire)
653 + strvec_pushf(args, "--unpack-unreachable=%s", opts->prune_expire);
654 }
655
656 if (keep_pack)
@@ -786,10 +818,8 @@ static int gc_foreground_tasks(struct maintenance_run_opts *opts,
818 return 0;
819 }
820
789 -static int maintenance_task_odb(struct maintenance_run_opts *opts,
790 - struct gc_config *cfg,
791 - int keep_largest_pack,
792 - int aggressive)
821 +static int odb_optimize(struct object_database *odb,
822 + const struct odb_optimize_options *opts)
823 {
824 struct child_process repack_cmd = CHILD_PROCESS_INIT;
825 unsigned long big_pack_threshold = 0;
@@ -801,21 +831,20 @@ static int maintenance_task_odb(struct maintenance_run_opts *opts,
831 repo_config_get_int(the_repository, "gc.autopacklimit", &gc_auto_pack_limit);
832 repo_config_get_ulong(the_repository, "gc.bigpackthreshold", &big_pack_threshold);
833
804 - if (the_repository->repository_format_precious_objects)
834 + if (odb->repo->repository_format_precious_objects)
835 return 0;
836
837 repack_cmd.git_cmd = 1;
838 repack_cmd.odb_to_close = the_repository->objects;
839
840 strvec_pushl(&repack_cmd.args, "repack", "-d", "-l", NULL);
811 - if (aggressive) {
841 + if (opts->flags & ODB_OPTIMIZE_NO_REUSE_DELTAS)
842 strvec_push(&repack_cmd.args, "-f");
813 - if (cfg->aggressive_depth > 0)
814 - strvec_pushf(&repack_cmd.args, "--depth=%d", cfg->aggressive_depth);
815 - if (cfg->aggressive_window > 0)
816 - strvec_pushf(&repack_cmd.args, "--window=%d", cfg->aggressive_window);
817 - }
818 - if (opts->quiet)
843 + if (opts->depth > 0)
844 + strvec_pushf(&repack_cmd.args, "--depth=%d", opts->depth);
845 + if (opts->window > 0)
846 + strvec_pushf(&repack_cmd.args, "--window=%d", opts->window);
847 + if (!(opts->flags & ODB_OPTIMIZE_VERBOSE))
848 strvec_push(&repack_cmd.args, "-q");
849
850 /*
@@ -829,47 +858,49 @@ static int maintenance_task_odb(struct maintenance_run_opts *opts,
858 *
859 * - Otherwise we perform an incremental repack.
860 */
832 - if (!opts->auto_flag) {
861 + if (!(opts->flags & ODB_OPTIMIZE_AUTO)) {
862 struct string_list keep_pack = STRING_LIST_INIT_NODUP;
863
835 - if (keep_largest_pack != -1) {
836 - if (keep_largest_pack)
864 + if (opts->keep_largest_pack != -1) {
865 + if (opts->keep_largest_pack)
866 find_base_packs(&keep_pack, 0);
867 } else if (big_pack_threshold) {
868 find_base_packs(&keep_pack, big_pack_threshold);
869 }
870
842 - add_repack_all_option(cfg, &keep_pack, &repack_cmd.args);
871 + add_repack_all_option(opts, &keep_pack, &repack_cmd.args);
872 string_list_clear(&keep_pack, 0);
844 - } else if (too_many_packs(gc_auto_pack_limit)) {
845 - struct string_list keep_pack = STRING_LIST_INIT_NODUP;
846 -
847 - if (big_pack_threshold) {
848 - find_base_packs(&keep_pack, big_pack_threshold);
849 - if (keep_pack.nr >= gc_auto_pack_limit) {
850 - string_list_clear(&keep_pack, 0);
851 - find_base_packs(&keep_pack, 0);
873 + } else {
874 + if (too_many_packs(gc_auto_pack_limit)) {
875 + struct string_list keep_pack = STRING_LIST_INIT_NODUP;
876 +
877 + if (big_pack_threshold) {
878 + find_base_packs(&keep_pack, big_pack_threshold);
879 + if (keep_pack.nr >= gc_auto_pack_limit) {
880 + string_list_clear(&keep_pack, 0);
881 + find_base_packs(&keep_pack, 0);
882 + }
883 + } else {
884 + struct packed_git *p = find_base_packs(&keep_pack, 0);
885 + uint64_t mem_have, mem_want;
886 +
887 + mem_have = total_ram();
888 + mem_want = estimate_repack_memory(p);
889 +
890 + /*
891 + * Only allow 1/2 of memory for pack-objects, leave
892 + * the rest for the OS and other processes in the
893 + * system.
894 + */
895 + if (!mem_have || mem_want < mem_have / 2)
896 + string_list_clear(&keep_pack, 0);
897 }
853 - } else {
854 - struct packed_git *p = find_base_packs(&keep_pack, 0);
855 - uint64_t mem_have, mem_want;
856 -
857 - mem_have = total_ram();
858 - mem_want = estimate_repack_memory(p);
898
860 - /*
861 - * Only allow 1/2 of memory for pack-objects, leave
862 - * the rest for the OS and other processes in the
863 - * system.
864 - */
865 - if (!mem_have || mem_want < mem_have / 2)
866 - string_list_clear(&keep_pack, 0);
899 + add_repack_all_option(opts, &keep_pack, &repack_cmd.args);
900 + string_list_clear(&keep_pack, 0);
901 + } else {
902 + add_repack_incremental_option(&repack_cmd.args);
903 }
868 -
869 - add_repack_all_option(cfg, &keep_pack, &repack_cmd.args);
870 - string_list_clear(&keep_pack, 0);
871 - } else {
872 - add_repack_incremental_option(&repack_cmd.args);
904 }
905
906 if (run_command(&repack_cmd)) {
@@ -877,13 +908,13 @@ static int maintenance_task_odb(struct maintenance_run_opts *opts,
908 goto out;
909 }
910
880 - if (cfg->prune_expire) {
911 + if (opts->prune_expire) {
912 struct child_process prune_cmd = CHILD_PROCESS_INIT;
913
914 strvec_pushl(&prune_cmd.args, "prune", "--expire", NULL);
915 /* run `git prune` even if using cruft packs */
885 - strvec_push(&prune_cmd.args, cfg->prune_expire);
886 - if (opts->quiet)
916 + strvec_push(&prune_cmd.args, opts->prune_expire);
917 + if (!(opts->flags & ODB_OPTIMIZE_VERBOSE))
918 strvec_push(&prune_cmd.args, "--no-progress");
919 if (repo_has_promisor_remote(the_repository))
920 strvec_push(&prune_cmd.args,
@@ -896,7 +927,7 @@ static int maintenance_task_odb(struct maintenance_run_opts *opts,
927 }
928 }
929
899 - if (opts->auto_flag && too_many_loose_objects(gc_auto_threshold))
930 + if (opts->flags & ODB_OPTIMIZE_AUTO && too_many_loose_objects(gc_auto_threshold))
931 warning(_("There are too many unreachable loose objects; "
932 "run 'git prune' to remove them."));
933
@@ -906,6 +937,26 @@ out:
937 return ret;
938 }
939
940 +static int maintenance_task_odb(struct maintenance_run_opts *opts,
941 + struct gc_config *cfg,
942 + int keep_largest_pack,
943 + int aggressive)
944 +{
945 + struct odb_optimize_options odb_opts = {
946 + .keep_largest_pack = keep_largest_pack,
947 + OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, aggressive),
948 + };
949 +
950 + if (opts->auto_flag)
951 + odb_opts.flags |= ODB_OPTIMIZE_AUTO;
952 + if (!opts->quiet)
953 + odb_opts.flags |= ODB_OPTIMIZE_VERBOSE;
954 + if (aggressive)
955 + odb_opts.flags |= ODB_OPTIMIZE_NO_REUSE_DELTAS;
956 +
957 + return odb_optimize(the_repository->objects, &odb_opts);
958 +}
959 +
960 int cmd_gc(int argc,
961 const char **argv,
962 const char *prefix,
@@ -1596,11 +1647,19 @@ static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,
1647 child.odb_to_close = the_repository->objects;
1648
1649 strvec_pushl(&child.args, "repack", "-d", "-l", NULL);
1599 - if (geometry.split < geometry.pack_nr)
1650 + if (geometry.split < geometry.pack_nr) {
1651 strvec_pushf(&child.args, "--geometric=%d",
1652 geometry.split_factor);
1602 - else
1603 - add_repack_all_option(cfg, NULL, &child.args);
1653 + } else {
1654 + struct odb_optimize_options odb_opts = {
1655 + OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
1656 + };
1657 +
1658 + if (!opts->quiet)
1659 + odb_opts.flags |= ODB_OPTIMIZE_VERBOSE;
1660 +
1661 + add_repack_all_option(&odb_opts, NULL, &child.args);
1662 + }
1663 if (opts->quiet)
1664 strvec_push(&child.args, "--quiet");
1665 if (the_repository->settings.core_multi_pack_index)