| # ***** END LICENSE BLOCK ***** */␊ | 
| ␊ | 
| /**␊ | 
| * Monotone utils.␊ | 
| * Monotone stdio class␊ | 
| *␊ | 
| * Connects to a monotone process and executes commands via its␊ | 
| * stdio interface␊ | 
| *␊ | 
| * @author Thomas Keller <me@thomaskeller.biz>␊ | 
| */␊ | 
| ␊ | 
| class IDF_Scm_Monotone_Stdio␊ | 
| {␊ | 
| /** this is the most recent STDIO version. The number is output␊ | 
| at the protocol start. Older versions of monotone (prior 0.47)␊ | 
| do not output it and are therefor incompatible */␊ | 
| public static $SUPPORTED_STDIO_VERSION = 2;␊ | 
| ␊ | 
| private $repo;␊ | 
|  | 
| private $cmdnum;␊ | 
| private $lastcmd;␊ | 
| ␊ | 
| /**␊ | 
| * Constructor - starts the stdio process␊ | 
| *␊ | 
| * @param string Repository path␊ | 
| */␊ | 
| public function __construct($repo)␊ | 
| {␊ | 
| $this->repo = $repo;␊ | 
| $this->start();␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Destructor - stops the stdio process␊ | 
| */␊ | 
| public function __destruct()␊ | 
| {␊ | 
| $this->stop();␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Starts the stdio process and resets the command counter␊ | 
| */␊ | 
| public function start()␊ | 
| {␊ | 
| if (is_resource($this->proc))␊ | 
|  | 
| $this->cmdnum = -1;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Stops the stdio process and closes all pipes␊ | 
| */␊ | 
| public function stop()␊ | 
| {␊ | 
| if (!is_resource($this->proc))␊ | 
|  | 
| $this->proc = null;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * select()'s on stdout and returns true as soon as we got new␊ | 
| * data to read, false if the select() timed out␊ | 
| *␊ | 
| * @return boolean␊ | 
| * @throws IDF_Scm_Exception␊ | 
| */␊ | 
| private function _waitForReadyRead()␊ | 
| {␊ | 
| if (!is_resource($this->pipes[1]))␊ | 
|  | 
| return true;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Checks the version of the used stdio protocol␊ | 
| *␊ | 
| * @throws IDF_Scm_Exception␊ | 
| */␊ | 
| private function _checkVersion()␊ | 
| {␊ | 
| $this->_waitForReadyRead();␊ | 
|  | 
| fgets($this->pipes[1]);␊ | 
| }␊ | 
| ␊ | 
| private function _write($args, $options = array())␊ | 
| /**␊ | 
| * Writes a command to stdio␊ | 
| *␊ | 
| * @param array␊ | 
| * @param array␊ | 
| * @throws IDF_Scm_Exception␊ | 
| */␊ | 
| private function _write(array $args, array $options = array())␊ | 
| {␊ | 
| $cmd = "";␊ | 
| if (count($options) > 0)␊ | 
|  | 
| $this->cmdnum++;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Reads the last output from the stdio process, parses and returns it␊ | 
| *␊ | 
| * @return string␊ | 
| * @throws IDF_Scm_Exception␊ | 
| */␊ | 
| private function _read()␊ | 
| {␊ | 
| $this->oob = array('w' => array(),␊ | 
|  | 
| return $output;␊ | 
| }␊ | 
| ␊ | 
| public function exec($args, $options = array())␊ | 
| /**␊ | 
| * Executes a command over stdio and returns its result␊ | 
| *␊ | 
| * @param array Array of arguments␊ | 
| * @param array Array of options as key-value pairs. Multiple options␊ | 
| *              can be defined in sub-arrays, like␊ | 
| *              "r" => array("123...", "456...")␊ | 
| * @return string␊ | 
| */␊ | 
| public function exec(array $args, array $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()␊ | 
| /**␊ | 
| * Returns the last out-of-band output for a previously executed␊ | 
| * command as associative array with 'e' (error), 'w' (warning),␊ | 
| * 'p' (progress) and 't' (ticker, unparsed) as keys␊ | 
| *␊ | 
| * @return array␊ | 
| */␊ | 
| public function getLastOutOfBandOutput()␊ | 
| {␊ | 
| return array_key_exists('e', $this->oob) ?␊ | 
| $this->oob['e'] : array();␊ | 
| return $this->oob;␊ | 
| }␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Monotone scm class␊ | 
| *␊ | 
| * @author Thomas Keller <me@thomaskeller.biz>␊ | 
| */␊ | 
| class IDF_Scm_Monotone extends IDF_Scm␊ | 
| {␊ | 
| /** the minimum supported interface version */␊ | 
| public static $MIN_INTERFACE_VERSION = 12.0;␊ | 
| ␊ | 
| private $stdio;␊ | 
| ␊ | 
| /* ============================================== *␊ | 
| *                                                *␊ | 
| *   Common Methods Implemented By All The SCMs   *␊ | 
| *                                                *␊ | 
| * ============================================== */␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::__construct()␊ | 
| */␊ | 
| public function __construct($repo, $project=null)␊ | 
| {␊ | 
| $this->repo = $repo;␊ | 
|  | 
| $this->stdio = new IDF_Scm_Monotone_Stdio($repo);␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getRepositorySize()␊ | 
| */␊ | 
| public function getRepositorySize()␊ | 
| {␊ | 
| if (!file_exists($this->repo)) {␊ | 
| return 0;␊ | 
| }␊ | 
| ␊ | 
| // FIXME: this won't work with remote databases - upstream␊ | 
| // needs to implement mtn db info in automate at first␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').'du -sk '␊ | 
| .escapeshellarg($this->repo);␊ | 
| $out = explode(' ',␊ | 
|  | 
| return (int) $out[0]*1024;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::isAvailable()␊ | 
| */␊ | 
| public function isAvailable()␊ | 
| {␊ | 
| try␊ | 
|  | 
| return false;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getBranches()␊ | 
| */␊ | 
| public function getBranches()␊ | 
| {␊ | 
| if (isset($this->cache['branches'])) {␊ | 
| return $this->cache['branches'];␊ | 
| }␊ | 
| // FIXME: introduce handling of suspended branches␊ | 
| // 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"));␊ | 
| ␊ | 
| // FIXME: we could expand each branch with one of its head revisions␊ | 
| // 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␊ | 
|  | 
| ␊ | 
| /**␊ | 
| * monotone has no concept of a "main" branch, so just return␊ | 
| * the first one (the branch list is already sorted)␊ | 
| * the confiured one␊ | 
| *␊ | 
| * @return string␊ | 
| * @see IDF_Scm::getMainBranch()␊ | 
| */␊ | 
| public function getMainBranch()␊ | 
| {␊ | 
| $branches = $this->getBranches();␊ | 
| return key($branches);␊ | 
| $conf = $this->project->getConf();␊ | 
| if (false === ($branch = $conf->getVal('mtn_master_branch', false))␊ | 
| || empty($branch)) {␊ | 
| $branch = "*";␊ | 
| }␊ | 
| return $branch;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
|  | 
| return $stanzas;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Queries the certs for a given revision and returns them in an␊ | 
| * associative array array("branch" => array("branch1", ...), ...)␊ | 
| *␊ | 
| * @param string␊ | 
| * @param array␊ | 
| */␊ | 
| private function _getCerts($rev)␊ | 
| {␊ | 
| static $certCache = array();␊ | 
|  | 
| return $certCache[$rev];␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Returns unique certificate values for the given revs and the specific␊ | 
| * cert name␊ | 
| *␊ | 
| * @param array␊ | 
| * @param string␊ | 
| * @return array␊ | 
| */␊ | 
| private function _getUniqueCertValuesFor($revs, $certName)␊ | 
| {␊ | 
| $certValues = array();␊ | 
|  | 
| return array_unique($certValues);␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Returns the revision in which the file has been last changed,␊ | 
| * starting from the start rev␊ | 
| *␊ | 
| * @param string␊ | 
| * @param string␊ | 
| * @return string␊ | 
| */␊ | 
| private function _getLastChangeFor($file, $startrev)␊ | 
| {␊ | 
| $out = $this->stdio->exec(array(␊ | 
|  | 
| $stanzas = self::_parseBasicIO($out);␊ | 
| ␊ | 
| // FIXME: we only care about the first returned content mark␊ | 
| // everything else seem to be very rare cases␊ | 
| // everything else seem to be very, very rare cases␊ | 
| foreach ($stanzas as $stanza)␊ | 
| {␊ | 
| foreach ($stanza as $stanzaline)␊ | 
|  | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::inBranches()␊ | 
| **/␊ | 
| */␊ | 
| public function inBranches($commit, $path)␊ | 
| {␊ | 
| $revs = $this->_resolveSelector($commit);␊ | 
|  | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getTags()␊ | 
| **/␊ | 
| */␊ | 
| public function getTags()␊ | 
| {␊ | 
| if (isset($this->cache['tags']))␊ | 
|  | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::inTags()␊ | 
| **/␊ | 
| */␊ | 
| public function inTags($commit, $path)␊ | 
| {␊ | 
| $revs = $this->_resolveSelector($commit);␊ | 
|  | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Given the string describing the author from the log find the␊ | 
| * author in the database.␊ | 
| *␊ | 
| * @param string Author␊ | 
| * @return mixed Pluf_User or null␊ | 
| * @see IDF_Scm::findAuthor()␊ | 
| */␊ | 
| public function findAuthor($author)␊ | 
| {␊ | 
|  | 
| return null;␊ | 
| }␊ | 
| ␊ | 
| private static function _getMasterBranch($project)␊ | 
| {␊ | 
| $conf = $project->getConf();␊ | 
| if (false === ($branch = $conf->getVal('mtn_master_branch', false))␊ | 
| || empty($branch)) {␊ | 
| $branch = "*";␊ | 
| }␊ | 
| return $branch;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getAnonymousAccessUrl()␊ | 
| */␊ | 
| public static function getAnonymousAccessUrl($project, $commit = null)␊ | 
| {␊ | 
| $branch = self::_getMasterBranch($project);␊ | 
| $scm = IDF_Scm::get($project);␊ | 
| $branch = $scm->getMainBranch();␊ | 
| ␊ | 
| if (!empty($commit))␊ | 
| {␊ | 
| $scm = IDF_Scm::get($project);␊ | 
| $revs = $scm->_resolveSelector($commit);␊ | 
| if (count($revs) > 0)␊ | 
| {␊ | 
|  | 
| )." ".$branch;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getAuthAccessUrl()␊ | 
| */␊ | 
| public static function getAuthAccessUrl($project, $user, $commit = null)␊ | 
| {␊ | 
| return self::getAnonymousAccessUrl($project, $commit);␊ | 
|  | 
| return new IDF_Scm_Monotone($rep, $project);␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::isValidRevision()␊ | 
| */␊ | 
| public function isValidRevision($commit)␊ | 
| {␊ | 
| $revs = $this->_resolveSelector($commit);␊ | 
|  | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Get the file info.␊ | 
| *␊ | 
| * @param string File␊ | 
| * @param string Commit ('HEAD')␊ | 
| * @return false Information␊ | 
| * @see IDF_Scm::getPathInfo()␊ | 
| */␊ | 
| public function getPathInfo($file, $commit = null)␊ | 
| {␊ | 
| if ($commit === null) {␊ | 
| $commit = 'h:' . self::_getMasterBranch($this->project);␊ | 
| $commit = 'h:' . $this->getMainBranch();␊ | 
| }␊ | 
| ␊ | 
| $revs = $this->_resolveSelector($commit);␊ | 
|  | 
| return false;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getFile()␊ | 
| */␊ | 
| public function getFile($def, $cmd_only=false)␊ | 
| {␊ | 
| // this won't work with remote databases␊ | 
|  | 
| return $this->stdio->exec(array("get_file", $def->hash));␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Returns the differences between two revisions as unified diff␊ | 
| *␊ | 
| * @param string    The target of the diff␊ | 
| * @param string    The source of the diff, if not given, the first␊ | 
| *                  parent of the target is used␊ | 
| * @return string␊ | 
| */␊ | 
| private function _getDiff($target, $source = null)␊ | 
| {␊ | 
| if (empty($source))␊ | 
|  | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Get commit details.␊ | 
| *␊ | 
| * @param string Commit␊ | 
| * @param bool Get commit diff (false)␊ | 
| * @return array Changes␊ | 
| * @see IDF_Scm::getCommit()␊ | 
| */␊ | 
| public function getCommit($commit, $getdiff=false)␊ | 
| {␊ | 
|  | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Check if a commit is big.␊ | 
| *␊ | 
| * @param string Commit ('HEAD')␊ | 
| * @return bool The commit is big␊ | 
| * @see IDF_Scm::isCommitLarge()␊ | 
| */␊ | 
| public function isCommitLarge($commit=null)␊ | 
| {␊ | 
| if (empty($commit))␊ | 
| {␊ | 
| $commit = "h:"+self::_getMasterBranch($this->project);␊ | 
| $commit = "h:"+$this->getMainBranch();␊ | 
| }␊ | 
| ␊ | 
| $revs = $this->_resolveSelector($commit);␊ | 
|  | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Get latest changes.␊ | 
| *␊ | 
| * @param string Commit ('HEAD').␊ | 
| * @param int Number of changes (10).␊ | 
| * @return array Changes.␊ | 
| * @see IDF_Scm::getChangeLog()␊ | 
| */␊ | 
| public function getChangeLog($commit=null, $n=10)␊ | 
| {␊ | 
|  | 
| return $logs;␊ | 
| }␊ | 
| }␊ | 
| ␊ |