Merge branch 'hn/fetch-refmap' into seen

In a shallow repository, adding a new remote and fetching from it
used to eagerly fetch all remote branches.  The default refspec
configured for remotes added in shallow repositories has been
changed to rely on `remote.<name>.refmap` rather than
`remote.<name>.fetch`, pulling only those remote branches tracked
by local branches.

* hn/fetch-refmap:
  remote: default to --limited-fetch in a shallow repository
  remote: add "git remote add --limited-fetch"
  fetch: infer branches to fetch from a refmap-only remote
  fetch: extract collect_upstream_from_remote() helper
  fetch: add remote.<name>.refmap
seen
Junio C Hamano 2026-10-12 14:23:53 -07:00
commit 091dad56d7
11 changed files with 470 additions and 34 deletions

View File

@ -33,6 +33,14 @@ remote.<name>.fetch::
The default set of "refspec" for linkgit:git-fetch[1]. See
linkgit:git-fetch[1].

remote.<name>.refmap::
The default value of the `--refmap` option for linkgit:git-fetch[1].
Used to map remote refs being fetched to remote-tracking refs to
store. If `remote.<name>.fetch` is not set either, a refspec-less
fetch infers what to fetch from local branches built on this
remote, instead of fetching every branch it has. 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].

View File

@ -244,6 +244,11 @@ endif::git-pull[]
refspecs and rely entirely on the refspecs supplied as
command-line arguments. See section on "Configured Remote-tracking
Branches" for details.
+
When a refmap is active (from `--refmap` or `remote.<name>.refmap`) but
nothing to fetch is specified on the command line, nor is there a
`remote.<name>.fetch`, branches from the remote that are used as the
`@{upstream}` of our local branches are fetched.

`-t`::
`--tags`::

View File

@ -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>)
@ -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.
+
@ -75,6 +80,13 @@ 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>.refmap` 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. `--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.
+

View File

@ -553,6 +553,14 @@ 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>.refmap.
*/
struct refspec *effective_refmap =
refmap.nr ? &refmap : remote ? &remote->refmap : NULL;
struct refspec inferred_rs;
int infer_from_refmap = 0;

/* opportunistically-updated references: */
struct ref *orefs = NULL, **oref_tail = &orefs;
@ -560,15 +568,32 @@ static struct ref *get_ref_map(struct remote *remote,
struct hashmap existing_refs;
int existing_refs_populated = 0;

refspec_init_fetch(&inferred_rs, the_hash_algo);

filter_prefetch_refspec(rs);
if (remote)
filter_prefetch_refspec(&remote->fetch);

if (!rs->nr && remote && !remote->fetch.nr &&
effective_refmap && effective_refmap->nr) {
struct string_list tracked = STRING_LIST_INIT_DUP;
struct string_list_item *item;

branches_tracking_remote(the_repository, remote, &tracked);
for_each_string_list_item(item, &tracked)
refspec_append(&inferred_rs, item->string);
string_list_clear(&tracked, 0);

rs = &inferred_rs;
infer_from_refmap = 1;
}

if (rs->nr) {
struct refspec *fetch_refspec;

for (i = 0; i < rs->nr; i++) {
get_fetch_map(remote_refs, &rs->items[i], &tail, 0);
get_fetch_map(remote_refs, &rs->items[i], &tail,
infer_from_refmap);
if (rs->items[i].dst && rs->items[i].dst[0])
*autotags = 1;
}
@ -596,13 +621,15 @@ 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;

for (i = 0; i < fetch_refspec->nr; i++)
get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1);
} else if (infer_from_refmap) {
/* Already fully handled above. */
} else if (refmap.nr) {
die("--refmap option is only meaningful with command-line refspec(s)");
} else {
@ -698,6 +725,7 @@ static struct ref *get_ref_map(struct remote *remote,
if (existing_refs_populated)
hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent);

refspec_clear(&inferred_rs);
return ref_map;
}

@ -1970,6 +1998,26 @@ static void collect_updated_tips(struct oidset *tips, struct ref *ref_map)
}
}

