| # ***** 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;␊ |
| }␊ |
| }␊ |
| ␊ |