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;
26 private $links_to_add = array();
27 private $links_to_remove = array();
28 private $links = null;
29 static $scms = array();
31 *load class and create instance using array as properties
34 static function factory($ar)
37 $type = ucfirst($ar['scmtype']);
38 $fn = 'MTrack/SCM/'.$type .'/Repo.php';
39 $cls = 'MTrack_SCM_'.$type .'_Repo';
49 static function getAvailableSCMs()
52 $ar = scandir(dirname(__FILE__).'/SCM');
55 if (empty($a) || $a[0] == '.') {
58 $fn = dirname(__FILE__).'/SCM/'.$a.'/Repo.php';
59 if (!file_exists($fn)) {
62 $ret[$a] = MTrack_Repo::factory(array('scmtype'=> $a));
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($ar = null) {
171 if (!is_array($ar)) {
172 return; // can accept empty ctrs
174 foreach($ar as $k=>$v) {
180 function reconcileRepoSettings()
182 $c = self::Factory(array('scmtype'=>$this->scmtype));
183 $s->reconcileRepoSettings($this);
185 // these are needed just to implement the abstract interface..
186 function getSCMMetaData() {
190 function getServerURL()
192 if ($this->serverurl) {
193 return $this->serverurl;
196 $url = MTrackConfig::get('repos', "$this->scmtype.serverurl");
198 return $url . $this->getBrowseRootName();
204 function getCheckoutCommand() {
205 $url = $this->getServerURL();
207 return $this->scmtype . ' clone ' . $this->getServerURL();
216 function getWorkingCopy() {
217 throw new Exception("cannot getWorkingCopy from a generic repo object");
220 function deleteRepo(MTrackChangeset $CS) {
221 MTrackDB::q('delete from repos where repoid = ?', $this->repoid);
222 mtrack_rmdir($this->repopath);
225 function save(MTrackChangeset $CS) {
226 if (!isset(self::$scms[$this->scmtype])) {
227 throw new Exception("unsupported repo type " . $this->scmtype);
231 list($row) = MTrackDB::q(
232 'select * from repos where repoid = ?',
233 $this->repoid)->fetchAll();
236 'update repos set shortname = ?, scmtype = ?, repopath = ?,
237 browserurl = ?, browsertype = ?, description = ?,
238 parent = ?, serverurl = ?, clonedfrom = ? where repoid = ?',
239 $this->shortname, $this->scmtype, $this->repopath,
240 $this->browserurl, $this->browsertype, $this->description,
241 $this->parent, $this->serverurl, $this->clonedfrom, $this->repoid);
245 if (!strlen($this->repopath)) {
246 if (!MTrackConfig::get('repos', 'allow_user_repo_creation')) {
247 throw new Exception("configuration does not allow repo creation");
249 $repodir = MTrackConfig::get('repos', 'basedir');
250 if ($repodir == null) {
251 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
253 if (!is_dir($repodir)) {
257 if (!$this->parent) {
258 $owner = mtrack_canon_username(MTrackAuth::whoami());
259 $this->parent = 'user:' . $owner;
261 list($type, $owner) = explode(':', $this->parent, 2);
264 $P = MTrackProject::loadByName($owner);
266 throw new Exception("invalid project $owner");
268 MTrackACL::requireAllRights("project:$P->projid", 'modify');
271 if ($owner != mtrack_canon_username(MTrackAuth::whoami())) {
272 throw new Exception("can't make a repo for another user");
276 throw new Exception("invalid parent ($this->parent)");
279 if (preg_match("/[^a-zA-Z0-9_.-]/", $owner)) {
280 throw new Exception("$owner must not contain special characters");
282 $this->repopath = $repodir . DIRECTORY_SEPARATOR . $owner;
283 if (!is_dir($this->repopath)) {
284 mkdir($this->repopath);
286 $this->repopath .= DIRECTORY_SEPARATOR . $this->shortname;
288 /* default ACL is allow user all rights, block everybody else */
290 array($owner, 'read', 1),
291 array($owner, 'modify', 1),
292 array($owner, 'delete', 1),
293 array($owner, 'checkout', 1),
294 array($owner, 'commit', 1),
295 array('*', 'read', 0),
296 array('*', 'modify', 0),
297 array('*', 'delete', 0),
298 array('*', 'checkout', 0),
299 array('*', 'commit', 0),
303 MTrackDB::q('insert into repos (shortname, scmtype,
304 repopath, browserurl, browsertype, description, parent,
305 serverurl, clonedfrom)
306 values (?, ?, ?, ?, ?, ?, ?, ?, ?)',
307 $this->shortname, $this->scmtype, $this->repopath,
308 $this->browserurl, $this->browsertype, $this->description,
309 $this->parent, $this->serverurl, $this->clonedfrom);
311 $this->repoid = MTrackDB::lastInsertId('repos', 'repoid');
315 MTrackACL::setACL("repo:$this->repoid", 0, $acl);
316 $me = mtrack_canon_username(MTrackAuth::whoami());
317 foreach (array('ticket', 'changeset') as $e) {
319 'insert into watches (otype, oid, userid, medium, event, active) values (?, ?, ?, ?, ?, 1)',
320 'repo', $this->repoid, $me, 'email', $e);
324 $this->reconcileRepoSettings();
325 if (!$this->parent) {
326 /* for SSH access, populate a symlink from the repos basedir to the
327 * actual path for this repo */
328 $repodir = MTrackConfig::get('repos', 'basedir');
329 if ($repodir == null) {
330 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
332 if (!is_dir($repodir)) {
335 $repodir .= '/default';
336 if (!is_dir($repodir)) {
339 $repodir .= '/' . $this->shortname;
340 if (!file_exists($repodir)) {
341 symlink($this->repopath, $repodir);
342 } else if (is_link($repodir) && readlink($repodir) != $this->repopath) {
344 symlink($this->repopath, $repodir);
347 $CS->add("repo:" . $this->repoid . ":shortname", $old['shortname'], $this->shortname);
348 $CS->add("repo:" . $this->repoid . ":scmtype", $old['scmtype'], $this->scmtype);
349 $CS->add("repo:" . $this->repoid . ":repopath", $old['repopath'], $this->repopath);
350 $CS->add("repo:" . $this->repoid . ":browserurl", $old['browserurl'], $this->browserurl);
351 $CS->add("repo:" . $this->repoid . ":browsertype", $old['browsertype'], $this->browsertype);
352 $CS->add("repo:" . $this->repoid . ":description", $old['description'], $this->description);
353 $CS->add("repo:" . $this->repoid . ":parent", $old['parent'], $this->parent);
354 $CS->add("repo:" . $this->repoid . ":clonedfrom", $old['clonedfrom'], $this->clonedfrom);
355 $CS->add("repo:" . $this->repoid . ":serverurl", $old['serverurl'], $this->serverurl);
357 foreach ($this->links_to_add as $link) {
358 MTrackDB::q('insert into project_repo_link (projid, repoid, repopathregex) values (?, ?, ?)', $link[0], $this->repoid, $link[1]);
360 foreach ($this->links_to_remove as $linkid) {
361 MTrackDB::q('delete from project_repo_link where repoid = ? and linkid = ?', $this->repoid, $linkid);
367 if ($this->links === null) {
368 $this->links = array();
369 foreach (MTrackDB::q('select linkid, projid, repopathregex
370 from project_repo_link where repoid = ? order by repopathregex',
371 $this->repoid)->fetchAll() as $row) {
372 $this->links[$row[0]] = array($row[1], $row[2]);
378 function addLink($proj, $regex)
380 if ($proj instanceof MTrackProject) {
381 $this->links_to_add[] = array($proj->projid, $regex);
383 $this->links_to_add[] = array($proj, $regex);
387 function removeLink($linkid)
389 $this->links_to_remove[$linkid] = $linkid;
392 function getBranches() {}
393 function getTags() {}
394 function readdir($path, $object = null, $ident = null) {}
395 function file($path, $object = null, $ident = null) {}
396 function history($path, $limit = null, $object = null, $ident = null){}
397 function diff($path, $from = null, $to = null) {}
398 function getRelatedChanges($revision) {}
400 function projectFromPath($filename)
402 static $links = array();
403 if (!isset($links[$this->repoid]) || $links[$this->repoid] === null) {
404 $links[$this->repoid] = array();
405 foreach (MTrackDB::q(
406 'select projid, repopathregex from project_repo_link where repoid = ?',
407 $this->repoid) as $row) {
408 $re = str_replace('/', '\\/', $row[1]);
409 $links[$this->repoid][] = array($row[0], "/$re/");
412 if (is_array($filename)) {
413 $proj_incidence = array();
414 foreach ($filename as $file) {
415 $proj = $this->projectFromPath($file);
416 if ($proj === null) continue;
417 if (isset($proj_incidence[$proj])) {
418 $proj_incidence[$proj]++;
420 $proj_incidence[$proj] = 1;
425 foreach ($proj_incidence as $proj => $count) {
426 if ($count > $the_proj_count) {
427 $the_proj_count = $count;
434 if ($filename instanceof MTrackSCMFileEvent) {
435 $filename = $filename->name;
438 // walk through the regexes; take the longest match as definitive
441 if ($filename[0] != '/') {
442 $filename = '/' . $filename;
444 foreach ($links[$this->repoid] as $link) {
445 if (preg_match($link[1], $filename, $M)) {
446 if (strlen($M[0]) > strlen($longest)) {
448 $longest_id = $link[0];
455 function historyWithChangelog($path, $limit = null, $object = null, $ident = null)
458 $ents = $this->history($path, $limit, $object, $ident);
459 $data = new StdClass;
467 // Determine project from the file list
468 $the_proj = $this->projectFromPath($ent->files);
470 $proj = MTrackProject::loadById($the_proj);
471 $changelog = $proj->adjust_links($ent->changelog, true);
473 $changelog = $ent->changelog;
475 $data->changelog = $changelog;
477 //if (is_array($ent->files)) foreach ($ent->files as $file) {
478 // $file->diff = mtrack_diff($repo->diff($file, $ent->rev));
484 function historyWithChangelogAndDiff($path, $limit = null, $object = null, $ident = null)
486 $ret = $this->historyWithChangelog($path, $limit, $object, $ident);
490 if (!is_array($ret->ent->files)) {
493 foreach ($ret->ent->files as $file) {
494 // where is mtrack_diff...
495 $file->diff = mtrack_diff($this->diff($file, $ret->ent->rev));
503 function displayName()
505 // fixme - this code needs to be in here.. rather than in SCM?
506 return MTrackSCM::makeDisplayName($this);
508 function descriptionToHtml()
510 return MTrack_Wiki::format_to_html($this->description);
513 static function defaultRepo($cfg = null)
515 static $defrepo = null;
516 if ($defrepo !== null) {
517 return $defrepo; // already to it..
521 if ($defrepo !== null) {
522 $defrepo = strpos($defrepo, '/') === false ? 'default/' . $defrepo : $defrepo;
527 $q = MTrackDB::q( 'select parent, shortname from repos order by shortname');
528 foreach($q->fetchAll() as $row) {
529 $defrepo = MTrackSCM::makeDisplayName($row);