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.
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.
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.
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.
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.
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.)
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
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
# <Now resolve possible conflicts…>
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 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.
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 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.
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!
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.
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