Advertisement
jterwilliger

trac2jirachangesetlinks

Apr 24th, 2014
394
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 13.57 KB | None | 0 0
  1. <?php
  2. // tracsvn2JiraFisheyeLinkConverter.php
  3.  
  4. //  (c) 2014 Kickboard, Inc.
  5. //  J Terwilliger
  6.  
  7. // free to reuse and modify
  8. // USER ACCEPTS ALL RESPONSIBILITY FOR ANY RESULTS USING THIS SCRIPT; MAKE BACKUPS OF YOUR TRAC DB FIRST!
  9.  
  10.  
  11. $_DEBUG = true;
  12.  
  13. class TracDB extends SQLite3 {
  14.     function __construct() {
  15.         $this->open('db/trac.db');
  16.     }
  17. }
  18.  
  19. function findMatch($row) {
  20.  
  21.     // $checks = array(
  22.     //              'raw' => '/\[(\d+)\]/', // use caution
  23.     //  This is the legacy format, not sure which version it switched up to the
  24.     //              'repo1' => '/\[(\d+)\/Repo1.*\]/',
  25.     //              'repo2' => '/\[(\d+)\/Repo2.*\]/'
  26.     //          );
  27.     $checks = array(   
  28.     //              'raw' => '/\[(\d+)\]/',  // might be too risky for your install, you've been warned
  29.     //  This is our current 0.12.2 changeset format for multiple repositories
  30.                     'repo1' => '/\[changeset:\"(\d+)\/Repo1.*\]/',
  31.                     'repo2' => '/\[changeset:\"(\d+)\/Repo2.*\]/'
  32.                 );             
  33.     foreach ($checks as $checkTitle=>$check) {
  34.         // adjust $row id when searching different table field.
  35.         $res = preg_match($check, $row['newvalue'], $match);
  36.        
  37.         if ($res) {
  38.             // these infiltrators came from cut and paste MySQL errors in ticket comments
  39.             // and descriptions, obviously use discretion or disable if not an issue
  40.             // these are only useful if the 'raw' check is enabled
  41.             // if (in_array($match[1],array("23000","42000",'0','4096'))) return false;
  42.             echo "Found $checkTitle changeset: ".$match[1]."\n";
  43.             $match[2] = $checkTitle;
  44.             return $match;
  45.         }
  46.     }
  47.    
  48.     return false;
  49. }
  50.  
  51. function updateChangesetLinks($row, $match) {
  52.     global $_DEBUG, $db;
  53.    
  54.     // Sample repo links for an independent FishEye installation and trac install
  55.     // customize these with link format that works for your install of JIRA and FishEye
  56.     // and/or existing trac install
  57.     if ($match[2] == 'repo2') {
  58.         $newLinks = "[Fisheye {$match[1]}|https://fisheye.yourcompany.com/fisheye/changelog/repo2?cs={$match[1]}] [trac {$match[1]}|https://dev.yourcompany.com/projects/YourProject/changeset/{$match[1]}/repo2]";
  59.     }
  60.     else {
  61.         $newLinks = "[Fisheye {$match[1]}|https://fisheye.yourcompany.com/fisheye/changelog/repo1?cs={$match[1]}] [trac {$match[1]}|https://dev.yourcompany.com/projects/YourProject/changeset/{$match[1]}/repo1]";
  62.     }
  63.     // adjust $row id when searching different table field.
  64.     $newComment = str_replace($match[0], $newLinks, $row['newvalue']);
  65.     // cleans these out from SVN autocommit messages, if found
  66.     $newComment = preg_replace('/#\!CommitTicket.*on="\d+"/', '', $newComment);
  67.    
  68.     // prevents errors when inserint comments with double quotes
  69.     // do this last or previous replaces may not work
  70.     $newComment = str_replace('"', '""', $newComment);
  71.    
  72.     if ($_DEBUG)
  73.         echo "NEW COMMENT:\n$newComment\n\n";
  74.    
  75.     // COMMENT FOLLOWING LINE TO MAKE UPDATES PERMANENT
  76.     // RECOMMEND TO DRY RUN, SCAN OUTPUT AND FIX ALL WARNINGS SEVERAL TIMES BEFORE PROCEEDING!
  77.     return $newComment;
  78.    
  79.     // the where clause is the unique key for this table,
  80.     // use care when porting other tables to include all unique key field where clauses
  81.     $query = "UPDATE ticket_change SET newvalue = \"$newComment\" WHERE ticket = '{$row['ticket']}' AND time = '{$row['time']}' AND field = '{$row['field']}'";
  82.     // alternate table query, remember to adjust ALL $row ids before switching to different tables
  83.     // $query = "UPDATE ticket SET description = \"$newComment\" WHERE id = '{$row['id']}'";
  84.    
  85.     $r = $db->exec($query);
  86.     if (!$r) {
  87.        
  88.         echo "Unable to update database with query:\n$query\n\n";
  89.     }
  90.     else {
  91.         echo "VERIFY: ".verifyChangeset($row);
  92.     }
  93.    
  94.     return $newComment;
  95. }
  96.  
  97. function verifyChangeset($row) {
  98.     global $db;
  99.    
  100.     // the where clause is the unique key for this table,
  101.     // use care when porting other tables to include all unique key field where clauses
  102.     $query = "select * from ticket_change where field = 'comment' and time = '{$row['time']}' and ticket = '{$row['ticket']}'";
  103.  
  104.     $result = $db->query($query);
  105.     if (!$result)
  106.        die("Error with query: $query");
  107.  
  108.     $verifiedRow = $result->fetchArray(SQLITE3_ASSOC);
  109.     return var_export($verifiedRow,true);
  110. }
  111.  
  112. // init main
  113.  
  114. $db = new TracDB();
  115.  
  116. // this is the table we are targetting for upgrade
  117. // you can switch this out with other tables but must also align all other
  118. // queries and $row key values accordingly.
  119. $query = "select * from ticket_change where field = 'comment'";
  120. // $query = "select * from ticket";
  121. $result = $db->query($query);
  122.  
  123. if (!$result)
  124.    die("Error with query: $query");
  125.  
  126. while ($row = $result->fetchArray(SQLITE3_ASSOC)) {
  127.    
  128.     $match = findMatch($row);
  129.    
  130.     if ($match) {
  131.         $replaced = updateChangesetLinks($row, $match);
  132.     }
  133. }
  134.  
  135. echo "\nDONE\n";
  136.  
  137. $db->close();
  138.  
  139. /*
  140. How I imported a 4 year old legacy trac with multiple subversion repositories and changeset links to Atlassian JIRA.
  141.    
  142. First, rolled up count(*) with group by queries on all schema tables with an sid, username, author, reporter, owner, cc. eg:
  143.  
  144. SELECT count(*), cc FROM ticket GROUP BY cc;
  145.  
  146. then used variations on sample queries below to fix name typos or account renames and delete items I knew were no longer needed.  You could also rename previous developers you no longer need to identify into a single 'devuser' account.  Another performance gain would be to clean out your ticket_custom entries you do not need to preserve going forward, this will save time on the issue import.
  147.  
  148. Obviously use care in this cleanup, one mistake and you need to start over.  I compiled a list of rollup queries and update commands before hand, and ran them in succession until it looked right.  Each time I failed I restored the trac.db and started over.
  149.    
  150. JIRA seems to only care about the ticket and ticket_change fields when importing users, but could also be looking at other fields.  ticket.cc field is the real tricky one since it is prone to typos and all kinds of dupes, email addresses, etc, which is why I just set them all to '' (see below.)
  151.        
  152. When I was satisfied the data was aligned for users, I made a backup of the trac.db in case the SVN link conversion failed.
  153.  
  154. I ran this PHP script to update trac subversion repo links to be fully portable to a JIRA install using Fisheye and also retain a remote trac link for archive access.  In order to pair down our users to < 11 for a starter JIRA account, we chose to start our Fisheye import at a later changeset so not all ported Fisheye links would work, hence the extra link to the legacy trac install.  Your mileage and needs may vary, so please read the PHP script thoroughly and customize for your liking and test run it until you are sure it will suffice and there are no warnings.  If you make a mistake you need to restart from a restored trac.db.  This script was written and tested on a standard PHP 5.4 install with the Sqlite3 plugin compiled.  Although it should work with PHP 5.2 or 5.3 with Sqlite3 plugin.  It only took a few seconds to update several thousand ticket_change records.
  155.  
  156. After this, I cleaned all files out of the trac install folder (working from a backup folder, of course) which meant everything but db/trac.db, attachments/ticket/* and conf/trac.ini. Then I made a zip from within the trac install folder.
  157.  
  158. With the 553 attachment files and our trac DB, the final zip file was about 126M and took about 10 minutes to upload to the JIRA trac project importer. Prior to import, I created an empty project on JIRA 'Trac Archive' and downloaded the free trac workflow from Atlassian marketplace and made sure I switched the scheme successfully. For some reason the workflow switch had a problem sticking for me, so I kept the workflow settings window open and used a new window for the actual Project Import process just in case selecting the workflow failed during import, I could try again without having to restart/reupload the whole import again.
  159.  
  160. Aside from the workflow hiccup on import, most everything else went smoothly. I did have to assign the status fields manually, but everything else mapped correctly.
  161.  
  162. The final import of our 4001 issues and 553 attachments took about another 10 minutes or so.
  163.  
  164. This took three tries before I got it right, so be prepared to delete your Trac Archive project and start over incase of a snafu; they can happen, it's an involved process.
  165.  
  166. // sample queries to use to trim or align users and custom fields prior to import
  167.  
  168. update attachment set author = 'correct' where author = 'Wrong';
  169. update component set owner = '';
  170. update ticket set cc = '';
  171. update ticket set reporter = 'correct' where reporter = 'Wrong';
  172. update ticket set owner = 'correct' where owner = 'Wrong';
  173. delete from session where sid = 'deleteduser';
  174. delete from session_attribute where sid = 'deleteduser';
  175. delete from permission where username = 'deleteduser';
  176. update ticket_change set author = 'correct' where author = 'Wrong';
  177. update report set author = 'correct' where author = 'Wrong';
  178. update revision set author = 'correct' where author = 'Wrong';
  179. update wiki set author = 'correct' where author = 'Wrong';
  180. delete from ticket_custom where name = 'billable' or name = 'internal';
  181.  
  182. // Our trac schema for comparison: we had several plugins installed:
  183. // advancedworkflow
  184. // burndown
  185. // datefield
  186. // mastertickets
  187. // timingandestimationplugin
  188. // tracjsgantt
  189. // tracopt
  190. // tracrpc
  191. // tracsubtickets
  192.  
  193. .schema
  194. CREATE TABLE attachment (
  195.     type text,
  196.     id text,
  197.     filename text,
  198.     size integer,
  199.     time integer,
  200.     description text,
  201.     author text,
  202.     ipnr text,
  203.     UNIQUE (type,id,filename)
  204. );
  205. CREATE TABLE auth_cookie (
  206.     cookie text,
  207.     name text,
  208.     ipnr text,
  209.     time integer,
  210.     UNIQUE (cookie,ipnr,name)
  211. );
  212. CREATE TABLE bill_date (
  213.             time integer,
  214.             set_when integer,
  215.             str_value text
  216.             );
  217. CREATE TABLE burndown (
  218.     id integer PRIMARY KEY,
  219.     component_name text,
  220.     milestone_name text,
  221.     date text,
  222.     hours_remaining integer
  223. );
  224. CREATE TABLE cache (
  225.     id text PRIMARY KEY,
  226.     generation integer
  227. );
  228. CREATE TABLE component (
  229.     name text PRIMARY KEY,
  230.     owner text,
  231.     description text
  232. );
  233. CREATE TABLE custom_report (id         INTEGER,uuid       VARCHAR(64),maingroup  VARCHAR(255),subgroup   VARCHAR(255),version    INTEGER,ordering   INTEGER);
  234. CREATE TABLE enum (
  235.     type text,
  236.     name text,
  237.     value text,
  238.     UNIQUE (type,name)
  239. );
  240. CREATE TABLE mastertickets (
  241.     source integer,
  242.     dest integer,
  243.     UNIQUE (source,dest)
  244. );
  245. CREATE TABLE milestone (
  246.     name text PRIMARY KEY,
  247.     due integer,
  248.     completed integer,
  249.     description text,
  250.     started integer
  251. );
  252. CREATE TABLE node_change (
  253.     repos integer,
  254.     rev text,
  255.     path text,
  256.     node_type text,
  257.     change_type text,
  258.     base_path text,
  259.     base_rev text,
  260.     UNIQUE (repos,rev,path,change_type)
  261. );
  262. CREATE TABLE permission (
  263.     username text,
  264.     action text,
  265.     UNIQUE (username,action)
  266. );
  267. CREATE TABLE report (
  268.     id integer PRIMARY KEY,
  269.     author text,
  270.     title text,
  271.     query text,
  272.     description text
  273. );
  274. CREATE TABLE repository (
  275.     id integer,
  276.     name text,
  277.     value text,
  278.     UNIQUE (id,name)
  279. );
  280. CREATE TABLE revision (
  281.     repos integer,
  282.     rev text,
  283.     time integer,
  284.     author text,
  285.     message text,
  286.     UNIQUE (repos,rev)
  287. );
  288. CREATE TABLE schedule (
  289.     ticket integer PRIMARY KEY,
  290.     start integer,
  291.     finish integer
  292. );
  293. CREATE TABLE schedule_change (
  294.     ticket integer,
  295.     time integer,
  296.     oldstart integer,
  297.     oldfinish integer,
  298.     newstart integer,
  299.     newfinish integer,
  300.     UNIQUE (ticket,time)
  301. );
  302. CREATE TABLE session (
  303.     sid text,
  304.     authenticated integer,
  305.     last_visit integer,
  306.     UNIQUE (sid,authenticated)
  307. );
  308. CREATE TABLE session_attribute (
  309.     sid text,
  310.     authenticated integer,
  311.     name text,
  312.     value text,
  313.     UNIQUE (sid,authenticated,name)
  314. );
  315. CREATE TABLE subtickets (
  316.     parent integer,
  317.     child integer,
  318.     UNIQUE (parent,child)
  319. );
  320. CREATE TABLE system (
  321.     name text PRIMARY KEY,
  322.     value text
  323. );
  324. CREATE TABLE ticket (
  325.     id integer PRIMARY KEY,
  326.     type text,
  327.     time integer,
  328.     changetime integer,
  329.     component text,
  330.     severity text,
  331.     priority text,
  332.     owner text,
  333.     reporter text,
  334.     cc text,
  335.     version text,
  336.     milestone text,
  337.     status text,
  338.     resolution text,
  339.     summary text,
  340.     description text,
  341.     keywords text
  342. );
  343. CREATE TABLE ticket_change (
  344.     ticket integer,
  345.     time integer,
  346.     author text,
  347.     field text,
  348.     oldvalue text,
  349.     newvalue text,
  350.     UNIQUE (ticket,time,field)
  351. );
  352. CREATE TABLE ticket_custom (
  353.     ticket integer,
  354.     name text,
  355.     value text,
  356.     UNIQUE (ticket,name)
  357. );
  358. CREATE TABLE version (
  359.     name text PRIMARY KEY,
  360.     time integer,
  361.     description text
  362. );
  363. CREATE TABLE wiki (
  364.     name text,
  365.     version integer,
  366.     time integer,
  367.     author text,
  368.     ipnr text,
  369.     text text,
  370.     comment text,
  371.     readonly integer,
  372.     UNIQUE (name,version)
  373. );
  374. CREATE INDEX burndown_id_idx ON burndown (id);
  375. CREATE INDEX node_change_repos_rev_idx ON node_change (repos,rev);
  376. CREATE INDEX revision_repos_time_idx ON revision (repos,time);
  377. CREATE INDEX schedule_change_ticket_idx ON schedule_change (ticket);
  378. CREATE INDEX schedule_change_time_idx ON schedule_change (time);
  379. CREATE INDEX schedule_ticket_idx ON schedule (ticket);
  380. CREATE INDEX session_authenticated_idx ON session (authenticated);
  381. CREATE INDEX session_last_visit_idx ON session (last_visit);
  382. CREATE INDEX ticket_change_ticket_idx ON ticket_change (ticket);
  383. CREATE INDEX ticket_change_time_idx ON ticket_change (time);
  384. CREATE INDEX ticket_status_idx ON ticket (status);
  385. CREATE INDEX ticket_time_idx ON ticket (time);
  386. CREATE INDEX wiki_time_idx ON wiki (time);
  387.  
  388.  
  389. */
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement