From: Usman Akinyemi <usmanakinyemi202@gmail.com>
To: git@vger.kernel.org, gitster@pobox.com
Cc: christian.couder@gmail.com, me@ttaylorr.com,
phillip.wood123@gmail.com, ps@pks.im
Subject: [RFC PATCH 2/2] push: support pushing to a remote group
Date: Fri, 6 Mar 2026 04:02:48 +0530 [thread overview]
Message-ID: <20260305223248.170785-3-usmanakinyemi202@gmail.com> (raw)
In-Reply-To: <20260305223248.170785-1-usmanakinyemi202@gmail.com>
`git fetch` accepts a remote group name (configured via `remotes.<name>`
in config) and fetches from each member remote. `git push` has no
equivalent — it only accepts a single remote name.
Teach `git push` to resolve its repository argument through
`add_remote_or_group()`, which was made public in the previous patch,
so that a user can push to all remotes in a group with:
git push <group>
When the argument resolves to a single remote the behaviour is
identical to before. When it resolves to a group, each member remote
is pushed in sequence.
The group push path rebuilds the refspec list (`rs`) from scratch for
each member remote so that per-remote push mappings configured via
`remote.<name>.push` are resolved correctly against each specific
remote. Without this, refspec entries would accumulate across iterations
and each subsequent remote would receive a growing list of duplicated
entries.
Mirror detection (`remote->mirror`) is also evaluated per remote using
a copy of the flags, so that a mirror remote in the group cannot set
TRANSPORT_PUSH_FORCE on subsequent non-mirror remotes in the same group.
A known interaction: push.default = simple will die when the current
branch has no upstream configured, because setup_default_push_refspecs()
requires an upstream for that mode. Users pushing to a group should set
push.default = current or supply explicit refspecs. This is consistent
with how fetch handles default refspec resolution per remote.
Suggested-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
---
builtin/push.c | 89 +++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 70 insertions(+), 19 deletions(-)
diff --git a/builtin/push.c b/builtin/push.c
index 5b6cebbb85..a98fb4c934 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -551,12 +551,13 @@ int cmd_push(int argc,
int flags = 0;
int tags = 0;
int push_cert = -1;
- int rc;
+ int rc = 0;
const char *repo = NULL; /* default repository */
struct string_list push_options_cmdline = STRING_LIST_INIT_DUP;
+ struct string_list remote_group = STRING_LIST_INIT_DUP; /* represent remote or remote group */
struct string_list *push_options;
const struct string_list_item *item;
- struct remote *remote;
+ struct remote *remote = NULL;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
@@ -625,25 +626,35 @@ int cmd_push(int argc,
if (argc > 0)
repo = argv[0];
- remote = pushremote_get(repo);
- if (!remote) {
- if (repo)
- die(_("bad repository '%s'"), repo);
- die(_("No configured push destination.\n"
- "Either specify the URL from the command-line or configure a remote repository using\n"
- "\n"
- " git remote add <name> <url>\n"
- "\n"
- "and then push using the remote name\n"
- "\n"
- " git push <name>\n"));
+ if (repo) {
+ if (!add_remote_or_group(repo, &remote_group))
+ die(_("no such remote or remote group: %s"), repo);
+ } else {
+ remote = pushremote_get(NULL);
+ if (!remote)
+ die(_("No configured push destination.\n"
+ "Either specify the URL from the command-line or configure a remote repository using\n"
+ "\n"
+ " git remote add <name> <url>\n"
+ "\n"
+ "and then push using the remote name\n"
+ "\n"
+ " git push <name>\n"));
}
- if (argc > 0)
- set_refspecs(argv + 1, argc - 1, remote);
+ /*
+ * set_refspecs and mirror detection must not use `remote`
+ * when it may be NULL (group path). For the single-remote case,
+ * handle them here. For the group case they are handled
+ * per-remote inside the loop below.
+ */
+ if (remote) {
+ if (argc > 0)
+ set_refspecs(argv + 1, argc - 1, remote);
- if (remote->mirror)
- flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (remote->mirror)
+ flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ }
if (flags & TRANSPORT_PUSH_ALL) {
if (argc >= 2)
@@ -661,10 +672,50 @@ int cmd_push(int argc,
if (strchr(item->string, '\n'))
die(_("push options must not have new line characters"));
- rc = do_push(flags, push_options, remote);
+ if (remote) {
+ rc = do_push(flags, push_options, remote);
+ } else {
+ int base_flags = flags;
+ for (int i = 0; i < remote_group.nr; i++) {
+ int iter_flags = base_flags;
+ struct remote *r = pushremote_get(remote_group.items[i].string);
+ if (!r)
+ die(_("no such remote or remote group: %s"),
+ remote_group.items[i].string);
+
+ /*
+ * Rebuild rs from scratch for each remote so that
+ * push mappings (remote.NAME.push config) are resolved
+ * against this specific remote. Without this, mappings
+ * from a previous iteration would accumulate in rs and
+ * each remote would be pushed an ever-growing refspec list.
+ */
+ refspec_clear(&rs);
+ rs = (struct refspec) REFSPEC_INIT_PUSH;
+
+ if (tags)
+ refspec_append(&rs, "refs/tags/*");
+ if (argc > 0)
+ set_refspecs(argv + 1, argc - 1, r);
+
+ /*
+ * Compute mirror flag from a fresh base each iteration
+ * so that a mirror remote does not bleed TRANSPORT_PUSH_FORCE
+ * into subsequent non-mirror remotes in the same group.
+ */
+ if (r->mirror)
+ iter_flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+
+ rc |= do_push(iter_flags, push_options, r);
+ }
+ }
+
+
string_list_clear(&push_options_cmdline, 0);
string_list_clear(&push_options_config, 0);
+ string_list_clear(&remote_group, 0);
clear_cas_option(&cas);
+
if (rc == -1)
usage_with_options(push_usage, options);
else
--
2.53.0
next prev parent reply other threads:[~2026-03-05 22:33 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-03-05 22:32 [RFC PATCH 0/2] push: add support for pushing to remote groups Usman Akinyemi
2026-03-05 22:32 ` [RFC PATCH 1/2] remote: move remote group resolution to remote.c Usman Akinyemi
2026-03-06 18:12 ` Junio C Hamano
2026-03-09 0:43 ` Usman Akinyemi
2026-03-05 22:32 ` Usman Akinyemi [this message]
2026-03-07 2:12 ` [RFC PATCH 2/2] push: support pushing to a remote group Junio C Hamano
2026-03-09 0:56 ` Usman Akinyemi
2026-03-09 13:38 ` Junio C Hamano
2026-03-18 20:40 ` [RFC PATCH v2 0/2] push: add support for pushing to remote groups Usman Akinyemi
2026-03-18 20:40 ` [RFC PATCH v2 1/2] remote: move remote group resolution to remote.c Usman Akinyemi
2026-03-18 20:40 ` [RFC PATCH v2 2/2] push: support pushing to a remote group Usman Akinyemi
2026-03-18 20:57 ` Junio C Hamano
2026-03-18 21:58 ` Junio C Hamano
2026-03-18 22:25 ` Junio C Hamano
2026-03-19 17:02 ` Junio C Hamano
2026-03-25 18:42 ` Usman Akinyemi
2026-03-18 21:57 ` [RFC PATCH v2 0/2] push: add support for pushing to remote groups Junio C Hamano
2026-03-18 23:13 ` Usman Akinyemi
2026-03-25 19:09 ` [RFC PATCH v3 " Usman Akinyemi
2026-03-25 19:09 ` [RFC PATCH v3 1/2] remote: move remote group resolution to remote.c Usman Akinyemi
2026-03-25 19:09 ` [RFC PATCH v3 2/2] push: support pushing to a remote group Usman Akinyemi
2026-03-25 19:47 ` Junio C Hamano
2026-03-27 22:18 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260305223248.170785-3-usmanakinyemi202@gmail.com \
--to=usmanakinyemi202@gmail.com \
--cc=christian.couder@gmail.com \
--cc=git@vger.kernel.org \
--cc=gitster@pobox.com \
--cc=me@ttaylorr.com \
--cc=phillip.wood123@gmail.com \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox