Mere-Moments Guide to installing a Subversion server on Windows

Update: This Guide is now largely obsolete, because Brian wrote an installer that will do all this stuff for you. Check out his annoucement*, or go straight to the svn1clicksetup project page on tigris.

* Or you can check out my announcement about his announcement. It’ll work either way.

Subversion sounds pretty cool. It’s a mature, powerful revision-control system that acts a lot like CVS, adds support for atomic commits and real renames, just won the Jolt award, and is free. What more can you ask for?

I’ve been intending to install Subversion for quite a while, but I kept putting it off, because it looked like a daunting task. But when I actually decided to go do it, it took me all of an hour and a half to get it installed and working. If somebody had just written down what I needed to do to set up Subversion on Windows, with a real server running as a real Windows service, then it probably would’ve only taken me ten minutes, and I would’ve done it weeks ago.

Here, then, is the Mere-Moments Guide to installing a Subversion server on Windows. (It may look a bit intimidating, but really, it’s not.)

Some quick notes on the Guide:

  • These instructions assume you’re using Windows 2000 or XP. (You’d better be; the Subversion server won’t run on Win9x.)
  • If you want to know more about Subversion than just how to install it, check out the free O’Reilly Subversion book online and the not-free Pragmatic Version Control using Subversion.
  • For Subversion to do you much good, you’ll have to add a new “project” (essentially a directory) to your repository, to put files in. In these instructions, I’m assuming that your new project will be called monkey (because mine was).
  • Feel free to skip steps and to play around; you’ll learn more that way, because things won’t work right and you’ll have to figure out why.

