Using Dropbox as a Private GitHub

This may be old news by now, but I only just realized how great it is, so consider this my rebroadcasting of the idea:

GitHub has now become the preferred way to manage source code for programming projects. There's good reason for this: it's a really wonderful site with a great following. However, there are times when it's really not desirable to have a project completely public. GitHub provides some paid options for this, as well as educational accounts (which you should really check out if you're a student or instructor). If you've used up your free repositories or simply don't think the project at hand warrants spending money on, you need another option.

That's where Dropbox comes in. Dropbox is a really great backup/sync/file sharing service that pretty seamlessly syncs files between computers. Some time ago, Dropbox added the ability to restore old versions of files. For really simple projects, this may very well be all the version control you need. If that's the case, by all means stop reading this post and start, uh, "drop-box-ing".

For programming projects, this level of version tracking more often than not isn't enough. That's why we use GitHub. My proposal for situations where neither GitHub nor Dropbox is a good fit (and what many people are already doing) is this: let's set up bare Git repositories in our Dropbox folders and use them as our Git remotes.

Let me explain. A "bare" Git repository is one that doesn't actually have any of the repository's files shown. Instead, it just contains the guts/mechanics of the repository (the stuff that's usually in the .git directory). This is the type of repository GitHub and others use, as it's only meant to be a place to push and pull from, not to do any direct file edits or commits from.

So let's go through the process of setting up what I'm talking about. First, in your Dropbox folder, create a place for all your repositories to live (mine is a top-level folder named "Git"):

Now, in a Terminal, cd into that folder and create a bare repository:

$ cd ~/Dropbox/Git $ git init --bare mytestrepo.git Initialized empty Git repository in /Users/jetheis/Dropbox/Git/mytestrepo.git/

You should see the new repository (it's just a folder) show up now:

Inside, you'll see the guts of the repo:

That's really all you need to do to get the bare repository set up. Let's try using it now. First let's make a sample project with a couple of commits:

$ cd ~ $ mkdir testrepo $ cd testrepo $ git init Initialized empty Git repository in /Users/jetheis/testrepo/.git/ $ echo "Some Text" > testfile.txt $ git add testfile.txt $ git commit -m "Add a test file" [master (root-commit) 0220f05] Add a test file 1 file changed, 1 insertion(+) create mode 100644 testfile.txt $ echo "More text" >> testfile.txt $ git commit testfile.txt -m "Add more text" [master f014b59] Add more text 1 file changed, 1 insertion(+)

We'll check out the log and file content (to verify against in a second):

$ cat testfile.txt Some Text More text $ git log commit f014b59d8d3af6e20bd5d671bd98f86e26fba743 Author: Jimmy Theis Date: Sun Feb 17 12:10:19 2013 -0500 Add more text commit 0220f050e087d92ad96a549c2b913363f551b7fc Author: Jimmy Theis Date: Sun Feb 17 12:08:50 2013 -0500 Add a test file

Now we can hook up our new bare repository as an upstream for this one:

$ git remote add origin ~/Dropbox/Git/mytestrepo.git

And push to it:

$ git push -u origin master Counting objects: 6, done. Delta compression using up to 4 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (6/6), 458 bytes, done. Total 6 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (6/6), done. To /Users/jetheis/Dropbox/Git/mytestrepo.git * [new branch] master -> master Branch master set up to track remote branch master from origin.

You should see your Dropbox icon spin for a second while it syncs the files for you, though you'll still notice that inside your Dropbox version of the repository, you still don't see any testfile.txt , because again, only the guts of the repository appear here.

Let's try cloning from the bare repository to make sure our files and content are actually stored there. On the same computer (or another one that Dropbox syncs to), clone the repository:

$ cd ~ $ git clone ~/Dropbox/Git/mytestrepo.git testrepo2 Cloning into 'testrepo2'... done.

Now we'll make sure we have both our current files and our history:

$ cd testrepo2 $ ls testfile.txt $ cat testfile.txt Some Text More text $ git log commit f014b59d8d3af6e20bd5d671bd98f86e26fba743 Author: Jimmy Theis Date: Sun Feb 17 12:10:19 2013 -0500 Add more text commit 0220f050e087d92ad96a549c2b913363f551b7fc Author: Jimmy Theis Date: Sun Feb 17 12:08:50 2013 -0500 Add a test file

No surprise, everything is reflected, just like we expected. We can now push and pull (the origin remote will already be hooked up since we cloned from the bare repository, so git pull and git push are all we need to run) and have those changes synced to all Dropbox-connected computers. Pretty cool, huh?

The really cool part is using Dropbox's sharing features with these repositories. Using the Dropbox website, you can either share your whole "Git" folder with others, or, because each repository is a folder, just share a single project:

A quick note about problems before we go:

If you try to pull or push while files are still syncing, or if something gets messed up in the repository, you may see an error message like this:

error: unable to find ea2ae105b8955b3f73d79065bc52ee3126cd2e4f error: refs/heads/master does not point to a valid object! Your configuration specifies to merge with the ref 'master' from the remote, but no such ref was fetched.

The error may vary depending on what's gone wrong, but the solution in any case is the same: wait until Dropbox finishes syncing to make sure it's not just out-of-date files, then if the problem still exists, delete your Dropbox repository (mytestrepo.git) and create a new bare repository with the same name in its place:

$ cd ~/Dropbox/Git $ rm -rf mytestrepo.git $ git init --bare mytestrepo.git Initialized empty Git repository in /Users/jetheis/Dropbox/Git/mytestrepo.git/

Then push to it from the most up-to-date copy of the code:

$ git push origin master Counting objects: 9, done. Delta compression using up to 4 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (9/9), 706 bytes, done. Total 9 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (9/9), done. To /Users/jetheis/Dropbox/Git/mytestrepo.git * [new branch] master -> master

That should restore your repository state (as long as the pushing copy of the code was entirely up-to-date).

Again, this is probably old news for some, but it's definitely worth some lip service for anyone it hasn't occurred to yet.

Cheers!