Reputation: 9511
I have two remote git repositories. origin
and github
I push my branch devel
to both repositories.
git push -u origin devel
git push -u github devel
But then, when I do. git push
It would only get pushed to github
.
Is there anyway I can set up my two remotes, so that I can push changes to both repositories with one command ?
Upvotes: 574
Views: 233048
Reputation: 7995
To send to both remote with one command, you can create an alias for it:
git config alias.pushall '!git push origin devel && git push github devel'
With this, when you use the command git pushall
, it will update both repositories.
Upvotes: 111
Reputation: 32260
In recent versions of Git you can add multiple pushurl
s for a given remote. Use the following to add two pushurl
s to your origin
:
git remote set-url --add --push origin git://original/repo.git
git remote set-url --add --push origin git://another/repo.git
So when you push to origin
, it will push to both repositories.
UPDATE 1: Git 1.8.0.1 and 1.8.1 (and possibly other versions) seem to have a bug that causes --add
to replace the original URL the first time you use it, so you need to re-add the original URL using the same command. Doing git remote -v
should reveal the current URLs for each remote.
UPDATE 2: Junio C. Hamano, the Git maintainer, explained it's how it was designed. Doing git remote set-url --add --push <remote_name> <url>
adds a pushurl
for a given remote, which overrides the default URL for pushes. However, you may add multiple pushurl
s for a given remote, which then allows you to push to multiple remotes using a single git push
. You can verify this behavior below:
$ git clone git://original/repo.git
$ git remote -v
origin git://original/repo.git (fetch)
origin git://original/repo.git (push)
$ git config -l | grep '^remote\.'
remote.origin.url=git://original/repo.git
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
Now, if you want to push to two or more repositories using a single command, you may create a new remote named all
(as suggested by @Adam Nelson in comments), or keep using the origin
, though the latter name is less descriptive for this purpose. If you still want to use origin
, skip the following step, and use origin
instead of all
in all other steps.
So let's add a new remote called all
that we'll reference later when pushing to multiple repositories:
$ git remote add all git://original/repo.git
$ git remote -v
all git://original/repo.git (fetch) <-- ADDED
all git://original/repo.git (push) <-- ADDED
origin git://original/repo.git (fetch)
origin git://original/repo.git (push)
$ git config -l | grep '^remote\.all'
remote.all.url=git://original/repo.git <-- ADDED
remote.all.fetch=+refs/heads/*:refs/remotes/all/* <-- ADDED
Then let's add a pushurl
to the all
remote, pointing to another repository:
$ git remote set-url --add --push all git://another/repo.git
$ git remote -v
all git://original/repo.git (fetch)
all git://another/repo.git (push) <-- CHANGED
origin git://original/repo.git (fetch)
origin git://original/repo.git (push)
$ git config -l | grep '^remote\.all'
remote.all.url=git://original/repo.git
remote.all.fetch=+refs/heads/*:refs/remotes/all/*
remote.all.pushurl=git://another/repo.git <-- ADDED
Here git remote -v
shows the new pushurl
for push, so if you do git push all master
, it will push the master
branch to git://another/repo.git
only. This shows how pushurl
overrides the default url (remote.all.url).
Now let's add another pushurl
pointing to the original repository:
$ git remote set-url --add --push all git://original/repo.git
$ git remote -v
all git://original/repo.git (fetch)
all git://another/repo.git (push)
all git://original/repo.git (push) <-- ADDED
origin git://original/repo.git (fetch)
origin git://original/repo.git (push)
$ git config -l | grep '^remote\.all'
remote.all.url=git://original/repo.git
remote.all.fetch=+refs/heads/*:refs/remotes/all/*
remote.all.pushurl=git://another/repo.git
remote.all.pushurl=git://original/repo.git <-- ADDED
You see both pushurl
s we added are kept. Now a single git push all master
will push the master
branch to both git://another/repo.git
and git://original/repo.git
.
IMPORTANT NOTE: If your remotes have distinct rules (hooks) to accept/reject a push, one remote may accept it while the other doesn't. Therefore, if you want them to have the exact same history, you'll need to fix your commits locally to make them acceptable by both remotes and push again, or you might end up in a situation where you can only fix it by rewriting history (using push -f
), and that could cause problems for people that have already pulled your previous changes from the repo.
Upvotes: 922
Reputation: 1328182
Note that you can now (Git 2.46, Q3 2024, batch 18) override pushurl
s:
See commit aecd794, commit ffce821, commit 7384e75, commit e2269a2, commit 9badf97, commit bd1b88d, commit b68118d, commit 8e80441, commit 52595c1, commit aa0595f, commit 0295ce7 (14 Jun 2024) by Jeff King (peff
).
(Merged by Junio C Hamano -- gitster
-- in commit ca46310, 02 Jul 2024)
remote
: allow resetting url listReported-by: Mathew George
Signed-off-by: Jeff King
Because
remote.*.url
is treated as a multi-valued key, there is no way to override previous config.
So for example if you have remote.origin.url set to some wrong value, doing:git -c remote.origin.url=right fetch
would not work.
It would append "right
" to the list, which means we'd still fetch from "wrong
" (since subsequent values are used only as push urls).Let's provide a mechanism to reset the list, like we do for other multi-valued keys (e.g.,
credential.helper
,http.extraheaders
, andmerge.suppressDest
all use this "empty string means reset
" pattern).
git config
now includes in its man page:
Setting this key to the empty string clears the list of urls, allowing you to override earlier config.
So if you have:
git config --add remote.multi.url right
git config --add remote.multi.pushurl wrong-push-one
git config --add remote.multi.pushurl wrong-push-two
You now can force correct push urls with:
git -c remote.multi.pushurl= \
-c remote.multi.pushurl=right-push-one \
-c remote.multi.pushurl=right-push-two \
remote show -n multi
You would see:
Fetch URL: right
Push URL: right-push-one
Push URL: right-push-two
The problem now is: remote.multi.pushurl=
reset the list of push URL, meaning that list is now empty
With Git 2.46 (Q3 2024), rc0 batch 3, "git push
"(man) it now dies with "fatal: bad repository ''
".
See commit 757c6ee (11 Jul 2024) by Karthik Nayak (KarthikNayak
).
(Merged by Junio C Hamano -- gitster
-- in commit e13feda, 17 Jul 2024)
builtin/push
: callset_refspecs
after validating remoteHelped-by: Jeff King
Helped-by: Junio C Hamano
Signed-off-by: Karthik Nayak
When an end-user runs "
git push
"(man) with an empty string for the remote repository name, e.g.$ git push '' main
"
git push
" fails with aBUG()
:must get a remote for repo
.
Even though this is a nonsense request that we want to fail, we shouldn't hit aBUG()
.
Instead we want to give a sensible error message, e.g., 'bad repository
'".This is because since 9badf97 ("
remote
: allow resetting url list", 2024-06-14, Git v2.46.0-rc0 -- merge listed in batch #18), we reset the remote URL if the provided URL is empty.
When a user of 'remotes_remote_get
' tries to fetch a remote with an empty repo name, the function initializes the remote via 'make_remote
'.
But the remote is still not a valid remote, since the URL is empty, so it tries to add the URL alias using 'add_url_alias
'.
This in-turn will call 'add_url
', but since the URL is empty we call 'strvec_clear
' on theremote->url
.
Back in 'remotes_remote_get
', we again check if the remote is valid, which fails, so we return 'NULL
' for the 'struct remote *' value.The '
builtin/push.c
' code, calls 'set_refspecs
' before validating the remote.
This worked with empty repo names earlier since we would get a remote, albeit with an empty URL.
With the new changes, we get a 'NULL
' remote value, this causes the check for remote to fail and raises the BUG in 'set_refspecs
'.Do a simple fix by doing remote validation first.
With this, we can also now directly pass remote to 'set_refspecs
' instead of it trying to lazily obtain it.
Upvotes: 2