Two-Man Git
December 15, 2013
Using Git with a central (bare) repository is fairly popular and widespread (e.g. GitHub). A less common and harder-to-get-right use-case is sharing some code directly with another developer with no central repository.
The use-case we’re going to look at is fairly simple:
- You’ve developed some code
- …and now a colleague wishes to collaborate
- …but you don’t want to set up (or use an existing) central repository
This might be because you want to keep things (somewhat more) private, or simply don’t wish to use a central repository for some other reason.
What we’ll outline here isn’t the only way to proceed, but it is fairly similar to a central-repository method and therefore hopefully a little easier to get right. It also makes “upgrading” to a central repository pretty straightforward.
In fact, the TL;DR here is that you simply treat the “other person” as your central repository, but never git pull
instead always git
pull
.
Some Notes
I usually try to use long options wherever possible, and use the long versions for refs etcetera. There are many shortcuts available to the below, but that’s for after you learn what’s going on ;)
The Use Cases
We want both people to have input into “the source code”, so we’ll outline two directions of sharing code (between our usual friends, Alice and Bob). Alice has written some code (“awesomesauce
”) and has a Git repository on her machine with several commits.
She is now going to collaborate with Bob. We need to cover three cases:
- setup (get Bob a copy)
- Alice writes new code, ships to Bob
- Bob writes new code, ships to Alice
We’re going to presume that Alice can see Bob’s machine and that Bob can see Alice’s machine. If this isn’t the case, it is my opinion that you’re going to want a “central repository” model instead. In that case, see chapter 4.2 in the Git book and/or look at gitolite or just use a Web service like GitHub or Bitbucket.
The Setup
So, Alice has a repository and it has no remotes. Her machine is called wonderland
for this example. Bob’s machine is called springfield
. She wants to share awesomesauce
with Bob, so she makes an account for bob on wonderland
. Sharing is a two-way street, so Bob makes an account for Alice on his machine springfield
also. So Bob can SSH to Alice’s machine and vice-versa.
Perhaps you don’t want full shell access both ways, but that’s an exercise for the reader. Or use gitolite.
Now, Bob can get a copy:
bob@springfield$ git clone bob@wonderland:/home/alice/src/awesomesauce
So now that Bob has his own clone, it will have a remote called origin
that points to his SSH account on the machine wonderland
. Behind the scenes, git clone
has really done two things:
- Created a remote called
origin
with a URL to Alice’s machine- Set up Bob’s local
master
branch to track Alice’s master
Alice now needs to add a remote. It could be called anything, but we’ll set hers up the same way as Bob’s (call it master
and make it track Bob’s repository). To get there from where she is, however, we need to do 3 things:
- Add the new remote
- Fetch the data
- Set up the tracking
So first we will set up the remote and get the data (that is, the first two steps):
alice@wonderland$ git remote add origin alice@springfield:/home/bob/src/awesomesauce
alice@wonderland$ git fetch
If you got the URL wrong, use git remote set-url origin new-uri
to fix it until the git fetch
works (you can pass -f
to remote
add
). Now that we have the data, we want to make Alice’s local master
branch track Bob’s master
. To Alice, Bob’s master is now called refs/remotes/origin/master
or just origin/master
for short.
A “remote tracking branch” is simply a snippet of configuration in .git/config
. You could just put a section like this in there:
[branch "master"]
remote = origin
merge = refs/heads/master
Another way to make Alice’s master
track Bob’s master
is to tell it explicitly (git 1.7.x):
alice@wonderland$ git --set-upstream master refs/remotes/origin/master
In the end, all this means is that git pull
magically becomes git fetch origin
and then git merge refs/remotes/origin/master
master
. Try it! (It also means git push
knows what to do).
Alice Writes Some Code
Now that Alice and Bob both have a full copy of all the awesomesauce
code, the fun begins. Alice hacks up an amazing new feature. For simplicity, she just does this on her master
:
alice@wonderland$ echo "TODO: write sweet features" >> README
alice@wonderland$ git add README
alice@wonderland$ git commit -m "note to self"
This makes Alice’s repository have one more commit. From her perspective, master
is one commit ahead of remotes/origin/master
(which is what Bob’s copy is called, to Alice).
Bob doesn’t know anything about this change yet but if he did, it would be in Alice’s copy which is called remotes/origin/master
to him.
Now Alice notifies Bob that she has amazing new content for him to look at. She could do this via email, IM, whatever. Now Bob needs to do two things: pull in the fresh objects from Alice’s Git databse, and update (“merge”) these into master
. There is one simple command which does both of these things: git pull
. Note that this only does this because the git clone
set up the tracking for us.
So, Bob gets all the changes from Alice:
bob@springfield$ git pull
What happens is that he gets all the objects in Alice’s databse that he needs, and then the ref remotes/origin/master
is updated and Bob’s local master
ref is also updated (by merging remotes/origin/master
into master
). This is because the git
clone
made Bob’s master “track” Alices master. This will be a “fast-forward” operation, so no actual merge commit will be added. It will not be a fast-forward if Bob has local changes, and then he’ll create a merge commit.
Of course, in both cases if you’ve changed the same code you have to follow Git’s workflow for resolving them; see the Git book
Bob Writes Some Code
Now if Bob hacks in some sweet new bug-fixes, he can share them with Alice in precisely the same way as above. That is, he tells Alice to git pull his changes. He does not use git push
.
This is the key point, and the main difference between a centralized repository approach. With a centralized repository (and like with Subversion), developers push changes to the centralized repo and pull updates from it.
In this person-to-person model, changes only propagate through pull operations. The only way “your” copy of master changes is if you explicitly merge in a change.
Summary and Some Notes
You should now understand how to collaborate with two people, two git repositories and two machines. Each person has an account on the other’s machine, and can see it on the network (at least sometimes). Sharing is always a notification of some sort followed by git pull
and there’s always an explicit git merge
to bring Alice’s changes into Bob’s master branch (or vice versa).
The Number One Key Point is: you only share via git pull
. That is, you tell your friend “there are changes” and they pull them in. No git push
. Certainly no --force
.
If you’re doing things on branches you’ll want git pull
--all
. This asks git to fetch every branch, not just master (the default) or whatever you specified. To pull a single (non-master) branch: git pull origin some-branch-name
. Note that you can certainly do git pull origin master
, which is what you’d have to do if you chose a name besides origin
for the remote.
I actually find it easier to have explicit names for the remotes other than origin
in this case. So, for example, Alice might call her remote bob
and Bob might call his remote alice
.
If you “upgrade” to a central repository, all you have to do is have Alice and Bob use git remote set-url
to re-target which URI they point to as the origin
remote. (Then you’d git push
to share changes). For example, if you got a GitHub account Alice could do:
alice@wonderland$ git remote set-url origin https://github.com/alice/aweseomesauce
alice@wonderland$ git fetch origin
There are other ways to do this whole setup. One way is transferring patches back and forth, facilitated by git format-patch
and git
apply
. If you can’t see your friend’s machine on the network, this might be easier as you can send the patches attached to your “I have some sweet new changes” email.