static int late_resolve_frh(const struct transport *transport,
const struct fetch_config *config)
{
/*
* See remote.c's handling of remote.<name>.followRemoteHEAD
* for the analogous, still-unresolved case.
*/

int follow_remote_head = FOLLOW_REMOTE_NEVER;

if (transport->remote->follow_remote_head)
follow_remote_head = transport->remote->follow_remote_head;
else if (config->follow_remote_head_seen)
follow_remote_head =
get_follow_remote_head(config->follow_remote_head_raw);
else
follow_remote_head = BUILTIN_FOLLOW_REMOTE_HEAD_DFLT;
return follow_remote_head;
}

static int do_fetch(struct transport *transport,
struct refspec *rs,
const struct fetch_config *config,
@ -2007,31 +2055,48 @@ 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);
} else if (transport->remote->fetch.nr) {
struct string_list tracked = STRING_LIST_INIT_DUP;
struct string_list_item *item;

if (transport->remote->fetch.nr) {
refspec_ref_prefixes(&transport->remote->fetch,
&transport_ls_refs_options.ref_prefixes);
/*
* See remote.c's handling of remote.<name>.followRemoteHEAD
* for the analogous, still-unresolved case.
*/
if (transport->remote->follow_remote_head)
follow_remote_head = transport->remote->follow_remote_head;
else if (config->follow_remote_head_seen)
follow_remote_head = get_follow_remote_head(config->follow_remote_head_raw);
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);
}
refspec_ref_prefixes(&transport->remote->fetch,
&transport_ls_refs_options.ref_prefixes);

follow_remote_head = late_resolve_frh(transport, config);

/*
* 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, &tracked,
transport->remote, NULL);
for_each_string_list_item(item, &tracked)
strvec_push(&transport_ls_refs_options.ref_prefixes,
item->string);
string_list_clear(&tracked, 0);
} else {
/*
* The --refmap command line option, if given, takes
* precedence over remote.<name>.refmap.
*/
struct refspec *effective_refmap = refmap.nr ? &refmap :
&transport->remote->refmap;
struct string_list tracked = STRING_LIST_INIT_DUP;
struct string_list_item *item;

if (effective_refmap->nr) {
branches_tracking_remote(the_repository,
transport->remote, &tracked);
follow_remote_head = late_resolve_frh(transport, config);
} else {
collect_upstream_from_remote(the_repository, &tracked,
transport->remote, NULL);
}
for_each_string_list_item(item, &tracked)
strvec_push(&transport_ls_refs_options.ref_prefixes,
item->string);
string_list_clear(&tracked, 0);

/*
* If there are no refs specified to fetch, then we just

View File

@ -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"
@ -179,6 +180,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 +200,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 +215,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 +238,24 @@ static int add(int argc, const char **argv, const char *prefix,
repo_config_set(the_repository, buf.buf, url);

if (!mirror || mirror & MIRROR_FETCH) {
int use_limited_fetch = mirror == MIRROR_NONE && track.nr == 0 &&
(limited_fetch == 1 ||
(limited_fetch == -1 &&
is_repository_shallow(the_repository)));

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.refmap", 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);
}
}


View File

@ -153,6 +153,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);
@ -177,6 +178,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);
@ -540,6 +542,12 @@ static int handle_config(const char *key, const char *value,
return -1;
refspec_append(&remote->fetch, v);
free(v);
} else if (!strcmp(subkey, "refmap")) {
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))
@ -996,7 +1004,9 @@ 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);
if (remote->fetch.nr)
return refspec_find_match(&remote->fetch, refspec);
return refspec_find_match(&remote->refmap, refspec);
}

static struct ref *alloc_ref_with_prefix(const char *prefix, size_t prefixlen,
@ -1962,6 +1972,45 @@ int branch_merge_matches(struct branch *branch,
return refname_match(branch->merge[i]->src, refname);
}

void collect_upstream_from_remote(struct repository *repo,
struct string_list *tracked,
struct remote *remote,
const char *refname)
{
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++)
string_list_insert(tracked, branch->merge[i]->src);
}

struct branches_tracking_remote_cb_data {
struct repository *repo;
struct remote *remote;
struct string_list *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->tracked, data->remote,
ref->name);
return 0;
}

void branches_tracking_remote(struct repository *repo, struct remote *remote,
struct string_list *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, ...)
{

View File

@ -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);
@ -357,6 +363,19 @@ 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,
struct string_list *tracked,
struct remote *remote,
const char *refname);

/* fills tracked with the refname of every local branch's upstream on remote */
void branches_tracking_remote(struct repository *repo, struct remote *remote,
struct string_list *tracked);

