2 require_once 'MTrack/SCM.php';
3 require_once 'MTrack/DB.php';
4 require_once 'MTrack/Config.php';
5 require_once 'MTrack/Project.php';
6 require_once 'MTrack/SCMFileEvent.php';
7 require_once 'MTrack/ACL.php';
8 require_once 'MTrack/Changeset.php';
9 require_once 'MTrack/Wiki.php';
12 class MTrack_Repo extends MTrackSCM
14 public $repoid = null;
15 public $shortname = null;
16 public $scmtype = null;
17 public $repopath = null;
18 public $browserurl = null;
19 public $browsertype = null;
20 public $description = null;
22 public $clonedfrom = null;
23 public $serverurl = null;
24 private $links_to_add = array();
25 private $links_to_remove = array();
26 private $links = null;
27 static $scms = array();
29 static function loadType($a) {
34 static function getAvailableSCMs()
37 $ar = scandir(dirname(__FILE__).'/SCM');
40 if (empty($a) || $a[0] == '.') {
43 $fn = dirname(__FILE__).'/SCM/'.$a.'/Repo.php';
44 if (!file_exists($fn)) {
47 $cls = 'MTrack_SCM_'.$a.'_Repo';
55 static function factory($ar)
57 $fn = 'MTrack/SCM/'.$ar['scmtype'].'/Repo.php';
58 $cls = 'MTrack_SCM_'.$a.'_Repo';
62 foreach($ar as $k=>$v) {
69 /*static function loadById($id) {
70 list($row) = MTrackDB::q(
71 'select repoid, scmtype from repos where repoid = ?',
75 if (isset(self::$scms[$type])) {
76 $class = self::$scms[$type];
77 return new $class($row[0]);
79 throw new Exception("unsupported repo type $type");
83 static function loadByName($name) {
84 $bits = explode('/', $name);
85 if (count($bits) > 1 && $bits[0] == 'default') {
89 if (count($bits) > 1) {
90 // wez/reponame -> per user repo
92 $p = "project:$bits[0]";
94 'select repoid, scmtype from repos where shortname = ? and (parent = ? OR parent = ?)',
95 $bits[1], $u, $p)->fetchAll();
98 "select repoid, scmtype from repos where shortname = ? and parent =''",
101 if (is_array($rows) && isset($rows[0])) {
103 if (isset($row[0])) {
105 if (isset(self::$scms[$type])) {
106 $class = self::$scms[$type];
107 return new $class($row[0]);
109 throw new Exception("unsupported repo type $type");
114 static function loadByLocation($path)
117 // we have magic configuration - end users commit into SVN
118 // backend is really git... - so pre-commit hooks have to be from svn
119 list($row) = MTrackDB::q('select repoid, scmtype from repos where repopath = ?', $path)->fetchAll();
120 if (isset($row[0])) {
122 if (isset(self::$scms[$type])) {
123 $class = self::$scms[$type];
124 return new $class($row[0]);
126 throw new Exception("unsupported repo type $type");
131 static function loadByChangeSet($cs)
134 static $re = array();
135 if (isset($re[$cs])) {
146 project_repo_link l on r.repoid = l.repoid
148 projects p on p.projid = r.projectid
150 (parent is null or length(parent) = 0)
153 ( ? like CONCAT(proj), '%')
155 ( ? like CONCAT(repo), '%')
158 $ar = $q->fetchAll(PDO::FETCH_ASSOC);
160 $re[$cs] = self::loadByName($ar['repo']);
169 function __construct($id = null) {
171 list($row) = MTrackDB::q(
172 'select * from repos where repoid = ?',
174 if (isset($row[0])) {
175 $this->repoid = $row['repoid'];
176 $this->shortname = $row['shortname'];
177 $this->scmtype = $row['scmtype'];
178 $this->repopath = $row['repopath'];
179 $this->browserurl = $row['browserurl'];
180 $this->browsertype = $row['browsertype'];
181 $this->description = $row['description'];
182 $this->parent = $row['parent'];
183 $this->clonedfrom = $row['clonedfrom'];
184 $this->serverurl = $row['serverurl'];
187 throw new Exception("unable to find repo with id = $id");
191 function reconcileRepoSettings() {
192 if (!isset(self::$scms[$this->scmtype])) {
193 throw new Exception("invalid scm type $this->scmtype");
195 $c = self::$scms[$this->scmtype];
197 $s->reconcileRepoSettings($this);
200 function getSCMMetaData() {
204 function getServerURL() {
205 if ($this->serverurl) {
206 return $this->serverurl;
208 $url = MTrackConfig::get('repos', "$this->scmtype.serverurl");
210 return $url . $this->getBrowseRootName();
215 function getCheckoutCommand() {
216 $url = $this->getServerURL();
218 return $this->scmtype . ' clone ' . $this->getServerURL();
227 function getWorkingCopy() {
228 throw new Exception("cannot getWorkingCopy from a generic repo object");
231 function deleteRepo(MTrackChangeset $CS) {
232 MTrackDB::q('delete from repos where repoid = ?', $this->repoid);
233 mtrack_rmdir($this->repopath);
236 function save(MTrackChangeset $CS) {
237 if (!isset(self::$scms[$this->scmtype])) {
238 throw new Exception("unsupported repo type " . $this->scmtype);
242 list($row) = MTrackDB::q(
243 'select * from repos where repoid = ?',
244 $this->repoid)->fetchAll();
247 'update repos set shortname = ?, scmtype = ?, repopath = ?,
248 browserurl = ?, browsertype = ?, description = ?,
249 parent = ?, serverurl = ?, clonedfrom = ? where repoid = ?',
250 $this->shortname, $this->scmtype, $this->repopath,
251 $this->browserurl, $this->browsertype, $this->description,
252 $this->parent, $this->serverurl, $this->clonedfrom, $this->repoid);
256 if (!strlen($this->repopath)) {
257 if (!MTrackConfig::get('repos', 'allow_user_repo_creation')) {
258 throw new Exception("configuration does not allow repo creation");
260 $repodir = MTrackConfig::get('repos', 'basedir');
261 if ($repodir == null) {
262 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
264 if (!is_dir($repodir)) {
268 if (!$this->parent) {
269 $owner = mtrack_canon_username(MTrackAuth::whoami());
270 $this->parent = 'user:' . $owner;
272 list($type, $owner) = explode(':', $this->parent, 2);
275 $P = MTrackProject::loadByName($owner);
277 throw new Exception("invalid project $owner");
279 MTrackACL::requireAllRights("project:$P->projid", 'modify');
282 if ($owner != mtrack_canon_username(MTrackAuth::whoami())) {
283 throw new Exception("can't make a repo for another user");
287 throw new Exception("invalid parent ($this->parent)");
290 if (preg_match("/[^a-zA-Z0-9_.-]/", $owner)) {
291 throw new Exception("$owner must not contain special characters");
293 $this->repopath = $repodir . DIRECTORY_SEPARATOR . $owner;
294 if (!is_dir($this->repopath)) {
295 mkdir($this->repopath);
297 $this->repopath .= DIRECTORY_SEPARATOR . $this->shortname;
299 /* default ACL is allow user all rights, block everybody else */
301 array($owner, 'read', 1),
302 array($owner, 'modify', 1),
303 array($owner, 'delete', 1),
304 array($owner, 'checkout', 1),
305 array($owner, 'commit', 1),
306 array('*', 'read', 0),
307 array('*', 'modify', 0),
308 array('*', 'delete', 0),
309 array('*', 'checkout', 0),
310 array('*', 'commit', 0),
314 MTrackDB::q('insert into repos (shortname, scmtype,
315 repopath, browserurl, browsertype, description, parent,
316 serverurl, clonedfrom)
317 values (?, ?, ?, ?, ?, ?, ?, ?, ?)',
318 $this->shortname, $this->scmtype, $this->repopath,
319 $this->browserurl, $this->browsertype, $this->description,
320 $this->parent, $this->serverurl, $this->clonedfrom);
322 $this->repoid = MTrackDB::lastInsertId('repos', 'repoid');
326 MTrackACL::setACL("repo:$this->repoid", 0, $acl);
327 $me = mtrack_canon_username(MTrackAuth::whoami());
328 foreach (array('ticket', 'changeset') as $e) {
330 'insert into watches (otype, oid, userid, medium, event, active) values (?, ?, ?, ?, ?, 1)',
331 'repo', $this->repoid, $me, 'email', $e);
335 $this->reconcileRepoSettings();
336 if (!$this->parent) {
337 /* for SSH access, populate a symlink from the repos basedir to the
338 * actual path for this repo */
339 $repodir = MTrackConfig::get('repos', 'basedir');
340 if ($repodir == null) {
341 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
343 if (!is_dir($repodir)) {
346 $repodir .= '/default';
347 if (!is_dir($repodir)) {
350 $repodir .= '/' . $this->shortname;
351 if (!file_exists($repodir)) {
352 symlink($this->repopath, $repodir);
353 } else if (is_link($repodir) && readlink($repodir) != $this->repopath) {
355 symlink($this->repopath, $repodir);
358 $CS->add("repo:" . $this->repoid . ":shortname", $old['shortname'], $this->shortname);
359 $CS->add("repo:" . $this->repoid . ":scmtype", $old['scmtype'], $this->scmtype);
360 $CS->add("repo:" . $this->repoid . ":repopath", $old['repopath'], $this->repopath);
361 $CS->add("repo:" . $this->repoid . ":browserurl", $old['browserurl'], $this->browserurl);
362 $CS->add("repo:" . $this->repoid . ":browsertype", $old['browsertype'], $this->browsertype);
363 $CS->add("repo:" . $this->repoid . ":description", $old['description'], $this->description);
364 $CS->add("repo:" . $this->repoid . ":parent", $old['parent'], $this->parent);
365 $CS->add("repo:" . $this->repoid . ":clonedfrom", $old['clonedfrom'], $this->clonedfrom);
366 $CS->add("repo:" . $this->repoid . ":serverurl", $old['serverurl'], $this->serverurl);
368 foreach ($this->links_to_add as $link) {
369 MTrackDB::q('insert into project_repo_link (projid, repoid, repopathregex) values (?, ?, ?)', $link[0], $this->repoid, $link[1]);
371 foreach ($this->links_to_remove as $linkid) {
372 MTrackDB::q('delete from project_repo_link where repoid = ? and linkid = ?', $this->repoid, $linkid);
378 if ($this->links === null) {
379 $this->links = array();
380 foreach (MTrackDB::q('select linkid, projid, repopathregex
381 from project_repo_link where repoid = ? order by repopathregex',
382 $this->repoid)->fetchAll() as $row) {
383 $this->links[$row[0]] = array($row[1], $row[2]);
389 function addLink($proj, $regex)
391 if ($proj instanceof MTrackProject) {
392 $this->links_to_add[] = array($proj->projid, $regex);
394 $this->links_to_add[] = array($proj, $regex);
398 function removeLink($linkid)
400 $this->links_to_remove[$linkid] = $linkid;
403 function getBranches() {}
404 function getTags() {}
405 function readdir($path, $object = null, $ident = null) {}
406 function file($path, $object = null, $ident = null) {}
407 function history($path, $limit = null, $object = null, $ident = null){}
408 function diff($path, $from = null, $to = null) {}
409 function getRelatedChanges($revision) {}
411 function projectFromPath($filename)
413 static $links = array();
414 if (!isset($links[$this->repoid]) || $links[$this->repoid] === null) {
415 $links[$this->repoid] = array();
416 foreach (MTrackDB::q(
417 'select projid, repopathregex from project_repo_link where repoid = ?',
418 $this->repoid) as $row) {
419 $re = str_replace('/', '\\/', $row[1]);
420 $links[$this->repoid][] = array($row[0], "/$re/");
423 if (is_array($filename)) {
424 $proj_incidence = array();
425 foreach ($filename as $file) {
426 $proj = $this->projectFromPath($file);
427 if ($proj === null) continue;
428 if (isset($proj_incidence[$proj])) {
429 $proj_incidence[$proj]++;
431 $proj_incidence[$proj] = 1;
436 foreach ($proj_incidence as $proj => $count) {
437 if ($count > $the_proj_count) {
438 $the_proj_count = $count;
445 if ($filename instanceof MTrackSCMFileEvent) {
446 $filename = $filename->name;
449 // walk through the regexes; take the longest match as definitive
452 if ($filename[0] != '/') {
453 $filename = '/' . $filename;
455 foreach ($links[$this->repoid] as $link) {
456 if (preg_match($link[1], $filename, $M)) {
457 if (strlen($M[0]) > strlen($longest)) {
459 $longest_id = $link[0];
466 function historyWithChangelog($path, $limit = null, $object = null, $ident = null)
469 $ents = $this->history($path, $limit, $object, $ident);
470 $data = new StdClass;
478 // Determine project from the file list
479 $the_proj = $this->projectFromPath($ent->files);
481 $proj = MTrackProject::loadById($the_proj);
482 $changelog = $proj->adjust_links($ent->changelog, true);
484 $changelog = $ent->changelog;
486 $data->changelog = $changelog;
488 //if (is_array($ent->files)) foreach ($ent->files as $file) {
489 // $file->diff = mtrack_diff($repo->diff($file, $ent->rev));
495 function historyWithChangelogAndDiff($path, $limit = null, $object = null, $ident = null)
497 $ret = $this->historyWithChangelog($path, $limit, $object, $ident);
501 if (!is_array($ret->ent->files)) {
504 foreach ($ret->ent->files as $file) {
505 // where is mtrack_diff...
506 $file->diff = mtrack_diff($this->diff($file, $ret->ent->rev));
514 function displayName()
516 // fixme - this code needs to be in here.. rather than in SCM?
517 return MTrackSCM::makeDisplayName($this);
519 function descriptionToHtml()
521 return MTrack_Wiki::format_to_html($this->description);
524 static function defaultRepo($cfg = null)
526 static $defrepo = null;
527 if ($defrepo !== null) {
528 return $defrepo; // already to it..
532 if ($defrepo !== null) {
533 $defrepo = strpos($defrepo, '/') === false ? 'default/' . $defrepo : $defrepo;
538 $q = MTrackDB::q( 'select parent, shortname from repos order by shortname');
539 foreach($q->fetchAll() as $row) {
540 $defrepo = MTrackSCM::makeDisplayName($row);