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