Git in my Subversion

Two weeks ago, I discovered magic: it’s possible to initialize a Git repo inside of Subversion (SVN).

Since the advent of Github as a leading collaboration platform, I’ve forever been trying to reconcile the Git to SVN workflow. Progress is faster in Git(hub) because it’s a far superior platform for fostering contributions. Seamless pull requests are much nicer than the multi-step process of creating and uploading patches. Distribution still happens in SVN, however, because many legacy deploy systems are coupled to it. Etsy took the leap of switching from SVN to Git, and essentially recommends against it. For the time being, we need to be able to live with both.

Two years ago (almost to the day), I wrote a post called “How to properly use Git with WordPress.org Subversion.” The thing is though — it wasn’t ever properly using Git with SVN. It was grinding the two tools together like a sixteen year-old learning to drive a stick. Furthermore, if I ever nuked my Git repo, I’d have to wait literally for hours to run git svn fetch on the WordPress.org repo. Boone has worked in a similar way for a while; I eventually gave up and started copying my files over manually when I needed to release. Mo does this with an rsync command.

A recent stroke of insight has changed my outlook on life, improved how I sleep, increased my libido, and generally made me a much happier developer. It’s this: Git and SVN can live side-by-side in the same directory. You just need to tell them to ignore each other.

This afternoon, for instance, I decided to spend a little bit of time on Edit Flow. I like to develop Edit Flow locally, keep the WordPress.com VIP shared plugins repo version on the bleeding edge, and occasionally release a version on WordPress.org. Because I haven’t yet applied this “Git in my Subversion” approach to what’s in the latter two repos, let me now walk through what that looks like.

The first thing you should do is edit the “svn:ignore” property on your existing SVN repo. You can do so with:

svn propedit svn:ignore edit-flow

In this situation, I specifically want SVN to ignore two things: .git and .gitignore. Add those, save, and commit. SVN will now respect Git’s living style.

Next, change into your target directory and initialize a new Git repo. Running the following pulls my full remote project into the working directory:

git remote add -f origin https://github.com/danielbachhuber/Edit-Flow.git

But, if I run git status, I’ll see that my local working files aren’t tracked by Git, I can’t checkout master or change branches, and Git is flummoxed. Don’t worry — as long as you’ve kept Git as master, and ported your SVN commits back to Git, you can run:

git checkout -f master

Boom, Git in my Subversion. Running git status I notice there’s a lot of .svn junk I don’t want tracked in my repo, so I create a .gitignore file to handle those (on WordPress.com trunk, I also ignore the wpcom-helper.php file we use for every community plugin to handle local action and filter modifications). The integration is complete.

With Git and SVN side-by-side, I can easily pull features I’ve developed locally into the VIP shared plugins repo, VIPs can create pull requests for community plugins, and I can push hotfixes discovered by WordPress.com usage back to the Git(hub) master project. It works well because Git and SVN are tracking the same files, but I don’t have to get them to live together harmoniously — they just ignore each other. Git-SVN be damned, this is much better.

What makes a good commit message

There’s a useful conversation happening in an internal Automattic P2 I thought I’d take the liberty to share.

From Mike:

Consider the audience when you write a commit message. What is that audience? It’s at least two groups of people:

  • Your coworkers: You’re telling everyone else what you did. Commit messages are one to many, asynchronous and textual. Sounds like email, so write the commit message like an email. The first line should be a descriptive subject. The next line should be blank (as a separator). Then comes the body of the message. Write everything as if you’re describing it to Nikolay.
  • Your future self: Think back on the times you were fixing something and needed to understand why an old commit was made. How often was the commit message useful? How often was it your own useless commit message? The commit message should say what the problem was (repro steps?), how you fixed it (briefly – the code itself gives more details), and why you changed what you did. That way the shiny pants people of the future have the information they need to decide if they can safely change your code.

From Matt:

I think as a company we need better commit messages. Very often in our messages we say what is happening but not the why, and most importantly the context of the change. I’m going to pick on this changeset, but you could really pick almost anything:

