Skip to content

Commit 8a40f8b

Browse files
pastelskygitster
authored andcommitted
maintenance: add prune-remote-refs task
Remote-tracking refs can accumulate in local repositories even as branches are deleted on remotes, impacting git performance negatively. Existing alternatives to keep refs pruned have a few issues: 1. Running `git fetch` with either `--prune` or `fetch.prune=true` set, with the default refspec to copy all their branches into our remote-tracking branches, will prune stale refs, but also pulls in new branches from remote. That is undesirable if the user wants to only work with a selected few remote branches. 2. `git remote prune` cleans up refs without adding to the existing list but requires periodic user intervention. Add a new maintenance task 'prune-remote-refs' that runs 'git remote prune' for each configured remote daily. Leave the task disabled by default, as it may be unexpected to see their remote-tracking branches to disappear while they are not watching for unsuspecting users. Signed-off-by: Shubham Kanodia <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 76cf4f6 commit 8a40f8b

File tree

3 files changed

+96
-0
lines changed

3 files changed

+96
-0
lines changed

Documentation/git-maintenance.txt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,26 @@ pack-refs::
158158
need to iterate across many references. See linkgit:git-pack-refs[1]
159159
for more information.
160160

161+
prune-remote-refs::
162+
The `prune-remote-refs` task runs `git remote prune` on each remote
163+
repository registered in the local repository. This task helps clean
164+
up deleted remote branches, improving the performance of operations
165+
that iterate through the refs. See linkgit:git-remote[1] for more
166+
information. This task is disabled by default.
167+
+
168+
NOTE: This task is opt-in to prevent unexpected removal of remote refs
169+
for users of git-maintenance. For most users, configuring `fetch.prune=true`
170+
is an acceptable solution, as it will automatically clean up stale remote-tracking
171+
branches during normal fetch operations. However, this task can be useful in
172+
specific scenarios:
173+
+
174+
--
175+
* When using selective fetching (e.g., `git fetch origin +foo:refs/remotes/origin/foo`)
176+
where `fetch.prune` would only affect refs that are explicitly fetched.
177+
* When third-party tools might perform unexpected full fetches, and you want
178+
periodic cleanup independently of fetch operations.
179+
--
180+
161181
OPTIONS
162182
-------
163183
--auto::

builtin/gc.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "lockfile.h"
2424
#include "parse-options.h"
2525
#include "run-command.h"
26+
#include "remote.h"
2627
#include "sigchain.h"
2728
#include "strvec.h"
2829
#include "commit.h"
@@ -916,6 +917,30 @@ static int maintenance_opt_schedule(const struct option *opt, const char *arg,
916917
return 0;
917918
}
918919

920+
static int prune_remote(struct remote *remote, void *cb_data UNUSED)
921+
{
922+
struct child_process child = CHILD_PROCESS_INIT;
923+
924+
if (!remote->url.nr)
925+
return 0;
926+
927+
child.git_cmd = 1;
928+
strvec_pushl(&child.args, "remote", "prune", remote->name, NULL);
929+
930+
return !!run_command(&child);
931+
}
932+
933+
static int maintenance_task_prune_remote(struct maintenance_run_opts *opts,
934+
struct gc_config *cfg UNUSED)
935+
{
936+
if (for_each_remote(prune_remote, opts)) {
937+
error(_("failed to prune remotes"));
938+
return 1;
939+
}
940+
941+
return 0;
942+
}
943+
919944
/* Remember to update object flag allocation in object.h */
920945
#define SEEN (1u<<0)
921946

@@ -1378,6 +1403,7 @@ enum maintenance_task_label {
13781403
TASK_GC,
13791404
TASK_COMMIT_GRAPH,
13801405
TASK_PACK_REFS,
1406+
TASK_PRUNE_REMOTE_REFS,
13811407

13821408
/* Leave as final value */
13831409
TASK__COUNT
@@ -1414,6 +1440,10 @@ static struct maintenance_task tasks[] = {
14141440
maintenance_task_pack_refs,
14151441
pack_refs_condition,
14161442
},
1443+
[TASK_PRUNE_REMOTE_REFS] = {
1444+
"prune-remote-refs",
1445+
maintenance_task_prune_remote,
1446+
},
14171447
};
14181448

14191449
static int compare_tasks_by_selection(const void *a_, const void *b_)
@@ -1508,6 +1538,8 @@ static void initialize_maintenance_strategy(void)
15081538
tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY;
15091539
tasks[TASK_PACK_REFS].enabled = 1;
15101540
tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY;
1541+
tasks[TASK_PRUNE_REMOTE_REFS].enabled = 0;
1542+
tasks[TASK_PRUNE_REMOTE_REFS].schedule = SCHEDULE_DAILY;
15111543
}
15121544
}
15131545

t/t7900-maintenance.sh

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,50 @@ test_expect_success 'pack-refs task' '
447447
test_subcommand git pack-refs --all --prune <pack-refs.txt
448448
'
449449

450+
test_expect_success 'prune-remote-refs task not enabled by default' '
451+
git clone . prune-test &&
452+
(
453+
cd prune-test &&
454+
GIT_TRACE2_EVENT="$(pwd)/prune.txt" git maintenance run 2>err &&
455+
test_subcommand ! git remote prune origin <prune.txt
456+
)
457+
'
458+
459+
test_expect_success 'prune-remote-refs task cleans stale remote refs' '
460+
test_commit initial &&
461+
462+
# Create two separate remote repos
463+
git clone . remote1 &&
464+
git clone . remote2 &&
465+
466+
git clone . prune-test-clean &&
467+
(
468+
cd prune-test-clean &&
469+
git config maintenance.prune-remote-refs.enabled true &&
470+
471+
# Add both remotes
472+
git remote add remote1 "../remote1" &&
473+
git remote add remote2 "../remote2" &&
474+
475+
# Create and push branches to both remotes
476+
git branch -f side2 HEAD &&
477+
git push remote1 side2 &&
478+
git push remote2 side2 &&
479+
480+
# Rename branches in each remote to simulate a stale branch
481+
git -C ../remote1 branch -m side2 side3 &&
482+
git -C ../remote2 branch -m side2 side4 &&
483+
484+
GIT_TRACE2_EVENT="$(pwd)/prune.txt" git maintenance run --task=prune-remote-refs &&
485+
486+
# Verify pruning happened for both remotes
487+
test_subcommand git remote prune remote1 <prune.txt &&
488+
test_subcommand git remote prune remote2 <prune.txt &&
489+
test_must_fail git rev-parse refs/remotes/remote1/side2 &&
490+
test_must_fail git rev-parse refs/remotes/remote2/side2
491+
)
492+
'
493+
450494
test_expect_success '--auto and --schedule incompatible' '
451495
test_must_fail git maintenance run --auto --schedule=daily 2>err &&
452496
test_grep "at most one" err

0 commit comments

Comments
 (0)