git: Simple solution for pushing between working copies

I solved this problem by using git-bundle. On my laptop, I do git pull, to get the upstream changes. This works over ssh, as expected. Then, I do git bundle to put my local changes into a bundle, copy the bundle file to the remote server, and then ssh to the server and do git pull from the bundle file.

From the documentation:

Some workflows require that one or more branches of development on one machine be replicated on another machine, but the two machines cannot be directly connected, and therefore the interactive Git protocols (git, ssh, http) cannot be used. This command provides support for git fetch and git pull to operate by packaging objects and references in an archive at the originating machine, then importing those into another repository using git fetch and git pull after moving the archive by some means (e.g., by sneakernet). As no direct connection between the repositories exists, the user must specify a basis for the bundle that is held by the destination repository: the bundle assumes that all objects in the basis are already in the destination repository.

My repository is small enough that I can add the entire git tree to the bundle by using --all. However, if your repository is bigger, you may want to add only recent changes to the bundle (e.g. with --since=10.days master to get the last 10 days). The docs have lots more examples.

Here's my code. In this case, the laptop and the server both have the repository in the same place: ~/src/ (which is a non-bare repository, i.e. a working copy). The ~/tmp directory exists on both the server and the laptop.

TMPDIR=~/tmp
cd ~/src
git pull $server:~/src/
git bundle create $TMPDIR/bundle --all
rsync $TMPDIR/bundle $server:$TMPDIR
ssh $server "cd ~/src;git pull $TMPDIR/bundle"

You really shouldn't push to the checked out branch as it effectively pulls the rug from under the remote working copy. It's then difficult to work out if the working tree is modified because the branch head has moved or if there were also local changes which would be lost by a reset --hard.

The simplest thing to do is to push to a different branch. You can then merge this into the working copy's checkout out branch (or rebase the local branch onto it) when you have access to the remote machine and need to work on it.

From home:

git push origin HEAD:from-home

From 'work':

git merge from-home

You can set up your config to default to a particular push refspec.

e.g.

git config remote.origin.push +master:from-home

A bare repository is often more natural. You can either clone it from an existing repository or, what I usually do, initialize a new repository and push the master branch that I want to it from an existing repository.

Better still, if you're going to use working copies at each location, is to use this trick to directly modify the remote's remotes, rather than a speically renamed branch.

So, on origin, create a remote called 'home' -- you obviously can't fetch from it because of your network configuration. That doesn't matter.

On home, tell it, "When I push to origin, have it update the origin's remote named home:

git config remote.origin.push +master:home/master

Now, things get really slick. From home, run git push origin, and go to origin, and run git status or git branch -a -v -- What you will see is something like: "master is behind home/master by 3 commits and can be fast forwarded."

In other words, using home to push a change to origin's remote named home, is functionally the same as using origin to pull from home.

The one downside here is that you'll need to continually do new git config settings as you create additional branches on home. That's the overhead you pay for your network setup. Thankfully, it's simple and only happens once per branch create.


  1. This question sort of sums up my experience with trying to sync two working copies. Eventually I figured it was more natural and simple to have a bare repository. It's not a "main" repository in the SVN sense - you can have one at the uni and one at home, for example, and push-pull between those.

  2. Somebody will probably post the proper way to set the bare repository as your push target, but without looking at the docs, I would simply delete the working copy and clone it from the bare repository again.