[link to changeset]

4 lines changed, with the message:

“Fixing incorrect $blogid variable, should be $blog_id.
Check if $current_blog is === false before trying to reset it.”

First a good thing: it’s a multi-line message, which is nice. Commit messages can use as many lines as you like, and be as verbose as you like.

However if I were to come across this changeset 3 years from now, say if I were debugging a similar area in the code, I’d have no idea why this change happened. The message might as well be blank, since it doesn’t really say anything I couldn’t tell in 2 seconds from looking at the diff. Some useful context would be:

  • What bug did this code cause? (This is most important.) Why change it?
  • Is there a relevant discussion, either on a P2 or in Trac?
  • Who was involved in the fix, IE who else would have context for this change either because they reported the bug or reviewed the fix.

From Lance:

Good commit messages are my gospel. The actual syntax should vary by context, though. For theme commits, for example, we always start with the theme name up front.

But, the goal of giving context and pointing to related items is key.

I personally don’t think long commit messages are better. Instead, point to a Trac ticket or P2 post with all the gory details.

How to properly use Git with WordPress.org Subversion

Find this post on the Google or someone’s shared link? I’ve discovered an even more magical way to work with Git and Subversion

Having Git properly interface with Subversion is a mysterious black art. If you’re into the Harry Potter stuff, then this post is for you.

First, I must give credit where credit is due. Boone Gorges has a nice writeup detailing how he uses GitHub with WordPress.org Subversion. Unfortunately, it only tempted me. What really set me on the right track was a short piece by Evgeni Golov, which had everything but one crucial piece: checkout instead of merge.

Using checkout instead of merge is likely the most critical piece to this puzzle. What Erik and I found the first time when using merge is that Git treats both versions of the code as equal and tries to find the middle ground between them. Instead, we want to update all of the files in the Subversion repository with their most recent counterparts from GitHub. Checkout gives us exactly this power.

Because I was working on this Monday night with the Assignment Desk, I’ll go step by step with those links as an example. This tutorial assumes you’re doing all of your development with Git, and need to occasionally push to Subversion with releases.

First, clone your WordPress.org Subversion into your local Git repository:

git svn clone -s -r274218 https://svn.wp-plugins.org/assignment-desk

Notice two important flags: -s and -r. The ‘s’ flag tells Git the code you’re importing follows the normal Subversion folder structure, or /trunk/, /tags/, and /branches/. The ‘r’ flag tells Git to import after the specified revision number; when pulling from a large Subversion repository like WordPress.org, this can save you days of time. You can find the ID for your first commit in your revision log (Assignment Desk example).

Change into your newly-created directory and pull in your Subversion history (could take a bit of time):

git svn fetch

Once all commits have downloaded, add your working GitHub repository as a branch to your local Git repository:

git remote add -f github [email protected]:studio20nyu/Assignment-Desk.git

What’s next is the magic part. We’re going to checkout the code from the ‘github’ branch to the ‘master’ branch (our Subversion checkout), instead of merging the two:

git checkout github/master *

If you use ‘git status‘ at this stage, you’ll notice all of the files you’ve changed since your last release have nice little M’s next to them. If you had merged, there would be a nasty mess of conflicts you’d have to resolve.

Add all of the files you want to save in the next commit:

git add *

And make your commit:

git commit -m "Updated from GitHub"

Aside: at this point, I tried to push back to WordPress.org and received an error of “Merge conflict during commit: File or directory ‘assignment_desk.php’ is out of date; try updating: resource out of date; try updating at /usr/local/git/libexec/git-core/git-svn line 572,” also known as nonsensical gibberish. Thankfully, the Google pointed me to this Stack Overflow thread.

Before you celebrate, you must practice one last piece of magical foo: rebasing. For reasons we don’t fully understand, rebase holds the key to resyncing your Git commit history with Subversion. Make it happen:

git svn rebase

Push all of your changes back to the original WordPress.org repository:

git svn dcommit

Congratulations! You’ve tamed the beast and progress to the next level.