Project

General

Profile

Patch #989 » bz2redmine.rb

Alexander Zhovnuvaty, 2010-04-06 23:17

 
1
#!/usr/local/bin/ruby
2
#
3
# Copyright (c) 2009, Ralph Juhnke
4
# All rights reserved.
5
#
6
# Redistribution and use in source and binary forms, with or without modification, 
7
# are permitted provided that the following conditions are met:
8
#
9
#    1. Redistributions of source code must retain the above copyright notice, 
10
#       this list of conditions and the following disclaimer.
11
#    2. Redistributions in binary form must reproduce the above copyright notice, 
12
#       this list of conditions and the following disclaimer in the documentation and/or other
13
#       materials provided with the distribution.
14
# 
15
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
16
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
17
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 
18
# EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
19
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
20
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 
21
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
22
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
23
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
24
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
# 
26

    
27
require "rubygems"
28
require "mysql"
29
require "settings"
30

    
31
class ConnectionInfo
32
  attr_accessor :host
33
  attr_accessor :user
34
  attr_accessor :password
35
  attr_accessor :dbname
36

    
37
  def initialize(host, user, password, dbname)
38
    @host = host
39
    @user = user
40
    @password = password
41
    @dbname = dbname
42
  end
43
end
44

    
45
class BugzillaToRedmine
46
  def initialize
47
    @bugzillainfo =  ConnectionInfo.new(BUGZILLA_HOST, BUGZILLA_USER, BUGZILLA_PASSWORD, BUGZILLA_DB)
48
    @redmineinfo = ConnectionInfo.new(REDMINE_HOST, REDMINE_USER, REDMINE_PASSWORD, REDMINE_DB)
49

    
50
    # Bugzilla priority to Redmine priority map
51
    @issuePriorities = ISSUE_PRIORITIES
52

    
53
    # Bugzilla severity to Redmine tracker map
54
    @issueTrackers = ISSUE_TRACKERS
55

    
56
    # Bugzilla status to Redmine status map
57
    @issueStatus = ISSUE_STATUS 
58
  end
59

    
60
  def migrate
61
    self.open_connections
62
    self.clear_redmine_tables
63
    self.migrate_projects
64
    self.migrate_versions
65
    self.migrate_users
66
	self.migrate_groups
67
    self.migrate_members
68
	self.migrate_member_roles
69
	self.migrate_groups_users
70
	self.migrate_categories
71
    self.migrate_issues
72
	self.migrate_watchers
73
    self.migrate_issue_relations
74
    self.migrate_attachments
75
    self.close_connections
76
  end
77

    
78
  def open_connections
79
    @bugzilladb = self.open_connection(@bugzillainfo)
80
    @redminedb = self.open_connection(@redmineinfo)
81
  end
82

    
83
  def close_connections
84
    self.log "closing database connections"
85
    @bugzilladb.close
86
    @redminedb.close
87
  end
88

    
89
  def 	open_connection(info)
90
    self.log "opening #{info.inspect}"
91
    return Mysql::new(info.host, info.user, info.password, info.dbname)
92
  end
93

    
94
  def clear_redmine_tables
95
    sqls = [
96
      "DELETE FROM projects",
97
      "DELETE FROM projects_trackers",
98
      "DELETE FROM enabled_modules",
99
      "DELETE FROM boards",
100
      "DELETE FROM custom_fields_projects",
101
      "DELETE FROM documents",
102
      "DELETE FROM news",
103
      "DELETE FROM queries",
104
      "DELETE FROM repositories",
105
      "DELETE FROM time_entries",
106
      "DELETE FROM wiki_content_versions",
107
      "DELETE FROM wiki_contents",
108
      "DELETE FROM wiki_pages",
109
      "DELETE FROM wiki_redirects",
110
      "DELETE FROM wikis",
111
    ]
112
    sqls.each do |sql|
113
      self.red_exec_sql(sql)
114
    end
115
  end
116

    
117
  def log(s)
118
    puts s
119
  end
120

    
121
  def migrate_projects
