Search code examples
gitgit-pushgit-pullgit-remotemirroring

How to deal with multiple remotes and branches


I want to have multiple remote servers which should mirror each other for all branches.

Starting this adventure, I found: pull/push from multiple remote locations

which tells me:

$ git remote set-url origin --push --add <a remote>
$ git remote set-url origin --push --add <another remote>

If I do a

$ git pull --all
Fetching origin
Fetching willi

Looks good!

But pushing did not work:

$ git push --all
To ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
f974ce2..c146f0a  master -> master

No push to my second remote! Why?

So if I try a different approach like:

$ git remote add mirroring ssh://gitmini@localhost/home/gitmini/gitrepos/repo2
$ git remote set-url mirroring --push --add ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
$ git remote -vv
mirroring   ssh://gitmini@localhost/home/gitmini/gitrepos/repo2 (fetch)
mirroring   ssh://gitmini@localhost/home/gitmini/gitrepos/repo1 (push)

Which is really not what I expect!

There is an additional option --mirror=fetch|pull but this also results in misconfigured results.

As mentioned in some comments, it is possible to add a url if a command is repeated. But I can never add more than one repo for fetching. As example I can go to this result, which to me seems buggy at best:

 $ git remote -vv
 mirroring  ssh://gitmini@localhost/home/gitmini/gitrepos/repo1 (fetch)
 mirroring  ssh://gitmini@localhost/home/gitmini/gitrepos/repo2 (push)
 mirroring  ssh://gitmini@localhost/home/gitmini/gitrepos/repo1 (push)
 mirroring  ssh://gitmini@localhost/home/gitmini/gitrepos/repo2 (push)

As a next try I run:

 $ git config -e

and added the following section:

