import
[web.mtrack] / bin / init.php
1 <?php # vim:ts=2:sw=2:et:
2 /* For licensing and copyright terms, see the file named LICENSE */
3
4 if (function_exists('date_default_timezone_set')) {
5   date_default_timezone_set('UTC');
6 }
7
8 ini_set('memory_limit', 256*1024*1024);
9 $_GLOBALS['MTRACK_CONFIG_SKIP_BOOT'] = true;
10 include 'inc/common.php';
11 include 'bin/import-trac.php';
12
13 if (!file_exists("bin/init.php")) {
14   echo "You must run me from the top-level mtrack dir\n";
15   exit(1);
16 }
17
18 /* People doing this are not necessarily sane, make sure we have PDO and
19  * pdo_sqlite */
20 if (!extension_loaded('PDO') || !extension_loaded('pdo_sqlite')) {
21   echo "Mtrack requires PDO and pdo_sqlite to function\n";
22   exit(1);
23 }
24
25 $projects = array();
26 $repos = array();
27 $tracs = array();
28 $links = array();
29 $config_file_name = 'config.ini';
30 $vardir = 'var';
31 $aliasfile = null;
32 $authorfile = null;
33 $wiki_repo_type = null;
34 $DSN = null;
35
36 $SCMS = MTrackRepo::getAvailableSCMs();
37
38 $args = array();
39 array_shift($argv);
40 while (count($argv)) {
41   $arg = array_shift($argv);
42
43   if ($arg == '--trac') {
44     if (count($argv) < 2) {
45       usage("Missing arguments to --trac");
46     }
47     $pname = array_shift($argv);
48     $tracdb = array_shift($argv);
49
50     if (!file_exists($tracdb)) {
51       usage("Tracdb path must be a sqlite database");
52     }
53     $tracs[$tracdb] = $pname;
54     $projects[$pname] = $pname;
55     continue;
56   }
57
58   if ($arg == '--author-alias') {
59     if (count($argv) < 1) {
60       usage("Missing argument to --author-alias");
61     }
62     $aliasfile = array_shift($argv);
63     continue;
64   }
65   if ($arg == '--author-info') {
66     if (count($argv) < 1) {
67       usage("Missing argument to --author-info");
68     }
69     $authorfile = array_shift($argv);
70     continue;
71   }
72
73   if ($arg == '--wiki-type') {
74     if (count($argv) < 1) {
75       usage("Missing argument to --wiki-type");
76     }
77     $wiki_repo_type = array_shift($argv);
78     if (!isset($SCMS[$wiki_repo_type])) {
79       usage("Invalid repo type $wiki_repo_type");
80     }
81     continue;
82   }
83
84   if ($arg == '--repo') {
85     if (count($argv) < 3) {
86       usage("Missing arguments to --repo");
87     }
88     $rname = array_shift($argv);
89     $rtype = array_shift($argv);
90     $rpath = realpath(array_shift($argv));
91
92     if (!isset($SCMS[$rtype])) {
93       usage("Invalid repo type $rtype");
94     }
95
96     switch ($rtype) {
97       case 'hg':
98         if (!is_dir("$rpath/.hg")) {
99           usage("Repo path must be a local hg repo dir");
100         }
101         break;
102       case 'git':
103         if (!is_dir("$rpath/.git")) {
104           usage("Repo path must be a local git repo dir");
105         }
106         break;
107       case 'svn':
108         if (!file_exists("$rpath/format")) {
109           usage("Repo path must be a svn repo");
110         }
111         break;
112       default:
113         usage("Invalid repo type $rtype");
114     }
115
116     $repos[$rname] = array($rname, $rtype, $rpath);
117     continue;
118   }
119
120   if ($arg == '--link') {
121     if (count($argv) < 3) {
122       usage("Missing arguments to --link");
123     }
124     $pname = array_shift($argv);
125     $rname = array_shift($argv);
126     $rloc = array_shift($argv);
127
128     $links[] = array($pname, $rname, $rloc);
129     $projects[$pname] = $pname;
130     continue;
131   }
132
133   if ($arg == '--vardir') {
134     if (count($argv) < 1) {
135       usage("Missing argument to --vardir");
136     }
137     $vardir = array_shift($argv);
138     continue;
139   }
140
141   if ($arg == '--config-file') {
142     if (count($argv) < 1) {
143       usage("Missing argument to --config-file");
144     }
145     $config_file_name = array_shift($argv);
146     continue;
147   }
148
149   if ($arg == '--dsn') {
150     if (count($argv) < 1) {
151       usage("Missing argument to --dsn");
152     }
153     $DSN = array_shift($argv);
154     continue;
155   }
156
157   $args[] = $arg;
158 }
159
160 if (count($args)) {
161   usage("Unhandled arguments");
162 }
163
164 if (file_exists("$vardir/mtrac.db")) {
165   echo "Nothing to do (already configured)\n";
166   exit(1);
167 }
168
169
170 echo "Setting up mtrack with:\n";
171
172 echo "Projects:\n  ";
173 echo join("\n  ", $projects);
174 echo "\n\n";
175
176 echo "Repos:\n";
177 foreach ($repos as $repo) {
178   echo "  " . join(" ", $repo) . "\n";
179   foreach ($links as $link) {
180     if ($link[1] == $repo[0]) {
181       echo "    $link[2] -> $link[0]\n";
182     }
183   }
184 }
185 echo "\n";
186
187 if (count($tracs)) {
188   foreach ($tracs as $tname => $pname) {
189     echo "Import trac $name -> $pname\n";
190   }
191 }
192
193 function usage($msg = '')
194 {
195   echo $msg, <<<TXT
196
197
198 Usage: init
199
200   --wiki-type              specify the repo type to use when initializing wiki
201                            Supported repo types are listed below.
202                            To use a pre-existing wiki, don't use this option,
203                            use --repo wiki instead.
204
205   --repo {name} {type} {repopath}
206                            define a repo named {name} of {type} at {repopath}
207   --link {project} {repo} {location}
208                            link a repo location to a project
209   --trac {project} {tracenv}
210                            import data from a trac environment at {tracenv}
211                            and associate with project {project}
212
213   --vardir {dir}           where to store database and search engine state.
214                            Defaults to "var" in the current directory; will
215                            be created if it does not exist.
216
217   --config-file {filename} Where to create the configuration file.
218                            defaults to config.ini in the current directory.
219
220   --author-alias {filename}
221                            where to find an authors file that maps usernames.
222                            This is used to initialize the canonicalizations
223                            used by the system.  The format is a file of the
224                            form: sourcename=canonicalname
225                            The import will replace all instances of sourcename
226                            with canonicalname in the history, and will record
227                            the mapping so that future items will respect it.
228
229   --author-info {filename}
230                            Where to find a file that will be used to initialize
231                            the userinfo table. The format is:
232                            canonid,fullname,email,active,timezone
233                            where canonid is the canonical username.
234
235   --dsn {dsn}
236                            If specified, should be a compatible PDO DSN
237                            locating the database to store the mtrack state.
238                            If you want to use sqlite, simply omit this
239                            parameter.  If you want to use PostgreSQL, then
240                            you should enter a string like:
241                            pgsql:host=dbhostname
242
243                            mtrack only supports SQLite and PostgreSQL in
244                            this version.
245
246
247 Supported repo types:
248
249
250 TXT;
251
252   foreach (MTrackRepo::getAvailableSCMs() as $t => $r) {
253     $d = $r->getSCMMetaData();
254     printf("  %10s   %s\n", $t, $d['name']);
255   }
256   echo "\n\n\n";
257
258   exit(1);
259 }
260
261 if (!is_dir($vardir)) {
262   mkdir($vardir);
263   chmod($vardir, 02777);
264 }
265 if (!is_dir("$vardir/attach")) {
266   mkdir("$vardir/attach");
267   chmod("$vardir/attach", 02777);
268 }
269
270 putenv("MTRACK_CONFIG_FILE=" . $config_file_name);
271 if (!file_exists($config_file_name)) {
272   /* create a new config file */
273   $CFG = file_get_contents("config.ini.sample");
274   $CFG = str_replace("@VARDIR@", realpath($vardir), $CFG);
275   if (count($projects)) {
276     list($pname) = array_keys($projects);
277   } else {
278     $pname = "mtrack demo";
279   }
280   $CFG = str_replace("@PROJECT@", $pname, $CFG);
281   if ($DSN == null) {
282     $DSN = "sqlite:@{core:dblocation}";
283   }
284   $CFG = str_replace("@DSN@", "\"$DSN\"", $CFG);
285
286   $tools_to_find = array('diff', 'diff3', 'php', 'svn', 'hg',
287     'git', 'svnserve', 'svnlook', 'svnadmin');
288   foreach ($SCMS as $S) {
289     $m = $S->getSCMMetaData();
290     if (isset($m['tools'])) {
291       foreach ($m['tools'] as $t) {
292         $tools_to_find[] = $t;
293       }
294     }
295   }
296
297   /* find reasonable defaults for tools */
298   $tools = array();
299   foreach ($tools_to_find as $toolname) {
300     foreach (explode(PATH_SEPARATOR, getenv('PATH')) as $pdir) {
301       if (DIRECTORY_SEPARATOR == '\\' &&
302           file_exists($pdir . DIRECTORY_SEPARATOR . $toolname . '.exe')) {
303         $tools[$toolname] = $pdir . DIRECTORY_SEPARATOR . $toolname . '.exe';
304         break;
305       } else if (file_exists($pdir . DIRECTORY_SEPARATOR . $toolname)) {
306         $tools[$toolname] = $pdir . DIRECTORY_SEPARATOR . $toolname;
307         break;
308       }
309     }
310     if (!isset($tools[$toolname])) {
311       // let the system find it in the path at runtime
312       $tools[$toolname] = $toolname;
313     }
314   }
315   $toolscfg = '';
316   foreach ($tools as $toolname => $toolpath) {
317     $toolscfg .= "$toolname = \"$toolpath\"\n";
318   }
319   $CFG = str_replace("@TOOLS@", $toolscfg, $CFG);
320   file_put_contents($config_file_name, $CFG);
321 }
322 unset($_GLOBALS['MTRACK_CONFIG_SKIP_BOOT']);
323 MTrackConfig::$ini = null;
324 MTrackDB::$db = null;
325 MTrackTicket_CustomFields::$me = null;
326 MTrackConfig::boot();
327
328 include dirname(__FILE__) . '/schema-tool.php';
329
330 if (file_exists("$vardir/mtrac.db")) {
331   chmod("$vardir/mtrac.db", 0666);
332 }
333
334 $db = MTrackDB::get();
335
336 # if the config has custom fields, or the runtime config from an earlier
337 # installation does, let's update the schema, if needed.
338 MTrackTicket_CustomFields::getInstance()->save();
339
340 MTrackChangeset::$use_txn = false;
341 $db->beginTransaction();
342
343 $CANON_USERS = array();
344 if ($aliasfile) {
345   foreach (file($aliasfile) as $line) {
346     if (preg_match('/^\s*([^=]+)\s*=\s*(.*)\s*$/', $line, $M)) {
347       if (!strlen($M[1])) {
348         continue;
349       }
350       $CANON_USERS[$M[1]] = $M[2];
351     }
352   }
353 }
354
355 foreach ($CANON_USERS as $src => $dest) {
356   MTrackDB::q('insert into useraliases (alias, userid) values (?, ?)',
357     $src, strlen($dest) ? $dest : null);
358 }
359
360 if ($authorfile) {
361   foreach (file($authorfile) as $line) {
362     $author = explode(',', trim($line));
363     if (strlen($author[0])) {
364       MTrackDB::q('insert into userinfo (
365         userid, fullname, email, active, timezone) values
366         (?, ?, ?, ?, ?)',
367         $author[0],
368         $author[1],
369         $author[2],
370         ((int)$author[3]) ? 1 : 0,
371         $author[4]);
372     }
373   }
374 }
375
376 /* set up initial ACL tree structure */
377 $rootobjects = array(
378   'Reports', 'Browser', 'Wiki', 'Timeline', 'Roadmap', 'Tickets',
379   'Enumerations', 'Components', 'Projects', 'User', 'Snippets',
380 );
381
382 foreach ($rootobjects as $rootobj) {
383   MTrackACL::addRootObjectAndRoles($rootobj);
384 }
385
386 # Add forking permissions
387 $ents = MTrackACL::getACL('Browser', false);
388 $ents[] = array('BrowserCreator', 'fork', true);
389 $ents[] = array('BrowserForker', 'fork', true);
390 $ents[] = array('BrowserForker', 'read', true);
391 MTrackACL::setACL('Browser', false, $ents);
392
393 $CS = MTrackChangeset::begin('~setup~', 'initial setup');
394
395 foreach ($projects as $pname) {
396   $p = new MTrackProject;
397   $p->shortname = $pname;
398   $p->name = $pname;
399   $p->save($CS);
400   $projects[$pname] = $p;
401 }
402
403 foreach ($repos as $repo) {
404   $r = new MTrackRepo;
405   $r->shortname = $repo[0];
406   $r->scmtype = $repo[1];
407   $r->repopath = $repo[2];
408
409   foreach ($links as $link) {
410     list($pname, $rname, $loc) = $link;
411     if ($rname == $r->shortname) {
412       $p = $projects[$pname];
413       $r->addLink($p, $loc);
414     }
415   }
416
417   $r->save($CS);
418   $repos[$r->shortname] = $r;
419 }
420
421 if (!isset($repos['wiki'])) {
422   // Set up the wiki repo (if they don't already have one named wiki)
423
424   if ($wiki_repo_type === null) {
425     $wiki_repo_type = MTrackConfig::get('tools', 'hg');
426     if (file_exists($wiki_repo_type)) {
427       $wiki_repo_type = 'hg';
428     } else {
429       $wiki_repo_type = 'svn';
430     }
431   }
432
433   $r = new MTrackRepo;
434   $r->shortname = 'wiki';
435   $r->scmtype = $wiki_repo_type;
436   $r->repopath = realpath($vardir) . DIRECTORY_SEPARATOR . 'wiki';
437   $r->description = 'The mtrack wiki pages are stored here';
438   echo " ** Creating repo 'wiki' of type $r->scmtype to hold Wiki content at $r->repopath\n";
439   echo " ** (use --repo option to specify an alternate location)\n";
440   echo " ** (use --wiki-type option to specify an alternate type)\n";
441   $r->save($CS);
442   $repos['wiki'] = $r;
443
444   $r->reconcileRepoSettings();
445 }
446
447
448 foreach (glob("defaults/wiki/*") as $filename) {
449   $name = basename($filename);
450   echo "wiki: $name\n";
451
452   $w = MTrackWikiItem::loadByPageName($name);
453   if ($name == 'WikiStart' && $w !== null) {
454     /* skip existing WikiStart, as it may have been customized */
455     continue;
456   }
457   if ($w === null) {
458     $w = new MTrackWikiItem($name);
459   }
460
461   $w->content = file_get_contents($filename);
462   $w->save($CS);
463 }
464 touch("$vardir/.initializing");
465 MTrackWikiItem::commitNow();
466
467 foreach (glob("defaults/reports/*") as $filename) {
468   $name = basename($filename);
469   echo "report: $name\n";
470
471   $rep = new MTrackReport;
472   $rep->summary = $name;
473
474   list($sql, $wiki) = explode("\n\n", file_get_contents($filename), 2);
475
476   $rep->description = $wiki;
477   $rep->query = $sql;
478   $rep->save($CS);
479 }
480 if (count($tracs) == 0) {
481   // Default enumerations
482   foreach (array('defect', 'enhancement', 'task') as $v => $c) {
483     $cl = new MTrackClassification;
484     $cl->name = $c;
485     $cl->value = $v;
486     $cl->save($CS);
487   }
488   foreach (array('fixed', 'invalid', 'wontfix', 'duplicate', 'worksforme')
489       as $v => $c) {
490     $cl = new MTrackResolution;
491     $cl->name = $c;
492     $cl->value = $v;
493     $cl->save($CS);
494   }
495   foreach (array('blocker', 'critical', 'major', 'normal', 'minor', 'trivial')
496       as $v => $c) {
497     $cl = new MTrackSeverity;
498     $cl->name = $c;
499     $cl->value = $v;
500     $cl->save($CS);
501   }
502   foreach (array('highest', 'high', 'normal', 'low', 'lowest')
503       as $v => $c) {
504     $cl = new MTrackPriority;
505     $cl->name = $c;
506     $cl->value = $v;
507     $cl->save($CS);
508   }
509   foreach (array('new', 'open', 'closed', 'reopened')
510       as $v => $c) {
511     $cl = new MTrackTicketState;
512     $cl->name = $c;
513     $cl->value = $v;
514     $cl->save($CS);
515   }
516 }
517 $CS->commit();
518
519 $i = 0;
520 foreach ($tracs as $tracdb => $pname) {
521   import_from_trac($projects[$pname], $tracdb, $i++);
522 }
523 echo "Committing\n"; flush();
524 $db->commit();
525 echo "Done\n";
526 unlink("$vardir/.initializing");