Dropbox and Git Play Well Together

Chrys Wu asked me my thoughts about how to have easy access to a software project she’s working on so that she can conveniently access them from the several computers she uses regularly. Not all of them can be set up as development machines, so a standard version control system can’t be used alone.

That calls for a way to sync files, and Dropbox is by far the most convenient way to do so. Dropbox Just Works to copy files between computers, whether on Linux, OS, or Windows. Even an IT department that doesn’t want to install a development suite will probably be OK with it.

But software always calls for version control, it’s essential to keep track of changes. Git happens to be the right version control software for her project, so here’s the steps we worked through to set up her environment.

First, she created her repository inside her Dropbox folder. We’ll call this the dropbox repo.

 
desktop:~/Dropbox $ git init project
desktop:~/Dropbox $ cd project
desktop:~/Dropbox/project $ # create and commit first files

Now she has convenient access to her files across her computers, but there’s a pitfall: if she were to edit and commit to the dropbox repo on two machines that were offline, Dropbox would merge the git metadata in unpredictable ways. Each machine needs its own repository for work done in it, then changes can be deliberately pulled up to the authoritative Dropbox repository for integration. Edits done on a machine without git installed can be left in the Dropbox repo until they can be checked in, which works well enough for this scenario.

Creating a local repository is simple:

 
laptop:~/ $ git clone ~/Dropbox/project
laptop:~/ $ git remote -v
origin  /home/username/Dropbox/project/ (fetch)
origin  /home/username/Dropbox/project/ (push)
laptop:~/ $ cd project
laptop:~/project/ $ git pull # to get the latest changes

The laptop repo shows that it knows about the dropbox repo, and a git pull will bring in any changes. But git push will give a giant error:

 
laptop:~/project/ $ git push
Counting objects: 157, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (152/152), done.
Writing objects: 100% (157/157), 982.50 KiB, done.
Total 157 (delta 44), reused 0 (delta 0)
remote: error: refusing to update checked out branch: refs/heads/master
remote: error: By default, updating the current branch in a non-bare repository
remote: error: is denied, because it will make the index and work tree inconsistent
remote: error: with what you pushed, and will require 'git reset --hard' to match
remote: error: the work tree to HEAD.
remote: error:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
remote: error: 'ignore' or 'warn' in the remote repository to allow pushing into
remote: error: its current branch; however, this is not recommended unless you
remote: error: arranged to update its work tree to match what you pushed in some
remote: error: other way.
remote: error:
remote: error: To squelch this message and still keep the default behaviour, set
remote: error: 'receive.denyCurrentBranch' configuration variable to 'refuse'.
To /home/username/Dropbox/project/
 ! [remote rejected] master -> master (branch is currently checked out)
error: failed to push some refs to '/home/username/Dropbox/project/'

This is one of git’s more unix-like error messages, in that it makes sense once you already understand it and happens because git is an incredibly powerful, flexible tool that assumes you know what you’re doing. Let’s walk through the error.

Everything down to the “Total” line is git giving you information about how it’s packaging the data to send between repositories and is normal.

The first paragraph is warning that you’re pushing code to what’s currently being worked on in the other repository (the “checked out branch”). Git can’t know that the other one is just files sitting around – what if it’s some other user’s, and he’s in the middle of editing things? Well, if someone else might be using it, we can’t update the files in the working copy, because it would trash his work. But if git doesn’t do that, the remote repo knows that it’s not at HEAD – meaning the next commit will confusingly create a branch.

The second paragraph is saying “If you’re sure you know what you’re doing, you can configure the remote repository to let you do this.”

The third is saying “Or you can just tell me to shut up.”

Finally, the last few lines are git attempting finish the push. It says “OK, with everything ready, I’m going to push” and then “Wait, I couldn’t push from my master to the remote master” and “you told me to push at least one branch and at least one failed” (in this case, excatly 1 and 1). In short, git is refusing to take the risk of maybe screwing up work.

There are two responses.

The first is to make the Dropbox repository a “bare repository”. That means it will have all the history of the project, but no checked-out, editable copies of the project. That would stop you from conveniently reading the files wherever you happened to be, though. When you have a repository on GitHub, that copy on their servers is a bare repo – GitHub doesn’t have any interest in editing the files and doesn’t want you to have this problem.

The second is simpler, and what you want to do for this situation. Pulling changes is always OK, because git knows you know what you’re doing with the current repo. So, from the dropbox repo:

 
laptop:~/Dropbox/project $ git remote add laptop ~/project
laptop:~/Dropbox/project $ git pull laptop master # note that you have to name what branch to pull from
From /home/username/project
 * branch           master -> FETCH_HEAD

Git reports the path to the repo it’s pulling from and which branches it was able to update, in this case master from what it found on the remote HEAD. Success!

All that’s needed to add more machines is to run the git clone on them and then, from the dropbox repo, add them as remotes for pulling changes.

Dropbox and git play nicely when you need their different strengths.

Want more? I'm not as good at forgetting to update @pushcx on Twitter.