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 MTrackRepo 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 registerSCM($scmtype, $classname) {
30 self::$scms[$scmtype] = $classname;
32 static function getAvailableSCMs() {
34 foreach (self::$scms as $t => $classname) {
40 static function loadById($id) {
41 list($row) = MTrackDB::q(
42 'select repoid, scmtype from repos where repoid = ?',
46 if (isset(self::$scms[$type])) {
47 $class = self::$scms[$type];
48 return new $class($row[0]);
50 throw new Exception("unsupported repo type $type");
54 static function loadByName($name) {
55 $bits = explode('/', $name);
56 if (count($bits) > 1 && $bits[0] == 'default') {
60 if (count($bits) > 1) {
61 /* wez/reponame -> per user repo */
63 $p = "project:$bits[0]";
65 'select repoid, scmtype from repos where shortname = ? and (parent = ? OR parent = ?)',
66 $bits[1], $u, $p)->fetchAll();
69 "select repoid, scmtype from repos where shortname = ? and parent =''",
72 if (is_array($rows) && isset($rows[0])) {
76 if (isset(self::$scms[$type])) {
77 $class = self::$scms[$type];
78 return new $class($row[0]);
80 throw new Exception("unsupported repo type $type");
85 static function loadByLocation($path)
88 // we have magic configuration - end users commit into SVN
89 // backend is really git... - so pre-commit hooks have to be from svn
90 list($row) = MTrackDB::q('select repoid, scmtype from repos where repopath = ?', $path)->fetchAll();
93 if (isset(self::$scms[$type])) {
94 $class = self::$scms[$type];
95 return new $class($row[0]);
97 throw new Exception("unsupported repo type $type");
102 static function loadByChangeSet($cs)
105 static $re = array();
106 if (isset($re[$cs])) {
117 project_repo_link l on r.repoid = l.repoid
119 projects p on p.projid = r.projectid
121 (parent is null or length(parent) = 0)
124 ( ? like CONCAT(proj), '%')
126 ( ? like CONCAT(repo), '%')
129 $ar = $q->fetchAll(PDO::FETCH_ASSOC);
131 $re[$cs] = self::loadByName($ar['repo']);
140 function __construct($id = null) {
142 list($row) = MTrackDB::q(
143 'select * from repos where repoid = ?',
145 if (isset($row[0])) {
146 $this->repoid = $row['repoid'];
147 $this->shortname = $row['shortname'];
148 $this->scmtype = $row['scmtype'];
149 $this->repopath = $row['repopath'];
150 $this->browserurl = $row['browserurl'];
151 $this->browsertype = $row['browsertype'];
152 $this->description = $row['description'];
153 $this->parent = $row['parent'];
154 $this->clonedfrom = $row['clonedfrom'];
155 $this->serverurl = $row['serverurl'];
158 throw new Exception("unable to find repo with id = $id");
162 function reconcileRepoSettings() {
163 if (!isset(self::$scms[$this->scmtype])) {
164 throw new Exception("invalid scm type $this->scmtype");
166 $c = self::$scms[$this->scmtype];
168 $s->reconcileRepoSettings($this);
171 function getSCMMetaData() {
175 function getServerURL() {
176 if ($this->serverurl) {
177 return $this->serverurl;
179 $url = MTrackConfig::get('repos', "$this->scmtype.serverurl");
181 return $url . $this->getBrowseRootName();
186 function getCheckoutCommand() {
187 $url = $this->getServerURL();
189 return $this->scmtype . ' clone ' . $this->getServerURL();
198 function getWorkingCopy() {
199 throw new Exception("cannot getWorkingCopy from a generic repo object");
202 function deleteRepo(MTrackChangeset $CS) {
203 MTrackDB::q('delete from repos where repoid = ?', $this->repoid);
204 mtrack_rmdir($this->repopath);
207 function save(MTrackChangeset $CS) {
208 if (!isset(self::$scms[$this->scmtype])) {
209 throw new Exception("unsupported repo type " . $this->scmtype);
213 list($row) = MTrackDB::q(
214 'select * from repos where repoid = ?',
215 $this->repoid)->fetchAll();
218 'update repos set shortname = ?, scmtype = ?, repopath = ?,
219 browserurl = ?, browsertype = ?, description = ?,
220 parent = ?, serverurl = ?, clonedfrom = ? where repoid = ?',
221 $this->shortname, $this->scmtype, $this->repopath,
222 $this->browserurl, $this->browsertype, $this->description,
223 $this->parent, $this->serverurl, $this->clonedfrom, $this->repoid);
227 if (!strlen($this->repopath)) {
228 if (!MTrackConfig::get('repos', 'allow_user_repo_creation')) {
229 throw new Exception("configuration does not allow repo creation");
231 $repodir = MTrackConfig::get('repos', 'basedir');
232 if ($repodir == null) {
233 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
235 if (!is_dir($repodir)) {
239 if (!$this->parent) {
240 $owner = mtrack_canon_username(MTrackAuth::whoami());
241 $this->parent = 'user:' . $owner;
243 list($type, $owner) = explode(':', $this->parent, 2);
246 $P = MTrackProject::loadByName($owner);
248 throw new Exception("invalid project $owner");
250 MTrackACL::requireAllRights("project:$P->projid", 'modify');
253 if ($owner != mtrack_canon_username(MTrackAuth::whoami())) {
254 throw new Exception("can't make a repo for another user");
258 throw new Exception("invalid parent ($this->parent)");
261 if (preg_match("/[^a-zA-Z0-9_.-]/", $owner)) {
262 throw new Exception("$owner must not contain special characters");
264 $this->repopath = $repodir . DIRECTORY_SEPARATOR . $owner;
265 if (!is_dir($this->repopath)) {
266 mkdir($this->repopath);
268 $this->repopath .= DIRECTORY_SEPARATOR . $this->shortname;
270 /* default ACL is allow user all rights, block everybody else */
272 array($owner, 'read', 1),
273 array($owner, 'modify', 1),
274 array($owner, 'delete', 1),
275 array($owner, 'checkout', 1),
276 array($owner, 'commit', 1),
277 array('*', 'read', 0),
278 array('*', 'modify', 0),
279 array('*', 'delete', 0),
280 array('*', 'checkout', 0),
281 array('*', 'commit', 0),
285 MTrackDB::q('insert into repos (shortname, scmtype,
286 repopath, browserurl, browsertype, description, parent,
287 serverurl, clonedfrom)
288 values (?, ?, ?, ?, ?, ?, ?, ?, ?)',
289 $this->shortname, $this->scmtype, $this->repopath,
290 $this->browserurl, $this->browsertype, $this->description,
291 $this->parent, $this->serverurl, $this->clonedfrom);
293 $this->repoid = MTrackDB::lastInsertId('repos', 'repoid');
297 MTrackACL::setACL("repo:$this->repoid", 0, $acl);
298 $me = mtrack_canon_username(MTrackAuth::whoami());
299 foreach (array('ticket', 'changeset') as $e) {
301 'insert into watches (otype, oid, userid, medium, event, active) values (?, ?, ?, ?, ?, 1)',
302 'repo', $this->repoid, $me, 'email', $e);
306 $this->reconcileRepoSettings();
307 if (!$this->parent) {
308 /* for SSH access, populate a symlink from the repos basedir to the
309 * actual path for this repo */
310 $repodir = MTrackConfig::get('repos', 'basedir');
311 if ($repodir == null) {
312 $repodir = MTrackConfig::get('core', 'vardir') . '/repos';
314 if (!is_dir($repodir)) {
317 $repodir .= '/default';
318 if (!is_dir($repodir)) {
321 $repodir .= '/' . $this->shortname;
322 if (!file_exists($repodir)) {
323 symlink($this->repopath, $repodir);
324 } else if (is_link($repodir) && readlink($repodir) != $this->repopath) {
326 symlink($this->repopath, $repodir);
329 $CS->add("repo:" . $this->repoid . ":shortname", $old['shortname'], $this->shortname);
330 $CS->add("repo:" . $this->repoid . ":scmtype", $old['scmtype'], $this->scmtype);
331 $CS->add("repo:" . $this->repoid . ":repopath", $old['repopath'], $this->repopath);
332 $CS->add("repo:" . $this->repoid . ":browserurl", $old['browserurl'], $this->browserurl);
333 $CS->add("repo:" . $this->repoid . ":browsertype", $old['browsertype'], $this->browsertype);
334 $CS->add("repo:" . $this->repoid . ":description", $old['description'], $this->description);
335 $CS->add("repo:" . $this->repoid . ":parent", $old['parent'], $this->parent);
336 $CS->add("repo:" . $this->repoid . ":clonedfrom", $old['clonedfrom'], $this->clonedfrom);
337 $CS->add("repo:" . $this->repoid . ":serverurl", $old['serverurl'], $this->serverurl);
339 foreach ($this->links_to_add as $link) {
340 MTrackDB::q('insert into project_repo_link (projid, repoid, repopathregex) values (?, ?, ?)', $link[0], $this->repoid, $link[1]);
342 foreach ($this->links_to_remove as $linkid) {
343 MTrackDB::q('delete from project_repo_link where repoid = ? and linkid = ?', $this->repoid, $linkid);
349 if ($this->links === null) {
350 $this->links = array();
351 foreach (MTrackDB::q('select linkid, projid, repopathregex
352 from project_repo_link where repoid = ? order by repopathregex',
353 $this->repoid)->fetchAll() as $row) {
354 $this->links[$row[0]] = array($row[1], $row[2]);
360 function addLink($proj, $regex)
362 if ($proj instanceof MTrackProject) {
363 $this->links_to_add[] = array($proj->projid, $regex);
365 $this->links_to_add[] = array($proj, $regex);
369 function removeLink($linkid)
371 $this->links_to_remove[$linkid] = $linkid;
374 function getBranches() {}
375 function getTags() {}
376 function readdir($path, $object = null, $ident = null) {}
377 function file($path, $object = null, $ident = null) {}
378 function history($path, $limit = null, $object = null, $ident = null){}
379 function diff($path, $from = null, $to = null) {}
380 function getRelatedChanges($revision) {}
382 function projectFromPath($filename)
384 static $links = array();
385 if (!isset($links[$this->repoid]) || $links[$this->repoid] === null) {
386 $links[$this->repoid] = array();
387 foreach (MTrackDB::q(
388 'select projid, repopathregex from project_repo_link where repoid = ?',
389 $this->repoid) as $row) {
390 $re = str_replace('/', '\\/', $row[1]);
391 $links[$this->repoid][] = array($row[0], "/$re/");
394 if (is_array($filename)) {
395 $proj_incidence = array();
396 foreach ($filename as $file) {
397 $proj = $this->projectFromPath($file);
398 if ($proj === null) continue;
399 if (isset($proj_incidence[$proj])) {
400 $proj_incidence[$proj]++;
402 $proj_incidence[$proj] = 1;
407 foreach ($proj_incidence as $proj => $count) {
408 if ($count > $the_proj_count) {
409 $the_proj_count = $count;
416 if ($filename instanceof MTrackSCMFileEvent) {
417 $filename = $filename->name;
420 // walk through the regexes; take the longest match as definitive
423 if ($filename[0] != '/') {
424 $filename = '/' . $filename;
426 foreach ($links[$this->repoid] as $link) {
427 if (preg_match($link[1], $filename, $M)) {
428 if (strlen($M[0]) > strlen($longest)) {
430 $longest_id = $link[0];
437 function historyWithChangelog($path, $limit = null, $object = null, $ident = null)
440 $ents = $this->history($path, $limit, $object, $ident);
441 $data = new StdClass;
449 // Determine project from the file list
450 $the_proj = $this->projectFromPath($ent->files);
452 $proj = MTrackProject::loadById($the_proj);
453 $changelog = $proj->adjust_links($ent->changelog, true);
455 $changelog = $ent->changelog;
457 $data->changelog = $changelog;
459 //if (is_array($ent->files)) foreach ($ent->files as $file) {
460 // $file->diff = mtrack_diff($repo->diff($file, $ent->rev));
466 function historyWithChangelogAndDiff($path, $limit = null, $object = null, $ident = null)
468 $ret = $this->historyWithChangelog($path, $limit, $object, $ident);
472 if (!is_array($ret->ent->files)) {
475 foreach ($ret->ent->files as $file) {
476 // where is mtrack_diff...
477 $file->diff = mtrack_diff($this->diff($file, $ret->ent->rev));
485 function displayName()
487 // fixme - this code needs to be in here.. rather than in SCM?
488 return MTrackSCM::makeDisplayName($this);
490 function descriptionToHtml()
492 return MTrack_Wiki::format_to_html($this->description);
495 static function defaultRepo($cfg = null)
497 static $defrepo = null;
498 if ($defrepo !== null) {
499 return $defrepo; // already to it..
503 if ($defrepo !== null) {
504 $defrepo = strpos($defrepo, '/') === false ? 'default/' . $defrepo : $defrepo;
509 $q = MTrackDB::q( 'select parent, shortname from repos order by shortname');
510 foreach($q->fetchAll() as $row) {
511 $defrepo = MTrackSCM::makeDisplayName($row);