Merge branch 'master' of github.com:tommyd3mdi/indefero-monotone

master
Thomas Keller 2010-08-24 22:57:52 +02:00
commit a442fd588e
10 changed files with 254 additions and 429 deletions

128
README
View File

@ -1,128 +0,0 @@
monotone implementation notes
-----------------------------
1. general
This branch contains an implementation of the monotone automation interface.
It needs at least monotone version 0.47 (interface version 12.0) or
newer. To set up a new project with monotone, all you need to do is
to create a new monotone database with
$ mtn db init -d project.mtn
in the configured repository path ('mtn_repositories'). To have a really
workable setup, this database needs an initial commit on the configured
master branch of the project. This can be done easily with
$ mkdir tmp && touch tmp/remove_me
$ mtn import -d project.mtn -b master.branch.name \
-m "initial commit" tmp
$ rm -rf tmp
2. current state / internals
The implementation should be fairly stable and fast, though some
information, such as individual file sizes or last change information,
won't scale well with the tree size. Its expected that the mtn
automation interface improves in this area in the future and that
these parts can then be rewritten with speed in mind.
Another area of improvement is the access pattern to the monotone
database. While only one process is started per request, the time
(and server resource) penalty for this could still be dramatic once
many clients try to access the service. Luckily, monotone has an
easy way to deliver its stdio protocol for automation usage over the
network (mtn au remote_stdio), so the following scenarios are possible:
a) setup a single mtn server serving one database on a different
(faster) server and let the stdio client connect to that
b) setup usher (available from branch net.venge.monotone.contrib.usher
from the official mtn repository on monotone.ca) as proxy in
front of several local monotone databases mirroring themselves
c) like b), but use usher as proxy in front of several other remote
monotone databases (forwarding)
The scenario in a) might be needed anyways for a shared hosting
environment, because a database which gets served via netsync cannot
be accessed by another local process at the same time (its locked then),
so ideally both, the network functionality as well as the indefero
browsing functionality should be delivered from one single database
per project via netsync.
The only alternative for this setup is a two-database approach, where one
database acts as network node and the other as backend for indefero.
The synchronization between these two would then have to happen via
standard tools (cron...) or a sync request from one database to the other.
While the current implementation is ready for the two database approach,
some code parts and configuration changes have to happen for the remote
stdio usage. Bascially this is replacing the initial call to
mtn -d project.mtn au stdio (Monotone.php, around line 74)
with
mtn au remote_stdio HOSTNAME
which could be made configurable in conf/idf.php. But again, this heavily
depends on the exact anticipated server setup.
To scale things up a bit, multiple projects should of course use
separated databases. The main reason for that is that while read access
can be granted on a branch level, write access gives total write
possibilities on the whole database. One approach would be to start
one serve process for each database, but the obvious downside here is
that each of those processes would need to get bound to another
(non-standard) port making it hard for users to "just clone" the
project sources without knowing the exact port.
Usher comes to the rescue here as well. It has three ways
to recognize the request for a particular database:
a) by looking at the requested host name (similar to SNI for Apache)
b) by evaluating the requested branch pattern
c) by evaluating the path part from an mtn:// uri (new in mtn 0.48)
The best way is probably to configure it with c) - instead of pulling
a project like this
$ mtn pull hostname branchname
a user uses the URI syntax (which will, btw. be the default from
mtn 0.99 onwards):
$ mtn pull mtn://hostname/database?branchname
Here, the "/database" part is used by usher to determine which backend
database should be used for the network action. The "clone" command
will also support this mtn:// uri syntax, but this didn't made it into
0.48, but will be available from 0.99 and later.
3. indefero critique:
It was not always 100% clear what some of the abstract SCM API method
wanted in return. While it helped a lot to have prior art in form of the
SVN and git implementation, the documentation of the abstract IDF_Scm
should probably still be improved.
Since branch and tag names can be of arbitrary size, it was not possible
to display them completely in the default layout. This might be a problem
in other SCMs as well, in particular for the monotone implementation I
introduced a special filter, called "IDF_Views_Source_ShortenString".
The API methods getPathInfo() and getTree() return similar VCS "objects"
which unfortunately do not have a well-defined structure - this should
probably addressed in future indefero releases.
While the returned objects from getTree() contain all the needed
information, indefero doesn't seem to use them to sort the output
f.e. alphabetically or in such a way that directories are outputted
before files. It was unclear if the SCM implementor should do this
task or not and what the admired default sorting should be.

View File

