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