TL;DR version
Add a remote for the each repository. I'll call the two repositories alex
and bob
, but you probably should choose more appropriate names. Note that if you git clone
one of them, that one will have the name origin
automatically (although you can choose another name). The below assumes you already have a related repository, and need to add both, but if you already have origin
you just need to add the other one.
$ git remote add alex ssh://their.domain.name/their/path/to/repo.git
$ git remote add bob ssh://bobs.domain.name/and/his/path.git
After that you can easily git fetch
everything (all branches, etc) from them:
$ git fetch alex
$ git fetch bob
You now have "remote tracking branches" named alex/master
, alex/develop
, etc., wherever Alex has branches named master
, develop
, etc.; and you have bob/master
and so on wherever Bob has master
.
Now if you wish to merge stuff, you can make your own local branch(es) corresponding to one of theirs:
$ git checkout -b alex-master alex/master
(alex-master
is a local branch; you could just call it master
if you prefer, but each local branch name must be unique, so you might have to move your own existing master
out of the way first) and merge with bob
's:
$ git merge bob/master
(this merges bob/master
into your local alex-master
). If you have permission you can push the result back to Alex's master
:
$ git push alex alex-master:master
and also to Bob's master
:
$ git push bob alex-master:master
The "refspec" given as the last argument to git push
takes your local branch name on the left hand side of the colon, and their local branch name on the right.
Unlike Mercurial, there is no special branch name; master
is just conventional, while in hg
, default
is a bit magic. (Well, there's one special magic bit for master
in git: it changes the way git merge
spells the default commit message. But that's really about it.)
There are a bunch of parts to this, some of which are mostly cosmetic/convenience, and some of which are crucial.
First, in git these days one mostly refers to repositories through "remotes". A remote is partly a convenience construct but this also plays into the crucial bits, in terms of obtaining and retaining commits from another repository.
Mercurial has something much simpler than remotes, but which serves part of the convenience: in a [paths]
section in your .hgrc
file you can list the long URL under a short name:
[paths]
short = ssh://something.very.long/with/a/very/long/path/that/is/a/pain/to/type/every/time
or whatever. Compare this to a git "remote", in .git/config
:
[remote "short"]
url = ssh://something.very.long/with/a/very/long/path/etc
So far these are exactly the same, just with different spellings/syntax. But then there's this bit:
fetch = +refs/heads/*:refs/remotes/short/*
(in fact, this usually appears before the url
but the order of these two does not matter). We'll get back to this in a bit.
Next, the most direct equivalent of hg pull
is indeed git fetch
. However, hg pull
has -u
, which git fetch
most definitely does not do, because the internals of the two operations wind up being quite different because of the difference between a Mercurial branch and a git branch.
A Mercurial branch is sort of a "real thing", while a git branch is much more ephemeral. More specifically / precisely, a git branch is much more like a Mercurial bookmark than it is like a Mercurial branch. However, there's a concept (or maybe "conceit" might be a better word) that simply does not translate properly here: specifically, commits added to a Mercurial repository are numbered sequentially and remain in the repository even if nothing names them: you just wind up with an anonymous head. For instance you can have a commit graph (shown with hg log --graph
etc) that looks like:
o
| o
o |
| o
o /
|/
o
There does not need to be any external name pointing to these two heads; they're just anonymous heads (within a branch since all commits in hg are within some branch by definition).
In git, by contrast, a commit with no external name is eligible to be garbage-collected (what hg
might call "stripped", although the details differ). So all of what hg calls "heads" must have some kind of name. This is where a remote's fetch =
line comes into effect.
"But wait," you might ask, "I can do git fetch ssh://...
and that has no named remote!" This is where that HEAD -> FETCH_HEAD
bit comes in.
If you don't have a "remote", you can still bring in some branch (or even more than one) from another repository. However, the heads of any branches thus obtained need a name. This "name" is stuffed into the pseudo-branch named FETCH_HEAD
. The details get complicated, but if you bring over only a single branch, as in this case, it's easier to explain and think-about: essentially, git brings over the one named branch and, in your local repository, gives this the name FETCH_HEAD
.
Note that a subsequent git fetch
generally overwrites FETCH_HEAD
, at which point all the commits you brought over are eligible for garbage-collection. Thus, you need another step, once you have the commits.
Now, once you have some commits and want to retain them, there are different ways to do this. The most hg pull
-like is to use the "remote" mechanism. Here, that fetch =
line takes care of the problem. When you git fetch
from the remote, the remote has branch names pointing to all the branch heads (by definition since git requires this). These branch names are references of the form refs/heads/name
.
The fetch =
line tells git how to rename the branches so that they have new, unique local names in your local repository. By renaming refs/heads/master
to refs/remotes/short/master
, for instance, you get short/master
as a new local name for what the remote called "branch master
". This retains the commits and we're good (though we still have to merge).
Alternatively, we can just get the one interesting branch over and call it FETCH_HEAD
and then immediately merge. When we do this, we pick some local branch (e.g., master
), check it out if needed, and run git merge FETCH_HEAD
.
Once the merge is done, we have our local branch (master
, in this example) pointing, perhaps through a merge commit, to the commits we brought over via git fetch
, so they are no longer vulnerable to a garbage-collection: they are on the branch, hence name-able via that branch name. We can now safely overwrite FETCH_HEAD
any time.
This last method—bringing stuff over but naming it FETCH_HEAD
, then merging a local branch with FETCH_HEAD
—is what the "git pull" command does. In that sense, it's just git fetch
followed by git merge
. I personally try to avoid the pull
script as it has a lot of extra magic to handle various corner cases, which in the old days would go awry in some un-considered corner cases; and in general I prefer to fetch, look over what happened, and then choose whether and how to merge or rebase my own code.
Ultimately there's no single right way, but if you are going to do this more than once, I would add a git remote, and use the "remote branch" notion (the fetch =
stuff that renames "their" branches to your own locally-stored "remote-tracking branches").