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
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.
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
originwith a URL to Alice’s machine
- Set up Bob’s local
masterbranch 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
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
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
master). This is because the
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
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
If you’re doing things on branches you’ll want
--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
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
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.