| # ***** END LICENSE BLOCK ***** */␊ | 
| ␊ | 
| /**␊ | 
| * Mercurial utils.␊ | 
| * A simple RAII helper that manages style files to format hg's log output␊ | 
| */␊ | 
| class IDF_Scm_Mercurial_LogStyle␊ | 
| {␊ | 
| const FULL_LOG = 1;␊ | 
| const CHANGES = 2;␊ | 
| ␊ | 
| public function __construct($type)␊ | 
| {␊ | 
| $this->file = tempnam(Pluf::f('tmp_folder'), 'hg-log-style-');␊ | 
| ␊ | 
| if ($type == self::FULL_LOG) {␊ | 
| $style = 'changeset = "'␊ | 
| . 'changeset: {node|short}\n'␊ | 
| . 'branch: {branch}\n'␊ | 
| . 'author: {author}\n'␊ | 
| . 'date: {date|isodate}\n'␊ | 
| . 'parents: {parents}\n\n'␊ | 
| . '{desc}\n'␊ | 
| . '\0\n"'␊ | 
| . "\n"␊ | 
| . 'parent = "{node|short} "'␊ | 
| . "\n";␊ | 
| } elseif ($type == self::CHANGES) {␊ | 
| $style = 'changeset = "'␊ | 
| . 'file_mods: {file_mods}\n'␊ | 
| . 'file_adds: {file_adds}\n'␊ | 
| . 'file_dels: {file_dels}\n'␊ | 
| . 'file_copies: {file_copies}\n\n'␊ | 
| . '\0\n"'␊ | 
| . "\n"␊ | 
| . 'file_mod = "{file_mod}\0"'␊ | 
| . "\n"␊ | 
| . 'file_add = "{file_add}\0"'␊ | 
| . "\n"␊ | 
| . 'file_del = "{file_del}\0"'␊ | 
| . "\n"␊ | 
| . 'file_copy = "{name}\0{source}\0"'␊ | 
| . "\n";␊ | 
| } else {␊ | 
| throw new IDF_Scm_Exception('invalid type ' . $type);␊ | 
| }␊ | 
| ␊ | 
| file_put_contents($this->file, $style);␊ | 
| }␊ | 
| ␊ | 
| public function __destruct()␊ | 
| {␊ | 
| @unlink($this->file);␊ | 
| }␊ | 
| ␊ | 
| public function get()␊ | 
| {␊ | 
| return $this->file;␊ | 
| }␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Main SCM class for Mercurial␊ | 
| *␊ | 
| * Note: Some commands take a --debug option, this is not lousy coding, but␊ | 
| *       totally wanted, as hg returns additional / different data in this␊ | 
| *       mode on which this largely depends.␊ | 
| */␊ | 
| class IDF_Scm_Mercurial extends IDF_Scm␊ | 
| {␊ | 
| protected $hg_log_template;␊ | 
| ␊ | 
| public function __construct($repo, $project=null)␊ | 
| {␊ | 
| $this->repo = $repo;␊ | 
| $this->project = $project;␊ | 
| $this->hg_log_template = "'".'changeset: {rev}:{node|short}\nauthor: {author}\ndate: {date|isodate}\nfiles: {files}\n{desc}\n'."'";␊ | 
| }␊ | 
| }␊ | 
| ␊ | 
| public function getRepositorySize()␊ | 
| {␊ | 
|  | 
| throw new Exception(sprintf(__('Not a valid tree: %s.'), $tree));␊ | 
| }␊ | 
| $cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';␊ | 
| $cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $tree, ($recurse) ? '' : '');␊ | 
| $cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo),␊ | 
| escapeshellarg($tree));␊ | 
| $out = array();␊ | 
| $res = array();␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ | 
|  | 
| public function getPathInfo($totest, $commit='tip')␊ | 
| {␊ | 
| $cmd_tmpl = Pluf::f('hg_path', 'hg').' manifest -R %s --debug -r %s';␊ | 
| $cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo), $commit);␊ | 
| $cmd = sprintf($cmd_tmpl, escapeshellarg($this->repo),␊ | 
| escapeshellarg($commit));␊ | 
| $out = array();␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ | 
| self::exec('IDF_Scm_Mercurial::getPathInfo', $cmd, $out);␊ | 
|  | 
| self::exec('IDF_Scm_Mercurial::getBranches', $cmd, $out);␊ | 
| $res = array();␊ | 
| foreach ($out as $b) {␊ | 
| preg_match('/(\S+).*\S+:(\S+)/', $b, $match);␊ | 
| preg_match('/(.+?)\s+\S+:(\S+)/', $b, $match);␊ | 
| $res[$match[1]] = '';␊ | 
| }␊ | 
| $this->cache['branches'] = $res;␊ | 
|  | 
| self::exec('IDF_Scm_Mercurial::getTags', $cmd, $out);␊ | 
| $res = array();␊ | 
| foreach ($out as $b) {␊ | 
| preg_match('/(\S+).*\S+:(\S+)/', $b, $match);␊ | 
| preg_match('/(.+?)\s+\S+:(\S+)/', $b, $match);␊ | 
| $res[$match[1]] = '';␊ | 
| }␊ | 
| $this->cache['tags'] = $res;␊ | 
|  | 
| if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {␊ | 
| return false;␊ | 
| }␊ | 
| ␊ | 
| $logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::FULL_LOG);␊ | 
| $tmpl = ($getdiff)␊ | 
| ? Pluf::f('hg_path', 'hg').' log -p -r %s -R %s --template %s'␊ | 
| : Pluf::f('hg_path', 'hg').' log -r %s -R %s --template %s';␊ | 
| ? Pluf::f('hg_path', 'hg').' log --debug -p -r %s -R %s --style %s'␊ | 
| : Pluf::f('hg_path', 'hg').' log --debug -r %s -R %s --style %s';␊ | 
| $cmd = sprintf($tmpl,␊ | 
| escapeshellarg($commit),␊ | 
| escapeshellarg($this->repo),␊ | 
| $this->hg_log_template);␊ | 
| ␊ | 
| escapeshellarg($logStyle->get()));␊ | 
| $out = array();␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ | 
| self::exec('IDF_Scm_Mercurial::getCommit', $cmd, $out);␊ | 
|  | 
| $log[] = $line;␊ | 
| }␊ | 
| }␊ | 
| $out = self::parseLog($log, 4);␊ | 
| $out = self::parseLog($log);␊ | 
| $out[0]->diff = implode("\n", $change);␊ | 
| return $out[0];␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * @see IDF_Scm::getChanges()␊ | 
| */␊ | 
| public function getChanges($commit)␊ | 
| {␊ | 
| if ($this->validateRevision($commit) != IDF_Scm::REVISION_VALID) {␊ | 
| return null;␊ | 
| }␊ | 
| ␊ | 
| $logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::CHANGES);␊ | 
| $tmpl = Pluf::f('hg_path', 'hg').' log --debug -r %s -R %s --style %s';␊ | 
| $cmd = sprintf($tmpl,␊ | 
| escapeshellarg($commit),␊ | 
| escapeshellarg($this->repo),␊ | 
| escapeshellarg($logStyle->get()));␊ | 
| $out = array();␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ | 
| self::exec('IDF_Scm_Mercurial::getChanges', $cmd, $out);␊ | 
| $log = self::parseLog($out);␊ | 
| // we expect only one log entry that contains all the needed information␊ | 
| $log = $log[0];␊ | 
| ␊ | 
| $return = (object) array(␊ | 
| 'additions'  => preg_split('/\0/', $log->file_adds, -1, PREG_SPLIT_NO_EMPTY),␊ | 
| 'deletions'  => preg_split('/\0/', $log->file_dels, -1, PREG_SPLIT_NO_EMPTY),␊ | 
| 'patches'    => preg_split('/\0/', $log->file_mods, -1, PREG_SPLIT_NO_EMPTY),␊ | 
| // hg has no support for built-in attributes, so this keeps empty␊ | 
| 'properties' => array(),␊ | 
| // this is filled below␊ | 
| 'renames'    => array(),␊ | 
| );␊ | 
| ␊ | 
| $file_copies = preg_split('/\0/', $log->file_copies, -1, PREG_SPLIT_NO_EMPTY);␊ | 
| ␊ | 
| // FIXME: copies are only treated as renames if they have an add _and_␊ | 
| // an drop, otherwise they're just treated as adds␊ | 
| for ($i=0; $i<count($file_copies); $i+=2) {␊ | 
| $new = $file_copies[$i];␊ | 
| $old = $file_copies[$i+1];␊ | 
| $newidx = array_search($new, $return->additions);␊ | 
| $oldidx = array_search($old, $return->deletions);␊ | 
| if ($newidx !== false && $oldidx !== false) {␊ | 
| $return->renames[$old] = $new;␊ | 
| unset($return->additions[$newidx]);␊ | 
| unset($return->deletions[$oldidx]);␊ | 
| }␊ | 
| }␊ | 
| ␊ | 
| return $return;␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Check if a commit is big.␊ | 
| *␊ | 
| * @param string Commit ('HEAD')␊ | 
|  | 
| */␊ | 
| public function getChangeLog($commit='tip', $n=10)␊ | 
| {␊ | 
| $cmd = sprintf(Pluf::f('hg_path', 'hg').' log -R %s -l%s --template %s', escapeshellarg($this->repo), $n, $this->hg_log_template, $commit);␊ | 
| $logStyle = new IDF_Scm_Mercurial_LogStyle(IDF_Scm_Mercurial_LogStyle::FULL_LOG);␊ | 
| ␊ | 
| // hg accepts revision IDs as arguments to --branch / -b as well and␊ | 
| // uses the branch of the revision in question to filter the other␊ | 
| // revisions␊ | 
| $cmd = sprintf(Pluf::f('hg_path', 'hg').' log --debug -R %s -l%s --style %s -b %s',␊ | 
| escapeshellarg($this->repo),␊ | 
| $n,␊ | 
| escapeshellarg($logStyle->get()),␊ | 
| escapeshellarg($commit));␊ | 
| $out = array();␊ | 
| $cmd = Pluf::f('idf_exec_cmd_prefix', '').$cmd;␊ | 
| self::exec('IDF_Scm_Mercurial::getChangeLog', $cmd, $out);␊ | 
| return self::parseLog($out, 4);␊ | 
| return self::parseLog($out);␊ | 
| }␊ | 
| ␊ | 
| /**␊ | 
| * Parse the log lines of a --pretty=medium log output.␊ | 
| * Parse the log lines of our custom style format.␊ | 
| *␊ | 
| * @param array Lines.␊ | 
| * @param int Number of lines in the headers (3)␊ | 
| * @return array Change log.␊ | 
| */␊ | 
| ␊ | 
| public static function parseLog($lines, $hdrs=3)␊ | 
| public static function parseLog($lines)␊ | 
| {␊ | 
| $res = array();␊ | 
| $c = array();␊ | 
| $i = 0;␊ | 
| $hdrs += 1;␊ | 
| $headers_processed = false;␊ | 
| foreach ($lines as $line) {␊ | 
| $i++;␊ | 
| if (0 === strpos($line, 'changeset:')) {␊ | 
| if ($line == "\0") {␊ | 
| $headers_processed = false;␊ | 
| if (count($c) > 0) {␊ | 
| $c['full_message'] = trim($c['full_message']);␊ | 
| $res[] = (object) $c;␊ | 
| }␊ | 
| $c = array();␊ | 
| $c['commit'] = substr(strrchr($line, ':'), 1);␊ | 
| $c['full_message'] = '';␊ | 
| $i=1;␊ | 
| continue;␊ | 
| ␊ | 
| }␊ | 
| if ($i == $hdrs) {␊ | 
| $c['title'] = trim($line);␊ | 
| if (!$headers_processed && empty($line)) {␊ | 
| $headers_processed = true;␊ | 
| continue;␊ | 
| }␊ | 
| $match = array();␊ | 
| if (preg_match('/^(\S+):\s*(.*)/', $line, $match)) {␊ | 
| if (!$headers_processed && preg_match('/^(\S+):\s*(.*)/', $line, $match)) {␊ | 
| $match[1] = strtolower($match[1]);␊ | 
| if ($match[1] == 'user') {␊ | 
| if ($match[1] == 'changeset') {␊ | 
| $c = array();␊ | 
| $c['commit'] = $match[2];␊ | 
| $c['tree'] = $c['commit'];␊ | 
| $c['full_message'] = '';␊ | 
| } elseif ($match[1] == 'user') {␊ | 
| $c['author'] = $match[2];␊ | 
| } elseif ($match[1] == 'summary') {␊ | 
| $c['title'] = $match[2];␊ | 
| } elseif ($match[1] == 'branch') {␊ | 
| $c['branch'] = $match[2];␊ | 
| $c['branch'] = empty($match[2]) ? 'default' : $match[2];␊ | 
| } elseif ($match[1] == 'parents') {␊ | 
| $parents = preg_split('/\s+/', $match[2], -1, PREG_SPLIT_NO_EMPTY);␊ | 
| for ($i=0, $j=count($parents); $i<$j; ++$i) {␊ | 
| if ($parents[$i] == '000000000000')␊ | 
| unset($parents[$i]);␊ | 
| }␊ | 
| $c['parents'] = $parents;␊ | 
| } else {␊ | 
| $c[$match[1]] = trim($match[2]);␊ | 
| }␊ | 
|  | 
| }␊ | 
| continue;␊ | 
| }␊ | 
| if ($i > ($hdrs+1)) {␊ | 
| $c['full_message'] .= trim($line)."\n";␊ | 
| if ($headers_processed) {␊ | 
| if (empty($c['title']))␊ | 
| $c['title'] = trim($line);␊ | 
| else␊ | 
| $c['full_message'] .= trim($line)."\n";␊ | 
| continue;␊ | 
| }␊ | 
| }␊ | 
| $c['tree'] = !empty($c['commit']) ? trim($c['commit']) : '';␊ | 
| $c['branch'] = empty($c['branch']) ? 'default' : $c['branch'];␊ | 
| $c['full_message'] = !empty($c['full_message']) ? trim($c['full_message']) : '';␊ | 
| $res[] = (object) $c;␊ | 
| return $res;␊ | 
| }␊ | 
| ␊ |