122
    self.bz_select_sql("SELECT products.id, products.name, products.description, products.classification_id, products.disallownew, classifications.name as classification_name FROM products, classifications WHERE products.classification_id = classifications.id") do |row|
123
      identifier = row[1].downcase
124
      status = row[3] == 1 ? 9 : 1
125
      created_at = self.find_min_created_at_for_product(row[0])
126
      updated_at = self.find_max_bug_when_for_product(row[0])
127
      self.red_exec_sql("INSERT INTO projects (id, name, description, is_public, identifier, created_on, updated_on, status) values (?, ?, ?, ?, ?, ?, ?, ?)", row[0], row[1], row[2], 1, identifier,
128
        created_at, updated_at, status)
129
      self.insert_project_trackers(row[0])
130
      self.insert_project_modules(row[0])
131
    end
132
  end
133

    
134
  def find_min_created_at_for_product(product_id)
135
    bug_when = '1970-01-01 10:22:25'
136
    sql = "select min(b.creation_ts) from products p join bugs b on b.product_id = p.id where product_id=?"
137
    self.bz_select_sql(sql, product_id) do |row|
138
      bug_when = row[0]
139
    end
140
    return bug_when
141
  end
142

    
143
  def find_max_bug_when_for_product(product_id)
144
    bug_when = '1970-01-01 10:22:25'
145
    sql = "select max(l.bug_when) from products p join bugs b on b.product_id = p.id join longdescs l on l.bug_id = b.bug_id where b.product_id=?"
146
    self.bz_select_sql(sql, product_id) do |row|
147
      bug_when = row[0]
148
    end
149
    return bug_when
150
  end
151

    
152
  def migrate_versions
153
    self.red_exec_sql("delete from versions")
154
    self.bz_select_sql("SELECT id, product_id AS project_id, value AS name FROM milestones") do |row|
155
      self.red_exec_sql("INSERT INTO versions (id, project_id, name) VALUES (?, ?, ?)", row[0], row[1], row[2])
156
    end
157
  end
158

    
159
  def migrate_users
160
    ["DELETE FROM users",
161
      "DELETE FROM user_preferences",
162
      "DELETE FROM members",
163
      "DELETE FROM member_roles",	  
164
      "DELETE FROM groups_users",	  	  
165
      "DELETE FROM messages",     
166
      "DELETE FROM tokens",
167
      "DELETE FROM watchers"].each do |sql|
168
      self.red_exec_sql(sql)
169
    end
170
    self.bz_select_sql("SELECT userid, login_name, realname, disabledtext FROM profiles") do |row|
171
      user_id = row[0]
172
      login_name = row[1]
173
      real_name = row[2]
174
      disabled_text = row[3]
175
      if real_name.nil? 
176
        (last_name, first_name) = ['empty', 'empty']
177
      else
178
        (last_name, first_name) = real_name.split(/[ ]+/)
179
        if first_name.to_s.strip.empty?
180
          first_name = 'empty'
181
        end
182
      end
183
      status = disabled_text.to_s.strip.empty? ? 1 : 3
184
      self.red_exec_sql("INSERT INTO users (id, login, mail, firstname, lastname, language, mail_notification, status, hashed_password, type) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
185
        user_id, login_name, login_name, last_name, first_name, 'en', 0, status, 'd033e22ae348aeb5660fc2140aec35850c4da997', 'User')
186
      other = """---
187
:comments_sorting: asc
188
:no_self_notified: true
189
      """
190
      self.red_exec_sql("INSERT INTO user_preferences (user_id,others) values (?, ?)", user_id, other)
191
    end
192
  end
193

    
194
  def migrate_groups
195
    self.bz_select_sql("select name from groups") do |row|
196
      name = row[0]
197
      self.red_exec_sql("insert into users (lastname, mail_notification, admin, status, type, language) values (?, ?, ?, ?, ?, ?)",
198
        name, 1, 0, 1, 'Group', 'en')
199
    end
200
  end
201
  
202
  def find_version_id(project_id, version)
