fetch: add config to avoid fetching every branch in shallow repo
Subsystems (2)
PATCH v12 2/5 fetch: extract collect_upstream_from_remote() helper
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
When a fetch has no refspec to work from, we decide what to fetch from two sources: the remote's configured fetch refspec, and the current branch's upstream, if it lives on that remote. For example, after
git clone --single-branch <url>
git switch -c work
git push -u origin work
the remote's refspec only covers "main", but a refspec-less "git fetch origin" still fetches "work" into FETCH_HEAD, so that a plain "git pull" finds the branch to merge. See 49ca2fba39 (fetch: add branch.*.merge to default ref-prefix extension, 2022-09-08).
That "does this branch build on this remote" check was inlined for just the current branch. Move it into its own function, collect_upstream_from_remote(), so the same logic can be reused for branches other than just the current one.
--- builtin/fetch.c | 18 ++++++++---------- remote.c | 15 +++++++++++++++ remote.h | 9 +++++++++ 3 files changed, 32 insertions(+), 10 deletions(-)
builtin/fetch.c +8 -10
@@ -1985,8 +1985,6 @@ static int do_fetch(struct transport *transport,if (rs->nr) {refspec_ref_prefixes (rs, &transport_ls_refs_options.ref_prefixes); }else { -struct branch *branch =branch_get (NULL ); -if (transport->remote->fetch.nr) {refspec_ref_prefixes (&transport->remote->fetch, &transport_ls_refs_options.ref_prefixes);
@@ -2001,14 +1999,14 @@ static int do_fetch(struct transport *transport,else follow_remote_head =BUILTIN_FOLLOW_REMOTE_HEAD_DFLT ; } -if (branch &&branch_has_merge_config (branch) && - !strcmp (branch->remote_name, transport->remote->name)) { -int i; -for (i =0 ; i < branch->merge_nr; i++) { -strvec_push (&transport_ls_refs_options.ref_prefixes, - branch->merge[i]->src); - } - } + +/* +* The configured refspec may not cover the current +* branch's upstream (e.g. a narrowed -t refspec), so +* make sure we can still fetch it regardless. +*/ +collect_upstream_from_remote (the_repository, transport->remote, +NULL , &transport_ls_refs_options.ref_prefixes);/* * If there are no refs specified to fetch, then we just
remote.c +15 -0
@@ -1899,6 +1899,21 @@ int branch_merge_matches(struct branch *branch,return refname_match (branch->merge[i]->src, refname); } +void collect_upstream_from_remote (struct repository *repo, +const struct remote *remote, +const char *refname, +struct strvec *tracked) +{ +struct branch *branch =repo_branch_get (repo, refname); + +if (!branch_has_merge_config (branch) || +strcmp (branch->remote_name, remote->name)) +return ; + +for (int i =0 ; i < branch->merge_nr; i++) +strvec_push (tracked, branch->merge[i]->src); +} + __attribute__((format (printf,2 ,3 )))static char *error_buf (struct strbuf *err,const char *fmt, ...) {
remote.h +9 -0
@@ -359,6 +359,15 @@ int branch_has_merge_config(struct branch *branch);int branch_merge_matches (struct branch *,int n,const char *); +/* +* If refname's branch builds on remote, add its upstream on remote to +* tracked. A NULL refname means the current branch. +*/ +void collect_upstream_from_remote (struct repository *repo, +const struct remote *remote, +const char *refname, +struct strvec *tracked); +/* list of the remote in a group as configured */ struct remote_group_data {const char *name;
Signature · 1 line
gitgitgadget
PATCH v12 3/5 fetch: infer branches to fetch from a refmap-only remote
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
We want remote.<name>.fetchRefmap to work on its own, without also setting remote.<name>.fetch, so a remote can be limited to the branches already in use locally, without listing them by hand or touching the command line every time.
Infer what to fetch from the local branches whose @{upstream} is already on that remote. The refmap itself still only controls where those fetched refs land, not which branches are selected.
--- Documentation/config/remote.adoc | 4 +- Documentation/fetch-options.adoc | 11 ++- builtin/fetch.c | 72 +++++++++++++---- remote.c | 25 ++++++ remote.h | 5 ++ t/meson.build | 1 + t/t5586-fetch-refmap.sh | 133 +++++++++++++++++++++++++++++++ 7 files changed, 232 insertions(+), 19 deletions(-) create mode 100755 t/t5586-fetch-refmap.sh
Documentation/config/remote.adoc +3 -1
@@ -38,7 +38,9 @@ remote.<name>.fetchRefmap:: fetched to remote-tracking refs to store, the same way `remote.<name>.fetch` does for an explicit refspec. `remote.<name>.fetch`, if also configured, still decides what is - fetched. See the `--refmap` entry in linkgit:git-fetch[1]. + fetched. If it is not set either, a refspec-less fetch instead + infers what to fetch from local branches built on this remote. + See the `--refmap` entry in linkgit:git-fetch[1]. remote.<name>.push:: The default set of "refspec" for linkgit:git-push[1]. See
Documentation/fetch-options.adoc +9 -2
@@ -245,8 +245,15 @@ endif::git-pull[] command-line arguments. See section on "Configured Remote-tracking Branches" for details. + -`remote.<name>.refmap` provides the default value for this option, the -same way `remote.<name>.fetch` provides the default refspecs to fetch. +When a refmap is active (from `--refmap` or `remote.<name>.fetchRefmap`) but +nothing to fetch is specified on the command line, nor is there a +`remote.<name>.fetch`, infer what to fetch from local branches built on +this remote: remote-tracking branches from this remote that are also +configured as the `@{upstream}` of a local branch are fetched. The +refmap only decides where those fetched refs land, the same as it +would for an explicit refspec, and does not narrow which branches are +selected. It is what turns this on, since without it the fetched +branches would have nowhere to land. `-t`:: `--tags`::
builtin/fetch.c +56 -16
130 lines not on this page · raw message
remote.c +25 -0
@@ -1914,6 +1914,31 @@ void collect_upstream_from_remote(struct repository *repo,strvec_push (tracked, branch->merge[i]->src); } +struct branches_tracking_remote_cb_data { +struct repository *repo; +const struct remote *remote; +struct strvec *tracked; +}; + +static int add_if_tracking_remote (const struct reference *ref,void *cb_data) +{ +struct branches_tracking_remote_cb_data *data = cb_data; + +collect_upstream_from_remote (data->repo, data->remote, ref->name, + data->tracked); +return 0 ; +} + +void branches_tracking_remote (struct repository *repo, +const struct remote *remote, +struct strvec *tracked) +{ +struct branches_tracking_remote_cb_data data = { repo, remote, tracked }; + +refs_for_each_branch_ref (get_main_ref_store (repo), + add_if_tracking_remote, &data); +} + __attribute__((format (printf,2 ,3 )))static char *error_buf (struct strbuf *err,const char *fmt, ...) {
remote.h +5 -0
@@ -368,6 +368,11 @@ void collect_upstream_from_remote(struct repository *repo,const char *refname, struct strvec *tracked); +/* Same as collect_upstream_from_remote(), but checked for every local branch. */ +void branches_tracking_remote (struct repository *repo, +const struct remote *remote, +struct strvec *tracked); +/* list of the remote in a group as configured */ struct remote_group_data {const char *name;
t/meson.build +1 -0
@@ -736,6 +736,7 @@ integration_tests = [ 't5582-fetch-negative-refspec.sh', 't5583-push-branches.sh', 't5584-http-429-retry.sh', + 't5586-fetch-refmap.sh', 't5600-clone-fail-cleanup.sh', 't5601-clone.sh', 't5602-clone-remote-exec.sh',
t/t5586-fetch-refmap.sh new file +133 -0
133 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v12 4/5 remote: add "git remote add --limited-fetch"
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
A remote added the ordinary way tracks every branch it has, via a wildcard remote.<name>.fetch refspec. That is wasteful for a remote whose history is only worth following for the branches actually in use locally, and it can make "git fetch" negotiate history for branches nobody asked for.
Give "git remote add" a --limited-fetch option that sets up remote.<name>.fetchRefmap instead of remote.<name>.fetch, so that "git fetch <name>" only fetches the branches already tracked, as described in the previous commit. It is rejected together with -t/--track or --mirror, since those already say explicitly what to fetch.
--- Documentation/git-remote.adoc | 8 +++++++- builtin/remote.c | 28 ++++++++++++++++++++++------ t/t5505-remote.sh | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-)
Documentation/git-remote.adoc +7 -1
@@ -10,7 +10,7 @@ SYNOPSIS -------- [synopsis] git remote [-v | --verbose] -git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL> +git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] [--[no-]limited-fetch] <name> <URL> git remote rename [--[no-]progress] <old> <new> git remote remove <name> git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
@@ -75,6 +75,12 @@ the `refs/remotes/<name>/` namespace, a refspec to track only _<branch>_ is created. You can give more than one `-t <branch>` to track multiple branches without grabbing all branches. + +With `--limited-fetch` option, instead of a `remote.<name>.fetch` refspec +that tracks all branches, `remote.<name>.fetchRefmap` is set up so that a +refspec-less `git fetch <name>` only fetches branches our local branches +are built on. See the `--refmap` entry in linkgit:git-fetch[1] for +details. ++ With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set up to point at remote's _<master>_ branch. See also the set-head command. +
builtin/remote.c +22 -6
@@ -179,6 +179,7 @@ static int add(int argc, const char **argv, const char *prefix, {int fetch =0 , fetch_tags =TAGS_DEFAULT ;unsigned mirror =MIRROR_NONE ; +int limited_fetch =-1 ;/* unspecified */ struct string_list track =STRING_LIST_INIT_NODUP ;const char *master =NULL ;struct remote *remote;
@@ -198,6 +199,8 @@ static int add(int argc, const char **argv, const char *prefix,OPT_CALLBACK_F (0 ,"mirror" , &mirror,"(push|fetch)" ,N_ ("set up remote as a mirror to push to or fetch from" ),PARSE_OPT_OPTARG |PARSE_OPT_COMP_ARG , parse_mirror_opt), +OPT_BOOL (0 ,"limited-fetch" , &limited_fetch, +N_ ("fetch only the branches we build on, instead of every branch" )), OPT_END() };
@@ -211,6 +214,10 @@ static int add(int argc, const char **argv, const char *prefix,die (_ ("specifying a master branch makes no sense with --mirror" ));if (mirror && !(mirror &MIRROR_FETCH ) && track.nr)die (_ ("specifying branches to track makes sense only with fetch mirrors" )); +if (limited_fetch ==1 && track.nr) +die (_ ("--limited-fetch does not make sense with -t/--track" )); +if (limited_fetch ==1 && mirror) +die (_ ("--limited-fetch does not make sense with --mirror" )); name = argv[0 ]; url = argv[1 ];
@@ -230,13 +237,22 @@ static int add(int argc, const char **argv, const char *prefix,repo_config_set (the_repository, buf.buf, url);if (!mirror || mirror &MIRROR_FETCH ) { +bool use_limited_fetch = mirror ==MIRROR_NONE && track.nr ==0 && + limited_fetch ==1 ; +strbuf_reset (&buf); -strbuf_addf (&buf,"remote.%s.fetch" , name); -if (track.nr ==0 ) -string_list_append (&track,"*" ); -for (size_t i =0 ; i < track.nr; i++) { -add_branch (buf.buf, track.items[i].string, - name, mirror, &buf2); +if (use_limited_fetch) { +strbuf_addf (&buf,"remote.%s.fetchRefmap" , name); +strbuf_reset (&buf2); +strbuf_addf (&buf2,"+refs/heads/*:refs/remotes/%s/*" , name); +repo_config_set (the_repository, buf.buf, buf2.buf); + }else { +strbuf_addf (&buf,"remote.%s.fetch" , name); +if (track.nr ==0 ) +string_list_append (&track,"*" ); +for (size_t i =0 ; i < track.nr; i++) +add_branch (buf.buf, track.items[i].string, + name, mirror, &buf2); } }
t/t5505-remote.sh +16 -0
@@ -137,6 +137,22 @@ test_expect_success 'filters are listed by git remote -v only' ' test_grep ! "\[blob:none\]" out ' +test_expect_success '--limited-fetch works in a full repository too' ' + test_when_finished "rm -rf full-add" && + git clone --no-local one full-add && + ( + cd full-add && + git remote add --limited-fetch upstream ../two && + test_cmp_config "+refs/heads/*:refs/remotes/upstream/*" \ + remote.upstream.fetchRefmap && + test_must_fail git config get remote.upstream.fetch + ) +' + +test_expect_success '--limited-fetch conflicts with -t' ' + test_must_fail git remote add --limited-fetch -t main upstream ../two +' + test_expect_success 'check remote-tracking' ' ( cd test &&
Signature · 1 line
gitgitgadget
PATCH v12 5/5 remote: default to --limited-fetch in a shallow repository
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
Adding a second remote to a shallow clone used to still fetch every branch that remote has, since "git remote add" always set up a wildcard remote.<name>.fetch refspec regardless of how shallow the repository already was. That defeats the purpose of having cloned shallow in the first place (for example with --single-branch), and can make a plain "git fetch" on that remote hang or take a very long time on a repository with many branches.
Turn --limited-fetch on by default when the repository is already shallow and neither -t/--track nor --mirror was given, so that adding a remote there does not by itself commit to following every branch it has. --no-limited-fetch keeps the previous behavior for whoever wants it.
--- Documentation/git-remote.adoc | 8 ++++- builtin/remote.c | 5 ++- t/t5505-remote.sh | 60 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-)
Documentation/git-remote.adoc +7 -1
@@ -57,6 +57,11 @@ Add a remote named _<name>_ for the repository at _<URL>_. The command `git fetch <name>` can then be used to create and update remote-tracking branches `<name>/<branch>`. + +If the repository is already a shallow repository (see linkgit:git-clone[1] +`--depth`) and neither `-t`, `--mirror` nor `--no-limited-fetch` is given, +`--limited-fetch` is turned on by default, so that `git fetch <name>` does +not need to negotiate history for every branch the remote has. ++ With `-f` option, `git fetch <name>` is run immediately after the remote information is set up. +
@@ -79,7 +84,8 @@ With `--limited-fetch` option, instead of a `remote.<name>.fetch` refspec that tracks all branches, `remote.<name>.fetchRefmap` is set up so that a refspec-less `git fetch <name>` only fetches branches our local branches are built on. See the `--refmap` entry in linkgit:git-fetch[1] for -details. +details. `--no-limited-fetch` explicitly disables this, overriding the +shallow-repository default described above. + With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set up to point at remote's _<master>_ branch. See also the set-head command.
builtin/remote.c +4 -1
@@ -16,6 +16,7 @@#include "rebase.h" #include "refs.h" #include "refspec.h" +#include "shallow.h" #include "odb.h" #include "strvec.h" #include "commit-reach.h"
@@ -238,7 +239,9 @@ static int add(int argc, const char **argv, const char *prefix,if (!mirror || mirror &MIRROR_FETCH ) {bool use_limited_fetch = mirror ==MIRROR_NONE && track.nr ==0 && - limited_fetch ==1 ; + (limited_fetch ==1 || + (limited_fetch ==-1 && +is_repository_shallow (the_repository)));strbuf_reset (&buf);if (use_limited_fetch) {
t/t5505-remote.sh +60 -0
66 lines not on this page · raw message
Signature · 1 line
gitgitgadget
v13 cover fetch: avoid fetching every branch of a new remote in a shallow repo
In reply to Harald Nordgren via GitGitGadgetAvoid fetching every branch of a new remote in a shallow repo.
Changes in v13:
* Added a new commit so @{push} resolves correctly for a --limited-fetch remote, falling back to remote.<name>.fetchRefmap the same way fetch already does when remote.<name>.fetch doesn't cover the branch being pushed.
* Added a regression test to fetch: add remote.<name>.fetchRefmap covering a remote.<name>.fetch entry with an explicit destination, confirming it's stored in both that destination and the refmap's.
Changes in v12:
* Renamed remote.<name>.refmap to remote.<name>.fetchRefmap throughout code, docs, and tests, so it reads unambiguously as fetch-only.
* Added the missing code path in get_ref_map() so a branch fetched by name via remote.<name>.fetch is also stored through remote.<name>.fetchRefmap, with a new test (the "cabal" example: fetch = master/next/seen plus a refmap storing them all under one namespace).
* Reworded the fetchRefmap commit message and doc entry to drop a redundant sentence, fix a confusing paragraph, and spell out that remote.<name>.fetch, when configured, still decides what's fetched while the refmap only remaps it.
* Added a prefetch coverage test to t5586-fetch-refmap.sh.
Changes in v11:
* remote_find_tracking() now falls back to remote.<name>.refmap when remote.<name>.fetch doesn't match. Negative refspecs in fetch still exclude refs, and both cases have tests
* The helpers write straight into a strvec, take the output parameter last and the remote as const
* The followRemoteHEAD lookup is done once, not duplicated in two branches
* Clearer commit messages and docs: a working example plus a reference to 49ca2fba39, the goal stated first, an incorrect "used to die" claim dropped, and an explanation of why a refmap turns on the inference
* use_limited_fetch is a bool
Changes in v10:
* Resolve merge conflict after master integrated
ch/fetch-followremotehead-lazy-validation.
Changes in v9:
* Moved the do_fetch() if/else-if/else split out of the preliminary helper-extraction commit and into the refmap-inference commit that needs it. Preliminary commit is now a simpler refactor.
Changes in v8:
* Split into a preliminary collect_upstream_from_remote() helper commit plus a simplified refmap-inference commit.
* Fixed a regression that could drop the current branch's @{upstream} when remote.<name>.fetch was narrowed.
* Threaded struct repository * through instead of hardcoding the_repository.
* Minor naming and comment cleanups.
Changes in v7:
* Fixed fatal: --refmap option is only meaningful with command-line refspec(s) firing just from configuring remote.<name>.refmap alone, with nothing else to fetch.
* When both remote.<name>.fetch and remote.<name>.refmap are configured, a refspec-less fetch follows .fetch as usual, .refmap only remaps something already being fetched by name. Added tests.
Changes in v6:
* Remove leftover reference to deleted default-branch logic in commit message.
Changes in v5:
* Renumber test file from t5585 to t5586.
Changes in v4:
* Removed the automatic default-branch fetch. A fresh remote fetches nothing until you track a branch explicitly.
* Fixed fetch report showing "new ref HEAD" instead of "new branch "
* Reworded remote..refmap docs and commit message.
Changes in v3:
* Replace the special ":"/"+:" fetch refspec with remote.<name>.refmap, reusing git's existing --refmap mechanism instead of inventing new refspec syntax.
* Split the change into 4 commits.
Changes in v2:
* Replaced the opt-in fetch.shallow config entirely with a new special fetch refspec (+:) that git remote add now defaults new remotes to in a shallow repository. The new refspec fetches whichever branches any local branch tracks at that remote, plus the remote's default branch.
Harald Nordgren (6):
fetch: add remote.<name>.fetchRefmap
fetch: extract collect_upstream_from_remote() helper
fetch: infer branches to fetch from a refmap-only remote
remote: add "git remote add --limited-fetch"
remote: default to --limited-fetch in a shallow repository
remote: resolve @{push} through remote.<name>.fetchRefmap
Documentation/config/remote.adoc | 9 ++ Documentation/fetch-options.adoc | 10 ++ Documentation/git-remote.adoc | 14 ++- builtin/fetch.c | 100 +++++++++++++++----- builtin/remote.c | 31 +++++-- remote.c | 62 ++++++++++++- remote.h | 20 ++++ t/meson.build | 1 + t/t1514-rev-parse-push.sh | 19 ++++ t/t5505-remote.sh | 76 ++++++++++++++++ t/t5510-fetch.sh | 151 +++++++++++++++++++++++++++++++ t/t5586-fetch-refmap.sh | 133 +++++++++++++++++++++++++++ 12 files changed, 593 insertions(+), 33 deletions(-) create mode 100755 t/t5586-fetch-refmap.sh
base-commit: b9e5cff1803ab24582201ef059516eb16014b9d9
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2412%2FHaraldNordgren%2Ffetch-shallow-narrow-refspec-v13
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2412/HaraldNordgren/fetch-shallow-narrow-refspec-v13
Pull-Request: https://github.com/git/git/pull/2412
Range-diff vs v12:
1: a1f5e81fdc ! 1: 07081828d1 fetch: add remote.<name>.fetchRefmap
@@ t/t5510-fetch.sh: test_expect_success 'explicit --refmap option overrides remote
+ )
+'
+
++test_expect_success 'remote.<name>.fetchRefmap stores an explicit-dst remote.<name>.fetch entry in both places' '
++ test_when_finished "rm -fr fetch-refmap-upstream downstream" &&
++ git init -b master fetch-refmap-upstream &&
++ test_commit -C fetch-refmap-upstream base &&
++ git init downstream &&
++ (
++ cd downstream &&
++ git remote add cabal ../fetch-refmap-upstream &&
++ git config --unset-all remote.cabal.fetch &&
++ git config --add remote.cabal.fetch \
++ "+refs/heads/master:refs/remotes/custom/master" &&
++ git config remote.cabal.fetchRefmap "+refs/heads/*:refs/remotes/cabal/*" &&
++ git fetch cabal &&
++ check_fetched_refs <<-\EOF
++ refs/remotes/cabal/HEAD
++ refs/remotes/cabal/master
++ refs/remotes/custom/master
++ EOF
++ )
++'
++
+test_expect_success 'remote.<name>.fetchRefmap tracks a branch outside remote.<name>.fetch' '
+ test_when_finished "rm -fr fetch-refmap-upstream downstream" &&
+ git init -b main fetch-refmap-upstream &&
2: e8af5bc76b = 2: 3f5808cbf3 fetch: extract collect_upstream_from_remote() helper
3: aad6e5f66f = 3: b02e0a5397 fetch: infer branches to fetch from a refmap-only remote
4: 97650da870 = 4: 3ee427de87 remote: add "git remote add --limited-fetch"
5: a802c9f82f = 5: 2cba4623ee remote: default to --limited-fetch in a shallow repository
-: ---------- > 6: 25eec8d967 remote: resolve @{push} through remote.<name>.fetchRefmapSignature · 1 line
gitgitgadget
PATCH v13 1/6 fetch: add remote.<name>.fetchRefmap
From: Harald Nordgren <[email protected]>
Add a per-remote config variable, remote.<name>.fetchRefmap, that provides the default value for --refmap the same way remote.<name>.fetch already provides the default refspecs to fetch.
Unlike passing --refmap on the command line, which requires an explicit refspec to go with it, configuring remote.<name>.fetchRefmap on its own does not change what a refspec-less fetch does: it falls back to the same defaults as a remote with no fetchRefmap at all.
When remote.<name>.fetch is also configured, it still decides what to fetch, and the refmap maps what it fetches. This lets a remote name only a few branches and store them all through one refmap:
[remote "cabal"]
fetch = master
fetch = next
fetch = seen
fetchRefmap = +refs/heads/*:refs/remotes/cabal/*
Looking up the remote-tracking branch for a remote branch, as @{upstream} and "git branch --track" do, falls back to the refmap when remote.<name>.fetch has no match, so that these work for such branches too. A branch excluded by a negative refspec in remote.<name>.fetch stays excluded.
--- Documentation/config/remote.adoc | 7 ++ Documentation/fetch-options.adoc | 3 + builtin/fetch.c | 16 +++- remote.c | 20 +++- remote.h | 6 ++ t/t5510-fetch.sh | 151 +++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 3 deletions(-)
Documentation/config/remote.adoc +7 -0
@@ -33,6 +33,13 @@ remote.<name>.fetch:: The default set of "refspec" for linkgit:git-fetch[1]. See linkgit:git-fetch[1]. +remote.<name>.fetchRefmap:: + The default value of the `--refmap` option, used to map refs being + fetched to remote-tracking refs to store, the same way + `remote.<name>.fetch` does for an explicit refspec. + `remote.<name>.fetch`, if also configured, still decides what is + fetched. See the `--refmap` entry in linkgit:git-fetch[1]. + remote.<name>.push:: The default set of "refspec" for linkgit:git-push[1]. See linkgit:git-push[1].
Documentation/fetch-options.adoc +3 -0
@@ -244,6 +244,9 @@ endif::git-pull[] refspecs and rely entirely on the refspecs supplied as command-line arguments. See section on "Configured Remote-tracking Branches" for details. ++ +`remote.<name>.refmap` provides the default value for this option, the +same way `remote.<name>.fetch` provides the default refspecs to fetch. `-t`:: `--tags`::
builtin/fetch.c +14 -2
@@ -518,6 +518,12 @@ static struct ref *get_ref_map(struct remote *remote,struct ref *rm;struct ref *ref_map =NULL ;struct ref **tail = &ref_map; +/* +* The --refmap command line option, if given, takes precedence +* over remote.<name>.fetchRefmap. +*/ +struct refspec *effective_refmap = + refmap.nr ? &refmap : remote ? &remote->refmap :NULL ;/* opportunistically-updated references: */ struct ref *orefs =NULL , **oref_tail = &orefs;
@@ -528,6 +534,8 @@ static struct ref *get_ref_map(struct remote *remote,filter_prefetch_refspec (rs);if (remote)filter_prefetch_refspec (&remote->fetch); +if (effective_refmap) +filter_prefetch_refspec (effective_refmap);if (rs->nr) {struct refspec *fetch_refspec;
@@ -561,8 +569,8 @@ static struct ref *get_ref_map(struct remote *remote,* by ref_remove_duplicates() in favor of one of these * opportunistic entries with FETCH_HEAD_IGNORE. */ -if (refmap.nr) - fetch_refspec = &refmap; +if (effective_refmap && effective_refmap->nr) + fetch_refspec = effective_refmap;else fetch_refspec = &remote->fetch;
@@ -597,6 +605,10 @@ static struct ref *get_ref_map(struct remote *remote,if (has_merge && !strcmp (branch->remote_name, remote->name))add_merge_config (&ref_map, remote_refs, branch, &tail); + +for (i =0 ; i < remote->refmap.nr; i++) +get_fetch_map (ref_map, &remote->refmap.items[i], + &oref_tail,1 ); }else if (!prefetch) { ref_map =get_remote_ref (remote_refs,"HEAD" );if (!ref_map)
remote.c +19 -1
@@ -152,6 +152,7 @@ static struct remote *make_remote(struct remote_state *remote_state, ret->name =xstrndup (name, len);refspec_init_push (&ret->push, the_hash_algo);refspec_init_fetch (&ret->fetch, the_hash_algo); +refspec_init_fetch (&ret->refmap, the_hash_algo);string_list_init_dup (&ret->server_options);string_list_init_dup (&ret->negotiation_restrict);string_list_init_dup (&ret->negotiation_include);
@@ -176,6 +177,7 @@ static void remote_clear(struct remote *remote)refspec_clear (&remote->push);refspec_clear (&remote->fetch); +refspec_clear (&remote->refmap);free ((char *)remote->receivepack);free ((char *)remote->uploadpack);
@@ -539,6 +541,12 @@ static int handle_config(const char *key, const char *value,return -1 ;refspec_append (&remote->fetch, v);free (v); + }else if (!strcmp (subkey,"fetchrefmap" )) { +char *v; +if (git_config_string (&v, key, value)) +return -1 ; +refspec_append (&remote->refmap, v); +free (v); }else if (!strcmp (subkey,"receivepack" )) {char *v;if (git_config_string (&v, key, value))
@@ -995,7 +1003,17 @@ void ref_push_report_free(struct ref_push_report *report)int remote_find_tracking (struct remote *remote,struct refspec_item *refspec) { -return refspec_find_match (&remote->fetch, refspec); +char **result = refspec->src ? &refspec->dst : &refspec->src; + +if (remote->fetch.nr && !refspec_find_match (&remote->fetch, refspec)) +return 0 ; +if (refspec_find_match (&remote->refmap, refspec)) +return -1 ; +if (refname_matches_negative_refspec_item (refspec->src, &remote->fetch)) { +FREE_AND_NULL (*result); +return -1 ; + } +return 0 ; } static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
remote.h +6 -0
@@ -90,6 +90,12 @@ struct remote {struct refspec fetch; +/* +* How to map refs fetched without an explicit destination into our +* own namespace, the same as the --refmap command line option. +*/ +struct refspec refmap; +/* * The setting for whether to fetch tags (as a separate rule from the * configured refspecs);
t/t5510-fetch.sh +151 -0
157 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v13 2/6 fetch: extract collect_upstream_from_remote() helper
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
When a fetch has no refspec to work from, we decide what to fetch from two sources: the remote's configured fetch refspec, and the current branch's upstream, if it lives on that remote. For example, after
git clone --single-branch <url>
git switch -c work
git push -u origin work
the remote's refspec only covers "main", but a refspec-less "git fetch origin" still fetches "work" into FETCH_HEAD, so that a plain "git pull" finds the branch to merge. See 49ca2fba39 (fetch: add branch.*.merge to default ref-prefix extension, 2022-09-08).
That "does this branch build on this remote" check was inlined for just the current branch. Move it into its own function, collect_upstream_from_remote(), so the same logic can be reused for branches other than just the current one.
--- builtin/fetch.c | 18 ++++++++---------- remote.c | 15 +++++++++++++++ remote.h | 9 +++++++++ 3 files changed, 32 insertions(+), 10 deletions(-)
builtin/fetch.c +8 -10
@@ -1985,8 +1985,6 @@ static int do_fetch(struct transport *transport,if (rs->nr) {refspec_ref_prefixes (rs, &transport_ls_refs_options.ref_prefixes); }else { -struct branch *branch =branch_get (NULL ); -if (transport->remote->fetch.nr) {refspec_ref_prefixes (&transport->remote->fetch, &transport_ls_refs_options.ref_prefixes);
@@ -2001,14 +1999,14 @@ static int do_fetch(struct transport *transport,else follow_remote_head =BUILTIN_FOLLOW_REMOTE_HEAD_DFLT ; } -if (branch &&branch_has_merge_config (branch) && - !strcmp (branch->remote_name, transport->remote->name)) { -int i; -for (i =0 ; i < branch->merge_nr; i++) { -strvec_push (&transport_ls_refs_options.ref_prefixes, - branch->merge[i]->src); - } - } + +/* +* The configured refspec may not cover the current +* branch's upstream (e.g. a narrowed -t refspec), so +* make sure we can still fetch it regardless. +*/ +collect_upstream_from_remote (the_repository, transport->remote, +NULL , &transport_ls_refs_options.ref_prefixes);/* * If there are no refs specified to fetch, then we just
remote.c +15 -0
@@ -1899,6 +1899,21 @@ int branch_merge_matches(struct branch *branch,return refname_match (branch->merge[i]->src, refname); } +void collect_upstream_from_remote (struct repository *repo, +const struct remote *remote, +const char *refname, +struct strvec *tracked) +{ +struct branch *branch =repo_branch_get (repo, refname); + +if (!branch_has_merge_config (branch) || +strcmp (branch->remote_name, remote->name)) +return ; + +for (int i =0 ; i < branch->merge_nr; i++) +strvec_push (tracked, branch->merge[i]->src); +} + __attribute__((format (printf,2 ,3 )))static char *error_buf (struct strbuf *err,const char *fmt, ...) {
remote.h +9 -0
@@ -359,6 +359,15 @@ int branch_has_merge_config(struct branch *branch);int branch_merge_matches (struct branch *,int n,const char *); +/* +* If refname's branch builds on remote, add its upstream on remote to +* tracked. A NULL refname means the current branch. +*/ +void collect_upstream_from_remote (struct repository *repo, +const struct remote *remote, +const char *refname, +struct strvec *tracked); +/* list of the remote in a group as configured */ struct remote_group_data {const char *name;
Signature · 1 line
gitgitgadget
PATCH v13 3/6 fetch: infer branches to fetch from a refmap-only remote
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
We want remote.<name>.fetchRefmap to work on its own, without also setting remote.<name>.fetch, so a remote can be limited to the branches already in use locally, without listing them by hand or touching the command line every time.
Infer what to fetch from the local branches whose @{upstream} is already on that remote. The refmap itself still only controls where those fetched refs land, not which branches are selected.
--- Documentation/config/remote.adoc | 4 +- Documentation/fetch-options.adoc | 11 ++- builtin/fetch.c | 72 +++++++++++++---- remote.c | 25 ++++++ remote.h | 5 ++ t/meson.build | 1 + t/t5586-fetch-refmap.sh | 133 +++++++++++++++++++++++++++++++ 7 files changed, 232 insertions(+), 19 deletions(-) create mode 100755 t/t5586-fetch-refmap.sh
Documentation/config/remote.adoc +3 -1
@@ -38,7 +38,9 @@ remote.<name>.fetchRefmap:: fetched to remote-tracking refs to store, the same way `remote.<name>.fetch` does for an explicit refspec. `remote.<name>.fetch`, if also configured, still decides what is - fetched. See the `--refmap` entry in linkgit:git-fetch[1]. + fetched. If it is not set either, a refspec-less fetch instead + infers what to fetch from local branches built on this remote. + See the `--refmap` entry in linkgit:git-fetch[1]. remote.<name>.push:: The default set of "refspec" for linkgit:git-push[1]. See
Documentation/fetch-options.adoc +9 -2
@@ -245,8 +245,15 @@ endif::git-pull[] command-line arguments. See section on "Configured Remote-tracking Branches" for details. + -`remote.<name>.refmap` provides the default value for this option, the -same way `remote.<name>.fetch` provides the default refspecs to fetch. +When a refmap is active (from `--refmap` or `remote.<name>.fetchRefmap`) but +nothing to fetch is specified on the command line, nor is there a +`remote.<name>.fetch`, infer what to fetch from local branches built on +this remote: remote-tracking branches from this remote that are also +configured as the `@{upstream}` of a local branch are fetched. The +refmap only decides where those fetched refs land, the same as it +would for an explicit refspec, and does not narrow which branches are +selected. It is what turns this on, since without it the fetched +branches would have nowhere to land. `-t`:: `--tags`::
builtin/fetch.c +56 -16
130 lines not on this page · raw message
remote.c +25 -0
@@ -1914,6 +1914,31 @@ void collect_upstream_from_remote(struct repository *repo,strvec_push (tracked, branch->merge[i]->src); } +struct branches_tracking_remote_cb_data { +struct repository *repo; +const struct remote *remote; +struct strvec *tracked; +}; + +static int add_if_tracking_remote (const struct reference *ref,void *cb_data) +{ +struct branches_tracking_remote_cb_data *data = cb_data; + +collect_upstream_from_remote (data->repo, data->remote, ref->name, + data->tracked); +return 0 ; +} + +void branches_tracking_remote (struct repository *repo, +const struct remote *remote, +struct strvec *tracked) +{ +struct branches_tracking_remote_cb_data data = { repo, remote, tracked }; + +refs_for_each_branch_ref (get_main_ref_store (repo), + add_if_tracking_remote, &data); +} + __attribute__((format (printf,2 ,3 )))static char *error_buf (struct strbuf *err,const char *fmt, ...) {
remote.h +5 -0
@@ -368,6 +368,11 @@ void collect_upstream_from_remote(struct repository *repo,const char *refname, struct strvec *tracked); +/* Same as collect_upstream_from_remote(), but checked for every local branch. */ +void branches_tracking_remote (struct repository *repo, +const struct remote *remote, +struct strvec *tracked); +/* list of the remote in a group as configured */ struct remote_group_data {const char *name;
t/meson.build +1 -0
@@ -736,6 +736,7 @@ integration_tests = [ 't5582-fetch-negative-refspec.sh', 't5583-push-branches.sh', 't5584-http-429-retry.sh', + 't5586-fetch-refmap.sh', 't5600-clone-fail-cleanup.sh', 't5601-clone.sh', 't5602-clone-remote-exec.sh',
t/t5586-fetch-refmap.sh new file +133 -0
133 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v13 4/6 remote: add "git remote add --limited-fetch"
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
A remote added the ordinary way tracks every branch it has, via a wildcard remote.<name>.fetch refspec. That is wasteful for a remote whose history is only worth following for the branches actually in use locally, and it can make "git fetch" negotiate history for branches nobody asked for.
Give "git remote add" a --limited-fetch option that sets up remote.<name>.fetchRefmap instead of remote.<name>.fetch, so that "git fetch <name>" only fetches the branches already tracked, as described in the previous commit. It is rejected together with -t/--track or --mirror, since those already say explicitly what to fetch.
--- Documentation/git-remote.adoc | 8 +++++++- builtin/remote.c | 28 ++++++++++++++++++++++------ t/t5505-remote.sh | 16 ++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-)
Documentation/git-remote.adoc +7 -1
@@ -10,7 +10,7 @@ SYNOPSIS -------- [synopsis] git remote [-v | --verbose] -git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] <name> <URL> +git remote add [-t <branch>] [-m <master>] [-f] [--[no-]tags] [--mirror=(fetch|push)] [--[no-]limited-fetch] <name> <URL> git remote rename [--[no-]progress] <old> <new> git remote remove <name> git remote set-head <name> (-a | --auto | -d | --delete | <branch>)
@@ -75,6 +75,12 @@ the `refs/remotes/<name>/` namespace, a refspec to track only _<branch>_ is created. You can give more than one `-t <branch>` to track multiple branches without grabbing all branches. + +With `--limited-fetch` option, instead of a `remote.<name>.fetch` refspec +that tracks all branches, `remote.<name>.fetchRefmap` is set up so that a +refspec-less `git fetch <name>` only fetches branches our local branches +are built on. See the `--refmap` entry in linkgit:git-fetch[1] for +details. ++ With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set up to point at remote's _<master>_ branch. See also the set-head command. +
builtin/remote.c +22 -6
@@ -179,6 +179,7 @@ static int add(int argc, const char **argv, const char *prefix, {int fetch =0 , fetch_tags =TAGS_DEFAULT ;unsigned mirror =MIRROR_NONE ; +int limited_fetch =-1 ;/* unspecified */ struct string_list track =STRING_LIST_INIT_NODUP ;const char *master =NULL ;struct remote *remote;
@@ -198,6 +199,8 @@ static int add(int argc, const char **argv, const char *prefix,OPT_CALLBACK_F (0 ,"mirror" , &mirror,"(push|fetch)" ,N_ ("set up remote as a mirror to push to or fetch from" ),PARSE_OPT_OPTARG |PARSE_OPT_COMP_ARG , parse_mirror_opt), +OPT_BOOL (0 ,"limited-fetch" , &limited_fetch, +N_ ("fetch only the branches we build on, instead of every branch" )), OPT_END() };
@@ -211,6 +214,10 @@ static int add(int argc, const char **argv, const char *prefix,die (_ ("specifying a master branch makes no sense with --mirror" ));if (mirror && !(mirror &MIRROR_FETCH ) && track.nr)die (_ ("specifying branches to track makes sense only with fetch mirrors" )); +if (limited_fetch ==1 && track.nr) +die (_ ("--limited-fetch does not make sense with -t/--track" )); +if (limited_fetch ==1 && mirror) +die (_ ("--limited-fetch does not make sense with --mirror" )); name = argv[0 ]; url = argv[1 ];
@@ -230,13 +237,22 @@ static int add(int argc, const char **argv, const char *prefix,repo_config_set (the_repository, buf.buf, url);if (!mirror || mirror &MIRROR_FETCH ) { +bool use_limited_fetch = mirror ==MIRROR_NONE && track.nr ==0 && + limited_fetch ==1 ; +strbuf_reset (&buf); -strbuf_addf (&buf,"remote.%s.fetch" , name); -if (track.nr ==0 ) -string_list_append (&track,"*" ); -for (size_t i =0 ; i < track.nr; i++) { -add_branch (buf.buf, track.items[i].string, - name, mirror, &buf2); +if (use_limited_fetch) { +strbuf_addf (&buf,"remote.%s.fetchRefmap" , name); +strbuf_reset (&buf2); +strbuf_addf (&buf2,"+refs/heads/*:refs/remotes/%s/*" , name); +repo_config_set (the_repository, buf.buf, buf2.buf); + }else { +strbuf_addf (&buf,"remote.%s.fetch" , name); +if (track.nr ==0 ) +string_list_append (&track,"*" ); +for (size_t i =0 ; i < track.nr; i++) +add_branch (buf.buf, track.items[i].string, + name, mirror, &buf2); } }
t/t5505-remote.sh +16 -0
@@ -137,6 +137,22 @@ test_expect_success 'filters are listed by git remote -v only' ' test_grep ! "\[blob:none\]" out ' +test_expect_success '--limited-fetch works in a full repository too' ' + test_when_finished "rm -rf full-add" && + git clone --no-local one full-add && + ( + cd full-add && + git remote add --limited-fetch upstream ../two && + test_cmp_config "+refs/heads/*:refs/remotes/upstream/*" \ + remote.upstream.fetchRefmap && + test_must_fail git config get remote.upstream.fetch + ) +' + +test_expect_success '--limited-fetch conflicts with -t' ' + test_must_fail git remote add --limited-fetch -t main upstream ../two +' + test_expect_success 'check remote-tracking' ' ( cd test &&
Signature · 1 line
gitgitgadget
PATCH v13 5/6 remote: default to --limited-fetch in a shallow repository
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
Adding a second remote to a shallow clone used to still fetch every branch that remote has, since "git remote add" always set up a wildcard remote.<name>.fetch refspec regardless of how shallow the repository already was. That defeats the purpose of having cloned shallow in the first place (for example with --single-branch), and can make a plain "git fetch" on that remote hang or take a very long time on a repository with many branches.
Turn --limited-fetch on by default when the repository is already shallow and neither -t/--track nor --mirror was given, so that adding a remote there does not by itself commit to following every branch it has. --no-limited-fetch keeps the previous behavior for whoever wants it.
--- Documentation/git-remote.adoc | 8 ++++- builtin/remote.c | 5 ++- t/t5505-remote.sh | 60 +++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-)
Documentation/git-remote.adoc +7 -1
@@ -57,6 +57,11 @@ Add a remote named _<name>_ for the repository at _<URL>_. The command `git fetch <name>` can then be used to create and update remote-tracking branches `<name>/<branch>`. + +If the repository is already a shallow repository (see linkgit:git-clone[1] +`--depth`) and neither `-t`, `--mirror` nor `--no-limited-fetch` is given, +`--limited-fetch` is turned on by default, so that `git fetch <name>` does +not need to negotiate history for every branch the remote has. ++ With `-f` option, `git fetch <name>` is run immediately after the remote information is set up. +
@@ -79,7 +84,8 @@ With `--limited-fetch` option, instead of a `remote.<name>.fetch` refspec that tracks all branches, `remote.<name>.fetchRefmap` is set up so that a refspec-less `git fetch <name>` only fetches branches our local branches are built on. See the `--refmap` entry in linkgit:git-fetch[1] for -details. +details. `--no-limited-fetch` explicitly disables this, overriding the +shallow-repository default described above. + With `-m <master>` option, a symbolic-ref `refs/remotes/<name>/HEAD` is set up to point at remote's _<master>_ branch. See also the set-head command.
builtin/remote.c +4 -1
@@ -16,6 +16,7 @@#include "rebase.h" #include "refs.h" #include "refspec.h" +#include "shallow.h" #include "odb.h" #include "strvec.h" #include "commit-reach.h"
@@ -238,7 +239,9 @@ static int add(int argc, const char **argv, const char *prefix,if (!mirror || mirror &MIRROR_FETCH ) {bool use_limited_fetch = mirror ==MIRROR_NONE && track.nr ==0 && - limited_fetch ==1 ; + (limited_fetch ==1 || + (limited_fetch ==-1 && +is_repository_shallow (the_repository)));strbuf_reset (&buf);if (use_limited_fetch) {
t/t5505-remote.sh +60 -0
66 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v13 6/6 remote: resolve @{push} through remote.<name>.fetchRefmap
In reply to Harald Nordgren via GitGitGadgetFrom: Harald Nordgren <[email protected]>
A branch pushed to a remote set up with "git remote add --limited-fetch" has no remote.<name>.fetch entry to map it through, so @{push} cannot tell where it landed and fails with "has no local tracking branch".
Give tracking_for_push_dest() the same fallback to remote.<name>.fetchRefmap that get_ref_map() uses on the fetch side, so a --limited-fetch remote supports @{push} resolution the same way any other remote does.
--- remote.c | 2 ++ t/t1514-rev-parse-push.sh | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+)
remote.c +2 -0
@@ -2018,6 +2018,8 @@ static char *tracking_for_push_dest(struct repository *repo, remote =repo_remote_for_push_tracking (repo, remote); ret =apply_refspecs (&remote->fetch, refname); +if (!ret) + ret =apply_refspecs (&remote->refmap, refname);if (!ret) return error_buf(err, _("push destination '%s' on remote '%s' has no local tracking branch"),
t/t1514-rev-parse-push.sh +19 -0
@@ -80,4 +80,23 @@ test_expect_success 'resolving @{push} fails with a detached HEAD' ' test_must_fail git rev-parse @{push} ' +test_expect_success '@{push} resolves after pushing to a --limited-fetch remote' ' + test_when_finished "rm -fr upstream fork downstream" && + git init -b main upstream && + test_commit -C upstream base && + git init --bare fork && + git clone --sparse upstream downstream && + ( + cd downstream && + git remote add --limited-fetch fork ../fork && + git config push.default current && + git config remote.pushDefault fork && + git checkout -b topic main && + test_commit on-topic && + git push && + resolve topic@{push} refs/remotes/fork/topic && + test_cmp_rev topic topic@{push} + ) +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v13 0/6] fetch: avoid fetching every branch of a new remote in a shallow repo
In reply to Harald Nordgren via GitGitGadgetHi, could I get some (hopefully final) reviews here?
Harald