PageRenderTime 45ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/inc/scm/hg.php

https://bitbucket.org/yoander/mtrack
PHP | 522 lines | 406 code | 62 blank | 54 comment | 60 complexity | f8cf42bb9e5568bd9f3c3e33c9401e53 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. /* Mercurial SCM browsing */
  4. class MTrackSCMFileHg extends MTrackSCMFile {
  5. public $name;
  6. public $rev;
  7. public $is_dir;
  8. public $repo;
  9. function __construct(MTrackSCM $repo, $name, $rev, $is_dir = false)
  10. {
  11. $this->repo = $repo;
  12. $this->name = $name;
  13. $this->rev = $rev;
  14. $this->is_dir = $is_dir;
  15. }
  16. public function _determineFileChangeEvent($repoid, $filename, $rev)
  17. {
  18. $repo = MTrackRepo::loadById($repoid);
  19. if ($filename == '') {
  20. $ents = $repo->_parse_log(array('-r', $rev));
  21. } else {
  22. $ents = $repo->_parse_log(array(
  23. '-r', "last(::$rev and filelog('$filename'), 1)"
  24. ));
  25. }
  26. if (!count($ents)) {
  27. throw new Exception("$filename is invalid");
  28. }
  29. return $ents[0];
  30. }
  31. public function getChangeEvent()
  32. {
  33. /* for performance reasons (eg: it can take seconds to execute
  34. * this for each file), we fake the change event here to match
  35. * that of the last known revision at the root if we are running
  36. * the browser view (there can be many files).
  37. * Otherwise we do the logical thing for other areas; this should
  38. * give a reasonable balance between accuracy and performance in
  39. * the right places */
  40. if (basename($_SERVER['SCRIPT_NAME']) == 'browse.php') {
  41. return mtrack_cache(
  42. array('MTrackSCMFileHg', '_determineFileChangeEvent'),
  43. array($this->repo->repoid, '', $this->rev),
  44. 300);
  45. }
  46. return mtrack_cache(
  47. array('MTrackSCMFileHg', '_determineFileChangeEvent'),
  48. array($this->repo->repoid, $this->name, $this->rev),
  49. 864000);
  50. }
  51. function cat()
  52. {
  53. return $this->repo->hg('cat', '-r', $this->rev, $this->name);
  54. }
  55. function annotate($include_line_content = false)
  56. {
  57. $i = 1;
  58. $ann = array();
  59. $fp = $this->repo->hg('annotate', '-r', $this->rev, '-uvc', $this->name);
  60. while ($line = fgets($fp)) {
  61. preg_match("/^\s*([^:]*)\s+([0-9a-fA-F]+): (.*)$/", $line, $M);
  62. $A = new MTrackSCMAnnotation;
  63. $A->changeby = $M[1];
  64. $A->rev = $M[2];
  65. if ($include_line_content) {
  66. $A->line = $M[3];
  67. }
  68. $ann[$i++] = $A;
  69. }
  70. return $ann;
  71. }
  72. }
  73. class MTrackWCHg extends MTrackSCMWorkingCopy {
  74. private $repo;
  75. function __construct(MTrackRepo $repo) {
  76. /* use a temp dir on the same filesystem as the repos dir to improve
  77. * clone performance */
  78. $tempdir = dirname(MTrackRepo::get_repos_dir()) . '/temp';
  79. if (!is_dir($tempdir)) {
  80. mkdir($tempdir);
  81. }
  82. $this->dir = mtrack_make_temp_dir(true, $tempdir);
  83. $this->repo = $repo;
  84. stream_get_contents($this->hg('init', $this->dir));
  85. stream_get_contents($this->hg('pull', $this->repo->repopath));
  86. stream_get_contents($this->hg('up'));
  87. }
  88. function __destruct() {
  89. $a = array("-y", "--cwd", $this->dir, 'push', $this->repo->repopath);
  90. list($proc, $pipes) = mtrack_run_tool('hg', 'proc', $a);
  91. $out = stream_get_contents($pipes[1]);
  92. $err = stream_get_contents($pipes[2]);
  93. $st = proc_close($proc);
  94. if ($st) {
  95. throw new Exception("push failed with status $st: $err $out");
  96. }
  97. mtrack_rmdir($this->dir);
  98. }
  99. function getFile($path)
  100. {
  101. return $this->repo->file($path);
  102. }
  103. function addFile($path)
  104. {
  105. // nothing to do; we use --addremove
  106. }
  107. function delFile($path)
  108. {
  109. // we use --addremove when we commit for this to take effect
  110. unlink($this->dir . DIRECTORY_SEPARATOR . $path);
  111. }
  112. function commit(MTrackChangeset $CS)
  113. {
  114. $hg_date = (int)strtotime($CS->when) . ' 0';
  115. $reason = trim($CS->reason);
  116. if (!strlen($reason)) {
  117. $reason = 'Changed';
  118. }
  119. $out = $this->hg('ci', '--addremove',
  120. '-m', $reason,
  121. '-d', $hg_date,
  122. '-u', $CS->who);
  123. $data = stream_get_contents($out);
  124. $st = pclose($out);
  125. if ($st != 0) {
  126. throw new Exception("commit failed $st $data");
  127. }
  128. }
  129. function hg()
  130. {
  131. $args = func_get_args();
  132. $a = array("-y", "--cwd", $this->dir);
  133. foreach ($args as $arg) {
  134. $a[] = $arg;
  135. }
  136. return mtrack_run_tool('hg', 'read', $a);
  137. }
  138. }
  139. class MTrackSCMHg extends MTrackRepo {
  140. protected $hg = 'hg';
  141. protected $branches = null;
  142. protected $tags = null;
  143. public function getSCMMetaData() {
  144. return array(
  145. 'name' => 'Mercurial',
  146. 'tools' => array('hg'),
  147. );
  148. }
  149. public function reconcileRepoSettings(MTrackSCM $r = null) {
  150. if ($r == null) {
  151. $r = $this;
  152. }
  153. $description = substr(preg_replace("/\r?\n/m", ' ', $r->description), 0, 64);
  154. $description = trim($description);
  155. if (!is_dir($r->repopath)) {
  156. if ($r->clonedfrom) {
  157. $S = MTrackRepo::loadById($r->clonedfrom);
  158. $stm = mtrack_run_tool('hg', 'read', array(
  159. 'clone', $S->repopath, $r->repopath));
  160. } else {
  161. $stm = mtrack_run_tool('hg', 'read', array('init', $r->repopath));
  162. }
  163. $out = stream_get_contents($stm);
  164. $st = pclose($stm);
  165. if ($st) {
  166. throw new Exception("hg: failed $out");
  167. }
  168. }
  169. $php = MTrackConfig::get('tools', 'php');
  170. $conffile = realpath(MTrackConfig::getLocation());
  171. $install = realpath(dirname(__FILE__) . '/../../');
  172. /* fixup config */
  173. $apply = array(
  174. "hooks" => array(
  175. "changegroup.mtrack" =>
  176. "$php $install/bin/hg-commit-hook changegroup $conffile",
  177. "commit.mtrack" =>
  178. "$php $install/bin/hg-commit-hook commit $conffile",
  179. "pretxncommit.mtrack" =>
  180. "$php $install/bin/hg-commit-hook pretxncommit $conffile",
  181. "pretxnchangegroup.mtrack" =>
  182. "$php $install/bin/hg-commit-hook pretxnchangegroup $conffile",
  183. ),
  184. "web" => array(
  185. "description" => $description,
  186. )
  187. );
  188. $cfg = @file_get_contents("$r->repopath/.hg/hgrc");
  189. $adds = array();
  190. foreach ($apply as $sect => $opts) {
  191. foreach ($opts as $name => $value) {
  192. if (preg_match("/^$name\s*=/m", $cfg)) {
  193. $cfg = preg_replace("/^$name\s*=.*$/m", "$name = $value", $cfg);
  194. } else {
  195. $adds[$sect][$name] = $value;
  196. }
  197. }
  198. }
  199. foreach ($adds as $sect => $opts) {
  200. $cfg .= "[$sect]\n";
  201. foreach ($opts as $name => $value) {
  202. $cfg .= "$name = $value\n";
  203. }
  204. }
  205. file_put_contents("$r->repopath/.hg/hgrc", $cfg, LOCK_EX);
  206. system("chmod -R 02777 $r->repopath");
  207. }
  208. function canFork() {
  209. return true;
  210. }
  211. function getServerURL() {
  212. $url = parent::getServerURL();
  213. if ($url) return $url;
  214. $url = MTrackConfig::get('repos', 'serverurl');
  215. if ($url) {
  216. $pp = MTrackConfig::get('repos', 'serverpathprefix');
  217. if ($pp) {
  218. return "ssh://$url/~$pp/" . $this->getBrowseRootName();
  219. }
  220. return "ssh://$url/" . $this->getBrowseRootName();
  221. }
  222. return null;
  223. }
  224. public function getBranches()
  225. {
  226. if ($this->branches !== null) {
  227. return $this->branches;
  228. }
  229. $this->branches = array();
  230. $fp = $this->hg('branches');
  231. while ($line = fgets($fp)) {
  232. list($branch, $revstr) = preg_split('/\s+/', $line);
  233. list($num, $rev) = explode(':', $revstr, 2);
  234. $this->branches[$branch] = $rev;
  235. }
  236. $fp = null;
  237. return $this->branches;
  238. }
  239. public function getTags()
  240. {
  241. if ($this->tags !== null) {
  242. return $this->tags;
  243. }
  244. $this->tags = array();
  245. $fp = $this->hg('tags');
  246. while ($line = fgets($fp)) {
  247. list($tag, $revstr) = preg_split('/\s+/', $line);
  248. list($num, $rev) = explode(':', $revstr, 2);
  249. $this->tags[$tag] = $rev;
  250. }
  251. $fp = null;
  252. return $this->tags;
  253. }
  254. public function readdir($path, $object = null, $ident = null)
  255. {
  256. $res = array();
  257. if ($object === null) {
  258. $object = 'branch';
  259. $ident = 'default';
  260. }
  261. $rev = $this->resolveRevision(null, $object, $ident);
  262. $fp = $this->hg('manifest', '-r', $rev);
  263. if (strlen($path)) {
  264. $path .= '/';
  265. }
  266. $plen = strlen($path);
  267. $dirs = array();
  268. $exists = false;
  269. while ($line = fgets($fp)) {
  270. $name = trim($line);
  271. if (!strncmp($name, $path, $plen)) {
  272. $exists = true;
  273. $ent = substr($name, $plen);
  274. if (strpos($ent, '/') === false) {
  275. $res[] = new MTrackSCMFileHg($this, "$path$ent", $rev);
  276. } else {
  277. list($d) = explode('/', $ent, 2);
  278. if (!isset($dirs[$d])) {
  279. $dirs[$d] = $d;
  280. $res[] = new MTrackSCMFileHg($this, "$path$d", $rev, true);
  281. }
  282. }
  283. }
  284. }
  285. if (!$exists) {
  286. throw new Exception("location $path does not exist");
  287. }
  288. return $res;
  289. }
  290. public function file($path, $object = null, $ident = null)
  291. {
  292. if ($object == null) {
  293. $branches = $this->getBranches();
  294. if (isset($branches['default'])) {
  295. $object = 'branch';
  296. $ident = 'default';
  297. } else {
  298. // fresh/empty repo
  299. $object = 'tag';
  300. $ident = 'tip';
  301. }
  302. }
  303. $rev = $this->resolveRevision(null, $object, $ident);
  304. return new MTrackSCMFileHg($this, $path, $rev);
  305. }
  306. public function _parse_log($args)
  307. {
  308. $res = array();
  309. $sep = uniqid();
  310. $fp = $this->hg('log', '--style', 'xml', '-v', $args);
  311. $xml = stream_get_contents($fp);
  312. $log = simplexml_load_string($xml);
  313. /*
  314. <?xml version="1.0"?>
  315. <log>
  316. <logentry revision="9" node="22e795f008e5743d60a1eca2fdf6cf9542d7d837">
  317. <tag>tip</tag>
  318. <author email="wez@wezfurlong.org">Wez Furlong</author>
  319. <date>2011-11-04T00:08:44-04:00</date>
  320. <msg xml:space="preserve">fix syntax, refs #1 (spent 1)</msg>
  321. <paths>
  322. <path action="M">hello.php</path>
  323. </paths>
  324. </logentry>
  325. <logentry revision="8" node="fcb0eaa717a96259678385b8f8d6a461d893a62f">
  326. <author email="wez@wezfurlong.org">Wez Furlong</author>
  327. <date>2011-11-04T00:08:21-04:00</date>
  328. <msg xml:space="preserve">and some more, refs #1 (spent 2)</msg>
  329. <paths>
  330. <path action="M">hello.php</path>
  331. </paths>
  332. </logentry>
  333. <logentry revision="7" node="b57cf18f1373d3156609863730b7f3f7354bef8a">
  334. <author email="wez@wezfurlong.org">Wez Furlong</author>
  335. <date>2011-11-04T00:06:29-04:00</date>
  336. <msg xml:space="preserve">more changes, refs #1 (spent 1)</msg>
  337. <paths>
  338. <path action="M">hello.php</path>
  339. </paths>
  340. </logentry>
  341. <logentry revision="6" node="dd713e9701cbbbecbb2b7bca57e78ee5f82ef874">
  342. <author email="wez@wezfurlong.org">Wez Furlong</author>
  343. <date>2011-11-04T00:05:54-04:00</date>
  344. <msg xml:space="preserve">add a php file (busted)
  345. refs #1 (spent 1)</msg>
  346. <paths>
  347. <path action="A">hello.php</path>
  348. </paths>
  349. </logentry>
  350. </log>
  351. */
  352. if (is_object($log) && isset($log->logentry))
  353. foreach ($log->logentry as $L) {
  354. $ent = new MTrackSCMEvent;
  355. $ent->rev = (string)$L['node'];
  356. $ent->branches = array();
  357. foreach ($L->branch as $br) {
  358. $ent->branches[] = (string)$br;
  359. }
  360. if (!count($ent->branches)) {
  361. $ent->branches[] = 'default';
  362. }
  363. $ent->tags = array();
  364. foreach ($L->tag as $tag) {
  365. $ent->tags[] = (string)$tag;
  366. }
  367. $ent->files = array();
  368. foreach ($L->paths->path as $p) {
  369. $f = new MTrackSCMFileEvent;
  370. $f->name = (string)$p;
  371. $f->status = (string)$p['action'];
  372. $ent->files[] = $f;
  373. }
  374. $ent->changeby = (string)$L->author['email'];
  375. $ent->ctime = (string)$L->date;
  376. $ent->changelog = (string)$L->msg;
  377. $res[] = $ent;
  378. }
  379. $fp = null;
  380. return $res;
  381. }
  382. public function history($path, $limit = null, $object = null, $ident = null)
  383. {
  384. $args = array();
  385. if ($limit > 1 && $object == 'branch') {
  386. $args[] = '-b';
  387. $args[] = $ident;
  388. } else if ($limit > 1 && $object == 'rev') {
  389. $args[] = '-r';
  390. $args[] = "limit(reverse(::$ident), $limit)";
  391. $limit = null;
  392. } else if ($object !== null) {
  393. $rev = $this->resolveRevision(null, $object, $ident);
  394. $args[] = '-r';
  395. $args[] = $rev;
  396. }
  397. if ($limit !== null) {
  398. if (is_int($limit)) {
  399. $args[] = '-l';
  400. $args[] = $limit;
  401. } else {
  402. $t = strtotime($limit);
  403. $args[] = '-d';
  404. $args[] = ">$t 0";
  405. }
  406. }
  407. $args[] = $path;
  408. return $this->_parse_log($args);
  409. }
  410. public function diff($path, $from = null, $to = null)
  411. {
  412. if ($path instanceof MTrackSCMFile) {
  413. if ($from === null) {
  414. $from = $path->rev;
  415. }
  416. $path = $path->name;
  417. }
  418. if ($to !== null) {
  419. return $this->hg('diff', '-r', $from, '-r', $to,
  420. '--git', $path);
  421. }
  422. return $this->hg('diff', '-c', $from, '--git', $path);
  423. }
  424. public function getWorkingCopy()
  425. {
  426. return new MTrackWCHg($this);
  427. }
  428. public function getRelatedChanges($revision)
  429. {
  430. $parents = array();
  431. $kids = array();
  432. foreach (preg_split('/\s+/',
  433. stream_get_contents($this->hg('parents', '-r', $revision,
  434. '--template', '{node|short}\n'))) as $p) {
  435. if (strlen($p)) {
  436. $parents[] = $p;
  437. }
  438. }
  439. foreach (preg_split('/\s+/',
  440. stream_get_contents($this->hg('--config',
  441. 'extensions.children=',
  442. 'children', '-r', $revision,
  443. '--template', '{node|short}\n'))) as $p) {
  444. if (strlen($p)) {
  445. $kids[] = $p;
  446. }
  447. }
  448. return array($parents, $kids);
  449. }
  450. function hg()
  451. {
  452. $args = func_get_args();
  453. $a = array("-y", "-R", $this->repopath, "--cwd", $this->repopath);
  454. foreach ($args as $arg) {
  455. $a[] = $arg;
  456. }
  457. return mtrack_run_tool('hg', 'read', $a);
  458. }
  459. }
  460. MTrackRepo::registerSCM('hg', 'MTrackSCMHg');