@ -0,0 +1,63 @@
# monotone implementation notes
## general
This version of indefero contains an implementation of the monotone
automation interface. It needs at least monotone version 0.47
(interface version 12.0) or newer, but as development continues, its
likely that this dependency has to be raised.
To set up a new IDF project with monotone quickly, all you need to do
is to create a new monotone database with
$ mtn db init -d project.mtn
in the configured repository path `$cfg['mtn_repositories']` and
configure `$cfg['mtn_db_access']` to "local".
To have a really workable setup, this database needs an initial commit
on the configured master branch of the project. This can be done easily
with
$ mkdir tmp && touch tmp/remove_me
$ mtn import -d project.mtn -b master.branch.name \
-m "initial commit" tmp
$ rm -rf tmp
Its expected that more scripts arrive soon to automate this and other
tasks in the future for (multi)forge setups.
## current state / internals
The implementation should be fairly stable and fast, though some
information, such as individual file sizes or last change information,
won't scale well with the tree size. Its expected that the mtn
automation interface improves in this area in the future and that
these parts can then be rewritten with speed in mind.
As the idf.conf-dist explains more in detail, different access patterns
are possible to retrieve changeset data from monotone. Please refer
to the documentation there for more information.
## indefero critique:
It was not always 100% clear what some of the abstract SCM API method
wanted in return. While it helped a lot to have prior art in form of the
SVN and git implementation, the documentation of the abstract IDF_Scm
should probably still be improved.
Since branch and tag names can be of arbitrary size, it was not possible
to display them completely in the default layout. This might be a problem
in other SCMs as well, in particular for the monotone implementation I
introduced a special filter, called "IDF_Views_Source_ShortenString".
The API methods getPathInfo() and getTree() return similar VCS "objects"
which unfortunately do not have a well-defined structure - this should
probably addressed in future indefero releases.
While the returned objects from getTree() contain all the needed
information, indefero doesn't seem to use them to sort the output
f.e. alphabetically or in such a way that directories are outputted
before files. It was unclear if the SCM implementor should do this
task or not and what the admired default sorting should be.

View File

