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