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