And now, on to the Guide.

  1. Download everything
    1. Go to and download the most recent svn-x.y.z-setup.exe. At the time of this writing, the latest version was svn-1.2.0-setup.exe.
    2. Go to and download
    3. Go to and download the most recent installer. At the time of this writing, the latest version was TortoiseSVN-1.1.7-UNICODE_svn-1.1.4.msi. (It doesn’t have to be the exact same version as the svn installer you got in step 1. See the compatibility chart.)
  2. Install the server and the command-line client
    1. Run svn-x.y.z-setup.exe and let it install stuff.
    2. Go to Control Panel > System, go to the Advanced tab, and click the “Environment Variables” button at the bottom. Click the “New” button (either one, but if you’re undecided, use the one under “System variables”), set “variable name” to SVN_EDITOR, and “variable value” to the path and filename of a text editor of your choice (e.g., C:\Windows\Notepad.exe). OK all the way out.
  3. Create a repository and configure access
    1. Create a new directory somewhere out of the way; this is where your repository will live, but you’ll almost never actually open the files directly. I made a directory called svn_repos directly under my C:\Documents and Settings, just so it’d be out of the way.
    2. Open a command prompt and type: svnadmin create “C:\Documents and Settings\svn_repos”
    3. In Windows Explorer, browse to the C:\Documents and Settings\svn_repos\conf directory (which svnadmin just created for you), and edit a couple of config files:
      1. Open the svnserve.conf file in a text editor, and uncomment the [general], anon-access = read, auth-access = write, and password-db = passwd lines. Save.
      2. Open the passwd file in a text editor, uncomment the [users] line, and add the username and password you want to use when connecting to your subversion server. Save.
  4. Start the server manually, and create a project
    1. In your command window, type: svnserve –daemon –root “C:\Documents and Settings\svn_repos”
    2. Open a second command window, and type svn mkdir svn://localhost/monkey
    3. You’ll see the text editor you specified in step II.2, with some text already in it. Type a comment, like “Created the monkey project”, at the beginning of the file (before the line starting with “–“). Save the file and close the editor.
    4. If your Subversion login is the same as your Windows login, then type your password (the one you put in the passwd file) at the prompt, and hit Enter. If your Subversion login is different from your Windows login, then just hit ENTER at the password prompt, and Subversion will then ask for both your login and your password.
    5. Subversion should tell you that it “Committed revision 1.” Congratulations! You just checked a change into Subversion. Throw yourself a party. (Yes, creating a directory is a revisioned change — you can go back and get the repository as of a time before that directory existed. This is novel stuff for folks like me who still use VSS at work.)
    6. It’s conventional to have /trunk, /branches, and /tags subdirectories for each project (your code goes into trunk, and the others are where you put, well, branches and tags). Go ahead and type svn mkdir svn://localhost/monkey/trunk (and notice that, after you enter a checkin comment, it doesn’t prompt you for your password again — it’s smart like that).
  5. Start the server for real
    1. Go back to the command window that’s running svnserve. Hit Ctrl+C to stop it.
    2. Open the that you downloaded earlier. Extract SVNService.exe into your Subversion bin directory (Program Files\Subversion\bin). Yes, it’s important that you put it in this directory; it has to be in the same place as svnserve.exe from the Subversion distribution.
    3. In a command prompt, type svnservice -install –daemon –root “C:\Documents and Settings\svn_repos”
    4. Go to Control Panel > Administrative Tools > Services, double-click the SVNService service, and change its startup type from “Manual” to “Automatic”. Now Subversion will start every time you start Windows.
    5. Start the SVNService service (by selecting it in the Services list, and clicking the “play” toolbar button).
    6. Go back to a command prompt, and type svn ls svn://localhost/
      This will list all the files in the root of the repository. If all is well and you’ve got a real Subversion server running now, you should see: monkey/
  6. Install TortoiseSVN
    Sure, you can get by with a command-line client, but TortoiseSVN is cool — it integrates Subversion into Windows Explorer. You get little overlay icons showing the status of each file (in sync, needs to be checked in, not yet in the repository, etc.), and you can do pretty much everything you need by right-clicking on files and folders.

    1. Run the TortoiseSVN installer you got back in part I.
    2. Create a monkey directory somewhere on your hard drive. Right-click somewhere in that folder and select “SVN Checkout…” Type svn://localhost/monkey/trunk/ for the repository URL and click OK.
    3. Create a file in that directory, any file. Right-click the file and select TortoiseSVN > Add. Notice the little plus-sign icon that appears.
      The file hasn’t actually been checked in yet — Subversion’s commits are both batched and atomic, so this new file, together with any other new files you added, any files you changed, any files you deleted, any files you renamed, any directories you added or deleted or renamed, will all show up on the server all at once, as a single revision and a single checkin, the next time you right-click and select “SVN Commit”.
  7. Make it run on the network
    Are you kidding? You’re already networked. Go to another computer on your LAN, install TortoiseSVN, and do an “SVN Checkout…”. When you specify the repository URL, use the same URL you did before, but replace “localhost” with the actual name of the computer that’s running the Subversion service (so in my case, the repository URL is svn://marsupial/monkey/trunk/ — nice little menagerie, there).

And there ya go — Subversion up and running on Windows, in mere moments or less.

Corrections, questions, etc. on this document are, as always, welcome; just speak up in the comments. Now go forth and control your revisions.

Also in the Mere Moments series: Mere-moments guide to creating custom ring tones for your Verizon RAZR V3m

General preference: keep the cat dry

I was supposed to take Tycho to the vet this evening, for another re-check on his eye. But after driving home from work, I decided against it. It’s raining a bit (sarcasm), and I decided I didn’t really want to drive my crabbiest child to the vet, during a thunderstorm, through 84th Street Lake.

Our regular vet will be in Thursday morning; we’ll go then. (hastily checks weather forecast) Yeah, we’ll go then.

Second bike ride of spring

There’s a bike trail that runs within about half a mile of our house. I looked up the satellite photos on Google Maps, and found that this is the same trail that runs within half a block of where I work. Today, I took my bike and checked it out.

It turns out I’m still out of shape.

Other things I learned today:

  1. Bike tires apparently don’t stay full for the entire summer like they used to. (This is ridiculous. I just filled the damn things six weeks ago.)
  2. Partly-flat bike tires have a lot more drag than you’d think — enough that, once I notice they’re going flat, it’s probably worth going back home and refilling them. Live and learn.
  3. When I collapse from exhaustion after riding a little over a mile, it takes more than ten minutes before I’m actually ready to go again. Half an hour is better.
  4. Recuperating in the shade is a better idea than recuperating in the sun.
  5. Next time, I’m not just going to bring a bottle of Gatorade like I did this time. I’m also going to bring a bottle of cold water, and a washcloth, so I can expedite the cooling-off process.
  6. (Suggested by somebody who stopped to see if I was OK) Eat something first. Two waffles four hours earlier might not cut it. At least eat an apple before I go.
  7. A bicycle makes a decent crutch when you’re still a little wobbly on the walk home.
  8. My next few bike rides are going to be on an exercise bike at the gym. That way, once I’ve reached the point of exhaustion, I only have to walk as far as my car.

Ruby coolness #1: Blocks

My co-worker Sam (yes, his blog is broken at the moment) is a big fan of the Ruby programming language, and lately I’ve been trying it out.

Ruby is an interesting language, and you can do some cool stuff with it. There are a few things I don’t like about it, but there are quite a few things that make me think, “Man, I wish Delphi and C# had that.”

The #1 thing I like about Ruby: blocks. (They may have stolen this idea from Smalltalk, but I don’t know enough about Smalltalk syntax to say for sure.)

When you call a Ruby method, you can pass parameters just like usual, and you can also, if you choose, pass a code block. Here’s an example that you might write in a unit test:

expect_exception(DatabaseError) do

This expect_exception method actually takes two parameters: one normal parameter, and one extra-special sorta-parameter with magic syntax. The normal parameters are delimited by parentheses, just like in Delphi, C#, and countless other languages. (Actually, you can omit the parens, if you like.)

The extra-special parameter, instead of parentheses, is delimited by the “do” and the “end” (or you can use C-style curly braces), and it’s actually a block of code (aka lambda, aka closure) that’s passed to the method. It’s passed sort of like a method pointer, so it’s up to the method to decide when and whether to invoke that block. The method might call that block once, twice, hundreds of times, or not at all. In the case of expect_exception, we would call the block once, catch any exceptions thrown by the block, and fail the unit test if the block doesn’t raise a DatabaseError.

If we wanted to do the same thing in Delphi — pass a block of code to a method — we would have to put the “block” in a separate method, and then pass a method pointer. Same thing in .NET 1.x. It’s doable, but it separates the code; the outer logic is in one method, and the inner logic is in a different method. Often you’d rather keep all the logic together. C# 2.0 will have some compiler magic for closures, but the syntax isn’t as elegant as Ruby’s — you need extra keywords, and the code block has to be inside the parentheses, which seems really clumsy after you get used to Ruby’s syntax. Ruby lets us just write what we mean, and without gunky syntax. It’s easy and natural. Expect an exception when you do this.

Blocks are so ingrained, they’re actually the standard way that Ruby handles iteration. Delphi has conventions for indexed properties. .NET has conventions for objects that implement one interface, which instantiates and returns an instance of another object implementing another interface, which then lets you step through the objects in the collection. Ruby cuts through the crap: you call the collection’s each method and pass it a block, and that block gets invoked once for each element in the collection.

some_list.each do |item|
  puts item

The vertical bars after “do” are where we declare the block’s parameter list; we’re declaring a block that takes one parameter, called item. And it’s that simple. No creating a separate enumerator object and maintaining complicated state (not even behind the scenes). No relying on naming conventions. No off-by-one errors. Nothing to free. And somehow they still make break and continue work (though continue is called next in Ruby, probably because that’s what Perl calls it).

Inside the each method, you just use the yield keyword every time you want to call the block you were given. (That handles the simple cases; you can get a little fancier if you want.) So if you wanted to write a Ruby method that does an in-order traversal of a binary tree, you could just write:

def traverse
  left.traverse {|node| yield node }
  yield self
  right.traverse {|node| yield node }

Totally recursive traversal in five lines of code, and the caller doesn’t even need to know it’s recursive — the caller just treats it like any other loop. Do that with Delphi’s indexed properties, or with .NET’s IEnumerable and IEnumerator.

Ruby’s blocks are just wicked cool, and utterly pervasive throughout the class library. Another of my favorites is"foo.txt") do |file|
  # do stuff with file

