Saturday, 13 April 2013

Abandoning commits in Subversion

Before we begin, I don’t normally keep my code in Subversion… any more. When I started at Kijiji, Subversion was our local VCS of choice until there was a organisational decision to switch to Git, and we local developers agreed that it made sense. In the roughly two years since then, I’ve become a total convert. However, I’ve continued to use Subversion for personal projects, simply because I’m the only person working on them, and in case I move from machine to machine, I want to have reasonably quick access to the code.

I have since lived to regret this decision. Despite the fact that Subversion externals work vastly better than Git submodules, there are still some things I can easily do in Git that simply aren't available in Subversion.

One of my favourite things about Git is that, as long as you haven’t shared your repository, you can really mess with history. Step back three commits in the history, branch, write a new commit, then even cherry-pick everything else back on… then reset your HEAD hard, fast-forward, and when you push, it’s like those commits never happened!

You don’t really get that option in Subversion. Since it’s a centralised repository, everything you commit goes off immediately, so good luck getting rid of it for good! So if you realise that your last two commits were completely wrong, what do you do?

Fortunately, you can still branch from your history in Subversion:

$ svn copy

Your tree now looks something like this:

--55--56--57--58--59 (trunk)
        \-------------60 (whoops)

Now, everything that was on trunk up to, and including, revision 56, is in the whoops branch. Update your project’s root directory (or check out the branch into a separate directory), and get back to work! Once you’re done, the challenge becomes how to squash everything on trunk since 56. This is disturbingly simple, and only incurs two commits. It sounds dangerous as all hell, but trust me on this.

You’re going to delete trunk.

Only for a second! Since you want to destroy the last few changes on trunk anyway, and replace them with what you’ve done on whoops, this is entirely safe. After deleting trunk and committing, you just move branches/whoops to trunk, commit, and you’re off to the races! The whole process looks like this:

$ cd ~/project/
$ svn copy
$ svn update
$ cd branches/whoops/
... work ...
$ cd ~/project/
$ svn rm trunk
$ svn commit
$ svn mv branches/whoops/ trunk/
$ svn commit

And, at the end of the day, you get a tree that looks like this:

--55--56--57--58--59    /-- Branched here
       \               /
        \-------------60--61--62 (trunk)
                          /    \
   Deleted trunk here -- /      \-- Renamed whoops to trunk

If you need to get any of 57, 58, or 59 back, they’ll be visible from the project root. Not remotely gone forever!

And so we see that, even in Subversion, you can achieve a measure of Git-like control of your history. The only difficulty is coordinating with your collaborators, along with the obvious evidence in the history of what you’ve done!

No comments:

Post a Comment