##// END OF EJS Templates
Trac importer: exclude more Trac wiki pages (#933)....
Jean-Philippe Lang -
r1282:ef80597c39e0
parent child
Show More
@@ -1,627 +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 TRAC_WIKI_PAGES = %w(TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
152 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox 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 TracReports TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
155 TracReports TracRevisionLog 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 327 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
328 328 r.save!
329 329 custom_field_map['resolution'] = r
330 330
331 331 # Tickets
332 332 print "Migrating tickets"
333 333 TracTicket.find(:all, :order => 'id ASC').each do |ticket|
334 334 print '.'
335 335 STDOUT.flush
336 336 i = Issue.new :project => @target_project,
337 337 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
338 338 :description => convert_wiki_text(encode(ticket.description)),
339 339 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
340 340 :created_on => ticket.time
341 341 i.author = find_or_create_user(ticket.reporter)
342 342 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
343 343 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
344 344 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
345 345 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
346 346 i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
347 347 i.id = ticket.id unless Issue.exists?(ticket.id)
348 348 next unless i.save
349 349 TICKET_MAP[ticket.id] = i.id
350 350 migrated_tickets += 1
351 351
352 352 # Owner
353 353 unless ticket.owner.blank?
354 354 i.assigned_to = find_or_create_user(ticket.owner, true)
355 355 i.save
356 356 end
357 357
358 358 # Comments and status/resolution changes
359 359 ticket.changes.group_by(&:time).each do |time, changeset|
360 360 status_change = changeset.select {|change| change.field == 'status'}.first
361 361 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
362 362 comment_change = changeset.select {|change| change.field == 'comment'}.first
363 363
364 364 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
365 365 :created_on => time
366 366 n.user = find_or_create_user(changeset.first.author)
367 367 n.journalized = i
368 368 if status_change &&
369 369 STATUS_MAPPING[status_change.oldvalue] &&
370 370 STATUS_MAPPING[status_change.newvalue] &&
371 371 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
372 372 n.details << JournalDetail.new(:property => 'attr',
373 373 :prop_key => 'status_id',
374 374 :old_value => STATUS_MAPPING[status_change.oldvalue].id,
375 375 :value => STATUS_MAPPING[status_change.newvalue].id)
376 376 end
377 377 if resolution_change
378 378 n.details << JournalDetail.new(:property => 'cf',
379 379 :prop_key => custom_field_map['resolution'].id,
380 380 :old_value => resolution_change.oldvalue,
381 381 :value => resolution_change.newvalue)
382 382 end
383 383 n.save unless n.details.empty? && n.notes.blank?
384 384 end
385 385
386 386 # Attachments
387 387 ticket.attachments.each do |attachment|
388 388 next unless attachment.exist?
389 389 a = Attachment.new :created_on => attachment.time
390 390 a.file = attachment
391 391 a.author = find_or_create_user(attachment.author)
392 392 a.container = i
393 393 migrated_ticket_attachments += 1 if a.save
394 394 end
395 395
396 396 # Custom fields
397 397 ticket.customs.each do |custom|
398 398 next if custom_field_map[custom.name].nil?
399 399 v = CustomValue.new :custom_field => custom_field_map[custom.name],
400 400 :value => custom.value
401 401 v.customized = i
402 402 next unless v.save
403 403 migrated_custom_values += 1
404 404 end
405 405 end
406 406
407 407 # update issue id sequence if needed (postgresql)
408 408 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
409 409 puts
410 410
411 411 # Wiki
412 412 print "Migrating wiki"
413 413 @target_project.wiki.destroy if @target_project.wiki
414 414 @target_project.reload
415 415 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
416 416 wiki_edit_count = 0
417 417 if wiki.save
418 418 TracWikiPage.find(:all, :order => 'name, version').each do |page|
419 419 # Do not migrate Trac manual wiki pages
420 420 next if TRAC_WIKI_PAGES.include?(page.name)
421 421 wiki_edit_count += 1
422 422 print '.'
423 423 STDOUT.flush
424 424 p = wiki.find_or_new_page(page.name)
425 425 p.content = WikiContent.new(:page => p) if p.new_record?
426 426 p.content.text = page.text
427 427 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
428 428 p.content.comments = page.comment
429 429 p.new_record? ? p.save : p.content.save
430 430
431 431 next if p.content.new_record?
432 432 migrated_wiki_edits += 1
433 433
434 434 # Attachments
435 435 page.attachments.each do |attachment|
436 436 next unless attachment.exist?
437 437 next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
438 438 a = Attachment.new :created_on => attachment.time
439 439 a.file = attachment
440 440 a.author = find_or_create_user(attachment.author)
441 441 a.container = p
442 442 migrated_wiki_attachments += 1 if a.save
443 443 end
444 444 end
445 445
446 446 wiki.reload
447 447 wiki.pages.each do |page|
448 448 page.content.text = convert_wiki_text(page.content.text)
449 449 page.content.save
450 450 end
451 451 end
452 452 puts
453 453
454 454 puts
455 455 puts "Components: #{migrated_components}/#{TracComponent.count}"
456 456 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
457 457 puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
458 458 puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
459 459 puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
460 460 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
461 461 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
462 462 end
463 463
464 464 def self.limit_for(klass, attribute)
465 465 klass.columns_hash[attribute.to_s].limit
466 466 end
467 467
468 468 def self.encoding(charset)
469 469 @ic = Iconv.new('UTF-8', charset)
470 470 rescue Iconv::InvalidEncoding
471 471 puts "Invalid encoding!"
472 472 return false
473 473 end
474 474
475 475 def self.set_trac_directory(path)
476 476 @@trac_directory = path
477 477 raise "This directory doesn't exist!" unless File.directory?(path)
478 478 raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
479 479 @@trac_directory
480 480 rescue Exception => e
481 481 puts e
482 482 return false
483 483 end
484 484
485 485 def self.trac_directory
486 486 @@trac_directory
487 487 end
488 488
489 489 def self.set_trac_adapter(adapter)
490 490 return false if adapter.blank?
491 491 raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter)
492 492 # If adapter is sqlite or sqlite3, make sure that trac.db exists
493 493 raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path)
494 494 @@trac_adapter = adapter
495 495 rescue Exception => e
496 496 puts e
497 497 return false
498 498 end
499 499
500 500 def self.set_trac_db_host(host)
501 501 return nil if host.blank?
502 502 @@trac_db_host = host
503 503 end
504 504
505 505 def self.set_trac_db_port(port)
506 506 return nil if port.to_i == 0
507 507 @@trac_db_port = port.to_i
508 508 end
509 509
510 510 def self.set_trac_db_name(name)
511 511 return nil if name.blank?
512 512 @@trac_db_name = name
513 513 end
514 514
515 515 def self.set_trac_db_username(username)
516 516 @@trac_db_username = username
517 517 end
518 518
519 519 def self.set_trac_db_password(password)
520 520 @@trac_db_password = password
521 521 end
522 522
523 523 def self.set_trac_db_schema(schema)
524 524 @@trac_db_schema = schema
525 525 end
526 526
527 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
528 528
529 529 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
530 530 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
531 531
532 532 def self.target_project_identifier(identifier)
533 533 project = Project.find_by_identifier(identifier)
534 534 if !project
535 535 # create the target project
536 536 project = Project.new :name => identifier.humanize,
537 537 :description => ''
538 538 project.identifier = identifier
539 539 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
540 540 # enable issues and wiki for the created project
541 541 project.enabled_module_names = ['issue_tracking', 'wiki']
542 542 else
543 543 puts
544 544 puts "This project already exists in your Redmine database."
545 545 print "Are you sure you want to append data to this project ? [Y/n] "
546 546 exit if STDIN.gets.match(/^n$/i)
547 547 end
548 548 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
549 549 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
550 550 @target_project = project.new_record? ? nil : project
551 551 end
552 552
553 553 def self.connection_params
554 554 if %w(sqlite sqlite3).include?(trac_adapter)
555 555 {:adapter => trac_adapter,
556 556 :database => trac_db_path}
557 557 else
558 558 {:adapter => trac_adapter,
559 559 :database => trac_db_name,
560 560 :host => trac_db_host,
561 561 :port => trac_db_port,
562 562 :username => trac_db_username,
563 563 :password => trac_db_password,
564 564 :schema_search_path => trac_db_schema
565 565 }
566 566 end
567 567 end
568 568
569 569 def self.establish_connection
570 570 constants.each do |const|
571 571 klass = const_get(const)
572 572 next unless klass.respond_to? 'establish_connection'
573 573 klass.establish_connection connection_params
574 574 end
575 575 end
576 576
577 577 private
578 578 def self.encode(text)
579 579 @ic.iconv text
580 580 rescue
581 581 text
582 582 end
583 583 end
584 584
585 585 puts
586 586 if Redmine::DefaultData::Loader.no_data?
587 587 puts "Redmine configuration need to be loaded before importing data."
588 588 puts "Please, run this first:"
589 589 puts
590 590 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
591 591 exit
592 592 end
593 593
594 594 puts "WARNING: a new project will be added to Redmine during this process."
595 595 print "Are you sure you want to continue ? [y/N] "
596 596 break unless STDIN.gets.match(/^y$/i)
597 597 puts
598 598
599 599 def prompt(text, options = {}, &block)
600 600 default = options[:default] || ''
601 601 while true
602 602 print "#{text} [#{default}]: "
603 603 value = STDIN.gets.chomp!
604 604 value = default if value.blank?
605 605 break if yield value
606 606 end
607 607 end
608 608
609 609 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
610 610
611 611 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
612 612 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
613 613 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
614 614 prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
615 615 prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
616 616 prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
617 617 prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
618 618 prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
619 619 prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
620 620 end
621 621 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
622 622 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
623 623 puts
624 624
625 625 TracMigrate.migrate
626 626 end
627 627 end
General Comments 0
You need to be logged in to leave comments. Login now