Merge branch 'ds/maintenance-prefetch-fix'
The prefetch task in "git maintenance" assumed that "git fetch" from any remote would fetch all its local branches, which would fetch too much if the user is interested in only a subset of branches there. * ds/maintenance-prefetch-fix: maintenance: respect remote.*.skipFetchAll maintenance: use 'git fetch --prefetch' fetch: add --prefetch option maintenance: simplify prefetch logic
This commit is contained in:
commit
d250f90359
@ -110,6 +110,11 @@ ifndef::git-pull[]
|
|||||||
setting `fetch.writeCommitGraph`.
|
setting `fetch.writeCommitGraph`.
|
||||||
endif::git-pull[]
|
endif::git-pull[]
|
||||||
|
|
||||||
|
--prefetch::
|
||||||
|
Modify the configured refspec to place all refs into the
|
||||||
|
`refs/prefetch/` namespace. See the `prefetch` task in
|
||||||
|
linkgit:git-maintenance[1].
|
||||||
|
|
||||||
-p::
|
-p::
|
||||||
--prune::
|
--prune::
|
||||||
Before fetching, remove any remote-tracking references that no
|
Before fetching, remove any remote-tracking references that no
|
||||||
|
@ -92,10 +92,8 @@ commit-graph::
|
|||||||
prefetch::
|
prefetch::
|
||||||
The `prefetch` task updates the object directory with the latest
|
The `prefetch` task updates the object directory with the latest
|
||||||
objects from all registered remotes. For each remote, a `git fetch`
|
objects from all registered remotes. For each remote, a `git fetch`
|
||||||
command is run. The refmap is custom to avoid updating local or remote
|
command is run. The configured refspec is modified to place all
|
||||||
branches (those in `refs/heads` or `refs/remotes`). Instead, the
|
requested refs within `refs/prefetch/`. Also, tags are not updated.
|
||||||
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
|
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
|
expect these refs to stay unmoved unless they initiate a fetch. With prefetch
|
||||||
|
@ -48,6 +48,7 @@ enum {
|
|||||||
static int fetch_prune_config = -1; /* unspecified */
|
static int fetch_prune_config = -1; /* unspecified */
|
||||||
static int fetch_show_forced_updates = 1;
|
static int fetch_show_forced_updates = 1;
|
||||||
static uint64_t forced_updates_ms = 0;
|
static uint64_t forced_updates_ms = 0;
|
||||||
|
static int prefetch = 0;
|
||||||
static int prune = -1; /* unspecified */
|
static int prune = -1; /* unspecified */
|
||||||
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
|
#define PRUNE_BY_DEFAULT 0 /* do we prune by default? */
|
||||||
|
|
||||||
@ -158,6 +159,8 @@ static struct option builtin_fetch_options[] = {
|
|||||||
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
|
N_("do not fetch all tags (--no-tags)"), TAGS_UNSET),
|
||||||
OPT_INTEGER('j', "jobs", &max_jobs,
|
OPT_INTEGER('j', "jobs", &max_jobs,
|
||||||
N_("number of submodules fetched in parallel")),
|
N_("number of submodules fetched in parallel")),
|
||||||
|
OPT_BOOL(0, "prefetch", &prefetch,
|
||||||
|
N_("modify the refspec to place all refs within refs/prefetch/")),
|
||||||
OPT_BOOL('p', "prune", &prune,
|
OPT_BOOL('p', "prune", &prune,
|
||||||
N_("prune remote-tracking branches no longer on remote")),
|
N_("prune remote-tracking branches no longer on remote")),
|
||||||
OPT_BOOL('P', "prune-tags", &prune_tags,
|
OPT_BOOL('P', "prune-tags", &prune_tags,
|
||||||
@ -436,6 +439,56 @@ static void find_non_local_tags(const struct ref *refs,
|
|||||||
oidset_clear(&fetch_oids);
|
oidset_clear(&fetch_oids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void filter_prefetch_refspec(struct refspec *rs)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!prefetch)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 0; i < rs->nr; i++) {
|
||||||
|
struct strbuf new_dst = STRBUF_INIT;
|
||||||
|
char *old_dst;
|
||||||
|
const char *sub = NULL;
|
||||||
|
|
||||||
|
if (rs->items[i].negative)
|
||||||
|
continue;
|
||||||
|
if (!rs->items[i].dst ||
|
||||||
|
(rs->items[i].src &&
|
||||||
|
!strncmp(rs->items[i].src, "refs/tags/", 10))) {
|
||||||
|
int j;
|
||||||
|
|
||||||
|
free(rs->items[i].src);
|
||||||
|
free(rs->items[i].dst);
|
||||||
|
|
||||||
|
for (j = i + 1; j < rs->nr; j++) {
|
||||||
|
rs->items[j - 1] = rs->items[j];
|
||||||
|
rs->raw[j - 1] = rs->raw[j];
|
||||||
|
}
|
||||||
|
rs->nr--;
|
||||||
|
i--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_dst = rs->items[i].dst;
|
||||||
|
strbuf_addstr(&new_dst, "refs/prefetch/");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If old_dst starts with "refs/", then place
|
||||||
|
* sub after that prefix. Otherwise, start at
|
||||||
|
* the beginning of the string.
|
||||||
|
*/
|
||||||
|
if (!skip_prefix(old_dst, "refs/", &sub))
|
||||||
|
sub = old_dst;
|
||||||
|
strbuf_addstr(&new_dst, sub);
|
||||||
|
|
||||||
|
rs->items[i].dst = strbuf_detach(&new_dst, NULL);
|
||||||
|
rs->items[i].force = 1;
|
||||||
|
|
||||||
|
free(old_dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static struct ref *get_ref_map(struct remote *remote,
|
static struct ref *get_ref_map(struct remote *remote,
|
||||||
const struct ref *remote_refs,
|
const struct ref *remote_refs,
|
||||||
struct refspec *rs,
|
struct refspec *rs,
|
||||||
@ -452,6 +505,10 @@ static struct ref *get_ref_map(struct remote *remote,
|
|||||||
struct hashmap existing_refs;
|
struct hashmap existing_refs;
|
||||||
int existing_refs_populated = 0;
|
int existing_refs_populated = 0;
|
||||||
|
|
||||||
|
filter_prefetch_refspec(rs);
|
||||||
|
if (remote)
|
||||||
|
filter_prefetch_refspec(&remote->fetch);
|
||||||
|
|
||||||
if (rs->nr) {
|
if (rs->nr) {
|
||||||
struct refspec *fetch_refspec;
|
struct refspec *fetch_refspec;
|
||||||
|
|
||||||
@ -520,7 +577,7 @@ static struct ref *get_ref_map(struct remote *remote,
|
|||||||
if (has_merge &&
|
if (has_merge &&
|
||||||
!strcmp(branch->remote_name, remote->name))
|
!strcmp(branch->remote_name, remote->name))
|
||||||
add_merge_config(&ref_map, remote_refs, branch, &tail);
|
add_merge_config(&ref_map, remote_refs, branch, &tail);
|
||||||
} else {
|
} else if (!prefetch) {
|
||||||
ref_map = get_remote_ref(remote_refs, "HEAD");
|
ref_map = get_remote_ref(remote_refs, "HEAD");
|
||||||
if (!ref_map)
|
if (!ref_map)
|
||||||
die(_("Couldn't find remote ref HEAD"));
|
die(_("Couldn't find remote ref HEAD"));
|
||||||
|
39
builtin/gc.c
39
builtin/gc.c
@ -873,55 +873,40 @@ static int maintenance_task_commit_graph(struct maintenance_run_opts *opts)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int fetch_remote(const char *remote, struct maintenance_run_opts *opts)
|
static int fetch_remote(struct remote *remote, void *cbdata)
|
||||||
{
|
{
|
||||||
|
struct maintenance_run_opts *opts = cbdata;
|
||||||
struct child_process child = CHILD_PROCESS_INIT;
|
struct child_process child = CHILD_PROCESS_INIT;
|
||||||
|
|
||||||
|
if (remote->skip_default_update)
|
||||||
|
return 0;
|
||||||
|
|
||||||
child.git_cmd = 1;
|
child.git_cmd = 1;
|
||||||
strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags",
|
strvec_pushl(&child.args, "fetch", remote->name,
|
||||||
|
"--prefetch", "--prune", "--no-tags",
|
||||||
"--no-write-fetch-head", "--recurse-submodules=no",
|
"--no-write-fetch-head", "--recurse-submodules=no",
|
||||||
"--refmap=", NULL);
|
NULL);
|
||||||
|
|
||||||
if (opts->quiet)
|
if (opts->quiet)
|
||||||
strvec_push(&child.args, "--quiet");
|
strvec_push(&child.args, "--quiet");
|
||||||
|
|
||||||
strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote);
|
|
||||||
|
|
||||||
return !!run_command(&child);
|
return !!run_command(&child);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int append_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(struct maintenance_run_opts *opts)
|
static int maintenance_task_prefetch(struct maintenance_run_opts *opts)
|
||||||
{
|
{
|
||||||
int result = 0;
|
|
||||||
struct string_list_item *item;
|
|
||||||
struct string_list remotes = STRING_LIST_INIT_DUP;
|
|
||||||
|
|
||||||
git_config_set_multivar_gently("log.excludedecoration",
|
git_config_set_multivar_gently("log.excludedecoration",
|
||||||
"refs/prefetch/",
|
"refs/prefetch/",
|
||||||
"refs/prefetch/",
|
"refs/prefetch/",
|
||||||
CONFIG_FLAGS_FIXED_VALUE |
|
CONFIG_FLAGS_FIXED_VALUE |
|
||||||
CONFIG_FLAGS_MULTI_REPLACE);
|
CONFIG_FLAGS_MULTI_REPLACE);
|
||||||
|
|
||||||
if (for_each_remote(append_remote, &remotes)) {
|
if (for_each_remote(fetch_remote, opts)) {
|
||||||
error(_("failed to fill remotes"));
|
error(_("failed to prefetch remotes"));
|
||||||
result = 1;
|
return 1;
|
||||||
goto cleanup;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for_each_string_list_item(item, &remotes)
|
return 0;
|
||||||
result |= fetch_remote(item->string, opts);
|
|
||||||
|
|
||||||
cleanup:
|
|
||||||
string_list_clear(&remotes, 0);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int maintenance_task_gc(struct maintenance_run_opts *opts)
|
static int maintenance_task_gc(struct maintenance_run_opts *opts)
|
||||||
|
@ -240,4 +240,47 @@ test_expect_success "push with matching +: and negative refspec" '
|
|||||||
git -C two push -v one
|
git -C two push -v one
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success '--prefetch correctly modifies refspecs' '
|
||||||
|
git -C one config --unset-all remote.origin.fetch &&
|
||||||
|
git -C one config --add remote.origin.fetch ^refs/heads/bogus/ignore &&
|
||||||
|
git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
|
||||||
|
git -C one config --add remote.origin.fetch "refs/heads/bogus/*:bogus/*" &&
|
||||||
|
|
||||||
|
git tag -a -m never never-fetch-tag HEAD &&
|
||||||
|
|
||||||
|
git branch bogus/fetched HEAD~1 &&
|
||||||
|
git branch bogus/ignore HEAD &&
|
||||||
|
|
||||||
|
git -C one fetch --prefetch --no-tags &&
|
||||||
|
test_must_fail git -C one rev-parse never-fetch-tag &&
|
||||||
|
git -C one rev-parse refs/prefetch/bogus/fetched &&
|
||||||
|
test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore &&
|
||||||
|
|
||||||
|
# correctly handle when refspec set becomes empty
|
||||||
|
# after removing the refs/tags/* refspec.
|
||||||
|
git -C one config --unset-all remote.origin.fetch &&
|
||||||
|
git -C one config --add remote.origin.fetch "refs/tags/*:refs/tags/*" &&
|
||||||
|
|
||||||
|
git -C one fetch --prefetch --no-tags &&
|
||||||
|
test_must_fail git -C one rev-parse never-fetch-tag &&
|
||||||
|
|
||||||
|
# The refspec for refs that are not fully qualified
|
||||||
|
# are filtered multiple times.
|
||||||
|
git -C one rev-parse refs/prefetch/bogus/fetched &&
|
||||||
|
test_must_fail git -C one rev-parse refs/prefetch/bogus/ignore
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success '--prefetch succeeds when refspec becomes empty' '
|
||||||
|
git checkout bogus/fetched &&
|
||||||
|
test_commit extra &&
|
||||||
|
|
||||||
|
git -C one config --unset-all remote.origin.fetch &&
|
||||||
|
git -C one config --unset branch.main.remote &&
|
||||||
|
git -C one config remote.origin.fetch "+refs/tags/extra" &&
|
||||||
|
git -C one config remote.origin.skipfetchall true &&
|
||||||
|
git -C one config remote.origin.tagopt "--no-tags" &&
|
||||||
|
|
||||||
|
git -C one fetch --prefetch
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
@ -141,19 +141,25 @@ test_expect_success 'prefetch multiple remotes' '
|
|||||||
test_commit -C clone1 one &&
|
test_commit -C clone1 one &&
|
||||||
test_commit -C clone2 two &&
|
test_commit -C clone2 two &&
|
||||||
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
|
GIT_TRACE2_EVENT="$(pwd)/run-prefetch.txt" git maintenance run --task=prefetch 2>/dev/null &&
|
||||||
fetchargs="--prune --no-tags --no-write-fetch-head --recurse-submodules=no --refmap= --quiet" &&
|
fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" &&
|
||||||
test_subcommand git fetch remote1 $fetchargs +refs/heads/\\*:refs/prefetch/remote1/\\* <run-prefetch.txt &&
|
test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt &&
|
||||||
test_subcommand git fetch remote2 $fetchargs +refs/heads/\\*:refs/prefetch/remote2/\\* <run-prefetch.txt &&
|
test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt &&
|
||||||
test_path_is_missing .git/refs/remotes &&
|
test_path_is_missing .git/refs/remotes &&
|
||||||
git log prefetch/remote1/one &&
|
git log prefetch/remotes/remote1/one &&
|
||||||
git log prefetch/remote2/two &&
|
git log prefetch/remotes/remote2/two &&
|
||||||
git fetch --all &&
|
git fetch --all &&
|
||||||
test_cmp_rev refs/remotes/remote1/one refs/prefetch/remote1/one &&
|
test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one &&
|
||||||
test_cmp_rev refs/remotes/remote2/two refs/prefetch/remote2/two &&
|
test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two &&
|
||||||
|
|
||||||
test_cmp_config refs/prefetch/ log.excludedecoration &&
|
test_cmp_config refs/prefetch/ log.excludedecoration &&
|
||||||
git log --oneline --decorate --all >log &&
|
git log --oneline --decorate --all >log &&
|
||||||
! grep "prefetch" log
|
! grep "prefetch" log &&
|
||||||
|
|
||||||
|
test_when_finished git config --unset remote.remote1.skipFetchAll &&
|
||||||
|
git config remote.remote1.skipFetchAll true &&
|
||||||
|
GIT_TRACE2_EVENT="$(pwd)/skip-remote1.txt" git maintenance run --task=prefetch 2>/dev/null &&
|
||||||
|
test_subcommand ! git fetch remote1 $fetchargs <skip-remote1.txt &&
|
||||||
|
test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'prefetch and existing log.excludeDecoration values' '
|
test_expect_success 'prefetch and existing log.excludeDecoration values' '
|
||||||
|
Loading…
Reference in New Issue
Block a user