This is a brilliant trick - and one that can help you develop code even without a local install of apache/etc for testing.
Let's say I've coded this site, and I want to be able to update it easily, while testing the code out as I go. The first step is to create a test subdomain - I may create test.djomp.co.uk in this case. (This may force you to write code that isn't dependent on your domain name!)
I can then check out my code to both www.djomp.co.uk and test.djomp.co.uk. Then, when I check in a change, I can check it out to the test subdomain first, verify it still works now it's on the server, and then check out the code to the main site.
There's quite a lot of manual steps in there though, so let's automate things so we don't have to log into the server every time.
Hooks to update the test site
Within your subversion repository stored on the server is a subdirectory 'hooks'. In here we can have scripts or programs to execute at certain points when using Subversion. The one we need is post-commit - that will be run after a successful commit.
What we want is some way of checking out the latest version to our test subdomain. Now, here's the issue - the post-commit script will execute as the user that Subversion runs as (in my case, "apache", as I use WebDAV to interact with Subversion). However, the svn update command must come from the user that checked out the repository. We could set this to be "apache", but I don't fancy giving all my scripts access to my repository. Bash scripts can't be run with setuid so we need to write a short C program to perform the update, and setuid the executable to the user that owns the local copy of the code. Still with me? If you are, I'm surprised!
Here's the code. In this example, user "djomp" owns the local copy:
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
execl("/usr/bin/svn",
"svn",
"update",
"/path-to-test-subdomain-checkout/",
"--non-interactive",
"--config-dir",
"/home/djomp/.subversion",
(const char*) NULL);
return(EXIT_FAILURE);
}
So, the script executes svn update at the required directory, using the subversion configuration for the correct user.
Now we can compile and set the permissions on the program:
gcc -o djomp.co.uk djomp.c
chown djomp:apache
chmod 750 djomp.co.uk
chmod u+s djomp.co.uk
This will allow apache to execute the script, and it will run as user "djomp". This does mean that any web script on my server could check out the latest code here, but I'm less worried about that than giving full repository access!
We can now set up our post-commit script to execute this program:
#!/bin/sh
/path-to-update-scripts/djomp.co.uk
So now, crossing our fingers and toes, if we commit a change to the repository, it will automatically be checked out to the test site!
Updating the live site
But how to get a checkout going to the main site? We don't want to automate that - we need to test the new code first.
Sidenote - many people wouldn't want to commit a change before testing it, otherwise their repository could be full of old code that doesn't work. I agree with this, but I find this too useful to mind about the odd mistake here and there - especially if the follow up commit makes it obvious that it's a fix for the previous one!
We can easily set up a second update script to point at the live site. If we give it the same permissions - allowing "apache" to execute it - we could then set up a web page to call the script.
We could even make that web page a little more clever and it could check the versions of the test and live site before offering the link. Thankfully we can test the version number of a local copy without being the user that owns it, so we just need a simple script:
#!/bin/sh
cd /path-to-local-copy/
svnversion
This will return a number.
We can then set up a PHP script to handle all of this, as below. This will take details of multiple sites, should you require:
<html>
<head></head>
<?
$pathToUpdateScripts = '/path-to-update-scripts/';
$pathToVersionScripts = '/path-to-version-scripts/';
$repos = array(
array(
'Name' => 'dJomp.co.uk',
'UpdateScript' => 'djomp.co.uk',
'LiveVersionScript' => 'djomp.co.uk',
'TestVersionScript' => 'test.djomp.co.uk',
),
/*
array(
'Name' => '',
'UpdateScript' => '',
'LiveVersionScript' => '',
'TestVersionScript' => '',
),
*/
);
?>
<body>
<?
$output = array();
foreach($repos AS $ref => $repo)
{
if (isset($_GET['do']) && ($_GET['do'] == $ref))
{
echo '<h2>Updating '.$repo['Name'].'</h2>';
exec($pathToUpdateScripts.$repo['UpdateScript'], $output);
}
}
if (count($output))
{
echo '<pre>',implode("\n",$output),'</pre>';
}
?>
<hr>
<table border>
<tr>
<th>Repo</th>
<th>Test Version</th>
<th>Real Version</th>
<th>Update</th>
</tr>
<?
foreach($repos AS $ref => $repo)
{
$testVersion = exec($pathToVersionScripts.$repo['TestVersionScript']);
$realVersion = exec($pathToVersionScripts.$repo['LiveVersionScript']);
echo '<tr>',
'<td>',$repo['Name'],'</td>',
'<td>',$testVersion,'</td>',
'<td>',$realVersion,'</td>',
'<td>',
($realVersion == $testVersion
? '-'
: '<a href="index.php?do='.$ref.'">Update</a>'),
'</td>',
'</tr>';
}
?>
</table>
</body>
</html>
Now this script is a little more sensitive so secure it however you wish - at least behind a password somewhere. The worst it can do is update your live site to your latest commit, but depending on what you commit that could be bad enough!
Conclusion
That's it. You can now update your live site in two steps:
- Commit your changes
- Click a link on a private webpage
I think that's pretty nifty. I do abuse a few of my subversion repositories, though; there are times when I need to submit a change but I don't have a local test area, so I make the changes and submit them to the test server to check they even work!
If you're using an editor that has subversion support built-in then it becomes even easier - you only need your editor and a web browser open to be developing your site remotely.