203
    result = -1
204
    self.red_select_sql("select id from versions where project_id=? and name=?", project_id, version) do |row|
205
      result = row[0]
206
    end
207
    return result
208
  end
209

    
210
  def find_max_bug_when(bug_id)
211
    bug_when = '1970-01-01 10:22:25'
212
    self.bz_select_sql("select max(bug_when) from longdescs where bug_id=?", bug_id) do |row|
213
      bug_when = row[0]
214
    end
215
    return bug_when
216
  end
217
  
218
  def migrate_categories
219
    self.red_exec_sql("delete from issue_categories")
220
    self.bz_select_sql("SELECT id, name, product_id AS project_id, initialowner AS assigned_to_id FROM components") do |row|
221
      self.red_exec_sql("INSERT INTO issue_categories (id, name, project_id, assigned_to_id) VALUES (?, ?, ?, ?)", row[0], row[1], row[2], row[3])
222
    end
223
  end
224

    
225
  def migrate_watchers
226
    self.red_exec_sql("delete from watchers")
227
    self.bz_select_sql("select bug_id, who FROM cc") do |row|
228
      self.red_exec_sql("insert into watchers (watchable_type, watchable_id, user_id) values (?, ?, ?)", 'Issue', row[0], row[1])
229
    end
230
  end
231

    
232
  def insert_custom_fields
233
	self.red_exec_sql("delete from custom_fields")
234
	self.red_exec_sql("delete from custom_fields_trackers")
235
	self.red_exec_sql("delete from custom_values")	
236
	self.red_exec_sql("INSERT INTO custom_fields (id, type, name, field_format, possible_values, max_length, is_for_all, is_filter, searchable, default_value) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", 1, 'IssueCustomField', 'URL', 'string', '--- []/n/n', 255, 1, 1, 1, '')
237
	[1,2,3].each do |tracker_id|
238
      self.red_exec_sql("INSERT INTO custom_fields_trackers (custom_field_id, tracker_id) VALUES (?, ?)", 1, tracker_id)
239
    end
240
  end
241
  
242
  def migrate_issues
243
    self.red_exec_sql("delete from issues")
244
    self.red_exec_sql("delete from journals")
245
	self.insert_custom_fields
246
    sql = "SELECT bugs.bug_id,
247
		       bugs.assigned_to,
248
		       bugs.bug_status,
249
		       bugs.creation_ts,
250
		       bugs.short_desc,
251
		       bugs.product_id,
252
		       bugs.reporter,
253
		       bugs.version,
254
		       bugs.resolution,
255
		       bugs.estimated_time,
256
		       bugs.remaining_time,
257
		       bugs.deadline,
258
			   bugs.target_milestone,
259
		       bugs.bug_severity,
260
		       bugs.priority,
261
		       bugs.component_id,
262
		       bugs.status_whiteboard AS whiteboard,
263
		       bugs.bug_file_loc AS url,
264
		       longdescs.comment_id,
265
		       longdescs.thetext,
266
		       longdescs.bug_when,
267
		       longdescs.who,
268
		       longdescs.isprivate
269
		   FROM bugs, longdescs
270
		   WHERE bugs.bug_id = longdescs.bug_id
271
		   ORDER BY bugs.bug_id, longdescs.bug_when"
272
    current_bug_id = -1
273
    self.bz_select_sql(sql) do |row|
274
      ( bug_id,
275
        assigned_to,
276
        bug_status,
277
        creation_ts,
278
        short_desc,
279
        product_id,
280
        reporter,
281
        version,
282
        resolution,
283
        estimated_time,
284
        remaining_time,
285
        deadline,
286
		target_milestone,
287
        bug_severity,
288
        priority,
289
        component_id,
290
        whiteboard,
291
        url,
292
        comment_id,
293
        thetext,
294
        bug_when,
295
        who,
296
        isprivate) = row
297
      if(current_bug_id != bug_id)
298
        sql = "INSERT INTO issues (id, project_id, subject, description, assigned_to_id, author_id, created_on, updated_on, start_date, estimated_hours, priority_id, fixed_version_id, category_id, tracker_id, status_id) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
