Sunday 7 March 2010

We've Got Issues...

Just because something's the best you've ever seen at doing what it does, does not mean that it's perfect – or even necessarily consistently good. Sometimes the issue you run into is so bad, you wonder how the offender managed to live as long and be as widely respected as it has.

As you probably know, every serious software developer, working on a project that has an intended lifetime measured in hours or larger units, uses some form of version control software. This has been true for over 20 years for PC/Mac/etc. developers, and much longer for folks working on larger systems have had tools for much longer. (The first such software was probably SCCS in 1972.) Numerous commercial (Perforce, IBM/Rational (formerly Atria) ClearCase, etc.) are on offer, as well as a cornucopia of free/open source systems – Subversion, Mercurial, and git.

For several years now, I have been a generally enthusiastic Subversion user. It is cross-platform, stable, reliable; well-supported by third-party tools, books and so on; and being an open-source project doesn't hurt a bit. Reading through the Subversion book, one gets the impression that this was created by people experienced not just in software development, but experienced in version control software.(Karl Fogel, one of the principal initial developers of Subversion, was well-known in the community built around the earlier cvs software; he'd literally written the book on it.)

But, as I alluded to in the first paragraph, Subversion isn't perfect. It's better for most projects than a lot of the stuff out there, but it's got weak points. The Book is refreshingly honest about some of them, but unless you've read that specific part recently, it might not stick in your memory. Incidents such as what happened with me this afternoon are likely to ensure that I remember for a while.

I've recently been using the PHP framework called Kohana to develop Web sites. It's generally well-designed, mature, very efficient, and encourages many "best practices", such as the model-view-controller (MVC) architecture. It's more functional than the truly lightweight packages like Ulysses, while being more learnable and rapidly-productive than the Zend Framework, the 900-pound gorilla in the room. And I strongly suspect that the problem which led to the problem that inspired this post might well not have been Kohana's fault at all.

Kohana, as I said, makes heavy use of the MVC pattern. It also uses PHP 5's class autoloading feature, as documented on the Learning Kohana site. (One of the other Good Things about Kohana is the amount, quality and organization of the online documentation; an apparently large and highly talented team has put in yeoman effort.) As long as you put your classes in any of the "right" places, with the right filenames, everything will Just Work™ no need to mess around with 'require', 'include' or the like (which, in the PHP pre-5 days, was a major cause of breakage).

So I had this Web site I was working on,

  • on my Mac;
  • using PHP and Kohana; and
  • using Subversion for version control.

I got everything done, ready to deploy to a server running the same versions of the Apache Web server and of PHP that I have on my development system. I copied the site over to the proper directories on the server, and started testing.

(insert spectacular breaking/crashing noise here.)

I'd forgotten two important details. The default file system for OS X, HFS+, supports either case-sensitive or -insensitive filename matching, based on a flag set at format time that defaults to case-insensitive. In other words, if your program searches for files named foo.c, Foo.C and fOo.C, all three would match a file with the name FOO.c, and only one such name could be used in a given directory. The default file systems in Linux, ext3 and ext4 (which are each evolutions of the older ext2), are (at least by default) fully case-sensitive.

Further, Kohana's autoloading mechanism depends on case sensitivity to figure out exactly what and where it's looking for code to include. Consider the following snippet from the core Kohana source file's auto_load() method:

                if (($suffix = strrpos($class, '_')) > 0)
                {
                        // Find the class suffix
                        $suffix = substr($class, $suffix + 1);
                }
                else
                {
                        // No suffix
                        $suffix = FALSE;
                }

                if ($suffix === 'Core')
                {
                        $type = 'libraries';
                        $file = substr($class, 0, -5);
                }
                elseif ($suffix === 'Controller')
                {
                        $type = 'controllers';
                        // Lowercase filename
                        $file = strtolower(substr($class, 0, -11));
                }
                elseif ($suffix === 'Model')
                {
                        $type = 'models';
                        // Lowercase filename
                        $file = strtolower(substr($class, 0, -6));
                }
                elseif ($suffix === 'Driver')
                {
                        $type = 'libraries/drivers';
                        $file = str_replace('_', '/', substr($class, 0, -7));
                }
                else
                {
                        // This could be either a library or a helper, but libraries must
                        // always be capitalized, so we check if the first character is
                        // uppercase. If it is, we are loading a library, not a helper.
                        $type = ($class[0] < 'a') ? 'libraries' : 'helpers';
                        $file = $class;
                }

Now, ordinarily, when I create these sites, I use naming conventions consistent with what the relevant documentation calls for. In this site's case, however, I was creating some new helper classes, and I'd been in a rush and fobbed the names; what went into Subversion was all lower case. Oops.

Now, here's the real fail. You can't easily rename files in Subversion. If I type the command "svn rename foo.php Foo.php", I just get back an error message saying svn: Path 'Foo.php' is not a directory. What I need to do instead is something like:

svn mv foo.php ../Foo.php && svn mv ../Foo.php .

The good news is that this preserves the modification history and isn't all that difficult to figure out. The bad news is that it happens at all, and takes a nonzero amount of time to figure out.

Does this mean that I'm going to be making more use of other version-control systems instead of Subversion? Not at all; if you think about it, the problem is at least as likely to be the way OS X handles file names as it is anything in Subversion. Well, am I going to stop using my Mac for development?

You can have my Mac keyboard when you pry it from my cold, dead fingers. Even if I had a problem like this once a day, that took me an hour to figure out (this didn't take nearly that long), I'd still be far more productive than on Linux or (no surprise here) Windows. And I suppose it would be too much to ask for to imagine Subversion looking at that rename-by-case command, realizing it's on a less-than-fully case-sensitive file system, and saying "I'm sorry, Dave; I can't do that. I can't rename a file to its original name."