This will open the file, run the block, and then close the file. You don’t need a try..finally Close. All you need to do is put your file-handling code in the block. The try..finally has already been written for you — it’s inside

Man, I wish Delphi and C# had that. The easy syntax, the integration into the library, the expressiveness it gives you for writing iterators and fancy unit-test assertions… cool stuff. I can just imagine the Delphi code…

CheckThrows(EInvalidOperation) do

Wouldn’t that be something? Whee…

On evangelism

A few years ago, Jennie and I attended a Baptist church for a year or two. While we were there, they started a social group for “young adults”. (I used to work in a library, so I always think of that phrase in the publishing-industry sense of “younger teens”, but this church took it to mean something more like “twenties and thirties”.)

After a few get-togethers, someone raised the question of what the group should be named. And somebody suggested “POWER” — I think it stood for “People Of Worship, Evangelism, and Religion”.

After a brief hesitation, I spoke up against that name. For three reasons. One, it didn’t describe the group; how was anyone supposed to read that name and know it was a group for Baptist twenties and thirties? (Or even what religion it was, for that matter — could’ve been a group of Scientologists for all the name tells you.) Two, a group that calls itself “POWER” sounds waaaay too much like a white-power, neo-Nazi group. And three, the word “evangelism” is a real turn-off for some people. Including me.

