1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
4 /* Mercurial SCM browsing */
5 require_once 'MTrack/SCMFile.php';
7 class MTrackSCMFileHg extends MTrackSCMFile {
13 function __construct(MTrackSCM $repo, $name, $rev, $is_dir = false)
18 $this->is_dir = $is_dir;
21 public function _determineFileChangeEvent($repoid, $filename, $rev)
23 $repo = MTrackRepo::loadById($repoid);
24 $ents = $repo->history($filename, 1, 'rev', "$rev:0");
26 throw new Exception("$filename is invalid");
31 public function getChangeEvent()
34 //FIXME - this should do something similar to git,
35 // and use the database rather than caching with expiry..
36 return MTrackSCMFileHg::_determineFileChangeEvent($this->repo->repoid, $this->name, $this->rev);
37 //return mtrack_cache(
38 // array('MTrackSCMFileHg', '_determineFileChangeEvent'),
39 // array($this->repo->repoid, $this->name, $this->rev),
45 return $this->repo->hg('cat', '-r', $this->rev, $this->name);
48 function annotate($include_line_content = false)
52 $fp = $this->repo->hg('annotate', '-r', $this->rev, '-uvc', $this->name);
53 while ($line = fgets($fp)) {
54 preg_match("/^\s*([^:]*)\s+([0-9a-fA-F]+): (.*)$/", $line, $M);
55 $A = new MTrackSCMAnnotation;
58 if ($include_line_content) {
67 class MTrackWCHg extends MTrackSCMWorkingCopy {
70 function __construct(MTrackRepo $repo) {
71 $this->dir = mtrack_make_temp_dir();
74 stream_get_contents($this->hg('init', $this->dir));
75 stream_get_contents($this->hg('pull', $this->repo->repopath));
76 stream_get_contents($this->hg('up'));
79 function __destruct() {
81 $a = array("-y", "--cwd", $this->dir, 'push', $this->repo->repopath);
83 list($proc, $pipes) = MTrackSCM::run('hg', 'proc', $a);
85 $out = stream_get_contents($pipes[1]);
86 $err = stream_get_contents($pipes[2]);
87 $st = proc_close($proc);
90 throw new Exception("push failed with status $st: $err $out");
92 mtrack_rmdir($this->dir);
95 function getFile($path)
97 return $this->repo->file($path);
100 function addFile($path)
102 // nothing to do; we use --addremove
105 function delFile($path)
107 // we use --addremove when we commit for this to take effect
108 unlink($this->dir . DIRECTORY_SEPARATOR . $path);
111 function commit(MTrackChangeset $CS)
113 $hg_date = (int)strtotime($CS->when) . ' 0';
114 $reason = trim($CS->reason);
115 if (!strlen($reason)) {
118 $out = $this->hg('ci', '--addremove',
122 $data = stream_get_contents($out);
125 throw new Exception("commit failed $st $data");
131 $args = func_get_args();
132 $a = array("-y", "--cwd", $this->dir);
133 foreach ($args as $arg) {
137 return MTrackSCM::run('hg', 'read', $a);
141 class MTrackSCMHg extends MTrackRepo {
142 protected $hg = 'hg';
143 protected $branches = null;
144 protected $tags = null;
146 public function getSCMMetaData() {
148 'name' => 'Mercurial',
149 'tools' => array('hg'),
153 public function reconcileRepoSettings(MTrackSCM $r = null) {
157 $description = substr(preg_replace("/\r?\n/m", ' ', $r->description), 0, 64);
158 $description = trim($description);
159 if (!is_dir($r->repopath)) {
160 if ($r->clonedfrom) {
161 $S = MTrackRepo::loadById($r->clonedfrom);
162 $stm = MTrackSCM::run('hg', 'read', array(
163 'clone', $S->repopath, $r->repopath));
165 $stm = MTrackSCM::run('hg', 'read', array('init', $r->repopath));
167 $out = stream_get_contents($stm);
170 throw new Exception("hg: failed $out");
174 $php = MTrackConfig::get('tools', 'php');
175 $conffile = realpath(MTrackConfig::getLocation());
177 $install = realpath(dirname(__FILE__) . '/../../');
182 "changegroup.mtrack" =>
183 "$php $install/bin/hg-commit-hook changegroup $conffile",
185 "$php $install/bin/hg-commit-hook commit $conffile",
186 "pretxncommit.mtrack" =>
187 "$php $install/bin/hg-commit-hook pretxncommit $conffile",
188 "pretxnchangegroup.mtrack" =>
189 "$php $install/bin/hg-commit-hook pretxnchangegroup $conffile",
192 "description" => $description,
196 $cfg = @file_get_contents("$r->repopath/.hg/hgrc");
199 foreach ($apply as $sect => $opts) {
200 foreach ($opts as $name => $value) {
201 if (preg_match("/^$name\s*=/m", $cfg)) {
202 $cfg = preg_replace("/^$name\s*=.*$/m", "$name = $value", $cfg);
204 $adds[$sect][$name] = $value;
209 foreach ($adds as $sect => $opts) {
211 foreach ($opts as $name => $value) {
212 $cfg .= "$name = $value\n";
215 file_put_contents("$r->repopath/.hg/hgrc", $cfg, LOCK_EX);
216 system("chmod -R 02777 $r->repopath");
223 function getServerURL() {
224 $url = parent::getServerURL();
225 if ($url) return $url;
226 $url = MTrackConfig::get('repos', 'serverurl');
228 return "ssh://$url/" . $this->getBrowseRootName();
233 public function getBranches()
235 if ($this->branches !== null) {
236 return $this->branches;
238 $this->branches = array();
239 $fp = $this->hg('branches');
240 while ($line = fgets($fp)) {
241 list($branch, $revstr) = preg_split('/\s+/', $line);
242 list($num, $rev) = explode(':', $revstr, 2);
243 $this->branches[$branch] = $rev;
246 return $this->branches;
249 public function getTags()
251 if ($this->tags !== null) {
254 $this->tags = array();
255 $fp = $this->hg('tags');
256 while ($line = fgets($fp)) {
257 list($tag, $revstr) = preg_split('/\s+/', $line);
258 list($num, $rev) = explode(':', $revstr, 2);
259 $this->tags[$tag] = $rev;
265 public function readdir($path, $object = null, $ident = null)
269 if ($object === null) {
273 $rev = $this->resolveRevision(null, $object, $ident);
275 $fp = $this->hg('manifest', '-r', $rev);
280 $plen = strlen($path);
285 while ($line = fgets($fp)) {
288 if (!strncmp($name, $path, $plen)) {
290 $ent = substr($name, $plen);
291 if (strpos($ent, '/') === false) {
292 $res[] = new MTrackSCMFileHg($this, "$path$ent", $rev);
294 list($d) = explode('/', $ent, 2);
295 if (!isset($dirs[$d])) {
297 $res[] = new MTrackSCMFileHg($this, "$path$d", $rev, true);
304 throw new Exception("location $path does not exist");
309 public function file($path, $object = null, $ident = null)
311 if ($object == null) {
312 $branches = $this->getBranches();
313 if (isset($branches['default'])) {
322 $rev = $this->resolveRevision(null, $object, $ident);
323 return new MTrackSCMFileHg($this, $path, $rev);
326 public function history($path, $limit = null, $object = null, $ident = null)
331 if ($object !== null) {
332 $rev = $this->resolveRevision(null, $object, $ident);
336 if ($limit !== null) {
337 if (is_int($limit)) {
341 $t = strtotime($limit);
348 $fp = $this->hg('log',
349 '--template', $sep . '\n{node|short}\n{branches}\n{tags}\n{file_adds}\n{file_copies}\n{file_mods}\n{file_dels}\n{author|email}\n{date|hgdate}\n{desc}\n', $args,
352 fgets($fp); # discard leading $sep
354 // corresponds to the file_adds, file_copies, file_modes, file_dels
355 // in the template above
356 static $file_status_order = array('A', 'C', 'M', 'D');
359 $ent = new MTrackSCMEvent;
361 $ent->rev = trim(fgets($fp));
362 if (!strlen($ent->rev)) {
366 $ent->branches = array();
367 foreach (preg_split('/\s+/', trim(fgets($fp))) as $b) {
369 $ent->branches[] = $b;
372 if (!count($ent->branches)) {
373 $ent->branches[] = 'default';
376 $ent->tags = array();
377 foreach (preg_split('/\s+/', trim(fgets($fp))) as $t) {
383 $ent->files = array();
385 foreach ($file_status_order as $status) {
386 foreach (preg_split('/\s+/', trim(fgets($fp))) as $t) {
388 $f = new MTrackSCMFileEvent;
390 $f->status = $status;
396 $ent->changeby = trim(fgets($fp));
397 list($ts) = preg_split('/\s+/', fgets($fp));
398 $ent->ctime = MTrackDB::unixtime((int)$ts);
399 $changelog = array();
400 while (($line = fgets($fp)) !== false) {
401 $line = rtrim($line, "\r\n");
405 $changelog[] = $line;
407 $ent->changelog = join("\n", $changelog);
411 if ($line === false) {
419 public function diff($path, $from = null, $to = null)
421 if ($path instanceof MTrackSCMFile) {
422 if ($from === null) {
428 return $this->hg('diff', '-r', $from, '-r', $to,
431 return $this->hg('diff', '-c', $from, '--git', $path);
434 public function getWorkingCopy()
436 return new MTrackWCHg($this);
439 public function getRelatedChanges($revision)
444 foreach (preg_split('/\s+/',
445 stream_get_contents($this->hg('parents', '-r', $revision,
446 '--template', '{node|short}\n'))) as $p) {
452 foreach (preg_split('/\s+/',
453 stream_get_contents($this->hg('--config',
454 'extensions.children=',
455 'children', '-r', $revision,
456 '--template', '{node|short}\n'))) as $p) {
461 return array($parents, $kids);
466 $args = func_get_args();
467 $a = array("-y", "-R", $this->repopath, "--cwd", $this->repopath);
468 foreach ($args as $arg) {
472 return MTrackSCM::run('hg', 'read', $a);