299
        status_id = 1
300
        version_id = self.find_version_id(product_id, version)
301
		target_milestone_id = self.find_version_id(product_id, target_milestone)
302
        updated_at = self.find_max_bug_when(bug_id)
303
        priority_id = @issuePriorities[priority]
304
        tracker_id = @issueTrackers[bug_severity]
305
        status_id = @issueStatus[bug_status]
306
        self.red_exec_sql(sql, bug_id, product_id, short_desc, thetext, assigned_to, reporter, creation_ts,  updated_at, creation_ts, estimated_time, priority_id, target_milestone_id, component_id, tracker_id,  status_id)
307
        current_bug_id = bug_id
308
		sql = "INSERT INTO custom_values (customized_type, customized_id, custom_field_id, value)  VALUES (?, ?, ?, ?)"
309
		self.red_exec_sql(sql, 'Issue', bug_id, 1, url)
310
      else
311
        sql = "INSERT INTO journals (id, journalized_id, journalized_type, user_id, notes, created_on)  VALUES (?, ?, ?, ?, ?, ?)"
312
        self.red_exec_sql(sql, comment_id, bug_id, "Issue", who, thetext, bug_when)
313
      end
314
    end
315
  end
316

    
317
  def migrate_issue_relations
318
    self.red_exec_sql("delete from issue_relations")
319
    sql = "SELECT dependson, blocked FROM dependencies"
320
    self.bz_select_sql(sql) do |row|
321
      self.red_exec_sql("INSERT INTO issue_relations (issue_from_id, issue_to_id, relation_type) values (?, ?, ?)", row[0], row[1], "blocks")
322
    end
323
    sql = "SELECT dupe, dupe_of FROM duplicates"
324
    self.bz_select_sql(sql) do |row|
325
      self.red_exec_sql("INSERT INTO issue_relations (issue_from_id, issue_to_id, relation_type) values (?, ?, ?)", row[0], row[1], "duplicates")
326
    end
327
  end
328

    
329
  def migrate_attachments
330
    self.red_exec_sql("DELETE FROM attachments")
331
    sql = "SELECT attachments.attach_id, attachments.bug_id, attachments.filename, attachments.mimetype, attachments.submitter_id, attachments.creation_ts, attachments.description, attach_data.thedata FROM attachments, attach_data WHERE attachments.attach_id = attach_data.id"
332
    self.bz_select_sql(sql) do |row|
333
      (attach_id, bug_id, filename, mimetype, submitter_id, creation_ts, description, thedata ) = row
334
      disk_filename = self.get_disk_filename(attach_id, filename)
335
      filesize = thedata.size()
336
      sql = "INSERT INTO attachments (id, container_id, container_type, filename, filesize, disk_filename, content_type, digest, downloads, author_id, created_on, description) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
337
      self.red_exec_sql(sql, attach_id, bug_id, 'Issue', filename, filesize, disk_filename, mimetype, '', 0, submitter_id, creation_ts, description)
338
      File.open("#{ATTACHMENT_PATH}/#{disk_filename}", "wb") do |f|
339
        f.write(thedata)
340
      end
341
    end
342
  end
343
  
344
  def get_disk_filename(attach_id, filename)
345
    return "a#{attach_id}.#{self.get_file_extension(filename)}".downcase
346
  end
347
    
348
  def get_file_extension(s)
349
    m = /\.(\w+)$/.match(s)
350
    if(m)
351
      return m[1]
352
    else
353
      return 'dat'
354
    end
355
  end
356

    
357
  def migrate_members
358
    self.log("*** migrate members ***")
359
    self.bz_select_sql("SELECT DISTINCT user_group_map.user_id, group_control_map.product_id AS project_id FROM group_control_map, user_group_map WHERE group_control_map.group_id = user_group_map.group_id") do |row|
360
      user_id = row[0]
361
      product_id = row[1]
362
      role_id = DEFAULT_ROLE_ID