/* list of the remote in a group as configured */
struct remote_group_data {
const char *name;

View File

@ -738,6 +738,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',

View File

@ -137,6 +137,82 @@ test_expect_success 'filters are listed by git remote -v only' '
test_grep ! "\[blob:none\]" out
'

test_expect_success 'add remote -t keeps an explicit refspec in a shallow repository' '
test_when_finished "rm -rf shallow-add" &&
git clone --no-local --depth=1 --branch main --single-branch \
one shallow-add &&
(
cd shallow-add &&
git remote add -t main upstream ../two &&
test_cmp_config "+refs/heads/main:refs/remotes/upstream/main" \
remote.upstream.fetch
)
'

test_expect_success 'add remote keeps the wildcard refspec in a full repository' '
test_when_finished "rm -rf full-add" &&
git clone --no-local one full-add &&
(
cd full-add &&
git remote add upstream ../two &&
test_cmp_config "+refs/heads/*:refs/remotes/upstream/*" \
remote.upstream.fetch
)
'

test_expect_success 'a remote added in a shallow repository defaults to --limited-fetch' '
test_when_finished "rm -rf shallow-add" &&
git clone --no-local --depth=1 --branch main --single-branch \
one shallow-add &&
(
cd shallow-add &&
git remote add upstream ../two &&
test_cmp_config "+refs/heads/*:refs/remotes/upstream/*" \
remote.upstream.refmap &&
test_must_fail git config get remote.upstream.fetch &&
git fetch upstream main &&
git branch --set-upstream-to=upstream/main &&
test_cmp_config upstream branch.main.remote &&
test_cmp_config refs/heads/main branch.main.merge &&
git fetch upstream &&
git for-each-ref --format="%(refname)" refs/remotes/upstream >actual &&
cat >expect <<-\EOF &&
refs/remotes/upstream/HEAD
refs/remotes/upstream/main
EOF
test_cmp expect actual
)
'

test_expect_success '--no-limited-fetch overrides the shallow-repository default' '
test_when_finished "rm -rf shallow-add" &&
git clone --no-local --depth=1 --branch main --single-branch \
one shallow-add &&
(
cd shallow-add &&
git remote add --no-limited-fetch upstream ../two &&
test_cmp_config "+refs/heads/*:refs/remotes/upstream/*" \
remote.upstream.fetch &&
test_must_fail git config get remote.upstream.refmap
)
'

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.refmap &&
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 &&

View File

@ -927,6 +927,69 @@ test_expect_success 'explicit --refmap option overrides remote.*.fetch' '
)
'

test_expect_success 'remote.*.refmap acts like --refmap on the command line' '
test_when_finished "git -C three config --unset remote.origin.refmap" &&
git branch -f side &&
git -C three config remote.origin.refmap \
"refs/heads/*:refs/remotes/other/*" &&
(
cd three &&
git update-ref refs/remotes/origin/main base-origin-main &&
o=$(git rev-parse --verify refs/remotes/origin/main) &&
git fetch origin main &&
n=$(git rev-parse --verify refs/remotes/origin/main) &&
test "$o" = "$n" &&
test_must_fail git rev-parse --verify refs/remotes/origin/side &&
git rev-parse --verify refs/remotes/other/main
)
'

check_fetched_refs () {
git for-each-ref --format="%(refname)" refs/remotes/ >actual &&
cat >expect &&
test_cmp expect actual
}