@ -210,32 +210,27 @@ class IDF_Form_UserAccount extends Pluf_Form
public static function checkPublicKey($key, $type, $user=0)
{
$key = trim($key);
if (strlen($key) == 0)
{
if (strlen($key) == 0) {
return '';
}
if ($type == 'ssh')
{
if ($type == 'ssh') {
$key = str_replace(array("\n", "\r"), '', $key);
if (!preg_match('#^ssh\-[a-z]{3}\s\S+\s\S+$#', $key))
{
if (!preg_match('#^ssh\-[a-z]{3}\s\S+\s\S+$#', $key)) {
throw new Pluf_Form_Invalid(
__('The format of the key is not valid. It must start '.
'with ssh-dss or ssh-rsa, a long string on a single '.
'line and at the end a comment.')
);
}
if (Pluf::f('idf_strong_key_check', false))
{
if (Pluf::f('idf_strong_key_check', false)) {
$tmpfile = Pluf::f('tmp_folder', '/tmp').'/'.$user.'-key';
file_put_contents($tmpfile, $key, LOCK_EX);
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
'ssh-keygen -l -f '.escapeshellarg($tmpfile).' > /dev/null 2>&1';
exec($cmd, $out, $return);
unlink($tmpfile);
if ($return != 0)
{
if ($return != 0) {
throw new Pluf_Form_Invalid(
__('Please check the key as it does not appears '.
'to be a valid key.')
@ -243,18 +238,15 @@ class IDF_Form_UserAccount extends Pluf_Form
}
}
}
else if ($type == 'mtn')
{
if (!preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key))
{
else if ($type == 'mtn') {
if (!preg_match('#^\[pubkey [^\]]+\]\s*\S+\s*\[end\]$#', $key)) {
throw new Pluf_Form_Invalid(
__('The format of the key is not valid. It must start '.
'with [pubkey KEYNAME], contain a long string on a single '.
'line and end with [end] in the final third line.')
);
}
if (Pluf::f('idf_strong_key_check', false))
{
if (Pluf::f('idf_strong_key_check', false)) {
// if monotone can read it, it should be valid
$mtn_opts = implode(' ', Pluf::f('mtn_opts', array()));
$cmd = Pluf::f('idf_exec_cmd_prefix', '').
@ -264,8 +256,7 @@ class IDF_Form_UserAccount extends Pluf_Form
fwrite($fp, $key);
$return = pclose($fp);
if ($return != 0)
{
if ($return != 0) {
throw new Pluf_Form_Invalid(
__('Please check the key as it does not appears '.
'to be a valid key.')
@ -279,15 +270,12 @@ class IDF_Form_UserAccount extends Pluf_Form
}
// If $user, then check if not the same key stored
if ($user)
{
if ($user) {
$ruser = Pluf::factory('Pluf_User', $user);
if ($ruser->id > 0)
{
if ($ruser->id > 0) {
$sql = new Pluf_SQL('content=%s AND type=%s', array($key, $type));
$keys = Pluf::factory('IDF_Key')->getList(array('filter' => $sql->gen()));
if (count($keys) > 0)
{
if (count($keys) > 0) {
throw new Pluf_Form_Invalid(
__('You already have uploaded this key.')
);

View File

@ -184,8 +184,7 @@ class IDF_Key extends Pluf_Model
public static function getAvailableKeyTypes()
{
$key_types = array(__("SSH") => 'ssh');
if (array_key_exists('mtn', Pluf::f('allowed_scm', array())))
{
if (array_key_exists('mtn', Pluf::f('allowed_scm', array()))) {
$key_types[__("monotone")] = 'mtn';
}
return $key_types;

View File

@ -27,7 +27,7 @@
function IDF_Migrations_16KeyType_up($params=null)
{
$table = Pluf::factory('IDF_Keys')->getSqlTable();
$table = Pluf::factory('IDF_Key')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' ADD COLUMN "type" VARCHAR(3) DEFAULT \'ssh\'';
$sql['MySQL'] = 'ALTER TABLE '.$table.' ADD COLUMN `type` VARCHAR(3) DEFAULT \'ssh\'';
@ -41,7 +41,7 @@ function IDF_Migrations_16KeyType_up($params=null)
function IDF_Migrations_16KeyType_down($params=null)
{
$table = Pluf::factory('IDF_Keys')->getSqlTable();
$table = Pluf::factory('IDF_Key')->getSqlTable();
$sql = array();
$sql['PostgreSQL'] = 'ALTER TABLE '.$table.' DROP COLUMN "type"';
$sql['MySQL'] = 'ALTER TABLE '.$table.' DROP COLUMN `type`';

View File

@ -54,8 +54,7 @@ class IDF_Scm_Monotone extends IDF_Scm
// FIXME: this obviously won't work with remote databases - upstream
// needs to implement mtn db info in automate at first
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
if (!file_exists($repo))
{
if (!file_exists($repo)) {
return 0;
}
@ -74,7 +73,7 @@ class IDF_Scm_Monotone extends IDF_Scm
{
try
{
$out = $this->stdio->exec(array("interface_version"));
$out = $this->stdio->exec(array('interface_version'));
return floatval($out) >= self::$MIN_INTERFACE_VERSION;
}
catch (IDF_Scm_Exception $e) {}
@ -92,15 +91,14 @@ class IDF_Scm_Monotone extends IDF_Scm
}
// FIXME: we could / should introduce handling of suspended
// (i.e. dead) branches here by hiding them from the user's eye...
$out = $this->stdio->exec(array("branches"));
$out = $this->stdio->exec(array('branches'));
// note: we could expand each branch with one of its head revisions
// here, but these would soon become bogus anyway and we cannot
// map multiple head revisions here either, so we just use the
// selector as placeholder
$res = array();
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b)
{
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b) {
$res["h:$b"] = $b;
}
@ -123,8 +121,7 @@ class IDF_Scm_Monotone extends IDF_Scm
$branch = "*";
}
if (count($this->_resolveSelector("h:$branch")) == 0)
{
if (count($this->_resolveSelector("h:$branch")) == 0) {
throw new IDF_Scm_Exception(
"Branch $branch is empty"
);
@ -142,7 +139,7 @@ class IDF_Scm_Monotone extends IDF_Scm
*/
private function _resolveSelector($selector)
{
$out = $this->stdio->exec(array("select", $selector));
$out = $this->stdio->exec(array('select', $selector));
return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
}
@ -157,16 +154,13 @@ class IDF_Scm_Monotone extends IDF_Scm
$pos = 0;
$stanzas = array();
while ($pos < strlen($in))
{
while ($pos < strlen($in)) {
$stanza = array();
while ($pos < strlen($in))
{
while ($pos < strlen($in)) {
if ($in[$pos] == "\n") break;
$stanzaLine = array("key" => "", "values" => array(), "hash" => null);
while ($pos < strlen($in))
{
$stanzaLine = array('key' => '', 'values' => array(), 'hash' => null);
while ($pos < strlen($in)) {
$ch = $in[$pos];
if ($ch == '"' || $ch == '[') break;
++$pos;
@ -174,8 +168,7 @@ class IDF_Scm_Monotone extends IDF_Scm
$stanzaLine['key'] .= $ch;
}
if ($in[$pos] == '[')
{
if ($in[$pos] == '[') {
++$pos; // opening square bracket
$stanzaLine['hash'] = substr($in, $pos, 40);
$pos += 40;
@ -184,12 +177,10 @@ class IDF_Scm_Monotone extends IDF_Scm
else
{
$valCount = 0;
while ($in[$pos] == '"')
{
while ($in[$pos] == '"') {
++$pos; // opening quote
$stanzaLine['values'][$valCount] = "";
while ($pos < strlen($in))
{
$stanzaLine['values'][$valCount] = '';
while ($pos < strlen($in)) {
$ch = $in[$pos]; $pr = $in[$pos-1];
if ($ch == '"' && $pr != '\\') break;
++$pos;
@ -197,15 +188,13 @@ class IDF_Scm_Monotone extends IDF_Scm
}
++$pos; // closing quote
if ($in[$pos] == ' ')
{
if ($in[$pos] == ' ') {
++$pos; // space
++$valCount;
}
}
for ($i = 0; $i <= $valCount; $i++)
{
for ($i = 0; $i <= $valCount; $i++) {
$stanzaLine['values'][$i] = str_replace(
array("\\\\", "\\\""),
array("\\", "\""),
@ -234,28 +223,22 @@ class IDF_Scm_Monotone extends IDF_Scm
{
static $certCache = array();
if (!array_key_exists($rev, $certCache))
{
$out = $this->stdio->exec(array("certs", $rev));
if (!array_key_exists($rev, $certCache)) {
$out = $this->stdio->exec(array('certs', $rev));
$stanzas = self::_parseBasicIO($out);
$certs = array();
foreach ($stanzas as $stanza)
{
foreach ($stanzas as $stanza) {
$certname = null;
foreach ($stanza as $stanzaline)
{
foreach ($stanza as $stanzaline) {
// luckily, name always comes before value
if ($stanzaline['key'] == "name")
{
if ($stanzaline['key'] == 'name') {
$certname = $stanzaline['values'][0];
continue;
}
if ($stanzaline['key'] == "value")
{
if (!array_key_exists($certname, $certs))
{
if ($stanzaline['key'] == 'value') {
if (!array_key_exists($certname, $certs)) {
$certs[$certname] = array();
}
@ -282,13 +265,11 @@ class IDF_Scm_Monotone extends IDF_Scm
private function _getUniqueCertValuesFor($revs, $certName, $prefix)
{
$certValues = array();
foreach ($revs as $rev)
{
foreach ($revs as $rev) {
$certs = $this->_getCerts($rev);
if (!array_key_exists($certName, $certs))
continue;
foreach ($certs[$certName] as $certValue)
{
foreach ($certs[$certName] as $certValue) {
$certValues[] = "$prefix$certValue";
}
}
@ -306,19 +287,16 @@ class IDF_Scm_Monotone extends IDF_Scm
private function _getLastChangeFor($file, $startrev)
{
$out = $this->stdio->exec(array(
"get_content_changed", $startrev, $file
'get_content_changed', $startrev, $file
));
$stanzas = self::_parseBasicIO($out);
// FIXME: we only care about the first returned content mark
// everything else seem to be very, very rare cases
foreach ($stanzas as $stanza)
{
foreach ($stanza as $stanzaline)
{
if ($stanzaline['key'] == "content_mark")
{
foreach ($stanzas as $stanza) {
foreach ($stanza as $stanzaline) {
if ($stanzaline['key'] == 'content_mark') {
return $stanzaline['hash'];
}
}
@ -333,7 +311,7 @@ class IDF_Scm_Monotone extends IDF_Scm
{
$revs = $this->_resolveSelector($commit);
if (count($revs) == 0) return array();
return $this->_getUniqueCertValuesFor($revs, "branch", "h:");
return $this->_getUniqueCertValuesFor($revs, 'branch', 'h:');
}
/**
@ -341,32 +319,26 @@ class IDF_Scm_Monotone extends IDF_Scm
*/
public function getTags()
{
if (isset($this->cache['tags']))
{
if (isset($this->cache['tags'])) {
return $this->cache['tags'];
}
$out = $this->stdio->exec(array("tags"));
$out = $this->stdio->exec(array('tags'));
$tags = array();
$stanzas = self::_parseBasicIO($out);
foreach ($stanzas as $stanza)
{
foreach ($stanzas as $stanza) {
$tagname = null;
foreach ($stanza as $stanzaline)
{
foreach ($stanza as $stanzaline) {
// revision comes directly after the tag stanza
if ($stanzaline['key'] == "tag")
{
if ($stanzaline['key'] == 'tag') {
$tagname = $stanzaline['values'][0];
continue;
}
if ($stanzaline['key'] == "revision")
{
if ($stanzaline['key'] == 'revision') {
// FIXME: warn if multiple revisions have
// equally named tags
if (!array_key_exists("t:$tagname", $tags))
{
if (!array_key_exists("t:$tagname", $tags)) {
$tags["t:$tagname"] = $tagname;
}
break;
@ -385,7 +357,7 @@ class IDF_Scm_Monotone extends IDF_Scm
{
$revs = $this->_resolveSelector($commit);
if (count($revs) == 0) return array();
return $this->_getUniqueCertValuesFor($revs, "tag", "t:");
return $this->_getUniqueCertValuesFor($revs, 'tag', 't:');
}
/**
@ -394,22 +366,20 @@ class IDF_Scm_Monotone extends IDF_Scm
public function getTree($commit, $folder='/', $branch=null)
{
$revs = $this->_resolveSelector($commit);
if (count($revs) == 0)
{
if (count($revs) == 0) {
return array();
}
$out = $this->stdio->exec(array(
"get_manifest_of", $revs[0]
'get_manifest_of', $revs[0]
));
$files = array();
$stanzas = self::_parseBasicIO($out);
$folder = $folder == '/' || empty($folder) ? '' : $folder.'/';
foreach ($stanzas as $stanza)
{
if ($stanza[0]['key'] == "format_version")
foreach ($stanzas as $stanza) {
if ($stanza[0]['key'] == 'format_version')
continue;
$path = $stanza[0]['values'][0];
@ -421,21 +391,19 @@ class IDF_Scm_Monotone extends IDF_Scm
$file['fullpath'] = $path;
$file['efullpath'] = self::smartEncode($path);
if ($stanza[0]['key'] == "dir")
{
$file['type'] = "tree";
if ($stanza[0]['key'] == 'dir') {
$file['type'] = 'tree';
$file['size'] = 0;
}
else
{
$file['type'] = "blob";
$file['type'] = 'blob';
$file['hash'] = $stanza[1]['hash'];
$file['size'] = strlen($this->getFile((object)$file));
}
$rev = $this->_getLastChangeFor($file['fullpath'], $revs[0]);
if ($rev !== null)
{
if ($rev !== null) {
$file['rev'] = $rev;
$certs = $this->_getCerts($rev);
@ -482,17 +450,14 @@ class IDF_Scm_Monotone extends IDF_Scm
$scm = IDF_Scm::get($project);
$branch = $scm->getMainBranch();
if (!empty($commit))
{
if (!empty($commit)) {
$revs = $scm->_resolveSelector($commit);
if (count($revs) > 0)
{
if (count($revs) > 0) {
$certs = $scm->_getCerts($revs[0]);
// for the very seldom case that a revision
// has no branch certificate
if (count($certs['branch']) == 0)
{
$branch = "*";
if (count($certs['branch']) == 0) {
$branch = '*';
}
else
{
@ -502,12 +467,11 @@ class IDF_Scm_Monotone extends IDF_Scm
}
$remote_url = Pluf::f('mtn_remote_url', '');
if (empty($remote_url))
{
if (empty($remote_url)) {
return '';
}
return sprintf($remote_url, $project->shortname)."?".$branch;
return sprintf($remote_url, $project->shortname).'?'.$branch;
}
/**
@ -527,8 +491,7 @@ class IDF_Scm_Monotone extends IDF_Scm
*/
public static function factory($project)
{
if (!array_key_exists($project->shortname, self::$instances))
{
if (!array_key_exists($project->shortname, self::$instances)) {
self::$instances[$project->shortname] =
new IDF_Scm_Monotone($project);
}
@ -558,15 +521,14 @@ class IDF_Scm_Monotone extends IDF_Scm
return false;
$out = $this->stdio->exec(array(
"get_manifest_of", $revs[0]
'get_manifest_of', $revs[0]
));
$files = array();
$stanzas = self::_parseBasicIO($out);
foreach ($stanzas as $stanza)
{
if ($stanza[0]['key'] == "format_version")
foreach ($stanzas as $stanza) {
if ($stanza[0]['key'] == 'format_version')
continue;
$path = $stanza[0]['values'][0];
@ -576,15 +538,14 @@ class IDF_Scm_Monotone extends IDF_Scm
$file = array();
$file['fullpath'] = $path;
if ($stanza[0]['key'] == "dir")
{
if ($stanza[0]['key'] == "dir") {
$file['type'] = "tree";
$file['hash'] = null;
$file['size'] = 0;
}
else
{
$file['type'] = "blob";
$file['type'] = 'blob';
$file['hash'] = $stanza[1]['hash'];
$file['size'] = strlen($this->getFile((object)$file));
}
@ -593,8 +554,7 @@ class IDF_Scm_Monotone extends IDF_Scm
$file['file'] = $pathinfo['basename'];
$rev = $this->_getLastChangeFor($file['fullpath'], $revs[0]);
if ($rev !== null)
{
if ($rev !== null) {
$file['rev'] = $rev;
$certs = $this->_getCerts($rev);
@ -619,12 +579,11 @@ class IDF_Scm_Monotone extends IDF_Scm
public function getFile($def, $cmd_only=false)
{
// this won't work with remote databases
if ($cmd_only)
{
if ($cmd_only) {
throw new Pluf_Exception_NotImplemented();
}
return $this->stdio->exec(array("get_file", $def->hash));
return $this->stdio->exec(array('get_file', $def->hash));
}
/**
@ -637,8 +596,7 @@ class IDF_Scm_Monotone extends IDF_Scm
*/
private function _getDiff($target, $source = null)
{
if (empty($source))
{
if (empty($source)) {
$source = "p:$target";
}
@ -647,20 +605,18 @@ class IDF_Scm_Monotone extends IDF_Scm
$targets = $this->_resolveSelector($target);
$sources = $this->_resolveSelector($source);
if (count($targets) == 0 || count($sources) == 0)
{
return "";
if (count($targets) == 0 || count($sources) == 0) {
return '';
}
// if target contains a root revision, we cannot produce a diff
if (empty($sources[0]))
{
return "";
if (empty($sources[0])) {
return '';
}
return $this->stdio->exec(
array("content_diff"),
array("r" => array($sources[0], $targets[0]))
array('content_diff'),
array('r' => array($sources[0], $targets[0]))
);
}
@ -676,7 +632,7 @@ class IDF_Scm_Monotone extends IDF_Scm
$certs = $this->_getCerts($revs[0]);
// FIXME: this assumes that author, date and changelog are always given
$res['author'] = implode(", ", $certs['author']);
$res['author'] = implode(', ', $certs['author']);
$dates = array();
foreach ($certs['date'] as $date)
@ -697,9 +653,8 @@ class IDF_Scm_Monotone extends IDF_Scm
*/
public function isCommitLarge($commit=null)
{
if (empty($commit))
{
$commit = "h:"+$this->getMainBranch();
if (empty($commit)) {
$commit = 'h:'.$this->getMainBranch();
}
$revs = $this->_resolveSelector($commit);
@ -707,15 +662,14 @@ class IDF_Scm_Monotone extends IDF_Scm
return false;
$out = $this->stdio->exec(array(
"get_revision", $revs[0]
'get_revision', $revs[0]
));
$newAndPatchedFiles = 0;
$stanzas = self::_parseBasicIO($out);
foreach ($stanzas as $stanza)
{
if ($stanza[0]['key'] == "patch" || $stanza[0]['key'] == "add_file")
foreach ($stanzas as $stanza) {
if ($stanza[0]['key'] == 'patch' || $stanza[0]['key'] == 'add_file')
$newAndPatchedFiles++;
}
@ -731,11 +685,9 @@ class IDF_Scm_Monotone extends IDF_Scm
$initialBranches = array();
$logs = array();
while (!empty($horizont) && $n > 0)
{
if (count($horizont) > 1)
{
$out = $this->stdio->exec(array("toposort") + $horizont);
while (!empty($horizont) && $n > 0) {
if (count($horizont) > 1) {
$out = $this->stdio->exec(array('toposort') + $horizont);
$horizont = preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
}
@ -743,14 +695,12 @@ class IDF_Scm_Monotone extends IDF_Scm
$certs = $this->_getCerts($rev);
// read in the initial branches we should follow
if (count($initialBranches) == 0)
{
if (count($initialBranches) == 0) {
$initialBranches = $certs['branch'];
}
// only add it to our log if it is on one of the initial branches
if (count(array_intersect($initialBranches, $certs['branch'])) > 0)
{
if (count(array_intersect($initialBranches, $certs['branch'])) > 0) {
--$n;
$log = array();
@ -771,7 +721,7 @@ class IDF_Scm_Monotone extends IDF_Scm
$logs[] = (object)$log;
}
$out = $this->stdio->exec(array("parents", $rev));
$out = $this->stdio->exec(array('parents', $rev));
$horizont += preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);
}

View File

@ -70,29 +70,26 @@ class IDF_Scm_Monotone_Stdio
if (is_resource($this->proc))
$this->stop();
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == "remote";
$remote_db_access = Pluf::f('mtn_db_access', 'remote') == 'remote';
$cmd = Pluf::f('idf_exec_cmd_prefix', '') .
Pluf::f('mtn_path', 'mtn') . ' ';
$opts = Pluf::f('mtn_opts', array());
foreach ($opts as $opt)
{
foreach ($opts as $opt) {
$cmd .= sprintf('%s ', escapeshellarg($opt));
}
// FIXME: we might want to add an option for anonymous / no key
// access, but upstream bug #30237 prevents that for now
if ($remote_db_access)
{
if ($remote_db_access) {
$host = sprintf(Pluf::f('mtn_remote_url'), $this->project->shortname);
$cmd .= sprintf('automate remote_stdio %s', escapeshellarg($host));
}
else
{
$repo = sprintf(Pluf::f('mtn_repositories'), $this->project->shortname);
if (!file_exists($repo))
{
if (!file_exists($repo)) {
throw new IDF_Scm_Exception(
"repository file '$repo' does not exist"
);
@ -101,19 +98,18 @@ class IDF_Scm_Monotone_Stdio
}
$descriptors = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w"),
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'),
);
$env = array("LANG" => "en_US.UTF-8");
$env = array('LANG' => 'en_US.UTF-8');
$this->proc = proc_open($cmd, $descriptors, $this->pipes,
null, $env);
if (!is_resource($this->proc))
{
throw new IDF_Scm_Exception("could not start stdio process");
if (!is_resource($this->proc)) {
throw new IDF_Scm_Exception('could not start stdio process');
}
$this->_checkVersion();
@ -154,15 +150,13 @@ class IDF_Scm_Monotone_Stdio
$read, $write = null, $except = null, 0, 20000
);
if ($streamsChanged === false)
{
if ($streamsChanged === false) {
throw new IDF_Scm_Exception(
"Could not select() on read pipe"
'Could not select() on read pipe'
);
}
if ($streamsChanged == 0)
{
if ($streamsChanged == 0) {
return false;
}
@ -179,8 +173,7 @@ class IDF_Scm_Monotone_Stdio
$this->_waitForReadyRead();
$version = fgets($this->pipes[1]);
if ($version === false)
{
if ($version === false) {
throw new IDF_Scm_Exception(
"Could not determine stdio version, stderr is:\n".
$this->_readStderr()
@ -191,8 +184,8 @@ class IDF_Scm_Monotone_Stdio
$m[1] != self::$SUPPORTED_STDIO_VERSION)
{
throw new IDF_Scm_Exception(
"stdio format version mismatch, expected '".
self::$SUPPORTED_STDIO_VERSION."', got '".@$m[1]."'"
'stdio format version mismatch, expected "'.
self::$SUPPORTED_STDIO_VERSION.'", got "'.@$m[1].'"'
);
}
@ -208,33 +201,28 @@ class IDF_Scm_Monotone_Stdio
*/
private function _write(array $args, array $options = array())
{
$cmd = "";
if (count($options) > 0)
{
$cmd = "o";
foreach ($options as $k => $vals)
{
$cmd = '';
if (count($options) > 0) {
$cmd = 'o';
foreach ($options as $k => $vals) {
if (!is_array($vals))
$vals = array($vals);
foreach ($vals as $v)
{
$cmd .= strlen((string)$k) . ":" . (string)$k;
$cmd .= strlen((string)$v) . ":" . (string)$v;
foreach ($vals as $v) {
$cmd .= strlen((string)$k) . ':' . (string)$k;
$cmd .= strlen((string)$v) . ':' . (string)$v;
}
}
$cmd .= "e ";
$cmd .= 'e ';
}
$cmd .= "l";
foreach ($args as $arg)
{
$cmd .= strlen((string)$arg) . ":" . (string)$arg;
$cmd .= 'l';
foreach ($args as $arg) {
$cmd .= strlen((string)$arg) . ':' . (string)$arg;
}
$cmd .= "e\n";
if (!fwrite($this->pipes[0], $cmd))
{
if (!fwrite($this->pipes[0], $cmd)) {
throw new IDF_Scm_Exception("could not write '$cmd' to process");
}
@ -250,11 +238,10 @@ class IDF_Scm_Monotone_Stdio
private function _readStderr()
{
$err = "";
while (($line = fgets($this->pipes[2])) !== false)
{
while (($line = fgets($this->pipes[2])) !== false) {
$err .= $line;
}
return empty($err) ? "<empty>" : $err;
return empty($err) ? '<empty>' : $err;
}
/**
@ -273,26 +260,22 @@ class IDF_Scm_Monotone_Stdio
$output = "";
$errcode = 0;
while (true)
{
while (true) {
if (!$this->_waitForReadyRead())
continue;
$data = array(0,"",0);
$idx = 0;
while (true)
{
while (true) {
$c = fgetc($this->pipes[1]);
if ($c === false)
{
if ($c === false) {
throw new IDF_Scm_Exception(
"No data on stdin, stderr is:\n".
$this->_readStderr()
);
}
if ($c == ':')
{
if ($c == ':') {
if ($idx == 2)
break;
@ -307,24 +290,21 @@ class IDF_Scm_Monotone_Stdio
}
// sanity
if ($this->cmdnum != $data[0])
{
if ($this->cmdnum != $data[0]) {
throw new IDF_Scm_Exception(
"command numbers out of sync; ".
"expected {$this->cmdnum}, got {$data[0]}"
'command numbers out of sync; expected '.
$this->cmdnum .', got '. $data[0]
);
}
$toRead = $data[2];
$buffer = "";
while ($toRead > 0)
{
while ($toRead > 0) {
$buffer .= fread($this->pipes[1], $toRead);
$toRead = $data[2] - strlen($buffer);
}
switch ($data[1])
{
switch ($data[1]) {
case 'w':
case 'p':
case 't':
@ -340,11 +320,10 @@ class IDF_Scm_Monotone_Stdio
}
}
if ($errcode != 0)
{
if ($errcode != 0) {
throw new IDF_Scm_Exception(
"command '{$this->lastcmd}' returned error code $errcode: ".
implode(" ", $this->oob['e'])
implode(' ', $this->oob['e'])
);
}

View File

@ -42,11 +42,11 @@ class IDF_Scm_Monotone_Usher
*/
public static function getServerList($state = null)
{
$conn = self::_triggerCommand("LIST $state");
if ($conn == "none")
$conn = self::_triggerCommand('LIST '.$state);
if ($conn == 'none')
return array();
return preg_split("/[ ]/", $conn);
return preg_split('/[ ]/', $conn);
}
/**
@ -67,19 +67,18 @@ class IDF_Scm_Monotone_Usher
*/
public static function getConnectionList($server = null)
{
$conn = self::_triggerCommand("LISTCONNECTIONS $server");
if ($conn == "none")
$conn = self::_triggerCommand('LISTCONNECTIONS '.$server);
if ($conn == 'none')
return array();
$single_conns = preg_split("/[ ]/", $conn);
$single_conns = preg_split('/[ ]/', $conn);
$ret = array();
foreach ($single_conns as $conn)
{
preg_match("/\(\w+\)([^:]):(\d+)/", $conn, $matches);
foreach ($single_conns as $conn) {
preg_match('/\(\w+\)([^:]):(\d+)/', $conn, $matches);
$ret[$matches[1]][] = (object)array(
"server" => $matches[1],
"address" => $matches[2],
"port" => $matches[3],
'server' => $matches[1],
'address' => $matches[2],
'port' => $matches[3],
);
}
@ -96,7 +95,7 @@ class IDF_Scm_Monotone_Usher
*/
public static function getStatus($server = null)
{
return self::_triggerCommand("STATUS $server");
return self::_triggerCommand('STATUS '.$server);
}
/**
@ -110,11 +109,11 @@ class IDF_Scm_Monotone_Usher
*/
public static function matchServer($host, $pattern)
{
$ret = self::_triggerCommand("MATCH $host $pattern");
if (preg_match("/^OK: (.+)/", $ret, $m))
$ret = self::_triggerCommand('MATCH '.$host.' '.$pattern);
if (preg_match('/^OK: (.+)/', $ret, $m))
return $m[1];
preg_match("/^ERROR: (.+)/", $ret, $m);
throw new IDF_Scm_Exception("could not match server: ".$m[1]);
preg_match('/^ERROR: (.+)/', $ret, $m);
throw new IDF_Scm_Exception('could not match server: '.$m[1]);
}
/**
@ -143,7 +142,7 @@ class IDF_Scm_Monotone_Usher
*/
public static function startServer($server)
{
return self::_triggerCommand("START $server");
return self::_triggerCommand('START '.$server);
}
/**
@ -156,7 +155,7 @@ class IDF_Scm_Monotone_Usher
*/
public static function killServer($server)
{
return self::_triggerCommand("KILL_NOW $server") == "ok";
return self::_triggerCommand('KILL_NOW '.$server) == 'ok';
}
/**
@ -166,7 +165,7 @@ class IDF_Scm_Monotone_Usher
*/
public static function shutDown()
{
return self::_triggerCommand("SHUTDOWN") == "ok";
return self::_triggerCommand('SHUTDOWN') == 'ok';
}
/**
@ -176,7 +175,7 @@ class IDF_Scm_Monotone_Usher
*/
public static function startUp()
{
return self::_triggerCommand("STARTUP") == "ok";
return self::_triggerCommand('STARTUP') == 'ok';
}
/**
@ -186,60 +185,53 @@ class IDF_Scm_Monotone_Usher
*/
public static function reload()
{
return self::_triggerCommand("RELOAD") == "ok";
return self::_triggerCommand('RELOAD') == 'ok';
}
private static function _triggerCommand($cmd)
{
$uc = Pluf::f('mtn_usher');
if (empty($uc['host']))
{
throw new IDF_Scm_Exception("usher host is empty");
if (empty($uc['host'])) {
throw new IDF_Scm_Exception('usher host is empty');
}
if (!preg_match('/^\d+$/', $uc['port']) ||
$uc['port'] == 0)
{
throw new IDF_Scm_Exception("usher port is invalid");
throw new IDF_Scm_Exception('usher port is invalid');
}
if (empty($uc['user']))
{
throw new IDF_Scm_Exception("usher user is empty");
if (empty($uc['user'])) {
throw new IDF_Scm_Exception('usher user is empty');
}
if (empty($uc['pass']))
{
throw new IDF_Scm_Exception("usher pass is empty");
if (empty($uc['pass'])) {
throw new IDF_Scm_Exception('usher pass is empty');
}
$sock = @fsockopen($uc['host'], $uc['port'], $errno, $errstr);
if (!$sock)
{
if (!$sock) {
throw new IDF_Scm_Exception(
"could not connect to usher: $errstr ($errno)"
);
}
fwrite($sock, "USERPASS {$uc['user']} {$uc['pass']}\n");
if (feof($sock))
{
fwrite($sock, 'USERPASS '.$uc['user'].' '.$uc['pass'].'\n');
if (feof($sock)) {
throw new IDF_Scm_Exception(
"usher closed the connection - probably wrong admin ".
"username or password"
'usher closed the connection - probably wrong admin '.
'username or password'
);
}
fwrite($sock, "$cmd\n");
$out = "";
while (!feof($sock))
{
fwrite($sock, $cmd.'\n');
$out = '';
while (!feof($sock)) {
$out .= fgets($sock);
}
fclose($sock);
$out = rtrim($out);
if ($out == "unknown command")
{
if ($out == 'unknown command') {
throw new IDF_Scm_Exception("unknown command: $cmd");
}

View File

@ -55,13 +55,11 @@ class IDF_Tests_TestMonotone extends UnitTestCase
$pipes,
$dir);
if (!is_resource($process))
{
if (!is_resource($process)) {
throw new Exception("could not create process");
}
if (!empty($stdin))
{
if (!empty($stdin)) {
fwrite($pipes[0], $stdin);
fclose($pipes[0]);
}
@ -70,8 +68,7 @@ class IDF_Tests_TestMonotone extends UnitTestCase
fclose($pipes[1]);
$ret = proc_close($process);
if ($ret != 0)
{
if ($ret != 0) {
throw new Exception(
"call ended with a non-zero error code (complete cmdline was: ".
implode(" ", $cmdline).")"
@ -102,12 +99,9 @@ class IDF_Tests_TestMonotone extends UnitTestCase
if (is_dir($dirname))
$dir_handle=opendir($dirname);
while ($file = readdir($dir_handle))
{
if ($file!="." && $file!="..")
{
if (!is_dir($dirname."/".$file))
{
while ($file = readdir($dir_handle)) {
if ($file!="." && $file!="..") {
if (!is_dir($dirname."/".$file)) {
unlink ($dirname."/".$file);
continue;
}
@ -123,8 +117,7 @@ class IDF_Tests_TestMonotone extends UnitTestCase
public function setUp()
{
if (is_dir($this->tmpdir))
{
if (is_dir($this->tmpdir)) {
self::deleteRecursive($this->tmpdir);
}

View File

@ -327,8 +327,7 @@ class IDF_Views_Admin
{
$title = __('Usher management');
$servers = array();
foreach (IDF_Scm_Monotone_Usher::getServerList() as $server)
{
foreach (IDF_Scm_Monotone_Usher::getServerList() as $server) {
$servers[] = (object)array(
"name" => $server,
"status" => IDF_Scm_Monotone_Usher::getStatus($server),
@ -355,21 +354,17 @@ class IDF_Views_Admin
$title = __('Usher control');
$action = $match[1];
if (!empty($action))
{
if (!in_array($action, array("reload", "shutdown", "startup")))
{
if (!empty($action)) {
if (!in_array($action, array('reload', 'shutdown', 'startup'))) {
throw new Pluf_HTTP_Error404();
}
$msg = null;
if ($action == "reload")
{
if ($action == 'reload') {
IDF_Scm_Monotone_Usher::reload();
$msg = __('Usher configuration has been reloaded');
}
else if ($action == "shutdown")
{
else if ($action == 'shutdown') {
IDF_Scm_Monotone_Usher::shutDown();
$msg = __('Usher has been shut down');
}
@ -402,25 +397,21 @@ class IDF_Views_Admin
public function usherServerControl($request, $match)
{
$server = $match[1];
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList()))
{
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
throw new Pluf_HTTP_Error404();
}
$action = $match[2];
if (!in_array($action, array("start", "stop", "kill")))
{
if (!in_array($action, array('start', 'stop', 'kill'))) {
throw new Pluf_HTTP_Error404();
}
$msg = null;
if ($action == "start")
{
if ($action == 'start') {
IDF_Scm_Monotone_Usher::startServer($server);
$msg = sprintf(__('The server "%s" has been started'), $server);
}
else if ($action == "stop")
{
else if ($action == 'stop') {
IDF_Scm_Monotone_Usher::stopServer($server);
$msg = sprintf(__('The server "%s" has been stopped'), $server);
}
@ -443,16 +434,14 @@ class IDF_Views_Admin
public function usherServerConnections($request, $match)
{
$server = $match[1];
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList()))
{
if (!in_array($server, IDF_Scm_Monotone_Usher::getServerList())) {
throw new Pluf_HTTP_Error404();
}
$title = sprintf(__('Open connections for "%s"'), $server);
$connections = IDF_Scm_Monotone_Usher::getConnectionList($server);
if (count($connections) == 0)
{
if (count($connections) == 0) {
$request->user->setMessage(sprintf(
__('no connections for server "%s"'), $server
));