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:

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:

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:

  1. Created a remote called origin with a URL to Alice’s machine
  2. 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:

  1. Add the new remote
  2. Fetch the data
  3. 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.