[remote "mirroring"]
    url = ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
    fetch = +refs/heads/*:refs/remotes/mirroring/*
    pushurl = ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
    url = ssh://gitmini@localhost/home/gitmini/gitrepos/repo2
    fetch = +refs/heads/*:refs/remotes/mirroring/*
    pushurl = ssh://gitmini@localhost/home/gitmini/gitrepos/repo2     

But

$ git remote -vv
mirroring   ssh://gitmini@localhost/home/gitmini/gitrepos/repo1 (fetch)
mirroring   ssh://gitmini@localhost/home/gitmini/gitrepos/repo1 (push)
mirroring   ssh://gitmini@localhost/home/gitmini/gitrepos/repo2 (push)

the line for fetch of repo2 is simply ignored!

In fact, I am not able to set up the configuration. My task is simple: Have two remotes in sync.

EDIT: Some comments on the given answer from torek:

It looks that it is possible to set:

[remote "mirroring"]
    url = ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
    fetch = +refs/heads/*:refs/remotes/mirroring/*
    pushurl = ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
    url = ssh://gitmini@localhost/home/gitmini/gitrepos/repo2
    fetch = +refs/heads/*:refs/remotes/mirroring/*
    pushurl = ssh://gitmini@localhost/home/gitmini/gitrepos/repo2

With this configuration a

$ git push mirroring 
...
To ssh://gitmini@localhost/home/gitmini/gitrepos/repo1
...
To ssh://gitmini@localhost/home/gitmini/gitrepos/repo2

results in pushing to both remotes.

I have no idea if this configuration is valid or not.

Torek writes:

If you configure more than one, only one of them works, all the others are ignored.

seems not to be true. In my given configuration all remotes will be accessed by push and pull as given my example above.

In fact the group as configured with [remotes] looks very useful for my use case!


Solution

  • Remotes and fetch

    Git can have many "remote"s, so that when you do git config -e you see something like this:

    [remote "r1"]
        url = ...
        fetch = +refs/heads/*:refs/remotes/r1/*
    [remote "r2"]
        url = ...
        fetch = +refs/heads/*:refs/remotes/r2/*
    

    Any one remote, however, can only have one url (and at most one pushurl). If you configure more than one, only one of them works, all the others are ignored.

    (Somewhat peculiarly, any one remote can have many fetch entries, and all are obeyed. Also, you can have a push = setting here to set a default push refspec, though I have never used this myself.)

    When you run git fetch you can name one specific remote:

    $ git fetch r1
    [fetches from r1]
    $ git fetch r2
    [fetches from r2]
    

    or name multiple remotes using --multiple:

    $ git fetch --multiple r1 r2
    [fetches from r1 and r2]
    

    or from all remotes:

    $ git fetch --all
    [fetches from r1, r2, and any other defined remote]
    

    or from "groups", which I'll define in a moment. The --multiple flag makes git fetch treat all its arguments as remote or group names. Otherwise, every argument after the remote name is taken as a refspec (e.g., git fetch r1 r2, without --all, means fetch ref r2 from remote r1).

    A "group" is something defined with, e.g.:

    [remotes]
        somegroup = r1 r2
    

    where you list the group on the left, and the set of remotes it denotes on the right. Note that this is remotes, plural, and you can set it with git config remotes.somegroup r1 r2, although when things get this complex I prefer to just use git config -e and my editor, so that I can see everything together.

    With this set, you could run git fetch somegroup, and it will fetch from r1 and r2.

    You can also run git remote update, which defaults to fetching from all your remotes but can be configured (via the remotes.groups items and also remotes.default) to fetch from a specific group or single remote.

    Push

    When using git push, you can only push to one remote. To push to several remotes, you must run several git pushes.

    (git push --all does not mean push to all remotes, but rather, push all refs, as if you'd given refs/*:refs/* as the refspec.)

    Refspecs, or, what gets fetched or pushed

    Both fetch and push use "refspecs" to determine how to do their work.

    A complete refspec looks like the ones you see in the fetch = lines under remotes, such as:

    +refs/heads/*:refs/remotes/r1/*
    

    There is an optional leading + sign, which sets the force flag (the same flag you can set with --force), then two references (such as refs/heads/master or refs/tags/v1.1) separated by a colon : character. An asterisk * may appear and it works somewhat like shell globbing, except that when it's on the right side, it means "use whatever the one on the left side matched". (It also cannot appear in arbitrary positions; generally you want it right after a /, as in refs/heads/* or refs/*.)

    The fetch and push commands are not quite symmetric. When doing a fetch, the name or pattern on the left is for the remote's references, and the name on the right is necessary1 as it tells git how to re-shape the name for your local repository. This is why the fetch line for remote origin reads refs/remotes/origin/* on the right, for instance: we want to re-shape all of their refs/heads/* references—all of their branches—to become our remote-tracking branches in refs/remotes/, and placed under origin/.

    With git push, however, the name or pattern on the left is for your own references—your branches, tags, notes, or whatever—and the name on the right is for the remote. If you leave out the right-hand side name, that usually means "use the same name on the remote". Hence refs/heads/master (no plus sign and no colon) means "push my branch master to the remote's master".

    (I think—my opinion only, not a technical requirement—that it's best, in a config file defining refspecs for pushing, that you use the colon and be explicit wth both left and right sides, even though you can omit the right hand side.)

    Mirroring

    "Mirroring" has a specific meaning in git, although as it turns out it has two different meanings (fetch mirror vs push mirror). The specific meaning is simply "set the fetch or pull refspec to copy all references". For instance:

    $ git remote add --mirror=fetch foo ssh://foo.foo/foo/foo.git
    

    causes git to put:

    [remote "foo"]
        url = ssh://foo.foo/foo/foo.git
        fetch = +refs/*:refs/*
    

    into the configuration file. (Oddly, this does not set prune = true under this configuration. It probably should, but you can run git remote update foo --prune or git fetch -p foo to get the same effect.) Or:

    $ git remote add --mirror=push foo ssh://foo.foo/foo/foo.git
    

    configures:

    [remote "foo"]
        url = ssh://foo.foo/foo/foo.git
        mirror = true
    

    (see the git push documentation).

    Note that it makes little sense to have mirror=fetch for multiple remotes,. For instance, suppose you set this for remotes r1 and r2. When you fetch from remote r1 as a fetch mirror, you wipe out all your branches and tags, replacing them with the branches and tags from r1. Then, you fetch from r2 as a fetch mirror, wiping out all your branches and tags copied from r1, replacing them with the branches and tags from r2. What good did the r1 fetch do in this case?


    1If you omit the right hand side, fetch simply fails to update any of your references. This would be completely useless, except that there's a historical mode, still used by the pull script, where fetched references are deposited in the FETCH_HEAD file.