They went with that name anyway. I think I may have gone to one more of their events before I dropped out.

Evangelism offends me for the same reason Fanta commercials offend me: they both start with the assumption that I’m a complete idiot.

See, faith is one thing. I don’t know whether I believe in God, but I do believe in faith. I’ve had the experience of leaning on faith in tough times. It got me through the time in college when I was thinking about suicide. In my case, it was faith in the strength of a friendship. For others, it’s a faith in God, or in themselves, or in freedom, or in their family. It can pull people through rough spots, and even help them turn their lives around. People lay down their lives for it. And it can help people stay alive.

I believe that faith can move mountains. I believe that it may be the most powerful force in the human experience. What I don’t believe is that my faith is automatically right for everyone else.

But where I stop is where evangelism starts: with a faith that is assumed to be the One Truth. And because it’s taken on faith, because it’s taken to be self-evident, it’s automatically beyond debate, beyond reason. It wasn’t reasoned into you, so it can’t be reasoned out. And everyone who believes something different is obviously wrong.

I posted about evangelism a while back, and Brad asked why I took offense at the idea. And he asked a pretty poignant question: do I see evangelists as being Obviously Wrong, Because They Don’t See Things The Way I Do?

I’ve been thinking about that. And I think the answer is, for the most part, no. I am only human, with the occasional arrogance and thoughtlessness that come with the package. But I don’t think the reason I dislike evangelists’ beliefs is just the fact that they’re different from mine.

Last September, one of the youth in our youth group asked me to be his mentor in our “Coming of Age” program. And that’s been interesting, and occasionally challenging — all the more so because he considers himself to be more Christian than Unitarian. He believes in God, and in Heaven and Hell. And I’m thrilled for him. He’s got it all figured out. (Well, maybe not all figured out, but certainly more than me.) And he’s got something to put his faith in. Pretty solid faith, in his case; he’s got a lot of strength and self-assurance for a ninth-grader. But he knows that it’s his faith, and he’s content to let others find their own way.

What offends me about evangelicals is not what they believe about God. What offends me is what they believe about their beliefs. What offends me is that they assume nobody else is capable of making up their own mind. What offends me is their… well, to put it bluntly, their arrogance. And their ignorance.

I don’t accept arrogance as a religious belief. And I don’t accept anyone who assumes everyone else is wrong. You can’t convince me someone is wrong until you can tell me what they’re wrong about — until you’ve at least asked them what they believe.

And even then, if it’s a matter of faith, you’re going to have a damn hard time convincing me that you’re right, and they’re wrong.

“Believe those who are seeking the truth. Doubt those who find it.” — Andre Gide (1869-1951)

Now, with all that said…

I’m usually wrong the first time. Like everybody else, I learn by making mistakes. And on some level, I really can’t believe that all evangelism is rooted in arrogance and ignorance. I just can’t come up with any other explanation. This is the sort of thing I tend to need someone else to explain to me.

