@@ -53,6 +53,21 @@ since it will not expire `.graph` files that were in the previous
`commit-graph-chain` file. They will be deleted by a later run based on
the expiration delay.
+prefetch::
+ The `prefetch` task updates the object directory with the latest
+ objects from all registered remotes. For each remote, a `git fetch`
+ command is run. The refmap is custom to avoid updating local or remote
+ branches (those in `refs/heads` or `refs/remotes`). Instead, the
+ remote refs are stored in `refs/prefetch/<remote>/`. Also, tags are
+ not updated.
++
+This is done to avoid disrupting the remote-tracking branches. The end users
+expect these refs to stay unmoved unless they initiate a fetch. With prefetch
+task, however, the objects necessary to complete a later real fetch would
+already be obtained, so the real fetch would go faster. In the ideal case,
+it will just become an update to bunch of remote-tracking branches without
+any object transfer.
+
gc::
Cleanup unnecessary files and optimize the local repository. "GC"
stands for "garbage collection," but this task performs many
@@ -28,6 +28,7 @@
#include "blob.h"
#include "tree.h"
#include "promisor-remote.h"
+#include "remote.h"
#define FAILED_RUN "failed to run %s"
@@ -769,6 +770,52 @@ static int maintenance_task_commit_graph(void)
return 1;
}
+static int fetch_remote(const char *remote)
+{
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.git_cmd = 1;
+ strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags",
+ "--no-write-fetch-head", "--refmap=", NULL);
+
+ strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote);
+
+ if (opts.quiet)
+ strvec_push(&child.args, "--quiet");
+
+ return !!run_command(&child);
+}
+
+static int fill_each_remote(struct remote *remote, void *cbdata)
+{
+ struct string_list *remotes = (struct string_list *)cbdata;
+
+ string_list_append(remotes, remote->name);
+ return 0;
+}
+
+static int maintenance_task_prefetch(void)
+{
+ int result = 0;
+ struct string_list_item *item;
+ struct string_list remotes = STRING_LIST_INIT_DUP;
+
+ if (for_each_remote(fill_each_remote, &remotes)) {
+ error(_("failed to fill remotes"));
+ result = 1;
+ goto cleanup;
+ }
+
+ for (item = remotes.items;
+ item && item < remotes.items + remotes.nr;
+ item++)
+ result |= fetch_remote(item->string);
+
+cleanup:
+ string_list_clear(&remotes, 0);
+ return result;
+}
+
static int maintenance_task_gc(void)
{
struct child_process child = CHILD_PROCESS_INIT;
@@ -796,6 +843,7 @@ struct maintenance_task {
};
enum maintenance_task_label {
+ TASK_PREFETCH,
TASK_GC,
TASK_COMMIT_GRAPH,
@@ -804,6 +852,10 @@ enum maintenance_task_label {
};
static struct maintenance_task tasks[] = {
+ [TASK_PREFETCH] = {
+ "prefetch",
+ maintenance_task_prefetch,
+ },
[TASK_GC] = {
"gc",
maintenance_task_gc,
@@ -43,4 +43,28 @@ test_expect_success 'run --task duplicate' '
test_i18ngrep "cannot be selected multiple times" err
'
+test_expect_success 'run --task=prefetch with no remotes' '
+ git maintenance run --task=prefetch 2>err &&
+ test_must_be_empty err
+'
+
+test_expect_success 'prefetch multiple remotes' '
+ git clone . clone1 &&
+ git clone . clone2 &&
+ git remote add remote1 "file://$(pwd)/clone1" &&
+ git remote add remote2 "file://$(pwd)/clone2" &&
+ git -C clone1 switch -c one &&
+ git -C clone2 switch -c two &&
+ test_commit -C clone1 one &&
+ test_commit -C clone2 two &&
+ GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch &&
+ grep ",\"fetch\",\"remote1\"" run-prefetch.txt &&
+ grep ",\"fetch\",\"remote2\"" run-prefetch.txt &&
+ test_path_is_missing .git/refs/remotes &&
+ test_cmp clone1/.git/refs/heads/one .git/refs/prefetch/remote1/one &&
+ test_cmp clone2/.git/refs/heads/two .git/refs/prefetch/remote2/two &&
+ git log prefetch/remote1/one &&
+ git log prefetch/remote2/two
+'
+
test_done