* Monotone utils.␊ |
*␊ |
*/␊ |
␊ |
class IDF_Scm_Monotone_Stdio␊ |
{␊ |
public static $SUPPORTED_STDIO_VERSION = 2;␊ |
␊ |
private $repo;␊ |
private $proc;␊ |
private $pipes;␊ |
private $oob;␊ |
private $cmdnum;␊ |
private $lastcmd;␊ |
␊ |
public function __construct($repo)␊ |
{␊ |
$this->repo = $repo;␊ |
$this->start();␊ |
}␊ |
␊ |
public function __destruct()␊ |
{␊ |
$this->stop();␊ |
}␊ |
␊ |
public function start()␊ |
{␊ |
if (is_resource($this->proc))␊ |
$this->stop();␊ |
␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate stdio --no-workspace --norc",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo));␊ |
␊ |
$descriptors = array(␊ |
0 => array("pipe", "r"),␊ |
1 => array("pipe", "w"),␊ |
2 => array("pipe", "r")␊ |
);␊ |
␊ |
$this->proc = proc_open($cmd, $descriptors, $this->pipes);␊ |
␊ |
if (!is_resource($this->proc))␊ |
{␊ |
throw new IDF_Scm_Exception("could not start stdio process");␊ |
}␊ |
␊ |
$this->_checkVersion();␊ |
␊ |
$this->cmdnum = -1;␊ |
}␊ |
␊ |
public function stop()␊ |
{␊ |
if (!is_resource($this->proc))␊ |
return;␊ |
␊ |
fclose($this->pipes[0]);␊ |
fclose($this->pipes[1]);␊ |
fclose($this->pipes[2]);␊ |
␊ |
proc_close($this->proc);␊ |
$this->proc = null;␊ |
}␊ |
␊ |
private function _checkVersion()␊ |
{␊ |
$version = fgets($this->pipes[1]);␊ |
if (!preg_match('/^format-version: (\d+)$/', $version, $m) ||␊ |
$m[1] != self::$SUPPORTED_STDIO_VERSION)␊ |
{␊ |
throw new IDF_Scm_Exception(␊ |
"stdio format version mismatch, expected '".␊ |
self::$SUPPORTED_STDIO_VERSION."', got '".@$m[1]."'"␊ |
);␊ |
}␊ |
fgets($this->pipes[1]);␊ |
}␊ |
␊ |
private function _write($args, $options = array())␊ |
{␊ |
$cmd = "";␊ |
if (count($options) > 0)␊ |
{␊ |
$cmd = "o";␊ |
foreach ($options as $k => $v)␊ |
{␊ |
$cmd .= strlen((string)$k) . ":" . (string)$k;␊ |
$cmd .= strlen((string)$v) . ":" . (string)$v;␊ |
}␊ |
$cmd .= "e ";␊ |
}␊ |
␊ |
$cmd .= "l";␊ |
foreach ($args as $arg)␊ |
{␊ |
$cmd .= strlen((string)$arg) . ":" . (string)$arg;␊ |
}␊ |
$cmd .= "e\n";␊ |
␊ |
if (!fwrite($this->pipes[0], $cmd))␊ |
{␊ |
throw new IDF_Scm_Exception("could not write '$cmd' to process");␊ |
}␊ |
␊ |
$this->lastcmd = $cmd;␊ |
$this->cmdnum++;␊ |
}␊ |
␊ |
private function _read()␊ |
{␊ |
$this->oob = array('w' => array(),␊ |
'p' => array(),␊ |
't' => array(),␊ |
'e' => array());␊ |
␊ |
$output = "";␊ |
$errcode = 0;␊ |
␊ |
while (true)␊ |
{␊ |
$read = array($this->pipes[1]);␊ |
$write = null;␊ |
$except = null;␊ |
␊ |
$streamsChanged = stream_select(␊ |
$read, $write, $except, 0, 20000␊ |
);␊ |
␊ |
if ($streamsChanged === false)␊ |
{␊ |
throw new IDF_Scm_Exception(␊ |
"Could not select() on read pipe"␊ |
);␊ |
}␊ |
␊ |
if ($streamsChanged == 0)␊ |
{␊ |
continue;␊ |
}␊ |
␊ |
$data = array(0,"",0);␊ |
$idx = 0;␊ |
while (true)␊ |
{␊ |
$c = fgetc($this->pipes[1]);␊ |
if ($c == ':')␊ |
{␊ |
if ($idx == 2)␊ |
break;␊ |
␊ |
++$idx;␊ |
continue;␊ |
}␊ |
␊ |
if (is_numeric($c))␊ |
$data[$idx] = $data[$idx] * 10 + $c;␊ |
else␊ |
$data[$idx] .= $c;␊ |
}␊ |
␊ |
// sanity␊ |
if ($this->cmdnum != $data[0])␊ |
{␊ |
throw new IDF_Scm_Exception(␊ |
"command numbers out of sync; ".␊ |
"expected {$this->cmdnum}, got {$data[0]}"␊ |
);␊ |
}␊ |
␊ |
$toRead = $data[2];␊ |
$buffer = "";␊ |
while ($toRead > 0)␊ |
{␊ |
$buffer .= fread($this->pipes[1], $toRead);␊ |
$toRead = $data[2] - strlen($buffer);␊ |
}␊ |
␊ |
switch ($data[1])␊ |
{␊ |
case 'w':␊ |
case 'p':␊ |
case 't':␊ |
case 'e':␊ |
$this->oob[$data[1]][] = $buffer;␊ |
continue;␊ |
case 'm':␊ |
$output .= $buffer;␊ |
continue;␊ |
case 'l':␊ |
$errcode = $buffer;␊ |
break 2;␊ |
}␊ |
}␊ |
␊ |
if ($errcode != 0)␊ |
{␊ |
throw new IDF_Scm_Exception(␊ |
"command '{$this->lastcmd}' returned error code $errcode: ".␊ |
implode(" ", $this->oob['e'])␊ |
);␊ |
}␊ |
␊ |
return $output;␊ |
}␊ |
␊ |
public function exec($args, $options = array())␊ |
{␊ |
$this->_write($args, $options);␊ |
return $this->_read();␊ |
}␊ |
␊ |
public function getLastWarnings()␊ |
{␊ |
return array_key_exists('w', $this->oob) ?␊ |
$this->oob['w'] : array();␊ |
}␊ |
␊ |
public function getLastProgress()␊ |
{␊ |
return array_key_exists('p', $this->oob) ?␊ |
$this->oob['p'] : array();␊ |
}␊ |
␊ |
public function getLastTickers()␊ |
{␊ |
return array_key_exists('t', $this->oob) ?␊ |
$this->oob['t'] : array();␊ |
}␊ |
␊ |
public function getLastErrors()␊ |
{␊ |
return array_key_exists('e', $this->oob) ?␊ |
$this->oob['e'] : array();␊ |
}␊ |
}␊ |
␊ |
class IDF_Scm_Monotone extends IDF_Scm␊ |
{␊ |
public static $MIN_INTERFACE_VERSION = 12.0;␊ |
␊ |
private $stdio;␊ |
␊ |
/* ============================================== *␊ |
* *␊ |
* Common Methods Implemented By All The SCMs *␊ |
|
{␊ |
$this->repo = $repo;␊ |
$this->project = $project;␊ |
$this->stdio = new IDF_Scm_Monotone_Stdio($repo);␊ |
}␊ |
␊ |
public function getRepositorySize()␊ |
|
␊ |
public function isAvailable()␊ |
{␊ |
$out = array();␊ |
try {␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate interface_version",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo));␊ |
self::exec('IDF_Scm_Monotone::isAvailable',␊ |
$cmd, $out, $return);␊ |
} catch (IDF_Scm_Exception $e) {␊ |
return false;␊ |
try␊ |
{␊ |
$out = $this->stdio->exec(array("interface_version"));␊ |
return floatval($out) >= self::$MIN_INTERFACE_VERSION;␊ |
}␊ |
catch (IDF_Scm_Exception $e) {}␊ |
␊ |
return count($out) > 0 && floatval($out[0]) >= self::$MIN_INTERFACE_VERSION;␊ |
return false;␊ |
}␊ |
␊ |
public function getBranches()␊ |
|
return $this->cache['branches'];␊ |
}␊ |
// FIXME: introduce handling of suspended branches␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate branches",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo));␊ |
self::exec('IDF_Scm_Monotone::getBranches',␊ |
$cmd, $out, $return);␊ |
if ($return != 0) {␊ |
throw new IDF_Scm_Exception(sprintf($this->error_tpl,␊ |
$cmd, $return,␊ |
implode("\n", $out)));␊ |
}␊ |
$res = array();␊ |
$out = $this->stdio->exec(array("branches"));␊ |
␊ |
// FIXME: 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␊ |
foreach ($out as $b) {␊ |
$res = array();␊ |
foreach (preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY) as $b)␊ |
{␊ |
$res["h:$b"] = $b;␊ |
}␊ |
␊ |
$this->cache['branches'] = $res;␊ |
return $res;␊ |
}␊ |
|
*/␊ |
private function _resolveSelector($selector)␊ |
{␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate select %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($selector));␊ |
self::exec('IDF_Scm_Monotone::_resolveSelector',␊ |
$cmd, $out, $return);␊ |
return $out;␊ |
$out = $this->stdio->exec(array("select", $selector));␊ |
return preg_split("/\n/", $out, -1, PREG_SPLIT_NO_EMPTY);␊ |
}␊ |
␊ |
/**␊ |
|
*/␊ |
private static function _parseBasicIO($in)␊ |
{␊ |
if (substr($in, -1) != "\n")␊ |
$in .= "\n";␊ |
␊ |
$pos = 0;␊ |
$stanzas = array();␊ |
␊ |
|
␊ |
if (!array_key_exists($rev, $certCache))␊ |
{␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate certs %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($rev));␊ |
self::exec('IDF_Scm_Monotone::_getCerts',␊ |
$cmd, $out, $return);␊ |
$out = $this->stdio->exec(array("certs", $rev));␊ |
␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
$certs = array();␊ |
foreach ($stanzas as $stanza)␊ |
{␊ |
|
␊ |
private function _getLastChangeFor($file, $startrev)␊ |
{␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate get_content_changed %s %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($startrev),␊ |
escapeshellarg($file));␊ |
self::exec('IDF_Scm_Monotone::_getLastChangeFor',␊ |
$cmd, $out, $return);␊ |
$out = $this->stdio->exec(array(␊ |
"get_content_changed", $startrev, $file␊ |
));␊ |
␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
␊ |
// FIXME: we only care about the first returned content mark␊ |
// everything else seem to be very rare cases␊ |
|
**/␊ |
public function getTags()␊ |
{␊ |
if (isset($this->cache['tags'])) {␊ |
if (isset($this->cache['tags']))␊ |
{␊ |
return $this->cache['tags'];␊ |
}␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate tags",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo));␊ |
self::exec('IDF_Scm_Monotone::getTags', $cmd, $out, $return);␊ |
␊ |
$out = $this->stdio->exec(array("tags"));␊ |
␊ |
$tags = array();␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
foreach ($stanzas as $stanza)␊ |
{␊ |
$tagname = null;␊ |
|
return array();␊ |
}␊ |
␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate get_manifest_of %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($revs[0]));␊ |
self::exec('IDF_Scm_Monotone::getTree', $cmd, $out, $return);␊ |
$out = $this->stdio->exec(array(␊ |
"get_manifest_of", $revs[0]␊ |
));␊ |
␊ |
$files = array();␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
$folder = $folder == '/' || empty($folder) ? '' : $folder.'/';␊ |
␊ |
foreach ($stanzas as $stanza)␊ |
|
if (count($revs) == 0)␊ |
return false;␊ |
␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate get_manifest_of %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($revs[0]));␊ |
self::exec('IDF_Scm_Monotone::getPathInfo', $cmd, $out, $return);␊ |
$out = $this->stdio->exec(array(␊ |
"get_manifest_of", $revs[0]␊ |
));␊ |
␊ |
$files = array();␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
␊ |
foreach ($stanzas as $stanza)␊ |
{␊ |
|
␊ |
public function getFile($def, $cmd_only=false)␊ |
{␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate get_file %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($def->hash));␊ |
return ($cmd_only)␊ |
? $cmd : self::shell_exec('IDF_Scm_Monotone::getFile', $cmd);␊ |
// this won't work with remote databases␊ |
if ($cmd_only)␊ |
{␊ |
throw new Pluf_Exception_NotImplemented();␊ |
}␊ |
␊ |
return $this->stdio->exec(array("get_file", $def->hash));␊ |
}␊ |
␊ |
private function _getDiff($target, $source = null)␊ |
|
return "";␊ |
}␊ |
␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate content_diff -r %s -r %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($sources[0]),␊ |
escapeshellarg($targets[0]));␊ |
self::exec('IDF_Scm_Monotone::_getDiff',␊ |
$cmd, $out, $return);␊ |
␊ |
return implode("\n", $out);␊ |
return $this->stdio->exec(␊ |
array("content_diff"),␊ |
array("r" => $sources[0], "r" => $targets[0])␊ |
);␊ |
}␊ |
␊ |
/**␊ |
|
if (count($revs) == 0)␊ |
return false;␊ |
␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '')␊ |
.sprintf("%s -d %s automate get_revision %s",␊ |
Pluf::f('mtn_path', 'mtn'),␊ |
escapeshellarg($this->repo),␊ |
escapeshellarg($revs[0]));␊ |
self::exec('IDF_Scm_Monotone::isCommitLarge',␊ |
$cmd, $out, $return);␊ |
$out = $this->stdio->exec(array(␊ |
"get_revision", $revs[0]␊ |
));␊ |
␊ |
$newAndPatchedFiles = 0;␊ |
$stanzas = self::_parseBasicIO(implode("\n", $out));␊ |
$stanzas = self::_parseBasicIO($out);␊ |
␊ |
foreach ($stanzas as $stanza)␊ |
{␊ |
|
* @param int Number of changes (10).␊ |
* @return array Changes.␊ |
*/␊ |
public function getChangeLog($commit='HEAD', $n=10)␊ |
{␊ |
if ($n === null) $n = '';␊ |
else $n = ' -'.$n;␊ |
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' log%s --date=iso --pretty=format:\'%s\' %s',␊ |
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,␊ |
escapeshellarg($commit));␊ |
$out = array();␊ |
$cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ |
self::exec('IDF_Scm_Monotone::getChangeLog', $cmd, $out);␊ |
return self::parseLog($out);␊ |
public function getChangeLog($commit=null, $n=10)␊ |
{␊ |
throw new Pluf_Exception_NotImplemented();␊ |
}␊ |
}␊ |