So I’ll open the floor to you guys. I know there are some Christians reading my blog, and probably some of you consider evangelism to be part of your faith. So tell me. What’s it all about?

Speak up. Challenge my assumptions. (But expect me to challenge yours right back. This could be an interesting discussion.)

Popup “menus” with custom controls (WinForms)

Just ran across an article on CodeProject titled “Display any UserControl as a popup menu” (for WinForms in .NET). Looks pretty decent; the screenshot is impressive.

The basic idea: you have a button (or something) which, when clicked, should show a dropdown containing some arbitrary controls. Maybe you need to drop down a two-column menu, or a set of checkboxes. Maybe you’ve got a “Preview 6” button, and when the user clicks it, they should see a dropdown thumbnail of an image they selected earlier. Etc.

This library takes care of the details. You give it the UserControl to put in the “dropdown”, and you also pass it the button that kicked off the dropdown. It will automatically position the control below the button, beside it, etc., whatever will fit on the screen. It also does drop shadows (Windows XP only, natch) and slide-in animation. And the programming interface is about as simple as you could ask for.

Note: I haven’t actually used this control, just read about it. So if you’ve used this code, feel free to leave comments, either here, or on the article itself on CodeProject.

Anniversary gift: mostly healthy cat

Two very good things happened yesterday.

One: we took Tycho back to the vet for a re-check, and she said that his eye looked “90% better”. She doesn’t think we’re going to need to take him to a specialist at this point (which saves us a five-hour car trip — each way — with a yowling cat in tow), so that was a relief. If the thing in his eye had been a tumor, she told us, then we wouldn’t have seen this kind of improvement with the meds we had him on. (She called us after we got home, with the lab results from his first visit, and there were indeed no cancerous cells. Hooray!) Not only is his eye doing better, but now we only have to put ointment in his eye four times a day, instead of six — much more manageable, given our work schedules. He goes back in a week for another re-check.

Two: yesterday was our eight-year anniversary. (Our anniversary celebration consisted of taking Tycho to the vet. See, we still know how to party till it hurts.)

Advantage Error 5035: That record is locked by… you

The Advantage database is nice for some things. But it has several significant downsides, which, of course, we knew nothing about until we started working with it. The one that’s given us the most grief so far is the way it deals with record locks inside transactions, and the (now) dreaded (to us) Error 5035.

There are two different ways to update records in Advantage. One is with an updatable recordset (either TAdsTable, or a SELECT TAdsQuery with RequestLive = True). The other is with SQL: INSERT, UPDATE, and DELETE queries. Advantage has two completely different, and incompatible, record-locking schemes for these. (In fact, the updatable-recordset record-locking scheme is incompatible with itself.)

When you start a transaction in Advantage, and then try to modify a record, Advantage puts a lock on that record. (This is true even if you request optimistic record locking. Yes.) The error can occur if you then try to modify that same record again, from the same client, from the same connection, under the same transaction. If you’re not insanely careful, that second update will fail, because the record is locked.

Yes. You locked the record, from this client, from this connection, from this transaction. And now, whoo boy, no, you can’t modify that same record again, because that record is locked, buddy. It doesn’t matter that you’re the freaking owner of that lock, that you’re the one who modified the record to begin with. Nope. The record is locked, and Advantage will throw a nice little “Error 5035” exception if you try to modify that record again.

Nice, eh?

There are two workarounds. One is to never modify the same record twice under the same transaction. This was not an option for us, without a huge amount of redesign that would set us back weeks or months (and introduce several more weeks’ or months’ worth of bugs to fix).

The other workaround is to understand what Advantage is doing with these locks, and how to avoid the error. We’ve already gone through that process, so I’ll share the benefit of our experience.

Advantage apparently has two different kinds of record locks behind the scenes. INSERT, UPDATE, and DELETE queries apply what I’ll call an “SQL lock”. Updatable recordsets apply what I’ll call a “cursor lock”, and that cursor lock belongs to one, specific cursor (i.e., one specific TAdsTable instance, or one specific RequestLive SELECT TAdsQuery).

