yasith
yasith

Reputation: 9511

Git - Pushing code to two remotes

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

Answers (3)

William Seiti Mizuta
William Seiti Mizuta

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

jweyrich
jweyrich

Reputation: 32260

In recent versions of Git you can add multiple pushurls for a given remote. Use the following to add two pushurls 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 pushurls 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 pushurls 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

VonC
VonC

Reputation: 1328182

Note that you can now (Git 2.46, Q3 2024, batch 18) override pushurls:

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 list

Reported-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, and merge.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: call set_refspecs after validating remote

Helped-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 a BUG(): must get a remote for repo.
Even though this is a nonsense request that we want to fail, we shouldn't hit a BUG().
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 the remote->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

Related Questions