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