Some locks are compatible with other locks, meaning they work the way they ought to — you can make the second change without getting an exception. The rule is: All SQL locks are compatible with all other SQL locks. Cursor locks are only compatible with cursor locks that originate from the same cursor (i.e., the same TAdsTable or TAdsQuery instance). All other combinations, when attempted under a transaction, will give you an Error 5035.

Examples: (just for illustration — obviously they should have try..finally, Commit, Rollback, etc.)

// All queries will work just fine
InsertQuery.SQL.Text := 'INSERT INTO Orders (ID, Value) VALUES (1, 5.50)';
UpdateQuery.SQL.Text := 'UPDATE Orders SET Value = Value + 1';
// No problems, no exceptions
// Multiple TAdsTable instances don't work
AdsTable1.TableName := 'Orders';
AdsTable1.AppendRecord([1, 5.50]);
AdsTable1.FieldByName('Value') := 6.50;
// Everything up to this point will work just fine,
// because it's all on the same TAdsTable instance.
// Add a second TAdsTable, and things break:
AdsTable2.Edit; // <-- exception, unless using optimistic locking
AdsTable2.FieldByName('Value') := 6.50;
AdsTable2.Post; // <-- exception, even if optimistic
// Mixing SQL and TAdsTable doesn't work
AdsQuery1.SQL.Text := 'INSERT INTO Orders (ID, Value) VALUES (1, 5.50)';
AdsTable1.TableName := 'Orders';
AdsTable1.Edit; // <-- exception, unless optimistic
AdsTable1.FieldByName('Value') := 6.50;
AdsTable1.Post; // <-- exception
// Yes, it fails even if you've freed the old table
// and then created a new one
Table := CreateAndOpenOrdersTable(AdsConnection1);
Table.AppendRecord([1, 5.50]);
Table := CreateAndOpenOrdersTable(AdsConnection1);
Table.Edit; // <-- exception, unless optimistic
Table.FieldByName('Value') := 6.50;
Table.Post; // <-- exception

Advantage not only knows about this problem, they apparently went out of their way to make it work this way. Something about different cursors not knowing about each others’ changes, and that leading to possible index corruption if they didn’t do the locks this way. There are several reasons why this doesn’t make a bit of sense to me, but whatever the case, updatable cursors place a harsher lock than queries do, and the Advantage programmers apparently have no intention of changing this anytime soon.

Bottom line: If you need to modify a table under an Advantage transaction, use all INSERT/UPDATE/DELETE queries if at all possible. If you absolutely must use updatable recordsets, make sure you only have one instance of that TAdsTable (or SELECT TAdsQuery) that’s shared by everybody who wants to update that table, and that you never, ever use queries for that table while you’re under that transaction. And if you can’t follow those rules, then sorry, buddy, but you’re screwed. Fortunately, you can mix and match for different tables; you can use SQL for some tables, and updatable recordsets for others, and have no problems.

(For what it’s worth, you can have multiple TAdsTables and SELECT queries open on the same table at the same time, as long as you only use one of them for edits while you’re in a transaction.)

We did a lot of work on this last week (and have more ahead of us next week), working around this nice little nightmare. Some of our tables now do all their updates via INSERT/UPDATE/DELETE queries; we redesigned and reimplemented others to use a single TAdsTable instance (thank God for unit tests), and when they want to delete a lot of records, they now set a filter, call TAdsTable.Delete until no records are left, and then clear the filter. Ugh — that’s a hell of a lot of round trips to the server, when there really should be (and used to be, until Advantage started throwing this exception) only one.

I shudder to think of what this Advantage design flaw will mean to us going forward. For one thing, we really want to have more parts of our code that use transactions, not fewer. And for another, I just can’t imagine what will happen when we start moving more parts of this code into server-side extended stored procedures. How, exactly, am I supposed to use the same TAdsTable instance from both the client and the server? Eww.

It’s not the end of the world; we’ll fix the last known case of this problem early next week, and hopefully there won’t be any more. So we’ve only lost about one and a half pair-weeks, maybe two, to this problem. Heck, we spent longer porting our queries to Advantage, and that was with a total change in the way we located and named the tables. But still, it’s extremely irksome that they wouldn’t see this as a bigger problem than they apparently do.