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