PageRenderTime 53ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/bin/import-trac.php

https://bitbucket.org/yoander/mtrack
PHP | 936 lines | 719 code | 119 blank | 98 comment | 104 complexity | eaf217f557833ff6935ad4ba9301ef76 MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0
  1. <?php # vim:ts=2:sw=2:et:
  2. /* For licensing and copyright terms, see the file named LICENSE */
  3. // Imports data from a trac sqlite database
  4. $name_map = array(
  5. 'description' => 'content',
  6. 'type' => 'classification',
  7. 'estimatedhours' => 'estimated',
  8. 'ec_branches' => 'branches',
  9. 'ec_features' => 'features',
  10. );
  11. $trac_wiki_names = array(
  12. 'TracAccessibility' => true,
  13. 'TracAdmin' => true,
  14. 'TracBackup' => true,
  15. 'TracBrowser' => true,
  16. 'TracCgi' => true,
  17. 'TracChangeset' => true,
  18. 'TracEnvironment' => true,
  19. 'TracFastCgi' => true,
  20. 'TracGuide' => true,
  21. 'TracImport' => true,
  22. 'TracIni' => true,
  23. 'TracInstall' => true,
  24. 'TracInstallPlatforms' => true,
  25. 'TracInterfaceCustomization' => true,
  26. 'TracLinks' => true,
  27. 'TracLogging' => true,
  28. 'TracModPython' => true,
  29. 'TracMultipleProjects' => true,
  30. 'TracNotification' => true,
  31. 'TracPermissions' => true,
  32. 'TracPlugins' => true,
  33. 'TracQuery' => true,
  34. 'TracReports' => true,
  35. 'TracRevisionLog' => true,
  36. 'TracRoadmap' => true,
  37. 'TracRss' => true,
  38. 'TracSearch' => true,
  39. 'TracStandalone' => true,
  40. 'TracSupport' => true,
  41. 'TracSyntaxColoring' => true,
  42. 'TracTickets' => true,
  43. 'TracTicketsCustomFields' => true,
  44. 'TracTimeline' => true,
  45. 'TracUnicode' => true,
  46. 'TracUpgrade' => true,
  47. 'TracWiki' => true,
  48. 'WikiDeletePage' => true,
  49. 'WikiFormatting' => true,
  50. 'WikiHtml' => true,
  51. 'WikiMacros' => true,
  52. 'WikiNewPage' => true,
  53. 'WikiPageNames' => true,
  54. 'WikiProcessors' => true,
  55. 'WikiRestructuredText' => true,
  56. 'WikiRestructuredTextLinks' => true,
  57. 'CamelCase' => true,
  58. 'InterMapTxt' => true,
  59. 'InterTrac' => true,
  60. 'InterWiki' => true,
  61. 'RecentChanges' => true,
  62. 'SandBox' => true,
  63. 'TitleIndex' => true,
  64. );
  65. function trac_date($unix, $trac_version) {
  66. if ($trac_version === '0.12')
  67. $unix = substr($unix, 0, count($unix)-7); /* Remove the last 6 characters which are milliseconds in trac 0.12 */
  68. return $unix;
  69. }
  70. function trac_get_comp($name, $deleted = true)
  71. {
  72. global $CS;
  73. global $components_by_name;
  74. if (!strlen($name)) return null;
  75. if (!isset($components_by_name[$name])) {
  76. /* no longer exists */
  77. $comp = new MTrackComponent;
  78. $comp->name = $name;
  79. $comp->deleted = $deleted;
  80. $comp->save($CS);
  81. $components_by_name[$comp->name] = $comp;
  82. return $comp;
  83. }
  84. return $components_by_name[$name];
  85. }
  86. function trac_assoc_comp_and_proj(MTrackComponent $comp, MTrackProject $proj)
  87. {
  88. static $comp_assoc = array();
  89. if (isset($comp_assoc[$proj->shortname][$comp->name])) {
  90. return;
  91. }
  92. MTrackDB::q('insert into components_by_project (projid, compid)
  93. values (?, ?)', $proj->projid, $comp->compid);
  94. $comp_assoc[$proj->shortname][$comp->name] = true;
  95. }
  96. function cache_mtrack_users()
  97. {
  98. trac_add_user();
  99. }
  100. function trac_add_user($username = NULL)
  101. {
  102. static $users = array();
  103. global $CANON_USERS;
  104. if(is_null($username)) {
  105. $q = MTrackDB::q('select userid from userinfo');
  106. foreach($q->fetchAll(PDO::FETCH_NUM) as $row) {
  107. $users[$row[0]] = true;
  108. }
  109. return null;
  110. }
  111. $username = trim($username);
  112. $username = strtolower($username);
  113. while (isset($CANON_USERS[$username])) {
  114. $username = strtolower($CANON_USERS[$username]);
  115. }
  116. if (preg_match('/[ ,]/', $username)) {
  117. // invalid: attempted to set multiple people.
  118. // take the first one
  119. list($username) = preg_split('/[ ,]+/', $username);
  120. while (isset($CANON_USERS[$username])) {
  121. $username = strtolower($CANON_USERS[$username]);
  122. }
  123. }
  124. if (preg_match('/^\d+(\.\d+)?$/', $username)) {
  125. // invalid (looks like a version number)
  126. return null;
  127. }
  128. if ($username == 'somebody' || $username == '') {
  129. return null;
  130. }
  131. if (isset($users[$username])) {
  132. return $username;
  133. }
  134. $users[$username] = true;
  135. switch ($username) {
  136. case 'trac':
  137. $active = 0;
  138. break;
  139. default:
  140. $active = 1;
  141. }
  142. try {
  143. MTrackDB::q(
  144. 'insert into userinfo (userid, active) values (?, ?)',
  145. $username, $active);
  146. } catch (Exception $e) {
  147. }
  148. return $username;
  149. }
  150. function trac_get_milestone($name, MTrackProject $proj)
  151. {
  152. global $CS;
  153. global $milestone_by_name;
  154. static $alias = array();
  155. $lname = strtolower($name);
  156. if (isset($alias[$proj->shortname][$lname])) {
  157. $name = $alias[$proj->shortname][$lname];
  158. } else {
  159. $alias[$proj->shortname][$lname] = $name;
  160. }
  161. if (!isset($milestone_by_name[$lname])) {
  162. /* first see if there's a milestone with this name in another project */
  163. $ms = MTrackMilestone::loadByName($name);
  164. if ($ms) {
  165. $alias[$proj->shortname][$lname] .= " ($proj->shortname)";
  166. $name = $alias[$proj->shortname][$lname];
  167. }
  168. $ms = new MTrackMilestone();
  169. $ms->name = $name;
  170. $ms->deleted = true;
  171. $ms->description = '';
  172. $ms->save($CS);
  173. $milestone_by_name[$lname] = $ms;
  174. }
  175. return $milestone_by_name[$lname];
  176. }
  177. function trac_get_keyword($word)
  178. {
  179. static $words = array();
  180. if (isset($words[$word])) {
  181. return $words[$word];
  182. }
  183. $kw = MTrackKeyword::loadByWord($word);
  184. if (!$kw) {
  185. global $CS;
  186. $kw = new MTrackKeyword;
  187. $kw->keyword = $word;
  188. $kw->save($CS);
  189. }
  190. $words[$word] = $kw;
  191. return $kw;
  192. }
  193. function progress($msg)
  194. {
  195. static $events = 0;
  196. static $last = 0;
  197. static $clr_eol = null;
  198. static $clr_eod = null;
  199. if ($clr_eol === null) {
  200. /* el: clr_eol
  201. * ed: clr_eos
  202. */
  203. $clr_eol = shell_exec("tput el");
  204. $clr_eod = shell_exec("tput ed");
  205. }
  206. $events++;
  207. $now = time();
  208. if ($events % 10 || $now - $last > 2) {
  209. echo "\r$clr_eod$msg"; flush();
  210. }
  211. $last = $now;
  212. }
  213. $components_by_name = array();
  214. function adjust_links($reason, $ticket_prefix, MTrackProject $project)
  215. {
  216. return $project->adjust_links($reason, $ticket_prefix);
  217. }
  218. function import_from_trac(MTrackProject $project, $import_from_db, $trac_version, $ticket_prefix = false,
  219. $skip_trac_commits = false)
  220. {
  221. global $components_by_name;
  222. global $milestone_by_name;
  223. cache_mtrack_users();
  224. echo "Importing trac database $import_from_db\n"; flush();
  225. $start_import = time();
  226. /* reset this list so that we can detect conflicting names
  227. * across projects */
  228. $milestone_by_name = array();
  229. if (!file_exists("$import_from_db/db/trac.db")) {
  230. echo "No such file $import_from_db/db/trac.db\n";
  231. exit(1);
  232. }
  233. $trac = new PDO('sqlite:' . $import_from_db . "/db/trac.db");
  234. $trac->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  235. //date_default_timezone_set('UTC');
  236. $CS = MTrackChangeset::begin('~import~', "Import trac from $import_from_db");
  237. foreach ($trac->query(
  238. "select type, name, value from enum")->fetchAll()
  239. as $row) {
  240. if ($row['type'] == 'priority') {
  241. try {
  242. $pri = MTrackPriority::loadByName($row['name']);
  243. } catch (Exception $e) {
  244. $pri = new MTrackPriority;
  245. $pri->name = $row['name'];
  246. $pri->value = $row['value'];
  247. $pri->save($CS);
  248. }
  249. }
  250. if ($row['type'] == 'severity') {
  251. try {
  252. $pri = MTrackSeverity::loadByName($row['name']);
  253. } catch (Exception $e) {
  254. $pri = new MTrackSeverity;
  255. $pri->name = $row['name'];
  256. $pri->value = $row['value'];
  257. $pri->save($CS);
  258. }
  259. }
  260. if ($row['type'] == 'resolution') {
  261. try {
  262. $pri = MTrackResolution::loadByName($row['name']);
  263. } catch (Exception $e) {
  264. $pri = new MTrackResolution;
  265. $pri->name = $row['name'];
  266. $pri->value = $row['value'];
  267. $pri->save($CS);
  268. }
  269. }
  270. if ($row['type'] == 'ticket_type') {
  271. try {
  272. $pri = MTrackClassification::loadByName($row['name']);
  273. } catch (Exception $e) {
  274. $pri = new MTrackClassification;
  275. $pri->name = $row['name'];
  276. $pri->value = $row['value'];
  277. $pri->save($CS);
  278. }
  279. }
  280. }
  281. foreach ($trac->query('select name from component')->fetchAll() as $row) {
  282. $comp = trac_get_comp($row['name'], false);
  283. trac_assoc_comp_and_proj($comp, $project);
  284. }
  285. foreach ($trac->query("SELECT * from milestone order by name")
  286. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  287. /* first see if there's a milestone with this name in another project */
  288. $name = $row['name'];
  289. $ms = MTrackMilestone::loadByName($name);
  290. if ($ms) {
  291. $name .= ' (' . $project->shortname . ')';
  292. }
  293. $ms = new MTrackMilestone();
  294. $ms->name = $name;
  295. /* for names of the form: sprint.1 sprint.2, tie them back as
  296. * children of "sprint" */
  297. if (preg_match("/^(.*)\.(\d+)$/", $name, $M)) {
  298. $pms = $milestone_by_name[strtolower($M[1])];
  299. if ($pms !== null) {
  300. $ms->pmid = $pms->mid;
  301. }
  302. }
  303. $ms->description = $row['description'];
  304. $ms->duedate = MTrackDB::unixtime(trac_date($row['due'], $trac_version));
  305. $ms->completed = MTrackDB::unixtime(trac_date($row['completed'], $trac_version));
  306. $ms->save($CS);
  307. $milestone_by_name[strtolower($row['name'])] = $ms;
  308. }
  309. $CS->commit();
  310. $CS = null;
  311. list($maxtkt) = $trac->query("select max(id) from ticket")->fetchAll(PDO::FETCH_COLUMN, 0);
  312. MTrackConfig::append('trac_import',
  313. "max_ticket:$project->shortname", $maxtkt);
  314. /* first pass is to reserve ticket ids that match the trac db */
  315. foreach ($trac->query(
  316. "SELECT * from ticket order by id")
  317. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  318. $row['reporter'] = trac_add_user($row['reporter']);
  319. progress("issue $row[id] $row[reporter]");
  320. $fields = array('summary', 'description', 'resolution', 'status',
  321. 'owner', 'summary', 'component', 'priority', 'severity',
  322. 'changelog',
  323. 'version', 'cc', 'keywords', 'milestone', 'reporter', 'type');
  324. foreach ($trac->query(
  325. "select name, value from ticket_custom where ticket='$row[id]'")
  326. ->fetchAll(PDO::FETCH_ASSOC) as $custom) {
  327. if (strlen($custom['value'])) {
  328. $field = $custom['name'];
  329. $row[$field] = $custom['value'];
  330. $fields[] = $field;
  331. }
  332. }
  333. /* take a peek at the change history on the ticket to see if we can
  334. * determine the original field values */
  335. foreach ($fields as $field) {
  336. foreach ($trac->query(
  337. "SELECT oldvalue from ticket_change where ticket = '" .
  338. $row['id'] . "' and field='$field' order by time LIMIT 1")
  339. ->fetchAll(PDO::FETCH_ASSOC) as $hist) {
  340. if (!strlen($hist['oldvalue'])) {
  341. $row[$field] = null;
  342. } else {
  343. $row[$field] = $hist['oldvalue'];
  344. }
  345. }
  346. }
  347. $ctime = trac_date($row['time'], $trac_version);
  348. MTrackAuth::su($row['reporter']);
  349. $CS = MTrackChangeset::begin('ticket:X', $row['summary'], $ctime);
  350. $issue = new MTrackIssue();
  351. $issue->summary = $row['summary'];
  352. $issue->description = adjust_links($row['description'], $ticket_prefix, $project);
  353. $issue->priority = $row['priority'];
  354. $issue->classification = $row['type'];
  355. $issue->resolution = $row['resolution'];
  356. $issue->severity = $row['severity'];
  357. $issue->changelog = $row['changelog'];
  358. $issue->cc = $row['cc'];
  359. $issue->addEffort(0, $row['estimatedhours']);
  360. $issue->addEffort($row['totalhours']);
  361. if (strlen($row['component'])) {
  362. $comp = trac_get_comp($row['component']);
  363. $issue->assocComponent($comp);
  364. }
  365. if (strlen($row['milestone'])) {
  366. $ms = trac_get_milestone($row['milestone'], $project);
  367. $issue->assocMilestone($ms);
  368. }
  369. foreach (array('keywords', 'features', 'ec_features',
  370. 'version',
  371. 'branches', 'ec_branches') as $field) {
  372. foreach (preg_split("/\s+/", $row[$field]) as $w) {
  373. if (strlen($w)) {
  374. $kw = trac_get_keyword($w);
  375. $issue->assocKeyword($kw);
  376. }
  377. }
  378. }
  379. if (strlen($row['owner']) && $row['owner'] != 'somebody') {
  380. $row['owner'] = trac_add_user($row['owner']);
  381. $issue->owner = $row['owner'];
  382. }
  383. if ($ticket_prefix) {
  384. $issue->nsident = $project->shortname . $row['id'];
  385. } else {
  386. $issue->nsident = $row['id'];
  387. }
  388. $issue->save($CS);
  389. # if ($issue->tid != $row['id']) {
  390. # throw new Exception(
  391. # "expected doc to be created with $row[id], got $issue->tid");
  392. # }
  393. $CS->setObject("ticket:" . $issue->tid);
  394. $CS->commit();
  395. $CS = null;
  396. $issue = null;
  397. MTrackAuth::drop();
  398. }
  399. /* now make a pass through the history to flesh out the comments and
  400. * other changes.
  401. * This can use up a surprising amount of memory, so we stage in
  402. * the work. */
  403. echo "\nLooking for changes in $import_from_db\n"; flush();
  404. $changes = $trac->query(
  405. "select distinct time, ticket, author from
  406. ticket_change order by ticket asc, time, author")
  407. ->fetchAll(PDO::FETCH_NUM);
  408. foreach ($changes as $i => $row) {
  409. // we order by field because we always want "estimatedhours"
  410. // to apply before "hours"
  411. $q = $trac->prepare(
  412. "select * from ticket_change
  413. where time = ? and ticket = ? and author = ?
  414. order by field
  415. ");
  416. $q->execute($row);
  417. $batch = $q->fetchAll(PDO::FETCH_ASSOC);
  418. if (empty($batch)) continue;
  419. list($first) = $batch;
  420. global $CS;
  421. $first['author'] = trac_add_user($first['author']);
  422. MTrackAuth::su($first['author']);
  423. try {
  424. progress("issue $first[ticket] changed by $first[author]");
  425. if ($ticket_prefix) {
  426. $issue = MTrackIssue::loadByNSIdent(
  427. $project->shortname . $first['ticket']);
  428. } else {
  429. $issue = MTrackIssue::loadByNSIdent($first['ticket']);
  430. }
  431. $CS = MTrackChangeset::begin("ticket:" . $issue->tid,
  432. "changed", trac_date($first['time'], $trac_version));
  433. foreach ($batch as $row) {
  434. switch ($row['field']) {
  435. case 'comment':
  436. // Trac commits start with "(In [<rev>])"
  437. if($skip_trac_commits && strpos($row['newvalue'], "(In [") === 0) break;
  438. $row['newvalue'] = adjust_links($row['newvalue'], $ticket_prefix, $project);
  439. $issue->addComment($row['newvalue']);
  440. $CS->setReason($row['newvalue']);
  441. break;
  442. case 'owner':
  443. $row['newvalue'] = trac_add_user($row['newvalue']);
  444. if ($row['newvalue'] == 'somebody') {
  445. $issue->owner = null;
  446. } else {
  447. $issue->owner = $row['newvalue'];
  448. }
  449. break;
  450. case 'status':
  451. if ($row['newvalue'] == 'closed') {
  452. $issue->close();
  453. } else {
  454. $issue->status = $row['newvalue'];
  455. }
  456. break;
  457. case 'description':
  458. $issue->description = adjust_links($row['newvalue'],
  459. $ticket_prefix, $project);
  460. break;
  461. case 'resolution':
  462. case 'summary':
  463. case 'priority':
  464. case 'severity':
  465. case 'changelog':
  466. case 'cc':
  467. $name = $row['field'];
  468. $issue->$name = $row['newvalue'];
  469. break;
  470. case 'component':
  471. foreach ($issue->getComponents() as $comp) {
  472. $comp = trac_get_comp($comp);
  473. if ($comp) {
  474. $issue->dissocComponent($comp);
  475. }
  476. }
  477. if (strlen($row['newvalue'])) {
  478. $comp = trac_get_comp($row['newvalue']);
  479. $issue->assocComponent($comp);
  480. }
  481. break;
  482. case 'milestone':
  483. foreach ($issue->getMilestones() as $ms) {
  484. $ms = trac_get_milestone($ms, $project);
  485. if ($ms) {
  486. $issue->dissocMilestone($ms);
  487. }
  488. }
  489. if (strlen($row['newvalue'])) {
  490. $ms = trac_get_milestone($row['newvalue'], $project);
  491. $issue->assocMilestone($ms);
  492. }
  493. break;
  494. case 'keywords':
  495. case 'features':
  496. case 'ec_features':
  497. case 'ec_branches':
  498. case 'branches':
  499. case 'version':
  500. foreach ($issue->getKeywords() as $w) {
  501. $kw = trac_get_keyword($w);
  502. $issue->dissocKeyword($kw);
  503. }
  504. foreach (preg_split("/\s+/", $row['newvalue']) as $w) {
  505. if (strlen($w)) {
  506. $kw = trac_get_keyword($w);
  507. $issue->assocKeyword($kw);
  508. }
  509. }
  510. break;
  511. case 'type':
  512. $issue->classification = $row['newvalue'];
  513. break;
  514. case 'totalhours':
  515. case 'reporter':
  516. /* ignore */
  517. break;
  518. case 'hours':
  519. $issue->addEffort($row['newvalue'] + 0);
  520. break;
  521. case 'duration':
  522. case 'estimatedhours':
  523. $issue->addEffort(0, $row['newvalue'] + 0);
  524. break;
  525. default:
  526. throw new Exception("cant handle field $row[field]");
  527. }
  528. }
  529. if(is_object($issue)) {
  530. $issue->save($CS);
  531. }
  532. else {
  533. print "\nWTF? Issue isn't an object now?\n";
  534. }
  535. $issue = null;
  536. $CS->commit();
  537. $CS = null;
  538. } catch (Exception $e) {
  539. MTrackAuth::drop();
  540. throw $e;
  541. }
  542. MTrackAuth::drop();
  543. }
  544. /* Find attachments */
  545. foreach ($trac->query(
  546. "select id, filename, size, time, description, author
  547. from attachment where type = 'ticket'")
  548. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  549. MTrackAuth::su($row['author']);
  550. try {
  551. $row['author'] = trac_add_user($row['author']);
  552. $row['filename'] = trac_attachment_name($row['filename']);
  553. progress("issue $row[id] attachment $row[filename] $row[author]");
  554. if ($ticket_prefix) {
  555. $issue = MTrackIssue::loadByNSIdent(
  556. $project->shortname . $row['id']);
  557. } else {
  558. $issue = MTrackIssue::loadByNSIdent($row['id']);
  559. }
  560. $CS = MTrackChangeset::begin("ticket:" . $issue->tid,
  561. $row['description'], trac_date($row['time'], $trac_version));
  562. $afile = $import_from_db . "/attachments/ticket/$row[id]/";
  563. // trac uses weird url encoding on the filename on disk.
  564. // this weird looking code is because I'm too lazy to reverse
  565. // engineer their encoding
  566. foreach (glob("$afile/*") as $potential) {
  567. if (trac_attachment_name(basename($potential)) == $row['filename']) {
  568. $afile = $potential;
  569. break;
  570. }
  571. }
  572. MTrackAttachment::add("ticket:$issue->tid",
  573. $afile, $row['filename'], $CS);
  574. $CS->commit();
  575. } catch (Exception $e) {
  576. MTrackAuth::drop();
  577. throw $e;
  578. }
  579. MTrackAuth::drop();
  580. }
  581. /* Make another pass over the tickets to catch changes made to the
  582. * database by hand that are not journalled in the trac change tables */
  583. MTrackAuth::su('trac');
  584. foreach ($trac->query(
  585. "SELECT * from ticket order by id")
  586. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  587. $fields = array('summary',
  588. 'description',
  589. 'resolution', 'status',
  590. 'owner', 'summary', 'component', 'priority', 'severity',
  591. 'changelog',
  592. 'version', 'cc', 'keywords', 'milestone', 'reporter', 'type');
  593. foreach ($trac->query(
  594. "select name, value from ticket_custom where ticket=$row[id]")
  595. ->fetchAll(PDO::FETCH_ASSOC) as $custom) {
  596. if (strlen($custom['value'])) {
  597. $field = $custom['name'];
  598. if ($field == 'description') {
  599. $custom['value'] = adjust_links($custom['value'], $ticket_prefix, $project);
  600. }
  601. $row[$field] = $custom['value'];
  602. $fields[] = $field;
  603. }
  604. }
  605. if ($ticket_prefix) {
  606. $issue = MTrackIssue::loadByNSIdent($project->shortname . $row['id']);
  607. } else {
  608. $issue = MTrackIssue::loadByNSIdent($row['id']);
  609. }
  610. $needed = false;
  611. $row['owner'] = trac_add_user($row['owner']);
  612. $fmap = array(
  613. 'summary',
  614. 'description',
  615. 'priority',
  616. 'status',
  617. 'classification' => 'type',
  618. 'resolution',
  619. 'owner',
  620. 'severity');
  621. foreach ($fmap as $sname => $fname) {
  622. if (is_int($sname) || ctype_digit($sname)) {
  623. $sname = $fname;
  624. }
  625. if ($fname == 'description') {
  626. $row[$fname] = adjust_links($row[$fname], $ticket_prefix, $project);
  627. }
  628. if ($issue->$sname != $row[$fname]) {
  629. $needed = true;
  630. $issue->$sname = $row[$fname];
  631. }
  632. }
  633. $comp = $issue->getComponents();
  634. $comp = reset($comp);
  635. if ($comp != $row['component']) {
  636. $needed = true;
  637. $issue->dissocComponent(trac_get_comp($comp));
  638. if (strlen($row['component'])) {
  639. $comp = trac_get_comp($row['component']);
  640. $issue->assocComponent($comp);
  641. }
  642. }
  643. $ms = $issue->getMilestones();
  644. $ms = reset($ms);
  645. if ($ms != $row['milestone']) {
  646. $needed = true;
  647. $issue->dissocMilestone(trac_get_milestone($ms, $project));
  648. if (strlen($row['milestone'])) {
  649. $ms = trac_get_milestone($row['milestone'], $project);
  650. $issue->assocMilestone($ms);
  651. }
  652. }
  653. if ($needed) {
  654. progress("$row[id] fixup");
  655. if ($issue->updated) {
  656. $last_cs = MTrackChangeset::get($issue->updated);
  657. } else {
  658. $last_cs = MTrackChangeset::get($issue->created);
  659. }
  660. $issue->addComment(
  661. "The importer detected manual database changes; " .
  662. "revising ticket to match");
  663. $CS = MTrackChangeset::begin("ticket:" . $issue->tid,
  664. "fixup",
  665. strtotime($last_cs->when));
  666. $issue->save($CS);
  667. $CS->commit();
  668. }
  669. }
  670. MTrackAuth::drop();
  671. echo "\nProcessing wiki pages\n"; flush();
  672. /* wiki, jungle is posse */
  673. global $trac_wiki_names;
  674. $wiki = null;
  675. $wiki_page_remap = array();
  676. $suf = MTrackConfig::get('core', 'wikifilenamesuffix');
  677. if (!strlen($suf)) {
  678. /* Here's a fun problem; trac allows both pages and dirs to exist with the
  679. * same name (because its dirs aren't really dirs, they're just illusions)
  680. * We need to notice those that are pages and that collide with dirs and
  681. * rename them */
  682. $all_wiki_page_names = array();
  683. foreach ($trac->query(
  684. "select distinct name from wiki")->fetchAll(PDO::FETCH_COLUMN, 0)
  685. as $name) {
  686. $all_wiki_page_names[$name] = $name;
  687. }
  688. foreach ($all_wiki_page_names as $name) {
  689. $elements = explode('/', $name);
  690. if (count($elements) > 1) {
  691. $accum = array();
  692. while (count($elements) > 1) {
  693. $accum[] = array_shift($elements);
  694. $n = join('/', $accum);
  695. if (isset($all_wiki_page_names[$n])) {
  696. // Collision; try adding a suffix of "Page"
  697. if (!isset($all_wiki_page_names[$n . 'Page'])) {
  698. $wiki_page_remap[$n] = $n . 'Page';
  699. } else {
  700. throw new Exception("wiki collision between $n and $name");
  701. }
  702. }
  703. }
  704. }
  705. }
  706. echo "The following pages will be renamed\n";
  707. print_r($wiki_page_remap);
  708. }
  709. foreach ($trac->query(
  710. "SELECT * from wiki order by time, name, version")
  711. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  712. if (isset($trac_wiki_names[$row['name']])) {
  713. continue;
  714. }
  715. if (isset($wiki_page_remap[$row['name']])) {
  716. $row['name'] = $wiki_page_remap[$row['name']];
  717. }
  718. $author = trac_add_user($row['author']);
  719. try {
  720. MTrackAuth::su($author);
  721. $row['author'] = $author;
  722. } catch (Exception $e) {
  723. echo "Error while assuming $author ($row[author])\n";
  724. MTrackAuth::drop();
  725. throw $e;
  726. }
  727. if ($ticket_prefix) {
  728. $row['name'] = $project->shortname . '/' . $row['name'];
  729. }
  730. $CS = MTrackChangeset::begin('wiki:' . $row['name'],
  731. $row['comment'], trac_date($row['time'], $trac_version));
  732. if (!is_object($wiki) || $wiki->pagename != $row['name']) {
  733. $wiki = MTrackWikiItem::loadByPageName($row['name']);
  734. }
  735. if (!$wiki) {
  736. $wiki = new MTrackWikiItem($row['name']);
  737. }
  738. progress("$row[name] $row[version]");
  739. $wiki->content = adjust_links($row['text'], $ticket_prefix, $project);
  740. $wiki->save($CS);
  741. $CS->commit();
  742. MTrackAuth::drop();
  743. }
  744. /* Find attachments */
  745. foreach ($trac->query(
  746. "select id, filename, size, time, description, author
  747. from attachment where type = 'wiki'")
  748. ->fetchAll(PDO::FETCH_ASSOC) as $row) {
  749. MTrackAuth::su($row['author']);
  750. try {
  751. $row['author'] = trac_add_user($row['author']);
  752. $row['filename'] = trac_attachment_name($row['filename']);
  753. progress("wiki $row[id] attachment $row[filename] $row[author]");
  754. if ($ticket_prefix) {
  755. $name = $project->shortname . '/' . $row['id'];
  756. } else {
  757. $name = $row['id'];
  758. }
  759. $wiki = MTrackWikiItem::loadByPageName($name);
  760. if (!$wiki) {
  761. MTrackAuth::drop();
  762. continue;
  763. }
  764. $CS = MTrackChangeset::begin('wiki:' . $name,
  765. $row['description'], trac_date($row['time'], $trac_version));
  766. $afile = $import_from_db . "/attachments/wiki/$row[id]/";
  767. // trac uses weird url encoding on the filename on disk.
  768. // this weird looking code is because I'm too lazy to reverse
  769. // engineer their encoding
  770. foreach (glob("$afile/*") as $potential) {
  771. if (trac_attachment_name(basename($potential)) == $row['filename']) {
  772. $afile = $potential;
  773. break;
  774. }
  775. }
  776. if (!is_file($afile)) {
  777. echo "Looking for attachment $row[filename]\n";
  778. echo "Didn't find it in $afile\n";
  779. $g = glob("$afile/*");
  780. print_r($g);
  781. foreach ($g as $f) {
  782. echo trac_attachment_name($f), "\n";
  783. }
  784. throw new Exception("fail");
  785. }
  786. MTrackAttachment::add("wiki:$name",
  787. $afile, $row['filename'], $CS);
  788. $CS->commit();
  789. } catch (Exception $e) {
  790. MTrackAuth::drop();
  791. throw $e;
  792. }
  793. MTrackAuth::drop();
  794. }
  795. $end_import = time();
  796. $elapsed = $end_import - $start_import;
  797. echo "\nDone with $import_from_db (in $elapsed seconds)\n"; flush();
  798. }
  799. function trac_attachment_name($name)
  800. {
  801. $name = urldecode($name);
  802. $name = str_replace('+', ' ', $name);
  803. return $name;
  804. }