test_expect_success 'remote.<name>.refmap without tracking (baseline)' '
test_when_finished "rm -fr fetch-refmap-baseline fetch-refmap-upstream" &&
git init -b main fetch-refmap-upstream &&
test_commit -C fetch-refmap-upstream base &&
git -C fetch-refmap-upstream branch other &&
git init fetch-refmap-baseline &&
(
cd fetch-refmap-baseline &&
git remote add origin ../fetch-refmap-upstream &&

# Without fetch refspec, but with fetch refmap.
git config --unset-all remote.origin.fetch &&
git config remote.origin.refmap "+refs/heads/*:refs/remotes/origin/*" &&

# Nothing tracked, nothing fetched, no error.
git fetch origin &&
check_fetched_refs <<-\EOF &&
EOF

# Nothing tracked, explicit ref on the command line.
git fetch origin main &&
check_fetched_refs <<-\EOF &&
refs/remotes/origin/main
EOF

# With both refmap and fetch configured, remote.<name>.fetch
# wins: a refspec-less fetch follows it as usual, and
# remote.<name>.refmap plays no part in deciding what to
# fetch, only in remapping something that is already being
# fetched by name.
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*" &&
git fetch origin &&
check_fetched_refs <<-\EOF
refs/remotes/origin/HEAD
refs/remotes/origin/main
refs/remotes/origin/other
EOF
)
'

test_expect_success 'explicitly empty --refmap option disables remote.*.fetch' '
git branch -f side &&
(

119
t/t5586-fetch-refmap.sh Executable file
View File

@ -0,0 +1,119 @@
#!/bin/sh

test_description='"git fetch" with a remote.<name>.refmap but no remote.<name>.fetch

When a remote has a refmap configured but no fetch refspec, a
refspec-less fetch infers what to fetch from the local branches whose
@{upstream} is on that remote.
'

GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME

. ./test-lib.sh

test_expect_success 'setup' '
test_commit main-1 &&
test_commit main-2 &&
git checkout -b side main-1 &&
test_commit side-1 &&
git checkout -b next main-1 &&
test_commit next-1 &&
git checkout main
'

test_expect_success 'clone shallow and single-branch, then add a second remote' '
git clone --no-local --depth=1 --branch main --single-branch . client &&
(
cd client &&
git remote add upstream .. &&
test_might_fail git config unset remote.upstream.fetch &&
git config remote.upstream.refmap \
"+refs/heads/*:refs/remotes/upstream/*"
)
'

test_expect_success 'a bare fetch needs nothing until a branch is tracked' '
(
cd client &&
git fetch upstream &&
git for-each-ref --format="%(refname)" refs/remotes/upstream >actual &&
test_must_be_empty actual
)
'

test_expect_success 'an explicit one-time fetch lets a branch be tracked' '
(
cd client &&
git fetch upstream main &&
git branch --set-upstream-to=upstream/main &&
test_cmp_config upstream branch.main.remote &&
test_cmp_config refs/heads/main branch.main.merge
)
'

test_expect_success 'a branch checked out from a one-time fetch is kept updated by later plain fetches' '
(
cd client &&
git fetch upstream side:refs/remotes/upstream/side &&
git branch side-topic upstream/side
) &&
git checkout side &&
test_commit side-2 &&
git checkout main &&
(
cd client &&
git fetch upstream &&
git for-each-ref --format="%(refname)" refs/remotes/upstream >actual &&
cat >expect <<-\EOF &&
refs/remotes/upstream/HEAD
refs/remotes/upstream/main
refs/remotes/upstream/side
EOF
test_cmp expect actual &&
git rev-parse refs/remotes/upstream/side >actual-oid &&
git -C .. rev-parse side >expect-oid &&
test_cmp expect-oid actual-oid
)
'

test_expect_success 'a second branch tracking the same upstream branch does not fetch it twice' '
(
cd client &&
git branch side-topic-2 upstream/side &&
git fetch upstream &&
git for-each-ref --format="%(refname)" refs/remotes/upstream >actual &&
cat >expect <<-\EOF &&
refs/remotes/upstream/HEAD
refs/remotes/upstream/main
refs/remotes/upstream/side
EOF
test_cmp expect actual
)
'

test_expect_success 'a branch tracking a different remote is not fetched from upstream' '
(
cd client &&
git remote add other .. &&
git fetch other next:refs/remotes/other/next &&
git branch next-topic other/next &&
git fetch upstream &&
git for-each-ref --format="%(refname)" refs/remotes/upstream >actual &&
cat >expect <<-\EOF &&
refs/remotes/upstream/HEAD
refs/remotes/upstream/main
refs/remotes/upstream/side
EOF
test_cmp expect actual
)
'

test_expect_success 'git remote show does not choke on a refmap-only remote' '
(
cd client &&
git remote show upstream
)
'

test_done