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