Skip to contentSkip to conversation

fetch: add config to avoid fetching every branch in shallow repo

PATCH In review Harald Nordgren via GitGitGadget 5 participants 132 messages Sep 19 – Nov 11 Page 2 of 2

Subsystems (2)
lore
Share & tools
lore
#120 lore raw

PATCH v12 2/5 fetch: extract collect_upstream_from_remote() helper

In reply to Harald Nordgren via GitGitGadget

From: 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
index 305934f5f4..31ec963db7 100644
@@ -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
index 9acab2e01a..d4259fe7f3 100644
@@ -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
index ac485a584d..003c8fd180 100644
@@ -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
#121 lore raw

PATCH v12 3/5 fetch: infer branches to fetch from a refmap-only remote

In reply to Harald Nordgren via GitGitGadget

From: 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
index 236ccca93e..0d24abfcbd 100644
@@ -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
index c2101a7b39..be27c03de7 100644
@@ -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
index d4259fe7f3..15cd96aa8e 100644
@@ -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
index 003c8fd180..6fd9b05a79 100644
@@ -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
index 11b3752f00..0953a4112f 100644
@@ -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
#122 lore raw

PATCH v12 4/5 remote: add "git remote add --limited-fetch"

In reply to Harald Nordgren via GitGitGadget

From: 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
index 315100409d..1f490819ba 100644
@@ -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
index fb7e5b114f..5f8a8a2f71 100644
@@ -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
index 5fbfcb0848..90cba75f93 100755
@@ -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
#123 lore raw

PATCH v12 5/5 remote: default to --limited-fetch in a shallow repository

In reply to Harald Nordgren via GitGitGadget

From: 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
index 1f490819ba..245c0c4558 100644
@@ -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
index 5f8a8a2f71..5d2388a26c 100644
@@ -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
#124 lore raw

v13 cover fetch: avoid fetching every branch of a new remote in a shallow repo

In reply to Harald Nordgren via GitGitGadget

Avoid 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>.fetchRefmap
Signature · 1 line
gitgitgadget
#125 lore raw

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
index 3a20d0f752..236ccca93e 100644
@@ -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
index 47dea1de8e..c2101a7b39 100644
@@ -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
index 1f76ae6242..305934f5f4 100644
@@ -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
index 49ee2ea318..9acab2e01a 100644
@@ -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
index cca02033b9..ac485a584d 100644
@@ -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
#126 lore raw

PATCH v13 2/6 fetch: extract collect_upstream_from_remote() helper

In reply to Harald Nordgren via GitGitGadget

From: 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
index 305934f5f4..31ec963db7 100644
@@ -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
index 9acab2e01a..d4259fe7f3 100644
@@ -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
index ac485a584d..003c8fd180 100644
@@ -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
#127 lore raw

PATCH v13 3/6 fetch: infer branches to fetch from a refmap-only remote

In reply to Harald Nordgren via GitGitGadget

From: 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
index 236ccca93e..0d24abfcbd 100644
@@ -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
index c2101a7b39..be27c03de7 100644
@@ -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
index d4259fe7f3..15cd96aa8e 100644
@@ -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
index 003c8fd180..6fd9b05a79 100644
@@ -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
index 11b3752f00..0953a4112f 100644
@@ -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
#128 lore raw

PATCH v13 4/6 remote: add "git remote add --limited-fetch"

In reply to Harald Nordgren via GitGitGadget

From: 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
index 315100409d..1f490819ba 100644
@@ -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
index fb7e5b114f..5f8a8a2f71 100644
@@ -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
index 5fbfcb0848..90cba75f93 100755
@@ -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
#129 lore raw

PATCH v13 5/6 remote: default to --limited-fetch in a shallow repository

In reply to Harald Nordgren via GitGitGadget

From: 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
index 1f490819ba..245c0c4558 100644
@@ -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
index 5f8a8a2f71..5d2388a26c 100644
@@ -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
#130 lore raw

PATCH v13 6/6 remote: resolve @{push} through remote.<name>.fetchRefmap

In reply to Harald Nordgren via GitGitGadget

From: 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
index 15cd96aa8e..ecd104766c 100644
@@ -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
index 5a4f16867a..bcf00107f5 100755
@@ -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