svn2zip.php
<?php
/*
= SVN2ZIP
Exports an SVN repository and creates a GZIP'd TAR (tgz) file for download.
Once the download file has been created it is cached until the repository's
revision number changes.
Only repositories that allow public access are accessible. There is
no support for password-protected repositories.
== Parameters
* src - either a relative or full public path to an
SVN repository
== Usage
* http://example.com/svn2zip.php?src=/myrepo
Would download http://example.com/myrepo
* http://example.com/svn2zip.php?src=code/myrepo/trunk
Would download http://example.com/code/myrepo/trunk
* http://example.com/svn2zip.php?src=http://code.example.com/myrepo
Would download http://code.example.com/myrepo but would only work
if ALLOW_REMOTE_SVN_DOWNLOAD was set to true (since it is on a
different domain)
Most likely you would add a rewrite rule to your .htaccess so that you
could have URLs like http://example.com/svn2zip/myrepo
RewriteRule ^svn2zip/(.+)$ /svn-to-zip.php?src=/$1 [L,NC]
Code by Alex Dunae (http://code.dunae.ca)
*/
// constants
define('CACHE_PATH', $_SERVER['DOCUMENT_ROOT'] . '/_cache/');
define('TMP_PATH', $_SERVER['DOCUMENT_ROOT'] . '/_tmp/');
// allow this script to download remote repositories?
define('ALLOW_REMOTE_SVN_DOWNLOAD', false);
// script begins
ob_start();
$src = isset($_REQUEST['src']) && !empty($_REQUEST['src']) ? trim($_REQUEST['src']) : null;
if(!$src)
exit_with_error('No repository was specified');
// parse and verify the repository's path
$src = get_svn_path($src);
// get current repository name and revision number
$info = get_svn_info($src);
// name of the download file
$filename = $info['path'] . '.rev' . $info['revision'] . '.tgz';
// temporary path used during export
$tmp_path = TMP_PATH . md5($src);
// calculate the cache file's name
$target = get_cache_file_name($filename);
// if possible, serve the file from the cache
// and exit
if(file_exists($target)) {
push_zip_and_exit($target, $filename);
exit(0);
}
// export the repository to the temporary path
$cmd = sprintf("svn export %s %s --force --quiet --ignore-externals",
escapeshellarg($src),
escapeshellarg($tmp_path)
);
system($cmd, $ret);
if($ret != 0)
exit_with_error('Unable to export repository');
// create the archive in the cache
$cmd = sprintf("tar -czf %s -C %s .",
escapeshellarg($target),
escapeshellarg($tmp_path)
);
system($cmd, $ret);
if($ret != 0)
exit_with_error('Unable to create archive');
// serve the file from the cache
push_zip_and_exit($target, $filename);
exit(0);
// functions
function get_svn_path($src) {
global $allow_remote_svn_download;
if(preg_match('/^(http[s]?)/i', $src) > 0) {
// check if repository is hosted on this server or if
// remote repository downloads are allowed
if(!ALLOW_REMOTE_SVN_DOWNLOAD && strcasecmp(parse_url($src, PHP_URL_HOST), $_SERVER['HTTP_HOST']) != 0) {
$src = null;
}
} else {
// add preceding slash if missing
if(preg_match('/^\//', $src) == 0)
$src = '/' . $src;
// build repository name based on current host
$src = ($_SERVER['SERVER_PORT'] == 443 ? 'https://' : 'http://') . $_SERVER['HTTP_HOST'] . $src;
}
if(!$src)
exit_with_error('Unable to access the repository');
return $src;
}
function get_svn_info($src) {
$info = array();
// run 'svn info' on the repository
$cmd = sprintf("svn info %s", escapeshellarg($src), escapeshellarg($tmp_path));
exec($cmd, $out, $ret);
if($ret != 0)
exit_with_error('Unable to get repository info');
$out = implode($out, "\n");
// extract the revision number
if(preg_match('/^revision\:[ ]?([0-9]+)[ ]?$/im', $out, $matches) > 0)
$info['revision'] = $matches[1];
// extract the path
if(preg_match('/^path\:[ ]?([a-z0-9_-]+)[ ]?$/im', $out, $matches) > 0)
$info['path'] = $matches[1];
return $info;
}
function push_zip_and_exit($zip_file, $filename) {
ob_clean();
header("Content-Type: application/x-compressed-tar\n");
header("Content-Disposition: attachment; filename=\"$filename\"\n");
header("Content-Length: " . filesize($zip_file) . "\n");
readfile($zip_file);
exit(0);
}
// additional functions from http://code.dunae.ca/web_toolkit.html
function exit_with_error($message = 'An error occurred') {
ob_clean();
header('HTTP/1.1 500 Internal Server Error');
print $message;
exit(1);
}
function get_cache_file_name($src, $params = '') {
return CACHE_PATH . basename($src) . '.' . md5($src . $params) . '.cache';
}
?>