An SVK primer

Today’s entry is a little primer I wrote for some co-workers at CEG, on setting up SVK to do remote development. We continue to use a central Subversion repository, but I often find myself working in cafés where I don’t have immediate access to the server. Also, I like to branch and check-in much more frequently than would be sane to do with Subversion – I also like the distinction between a “check-in” being a simple, quick snapshot, and an svk push as the real deal.

Setting up remote mirrors

SVK is a simple wrapper around Subversion that provides some of the better features of Distributed Version Control systems without a lot of the complexity that such systems usually involve. This primer aims at giving a moderate to seasoned Subversion user quick access to the better features of SVK.

Creating “depots”

The first step to using SVK is to create a local mirror of a remote repository. But even before that, you need a local depot to track them in. Here is the first command you need to run to get started:

svk depotmap -i

This initializes a private depot named “//” in ~/.svk/local. All of the projects you mirror locally will be tracked here, and any projects you create on your own machine are kept here. ~/.svk is where SVK “lives”. When SVK asks if you want to create the missing directory,just say yes.

It’s also possible to have multiple depots – you might have one for personal projects, one for work, and one for tracking free software. Here’s how you’d go about creating a depot named /CEG/ for tracking CEG projects, in the directory ~/CEG/.svk:

svk depotmap -i /CEG/ ~/CEG/.svk

SVK asks to create the directory for you, and then initializes it so it’s ready for importing/creating projects within it.

Mirroring projects

To recap, every project you track lives in a depot. The depot name occurs at the start of every directory string you use to identify projects. The default depot name is “//”; if you created a CEG depot,that one is named “CEG”. Here’s an example of how to mirror a personal project in //, and a CEG project in /CEG/:

svk mirror https://ledger.svn.sourceforge.net/svnroot/ledger/trunk \
           //mirror/ledger/trunk

svk mirror svn://svnhost.3dex.com/project/MyProject/trunk \
           /CEG/mirror/MyProject/trunk

The path “CEG/mirror” is just a convention, but it will be very helpful later on. Also, rather than mirroring the entire project MyProject, it’s much better just to mirror the trunk and any specific branches you need. With SVK, it’s easy to integrate mirrors of other branches later on. Let’s quickly add a mirror for the a-new-port project to the CEG depot:

svk mirror svn://svnhost.3dex.com/project/MyProject/branches/a-new-port \
           /CEG/mirror/MyProject/branches/a-new-port

Voila! the a-new-port branch is now being mirrored also, alongside the trunk.

Getting remote changes

Now that you have mirroring setup, you must “sync” to get all the latest changes – which on the first run means all changes. This first run will take a long time, so don’t be dismayed, or abort the process thinking that it’s hung. The command to sync all of your mirrors is:

svk sync --all

To sync a specific mirror, name the depot path. You can also sync all the mirrors for a particular depot:

svk sync /CEG/mirror/MyProject/trunk

svk sync --all /CEG/

To get a list of your depots, use:

svk depotmap -l

To see a list of all mirrored projects, use:

svk mirror -l

As time goes by, you can now periodically update your mirrors using svk sync --all, which downloads all the changes that have been committed since the last time you ran it.

Using SVK, Subversion-style

It’s possible to use SVK solely as a mirroring Subversion client. In this form of usage, checkins are committed to the Subversion repository immediately, just as if you were using Subversion itself. The only benefit gained by using SVK in this mode is that you have full access to the repository’s history, even when you’re not connected.

Checking out a working tree from a mirror is a lot like svn checkout, except that you give the depot path, not the Subversion URL:

svk checkout /CEG/mirror/MyProject/trunk MyProject

This creates a local working tree named MyProject, following the remote trunk.

Now let’s say you disconnect from the network. You will still be able to run the following command, showing differences between version 200 and the HEAD:

svk diff -r200:HEAD META-INF/ejb-jar.xml

With Subversion, this command would need access to the remote repository to succeed; with SVK, it always happens at the speed of local access.

(NOTE: SVK uses it own revision numbers, which are not identical to those used in the Subversion repository. This is because SVK revision numbers track the number of changes that have occurred in your depot, whereas Subversion tracks the number of changes that have happened to the remote repository overall (including changes in branches you may not be tracking). So it always helps to use svk log to determine the correct revision numbers of the changes you’re looking for.)

Updating your working tree

If someone commits a change to the remote Subversion tree, you can get it by doing an svk sync --all, following by an svk update in your working tree:

svk sync --all
svk update

Checking in changes

To check in changes, just do a svk sync --all, following by an svk update; resolve any merges conflicts – just as you would with Subversion – and then commit the changes:

svk sync --all
svk update
# 
svk commit -m "My commit comment"

The changes are posted immediately to the remote Subversion repository, and your local mirror is updated at the same time.

Using SVK, Distributed-style

Using SVK in distributed mode requires only one extra step: creating a local branch of the remote mirrored project. This local branch lives on your own machine, and all your future commits are made against it. To get changes down from the server, or push them back up to the server, SVK provides the commands “push” and “pull”. Here is a quick guide to setting up a local branch for distributed development:

# Create the branch by doing a cheap copy
# (this is identical to creating a branch in Subversion)

svk cp -p -m "Created branch" \
    /CEG/mirror/MyProject/trunk \
    /CEG/local/MyProject/trunk

This command create a local branch in the /CEG/ depot, with almost the same name as the mirror of the remote repository. The mirror path begins with /CEG/mirror to show its contents are tracking the remote; the local branch begins with /CEG/local to show its contents live only on the local machine.

Once we’ve created the local branch, we can checkout a copy exactly as we did above, only using the new local branch path instead:

svk checkout /CEG/local/MyProject/trunk MyProject

Now we have our local working tree again, whose contents (at the moment) are identical to what would have happened from a regular Subversion checkout.

