builtin/gc: introduce `odb_optimize_required()`

When invoking either git-gc(1) or git-maintenance(1) with the "--auto" flag then we only perform those maintenance tasks that are actually required. This logic is inherently an implementation detail of the object database backend that's in use. But the logic is scattered around multiple different functions, which makes it hard to make the logic pluggable. Introduce a new `odb_optimize_required()` function that allows us to check these conditions in a generic way. 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 841ca632be7ad515c4e8f0d5b4ab2cba7454f402
1 file changed +92 -68
builtin/gc.c
+92 -68
@@ -676,25 +676,84 @@ static void add_repack_incremental_option(struct strvec *args)
676 strvec_push(args, "--no-write-bitmap-index");
677 }
678
679 -static int need_to_gc(struct repository *repo)
679 +static bool odb_optimize_required(struct object_database *odb,
680 + const struct odb_optimize_options *opts)
681 {
681 - int gc_auto_threshold = 6700;
682 - int gc_auto_pack_limit = 50;
682 + switch (opts->strategy) {
683 + case ODB_OPTIMIZE_INCREMENTAL: {
684 + int gc_auto_threshold = 6700;
685 + int gc_auto_pack_limit = 50;
686
684 - repo_config_get_int(repo, "gc.auto", &gc_auto_threshold);
685 - repo_config_get_int(repo, "gc.autopacklimit", &gc_auto_pack_limit);
687 + repo_config_get_int(odb->repo, "gc.auto", &gc_auto_threshold);
688 + repo_config_get_int(odb->repo, "gc.autopacklimit", &gc_auto_pack_limit);
689
687 - /*
688 - * Setting gc.auto to 0 or negative can disable the
689 - * automatic gc.
690 - */
691 - if (gc_auto_threshold <= 0)
692 - return 0;
693 - if (!too_many_packs(gc_auto_pack_limit) &&
694 - !too_many_loose_objects(gc_auto_threshold))
695 - return 0;
690 + /*
691 + * Setting gc.auto to 0 or negative can disable the
692 + * automatic gc.
693 + */
694 + if (gc_auto_threshold <= 0)
695 + return false;
696 + if (!too_many_packs(gc_auto_pack_limit) &&
697 + !too_many_loose_objects(gc_auto_threshold))
698 + return false;
699
697 - return 1;
700 + return true;
701 + }
702 + case ODB_OPTIMIZE_GEOMETRIC: {
703 + struct pack_geometry geometry = {
704 + .split_factor = 2,
705 + };
706 + struct pack_objects_args po_args = {
707 + .local = 1,
708 + };
709 + struct existing_packs existing_packs = EXISTING_PACKS_INIT;
710 + struct string_list kept_packs = STRING_LIST_INIT_DUP;
711 + int auto_value = 100;
712 + bool ret;
713 +
714 + repo_config_get_int(odb->repo, "maintenance.geometric-repack.auto",
715 + &auto_value);
716 + if (!auto_value)
717 + return false;
718 + if (auto_value < 0)
719 + return true;
720 +
721 + repo_config_get_int(odb->repo, "maintenance.geometric-repack.splitFactor",
722 + &geometry.split_factor);
723 +
724 + existing_packs.repo = odb->repo;
725 + existing_packs_collect(&existing_packs, &kept_packs);
726 + pack_geometry_init(&geometry, &existing_packs, &po_args);
727 + pack_geometry_split(&geometry);
728 +
729 + /*
730 + * When we'd merge at least two packs with one another we always
731 + * perform the repack.
732 + */
733 + if (geometry.split) {
734 + ret = true;
735 + goto out;
736 + }
737 +
738 + /*
739 + * Otherwise, we estimate the number of loose objects to determine
740 + * whether we want to create a new packfile or not.
741 + */
742 + if (too_many_loose_objects(auto_value)) {
743 + ret = true;
744 + goto out;
745 + }
746 +
747 + ret = false;
748 +
749 + out:
750 + existing_packs_release(&existing_packs);
751 + pack_geometry_release(&geometry);
752 + return ret;
753 + }
754 + default:
755 + BUG("unknown maintenance strategy '%d'", opts->strategy);
756 + }
757 }
758
759 /* return NULL on success, else hostname running the gc */
@@ -1076,13 +1135,19 @@ int cmd_gc(int argc,
1135 die(_("failed to parse prune expiry value %s"), cfg.prune_expire);
1136
1137 if (opts.auto_flag) {
1138 + struct odb_optimize_options optimize_opts = {
1139 + .strategy = ODB_OPTIMIZE_INCREMENTAL,
1140 + OPTIMIZE_FIELDS_FROM_GC_CONFIG(&cfg, 0),
1141 + };
1142 +
1143 if (cfg.detach_auto && opts.detach < 0)
1144 opts.detach = 1;
1145
1146 /*
1147 * Auto-gc should be least intrusive as possible.
1148 */
1085 - if (!need_to_gc(the_repository) || run_hooks(the_repository, "pre-auto-gc")) {
1149 + if (!odb_optimize_required(the_repository->objects, &optimize_opts) ||
1150 + run_hooks(the_repository, "pre-auto-gc")) {
1151 ret = 0;
1152 goto out;
1153 }
@@ -1379,9 +1444,13 @@ static int maintenance_task_gc_background(struct maintenance_run_opts *opts,
1444 return run_command(&child);
1445 }
1446
1382 -static int gc_condition(struct gc_config *cfg UNUSED)
1447 +static int gc_condition(struct gc_config *cfg)
1448 {
1384 - return need_to_gc(the_repository);
1449 + struct odb_optimize_options opts = {
1450 + .strategy = ODB_OPTIMIZE_INCREMENTAL,
1451 + OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
1452 + };
1453 + return odb_optimize_required(the_repository->objects, &opts);
1454 }
1455
1456 static int prune_packed(struct maintenance_run_opts *opts)
@@ -1681,58 +1750,13 @@ static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,
1750 return odb_optimize(the_repository->objects, &odb_opts);
1751 }
1752
1684 -static int geometric_repack_auto_condition(struct gc_config *cfg UNUSED)
1753 +static int geometric_repack_auto_condition(struct gc_config *cfg)
1754 {
1686 - struct pack_geometry geometry = {
1687 - .split_factor = 2,
1688 - };
1689 - struct pack_objects_args po_args = {
1690 - .local = 1,
1755 + struct odb_optimize_options opts = {
1756 + .strategy = ODB_OPTIMIZE_GEOMETRIC,
1757 + OPTIMIZE_FIELDS_FROM_GC_CONFIG(cfg, 0),
1758 };
1692 - struct existing_packs existing_packs = EXISTING_PACKS_INIT;
1693 - struct string_list kept_packs = STRING_LIST_INIT_DUP;
1694 - int auto_value = 100;
1695 - int ret;
1696 -
1697 - repo_config_get_int(the_repository, "maintenance.geometric-repack.auto",
1698 - &auto_value);
1699 - if (!auto_value)
1700 - return 0;
1701 - if (auto_value < 0)
1702 - return 1;
1703 -
1704 - repo_config_get_int(the_repository, "maintenance.geometric-repack.splitFactor",
1705 - &geometry.split_factor);
1706 -
1707 - existing_packs.repo = the_repository;
1708 - existing_packs_collect(&existing_packs, &kept_packs);
1709 - pack_geometry_init(&geometry, &existing_packs, &po_args);
1710 - pack_geometry_split(&geometry);
1711 -
1712 - /*
1713 - * When we'd merge at least two packs with one another we always
1714 - * perform the repack.
1715 - */
1716 - if (geometry.split) {
1717 - ret = 1;
1718 - goto out;
1719 - }
1720 -
1721 - /*
1722 - * Otherwise, we estimate the number of loose objects to determine
1723 - * whether we want to create a new packfile or not.
1724 - */
1725 - if (too_many_loose_objects(auto_value)) {
1726 - ret = 1;
1727 - goto out;
1728 - }
1729 -
1730 - ret = 0;
1731 -
1732 -out:
1733 - existing_packs_release(&existing_packs);
1734 - pack_geometry_release(&geometry);
1735 - return ret;
1759 + return odb_optimize_required(the_repository->objects, &opts);
1760 }
1761
1762 typedef int (*maintenance_task_fn)(struct maintenance_run_opts *opts,