363
      created_on = "2007-01-01 12:00:00"
364
      mail_notification = 0
365
      self.red_exec_sql("INSERT INTO members (user_id, project_id, created_on, mail_notification) VALUES (?,?,?,?)", user_id, product_id, created_on, mail_notification)
366
    end
367
  end
368

    
369
  def migrate_member_roles
370
    self.log("*** migrate member roles ***")
371
    self.bz_select_sql("SELECT DISTINCT groups.name, group_control_map.product_id AS project_id FROM group_control_map, groups WHERE groups.id = group_control_map.group_id") do |row|
372
      group_name = row[0]
373
      product_id = row[1]
374
      role_id = DEFAULT_ROLE_ID
375
      created_on = "2007-01-01 12:00:00"
376
      mail_notification = 0
377
      self.red_exec_sql("INSERT INTO members (user_id, project_id, created_on, mail_notification) select (select id from users where lastname = ?),?,?,?", group_name, product_id, created_on, mail_notification)
378
	  self.red_exec_sql("INSERT INTO member_roles (member_id, role_id, inherited_from) select (select members.id from members, users where members.user_id = users.id and users.lastname = ?),?,?", group_name, role_id, 0)
379
	  self.red_exec_sql("INSERT INTO member_roles (member_id, role_id, inherited_from) select members.id, ?, (select members.id from members, users where members.user_id = users.id and users.lastname = ?) FROM members,users where members.project_id = ? and members.user_id = users.id and users.type = ?", role_id, group_name, product_id, 'User')
380
    end
381
  end
382

    
383
  def migrate_groups_users
384
    self.log("*** migrate groups users ***")
385
    self.red_select_sql("select (select members.user_id from members where members.id = mr.inherited_from) as group_id, m.user_id FROM member_roles as mr, members as m where mr.inherited_from is not null and mr.inherited_from <> 0 and mr.member_id = m.id") do |row|
386
      group_id = row[0]
387
      user_id = row[1]
388
      self.red_exec_sql("INSERT INTO groups_users (group_id, user_id) values (?, ?)", group_id, user_id)
389
    end
390
  end
391
  
392
  def insert_project_trackers(project_id)
393
    [1,2,3].each do |tracker_id|
394
      self.red_exec_sql("INSERT INTO projects_trackers (project_id, tracker_id) VALUES (?, ?)", project_id, tracker_id)
395
    end
396
  end
397

    
398
  def insert_project_modules(project_id)
399
    ['issue_tracking',
400
      'time_tracking',
401
      'news',
402
      'documents',
403
      'files',
404
      'wiki',
405
      'repository',
406
      'boards',].each do |m|
407
      self.red_exec_sql("INSERT INTO enabled_modules (project_id, name) VALUES (?, ?)", project_id, m)
408
    end
409
  end
410

    
411
  def bz_exec_sql(sql)
412
    self.log("bugzilla: #{sql}")
413
  end
414

    
415
  def bz_select_sql(sql, *args, &block)
416
    self.log("bugzilla: #{sql} args=#{args.join(',')}")
417
    statement = @bugzilladb.prepare(sql)
418
    statement.execute(*args)
419
    while row = statement.fetch do
420
      yield row
421
    end
422
    statement.close()
423
  end
424

    
425
  def red_exec_sql(sql, *args)
426
    self.log("redmine: #{sql} args=#{args.join(',')}")
427
    statement = @redminedb.prepare(sql)
428
    statement.execute(*args)
429
    statement.close()
430
  end
431

    
432
  def red_select_sql(sql, *args, &block)
433
    self.log("redmine: #{sql} args=#{args.join(',')}")
434
    statement = @redminedb.prepare(sql)
435
    statement.execute(*args)
436
    while row = statement.fetch do
437
      yield row
438
    end
439
    statement.close()
440
  end
441
end
442

    
443
begin
444
  bzred = BugzillaToRedmine.new
445
  bzred.migrate
446
rescue => e
447
  puts e.inspect
448
  puts e.backtrace
449
end
(2-2/4)