Updating your working tree

Let’s say someone checks in changes to the Subversion repository. We need to: 1) synchronize our mirror, 2) merge the changes from the mirror to our local branch, and 3) merge these new changes from the local branch into our working tree. Fortunately, SVK has rolled all these commands into one:

svk pull

That’s it. It will do the sync, update the mirror, update the branch, and then update our local working tree. If you had wanted to do it manually, the steps would have been:

svk sync --all
svk smerge /CEG/mirror/MyProject/trunk \
           /CEG/local/MyProject/trunk
svk update

The smerge command is described later. Most of the time, all you need will be svk pull. You won’t even have to do a sync anymore!

Checking in changes

Checking in changes to a local branch is the best part about SVK, because they don’t have to go to the remote repository right away. This means you can do multiple, quick checkins during a large work in progress without breaking any builds.

You can commit to the local branch in the same way as any Subversion commit:

svk commit -m "First change"
svk commit -m "Second change"
svk commit -m "Third change"

These commits are quick and cheap, since they all go to a local branch on your own machine. When you next do an svk pull, it will merge in any changes from the remote repository “underneath” your new changes, meaning it’s easy to keep up-to-date with the latest trunk revision without interrupting your workflow. This is the real beauty of distributed version control.

Posting your changes

Because we’ve only committed our changes locally, we now have to “post” them back to the remote repository. SVK also has an equally easy command for this:

svk push

The push command can work in one of two modes: it can “replay” each local commit on the remote server, in order to preserve all your commit history; or it can post all your local changes into one big commit, with all the merge comments glommed together in one comment:

svk push      # push each local commit as a remote commit
svk push -l   # "lump" all local changes into one remote commit
svk push -C   # don't actually commit; show if it would conflict

For interests sake, the individual steps of the push command in this example would look like this:

svk sync --all
svk smerge /CEG/local/MyProject/trunk \
           /CEG/mirror/MyProject/trunk

The process of merging into /CEG/mirror causes those commits to be immediately staged into the remote Subversion repository, since SVK maintains the mirror in perfect sync with the remote repository. We are now back in line with the main trunk!

Creating a local topic branch.

Let’s say you’re doing some heavy work, and you want to experiment with a possible optimization. This means you want to pause current development in your local branch – but you want to do your test work on top of these local changes, without having to check them in first. In SVK this is a breeze.

First, fully commit your current work into the local branch. Then, make a snapshot of your local branch to a local topic name:

svk commit -m "Committing work to begin topic branch"
svk cp -p -m "Created topic branch" \
    /CEG/local/MyProject/trunk \
    /CEG/local/MyProject/branches/optimization-test

Now switch your local working tree to track “optimization-test”:

svk switch /CEG/local/MyProject/branches/optimization-test

The changes you commit from this point onward are committed to the “optimization-test” topic branch. If you ever need to switch back to the main local branch for any reason, just commit all current changes into your topic branch and say:

svk switch /CEG/local/MyProject/trunk

As long as you commit before switching, you can switch back and forth as much as you like. Plus, using svk pull in either working tree will pull in whatever recent changes have been made to the remote repository. This lets you work on multiple branches of local development easily, without ever getting out of sync with the main trunk.

If you end up not liking your changes to the optimization-test branch, just switch back to your main local branch and delete the topic branch:

svk switch /CEG/local/MyProject/trunk
svk rm -m "Bad code" \
    /CEG/local/MyProject/branches/optimization-test

If instead you really liked the changes and want to integrate them into your main local branch (to prepare them for committing to the remote), use the powerful smerge command to copy the changes over:

svk switch /CEG/local/MyProject/trunk
svk smerge /CEG/local/MyProject/branches/optimization-test .

The smerge command says to merge all changes committed in the optimization-test branch into the current working tree (.). If you like the result, svk commit the changes back into your local branch. Then you can svk push to reflect them up to the remote repository.

The power of “smerge”

svk smerge can be used not only for merging branch changes into a working tree, but also for merging changes directly from repository to repository, without involving any working tree at all. However, it’s easier to test the results of a merge if you use a clean working tree as the “staging area”.

You can also use the -C option to smerge to do a “merge check”. This doesn’t actually do any merging, but instead tells you what would have happened, and if any conflicts would have resulted from the merge.

Further, the smerge command maintains a historical state of all past merge operations, using regular Subversion properties. This means that if you merge in changes from a topic branch one week, and then merge in later changes from the same branch a week later, only the new changes get merged in the second time. smerge knows that it already has the older changes.

Here’s how you would successively merge changes from the MyProject Subversion trunk into the a-new-port branch, using SVK. I personally run this command every time I see new changes committed to the trunk:

svk smerge /CEG/mirror/MyProject/trunk \
           /CEG/mirror/MyProject/branches/a-new-port

By running this command every week, the “a-new-port” port in the remote repository stays up to date with changes in the trunk.

On the day when a-new-port is finally ready for prime time use, the reverse command will merge all those changes back into the trunk – without overlapping any changes from those previous smerge runs:

svk smerge /CEG/mirror/MyProject/branches/a-new-port \
           /CEG/mirror/MyProject/trunk

Of course, with a command like this, it’s MUCH safer to stage the merge results into a working tree for verification first. Here’s how such a session might play out:

svk checkout /CEG/mirror/MyProject/trunk
svk smerge /CEG/mirror/MyProject/branches/a-new-port .

# resolve conflicts and/or correct any breakages
svk commit -m "Merged in a-new-port"

svk rm -m "Removed SVK mirror; we don't need it anymore!" \
    /CEG/mirror/MyProject/branches/a-new-port

svn rm -m "Removed Subversion branch; we don't need it anymore!" \
    svn://svnhost.3dex.com/project/MyProject/branches/a-new-port