##// END OF EJS Templates
Quote ids for attachment association since Trac's attachment.id is varchar (#1759)....
Jean-Philippe Lang -
r1727:ab4873b83d6f
parent child
Show More
@@ -1,674 +1,680
1 1 # redMine - project management software
2 2 # Copyright (C) 2006-2007 Jean-Philippe Lang
3 3 #
4 4 # This program is free software; you can redistribute it and/or
5 5 # modify it under the terms of the GNU General Public License
6 6 # as published by the Free Software Foundation; either version 2
7 7 # of the License, or (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 17
18 18 require 'active_record'
19 19 require 'iconv'
20 20 require 'pp'
21 21
22 22 namespace :redmine do
23 23 desc 'Trac migration script'
24 24 task :migrate_from_trac => :environment do
25 25
26 26 module TracMigrate
27 27 TICKET_MAP = []
28 28
29 29 DEFAULT_STATUS = IssueStatus.default
30 30 assigned_status = IssueStatus.find_by_position(2)
31 31 resolved_status = IssueStatus.find_by_position(3)
32 32 feedback_status = IssueStatus.find_by_position(4)
33 33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
34 34 STATUS_MAPPING = {'new' => DEFAULT_STATUS,
35 35 'reopened' => feedback_status,
36 36 'assigned' => assigned_status,
37 37 'closed' => closed_status
38 38 }
39 39
40 40 priorities = Enumeration.get_values('IPRI')
41 41 DEFAULT_PRIORITY = priorities[0]
42 42 PRIORITY_MAPPING = {'lowest' => priorities[0],
43 43 'low' => priorities[0],
44 44 'normal' => priorities[1],
45 45 'high' => priorities[2],
46 46 'highest' => priorities[3],
47 47 # ---
48 48 'trivial' => priorities[0],
49 49 'minor' => priorities[1],
50 50 'major' => priorities[2],
51 51 'critical' => priorities[3],
52 52 'blocker' => priorities[4]
53 53 }
54 54
55 55 TRACKER_BUG = Tracker.find_by_position(1)
56 56 TRACKER_FEATURE = Tracker.find_by_position(2)
57 57 DEFAULT_TRACKER = TRACKER_BUG
58 58 TRACKER_MAPPING = {'defect' => TRACKER_BUG,
59 59 'enhancement' => TRACKER_FEATURE,
60 60 'task' => TRACKER_FEATURE,
61 61 'patch' =>TRACKER_FEATURE
62 62 }
63 63
64 64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
65 65 manager_role = roles[0]
66 66 developer_role = roles[1]
67 67 DEFAULT_ROLE = roles.last
68 68 ROLE_MAPPING = {'admin' => manager_role,
69 69 'developer' => developer_role
70 70 }
71 71
72 72 class ::Time
73 73 class << self
74 74 alias :real_now :now
75 75 def now
76 76 real_now - @fake_diff.to_i
77 77 end
78 78 def fake(time)
79 79 @fake_diff = real_now - time
80 80 res = yield
81 81 @fake_diff = 0
82 82 res
83 83 end
84 84 end
85 85 end
86 86
87 87 class TracComponent < ActiveRecord::Base
88 88 set_table_name :component
89 89 end
90 90
91 91 class TracMilestone < ActiveRecord::Base
92 92 set_table_name :milestone
93 93
94 94 def due
95 95 if read_attribute(:due) && read_attribute(:due) > 0
96 96 Time.at(read_attribute(:due)).to_date
97 97 else
98 98 nil
99 99 end
100 100 end
101 101
102 102 def description
103 103 # Attribute is named descr in Trac v0.8.x
104 104 has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
105 105 end
106 106 end
107 107
108 108 class TracTicketCustom < ActiveRecord::Base
109 109 set_table_name :ticket_custom
110 110 end
111 111
112 112 class TracAttachment < ActiveRecord::Base
113 113 set_table_name :attachment
114 114 set_inheritance_column :none
115 115
116 116 def time; Time.at(read_attribute(:time)) end
117 117
118 118 def original_filename
119 119 filename
120 120 end
121 121
122 122 def content_type
123 123 Redmine::MimeType.of(filename) || ''
124 124 end
125 125
126 126 def exist?
127 127 File.file? trac_fullpath
128 128 end
129 129
130 130 def read
131 131 File.open("#{trac_fullpath}", 'rb').read
132 132 end
133 133
134 134 def description
135 135 read_attribute(:description).to_s.slice(0,255)
136 136 end
137 137
138 138 private
139 139 def trac_fullpath
140 140 attachment_type = read_attribute(:type)
141 141 trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
142 142 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
143 143 end
144 144 end
145 145
146 146 class TracTicket < ActiveRecord::Base
147 147 set_table_name :ticket
148 148 set_inheritance_column :none
149 149
150 150 # ticket changes: only migrate status changes and comments
151 151 has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
152 has_many :attachments, :class_name => "TracAttachment", :foreign_key => :id, :conditions => "#{TracMigrate::TracAttachment.table_name}.type = 'ticket'"
152 has_many :attachments, :class_name => "TracAttachment",
153 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
154 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
155 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
153 156 has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
154 157
155 158 def ticket_type
156 159 read_attribute(:type)
157 160 end
158 161
159 162 def summary
160 163 read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
161 164 end
162 165
163 166 def description
164 167 read_attribute(:description).blank? ? summary : read_attribute(:description)
165 168 end
166 169
167 170 def time; Time.at(read_attribute(:time)) end
168 171 def changetime; Time.at(read_attribute(:changetime)) end
169 172 end
170 173
171 174 class TracTicketChange < ActiveRecord::Base
172 175 set_table_name :ticket_change
173 176
174 177 def time; Time.at(read_attribute(:time)) end
175 178 end
176 179
177 180 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
178 181 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
179 182 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
180 183 TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
181 184 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
182 185 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
183 186 CamelCase TitleIndex)
184 187
185 188 class TracWikiPage < ActiveRecord::Base
186 189 set_table_name :wiki
187 190 set_primary_key :name
188 191
189 has_many :attachments, :class_name => "TracAttachment", :foreign_key => :id, :conditions => "#{TracMigrate::TracAttachment.table_name}.type = 'wiki'"
192 has_many :attachments, :class_name => "TracAttachment",
193 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
194 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
195 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
190 196
191 197 def self.columns
192 198 # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
193 199 super.select {|column| column.name.to_s != 'readonly'}
194 200 end
195 201
196 202 def time; Time.at(read_attribute(:time)) end
197 203 end
198 204
199 205 class TracPermission < ActiveRecord::Base
200 206 set_table_name :permission
201 207 end
202 208
203 209 class TracSessionAttribute < ActiveRecord::Base
204 210 set_table_name :session_attribute
205 211 end
206 212
207 213 def self.find_or_create_user(username, project_member = false)
208 214 return User.anonymous if username.blank?
209 215
210 216 u = User.find_by_login(username)
211 217 if !u
212 218 # Create a new user if not found
213 219 mail = username[0,limit_for(User, 'mail')]
214 220 if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email')
215 221 mail = mail_attr.value
216 222 end
217 223 mail = "#{mail}@foo.bar" unless mail.include?("@")
218 224
219 225 name = username
220 226 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
221 227 name = name_attr.value
222 228 end
223 229 name =~ (/(.*)(\s+\w+)?/)
224 230 fn = $1.strip
225 231 ln = ($2 || '-').strip
226 232
227 233 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
228 234 :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'),
229 235 :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-')
230 236
231 237 u.login = username[0,limit_for(User, 'login')].gsub(/[^a-z0-9_\-@\.]/i, '-')
232 238 u.password = 'trac'
233 239 u.admin = true if TracPermission.find_by_username_and_action(username, 'admin')
234 240 # finally, a default user is used if the new user is not valid
235 241 u = User.find(:first) unless u.save
236 242 end
237 243 # Make sure he is a member of the project
238 244 if project_member && !u.member_of?(@target_project)
239 245 role = DEFAULT_ROLE
240 246 if u.admin
241 247 role = ROLE_MAPPING['admin']
242 248 elsif TracPermission.find_by_username_and_action(username, 'developer')
243 249 role = ROLE_MAPPING['developer']
244 250 end
245 251 Member.create(:user => u, :project => @target_project, :role => role)
246 252 u.reload
247 253 end
248 254 u
249 255 end
250 256
251 257 # Basic wiki syntax conversion
252 258 def self.convert_wiki_text(text)
253 259 # Titles
254 260 text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
255 261 # External Links
256 262 text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
257 263 # Internal Links
258 264 text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
259 265 text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
260 266 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
261 267 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
262 268 text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
263 269 text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
264 270
265 271 # Links to pages UsingJustWikiCaps
266 272 text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
267 273 # Normalize things that were supposed to not be links
268 274 # like !NotALink
269 275 text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
270 276 # Revisions links
271 277 text = text.gsub(/\[(\d+)\]/, 'r\1')
272 278 # Ticket number re-writing
273 279 text = text.gsub(/#(\d+)/) do |s|
274 280 if $1.length < 10
275 281 TICKET_MAP[$1.to_i] ||= $1
276 282 "\##{TICKET_MAP[$1.to_i] || $1}"
277 283 else
278 284 s
279 285 end
280 286 end
281 287 # Preformatted blocks
282 288 text = text.gsub(/\{\{\{/, '<pre>')
283 289 text = text.gsub(/\}\}\}/, '</pre>')
284 290 # Highlighting
285 291 text = text.gsub(/'''''([^\s])/, '_*\1')
286 292 text = text.gsub(/([^\s])'''''/, '\1*_')
287 293 text = text.gsub(/'''/, '*')
288 294 text = text.gsub(/''/, '_')
289 295 text = text.gsub(/__/, '+')
290 296 text = text.gsub(/~~/, '-')
291 297 text = text.gsub(/`/, '@')
292 298 text = text.gsub(/,,/, '~')
293 299 # Lists
294 300 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
295 301
296 302 text
297 303 end
298 304
299 305 def self.migrate
300 306 establish_connection
301 307
302 308 # Quick database test
303 309 TracComponent.count
304 310
305 311 migrated_components = 0
306 312 migrated_milestones = 0
307 313 migrated_tickets = 0
308 314 migrated_custom_values = 0
309 315 migrated_ticket_attachments = 0
310 316 migrated_wiki_edits = 0
311 317 migrated_wiki_attachments = 0
312 318
313 319 # Components
314 320 print "Migrating components"
315 321 issues_category_map = {}
316 322 TracComponent.find(:all).each do |component|
317 323 print '.'
318 324 STDOUT.flush
319 325 c = IssueCategory.new :project => @target_project,
320 326 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
321 327 next unless c.save
322 328 issues_category_map[component.name] = c
323 329 migrated_components += 1
324 330 end
325 331 puts
326 332
327 333 # Milestones
328 334 print "Migrating milestones"
329 335 version_map = {}
330 336 TracMilestone.find(:all).each do |milestone|
331 337 print '.'
332 338 STDOUT.flush
333 339 v = Version.new :project => @target_project,
334 340 :name => encode(milestone.name[0, limit_for(Version, 'name')]),
335 341 :description => encode(milestone.description.to_s[0, limit_for(Version, 'description')]),
336 342 :effective_date => milestone.due
337 343 next unless v.save
338 344 version_map[milestone.name] = v
339 345 migrated_milestones += 1
340 346 end
341 347 puts
342 348
343 349 # Custom fields
344 350 # TODO: read trac.ini instead
345 351 print "Migrating custom fields"
346 352 custom_field_map = {}
347 353 TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
348 354 print '.'
349 355 STDOUT.flush
350 356 # Redmine custom field name
351 357 field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
352 358 # Find if the custom already exists in Redmine
353 359 f = IssueCustomField.find_by_name(field_name)
354 360 # Or create a new one
355 361 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
356 362 :field_format => 'string')
357 363
358 364 next if f.new_record?
359 365 f.trackers = Tracker.find(:all)
360 366 f.projects << @target_project
361 367 custom_field_map[field.name] = f
362 368 end
363 369 puts
364 370
365 371 # Trac 'resolution' field as a Redmine custom field
366 372 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
367 373 r = IssueCustomField.new(:name => 'Resolution',
368 374 :field_format => 'list',
369 375 :is_filter => true) if r.nil?
370 376 r.trackers = Tracker.find(:all)
371 377 r.projects << @target_project
372 378 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
373 379 r.save!
374 380 custom_field_map['resolution'] = r
375 381
376 382 # Tickets
377 383 print "Migrating tickets"
378 384 TracTicket.find(:all, :order => 'id ASC').each do |ticket|
379 385 print '.'
380 386 STDOUT.flush
381 387 i = Issue.new :project => @target_project,
382 388 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
383 389 :description => convert_wiki_text(encode(ticket.description)),
384 390 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
385 391 :created_on => ticket.time
386 392 i.author = find_or_create_user(ticket.reporter)
387 393 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
388 394 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
389 395 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
390 396 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
391 397 i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
392 398 i.id = ticket.id unless Issue.exists?(ticket.id)
393 399 next unless Time.fake(ticket.changetime) { i.save }
394 400 TICKET_MAP[ticket.id] = i.id
395 401 migrated_tickets += 1
396 402
397 403 # Owner
398 404 unless ticket.owner.blank?
399 405 i.assigned_to = find_or_create_user(ticket.owner, true)
400 406 Time.fake(ticket.changetime) { i.save }
401 407 end
402 408
403 409 # Comments and status/resolution changes
404 410 ticket.changes.group_by(&:time).each do |time, changeset|
405 411 status_change = changeset.select {|change| change.field == 'status'}.first
406 412 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
407 413 comment_change = changeset.select {|change| change.field == 'comment'}.first
408 414
409 415 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
410 416 :created_on => time
411 417 n.user = find_or_create_user(changeset.first.author)
412 418 n.journalized = i
413 419 if status_change &&
414 420 STATUS_MAPPING[status_change.oldvalue] &&
415 421 STATUS_MAPPING[status_change.newvalue] &&
416 422 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
417 423 n.details << JournalDetail.new(:property => 'attr',
418 424 :prop_key => 'status_id',
419 425 :old_value => STATUS_MAPPING[status_change.oldvalue].id,
420 426 :value => STATUS_MAPPING[status_change.newvalue].id)
421 427 end
422 428 if resolution_change
423 429 n.details << JournalDetail.new(:property => 'cf',
424 430 :prop_key => custom_field_map['resolution'].id,
425 431 :old_value => resolution_change.oldvalue,
426 432 :value => resolution_change.newvalue)
427 433 end
428 434 n.save unless n.details.empty? && n.notes.blank?
429 435 end
430 436
431 437 # Attachments
432 438 ticket.attachments.each do |attachment|
433 439 next unless attachment.exist?
434 440 a = Attachment.new :created_on => attachment.time
435 441 a.file = attachment
436 442 a.author = find_or_create_user(attachment.author)
437 443 a.container = i
438 444 a.description = attachment.description
439 445 migrated_ticket_attachments += 1 if a.save
440 446 end
441 447
442 448 # Custom fields
443 449 ticket.customs.each do |custom|
444 450 next if custom_field_map[custom.name].nil?
445 451 v = CustomValue.new :custom_field => custom_field_map[custom.name],
446 452 :value => custom.value
447 453 v.customized = i
448 454 next unless v.save
449 455 migrated_custom_values += 1
450 456 end
451 457 end
452 458
453 459 # update issue id sequence if needed (postgresql)
454 460 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
455 461 puts
456 462
457 463 # Wiki
458 464 print "Migrating wiki"
459 465 @target_project.wiki.destroy if @target_project.wiki
460 466 @target_project.reload
461 467 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
462 468 wiki_edit_count = 0
463 469 if wiki.save
464 470 TracWikiPage.find(:all, :order => 'name, version').each do |page|
465 471 # Do not migrate Trac manual wiki pages
466 472 next if TRAC_WIKI_PAGES.include?(page.name)
467 473 wiki_edit_count += 1
468 474 print '.'
469 475 STDOUT.flush
470 476 p = wiki.find_or_new_page(page.name)
471 477 p.content = WikiContent.new(:page => p) if p.new_record?
472 478 p.content.text = page.text
473 479 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
474 480 p.content.comments = page.comment
475 481 Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
476 482
477 483 next if p.content.new_record?
478 484 migrated_wiki_edits += 1
479 485
480 486 # Attachments
481 487 page.attachments.each do |attachment|
482 488 next unless attachment.exist?
483 489 next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
484 490 a = Attachment.new :created_on => attachment.time
485 491 a.file = attachment
486 492 a.author = find_or_create_user(attachment.author)
487 493 a.description = attachment.description
488 494 a.container = p
489 495 migrated_wiki_attachments += 1 if a.save
490 496 end
491 497 end
492 498
493 499 wiki.reload
494 500 wiki.pages.each do |page|
495 501 page.content.text = convert_wiki_text(page.content.text)
496 502 Time.fake(page.content.updated_on) { page.content.save }
497 503 end
498 504 end
499 505 puts
500 506
501 507 puts
502 508 puts "Components: #{migrated_components}/#{TracComponent.count}"
503 509 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
504 510 puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
505 511 puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
506 512 puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
507 513 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
508 514 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
509 515 end
510 516
511 517 def self.limit_for(klass, attribute)
512 518 klass.columns_hash[attribute.to_s].limit
513 519 end
514 520
515 521 def self.encoding(charset)
516 522 @ic = Iconv.new('UTF-8', charset)
517 523 rescue Iconv::InvalidEncoding
518 524 puts "Invalid encoding!"
519 525 return false
520 526 end
521 527
522 528 def self.set_trac_directory(path)
523 529 @@trac_directory = path
524 530 raise "This directory doesn't exist!" unless File.directory?(path)
525 531 raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
526 532 @@trac_directory
527 533 rescue Exception => e
528 534 puts e
529 535 return false
530 536 end
531 537
532 538 def self.trac_directory
533 539 @@trac_directory
534 540 end
535 541
536 542 def self.set_trac_adapter(adapter)
537 543 return false if adapter.blank?
538 544 raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter)
539 545 # If adapter is sqlite or sqlite3, make sure that trac.db exists
540 546 raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path)
541 547 @@trac_adapter = adapter
542 548 rescue Exception => e
543 549 puts e
544 550 return false
545 551 end
546 552
547 553 def self.set_trac_db_host(host)
548 554 return nil if host.blank?
549 555 @@trac_db_host = host
550 556 end
551 557
552 558 def self.set_trac_db_port(port)
553 559 return nil if port.to_i == 0
554 560 @@trac_db_port = port.to_i
555 561 end
556 562
557 563 def self.set_trac_db_name(name)
558 564 return nil if name.blank?
559 565 @@trac_db_name = name
560 566 end
561 567
562 568 def self.set_trac_db_username(username)
563 569 @@trac_db_username = username
564 570 end
565 571
566 572 def self.set_trac_db_password(password)
567 573 @@trac_db_password = password
568 574 end
569 575
570 576 def self.set_trac_db_schema(schema)
571 577 @@trac_db_schema = schema
572 578 end
573 579
574 580 mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
575 581
576 582 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
577 583 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
578 584
579 585 def self.target_project_identifier(identifier)
580 586 project = Project.find_by_identifier(identifier)
581 587 if !project
582 588 # create the target project
583 589 project = Project.new :name => identifier.humanize,
584 590 :description => ''
585 591 project.identifier = identifier
586 592 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
587 593 # enable issues and wiki for the created project
588 594 project.enabled_module_names = ['issue_tracking', 'wiki']
589 595 else
590 596 puts
591 597 puts "This project already exists in your Redmine database."
592 598 print "Are you sure you want to append data to this project ? [Y/n] "
593 599 exit if STDIN.gets.match(/^n$/i)
594 600 end
595 601 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
596 602 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
597 603 @target_project = project.new_record? ? nil : project
598 604 end
599 605
600 606 def self.connection_params
601 607 if %w(sqlite sqlite3).include?(trac_adapter)
602 608 {:adapter => trac_adapter,
603 609 :database => trac_db_path}
604 610 else
605 611 {:adapter => trac_adapter,
606 612 :database => trac_db_name,
607 613 :host => trac_db_host,
608 614 :port => trac_db_port,
609 615 :username => trac_db_username,
610 616 :password => trac_db_password,
611 617 :schema_search_path => trac_db_schema
612 618 }
613 619 end
614 620 end
615 621
616 622 def self.establish_connection
617 623 constants.each do |const|
618 624 klass = const_get(const)
619 625 next unless klass.respond_to? 'establish_connection'
620 626 klass.establish_connection connection_params
621 627 end
622 628 end
623 629
624 630 private
625 631 def self.encode(text)
626 632 @ic.iconv text
627 633 rescue
628 634 text
629 635 end
630 636 end
631 637
632 638 puts
633 639 if Redmine::DefaultData::Loader.no_data?
634 640 puts "Redmine configuration need to be loaded before importing data."
635 641 puts "Please, run this first:"
636 642 puts
637 643 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
638 644 exit
639 645 end
640 646
641 647 puts "WARNING: a new project will be added to Redmine during this process."
642 648 print "Are you sure you want to continue ? [y/N] "
643 649 break unless STDIN.gets.match(/^y$/i)
644 650 puts
645 651
646 652 def prompt(text, options = {}, &block)
647 653 default = options[:default] || ''
648 654 while true
649 655 print "#{text} [#{default}]: "
650 656 value = STDIN.gets.chomp!
651 657 value = default if value.blank?
652 658 break if yield value
653 659 end
654 660 end
655 661
656 662 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
657 663
658 664 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
659 665 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
660 666 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
661 667 prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
662 668 prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
663 669 prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
664 670 prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
665 671 prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
666 672 prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
667 673 end
668 674 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
669 675 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
670 676 puts
671 677
672 678 TracMigrate.migrate
673 679 end
674 680 end
General Comments 0
You need to be logged in to leave comments. Login now