Mercurial > hg > soundsoftware-site
comparison lib/tasks/migrate_from_trac.rake @ 1464:261b3d9a4903 redmine-2.4
Update to Redmine 2.4 branch rev 12663
author | Chris Cannam |
---|---|
date | Tue, 14 Jan 2014 14:37:42 +0000 |
parents | 433d4f72a19b |
children | e248c7af89ec |
comparison
equal
deleted
inserted
replaced
1296:038ba2d95de8 | 1464:261b3d9a4903 |
---|---|
1 # Redmine - project management software | 1 # Redmine - project management software |
2 # Copyright (C) 2006-2012 Jean-Philippe Lang | 2 # Copyright (C) 2006-2013 Jean-Philippe Lang |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or | 4 # This program is free software; you can redistribute it and/or |
5 # modify it under the terms of the GNU General Public License | 5 # modify it under the terms of the GNU General Public License |
6 # as published by the Free Software Foundation; either version 2 | 6 # as published by the Free Software Foundation; either version 2 |
7 # of the License, or (at your option) any later version. | 7 # of the License, or (at your option) any later version. |
14 # You should have received a copy of the GNU General Public License | 14 # You should have received a copy of the GNU General Public License |
15 # along with this program; if not, write to the Free Software | 15 # along with this program; if not, write to the Free Software |
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
17 | 17 |
18 require 'active_record' | 18 require 'active_record' |
19 require 'iconv' | 19 require 'iconv' if RUBY_VERSION < '1.9' |
20 require 'pp' | 20 require 'pp' |
21 | 21 |
22 namespace :redmine do | 22 namespace :redmine do |
23 desc 'Trac migration script' | 23 desc 'Trac migration script' |
24 task :migrate_from_trac => :environment do | 24 task :migrate_from_trac => :environment do |
28 | 28 |
29 DEFAULT_STATUS = IssueStatus.default | 29 DEFAULT_STATUS = IssueStatus.default |
30 assigned_status = IssueStatus.find_by_position(2) | 30 assigned_status = IssueStatus.find_by_position(2) |
31 resolved_status = IssueStatus.find_by_position(3) | 31 resolved_status = IssueStatus.find_by_position(3) |
32 feedback_status = IssueStatus.find_by_position(4) | 32 feedback_status = IssueStatus.find_by_position(4) |
33 closed_status = IssueStatus.find :first, :conditions => { :is_closed => true } | 33 closed_status = IssueStatus.where(:is_closed => true).first |
34 STATUS_MAPPING = {'new' => DEFAULT_STATUS, | 34 STATUS_MAPPING = {'new' => DEFAULT_STATUS, |
35 'reopened' => feedback_status, | 35 'reopened' => feedback_status, |
36 'assigned' => assigned_status, | 36 'assigned' => assigned_status, |
37 'closed' => closed_status | 37 'closed' => closed_status |
38 } | 38 } |
59 'enhancement' => TRACKER_FEATURE, | 59 'enhancement' => TRACKER_FEATURE, |
60 'task' => TRACKER_FEATURE, | 60 'task' => TRACKER_FEATURE, |
61 'patch' =>TRACKER_FEATURE | 61 'patch' =>TRACKER_FEATURE |
62 } | 62 } |
63 | 63 |
64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC') | 64 roles = Role.where(:builtin => 0).order('position ASC').all |
65 manager_role = roles[0] | 65 manager_role = roles[0] |
66 developer_role = roles[1] | 66 developer_role = roles[1] |
67 DEFAULT_ROLE = roles.last | 67 DEFAULT_ROLE = roles.last |
68 ROLE_MAPPING = {'admin' => manager_role, | 68 ROLE_MAPPING = {'admin' => manager_role, |
69 'developer' => developer_role | 69 'developer' => developer_role |
151 end | 151 end |
152 | 152 |
153 private | 153 private |
154 def trac_fullpath | 154 def trac_fullpath |
155 attachment_type = read_attribute(:type) | 155 attachment_type = read_attribute(:type) |
156 trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) } | 156 #replace exotic characters with their hex representation to avoid invalid filenames |
157 trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) do |x| | |
158 codepoint = RUBY_VERSION < '1.9' ? x[0] : x.codepoints.to_a[0] | |
159 sprintf('%%%02x', codepoint) | |
160 end | |
157 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" | 161 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}" |
158 end | 162 end |
159 end | 163 end |
160 | 164 |
161 class TracTicket < ActiveRecord::Base | 165 class TracTicket < ActiveRecord::Base |
243 | 247 |
244 name = username | 248 name = username |
245 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') | 249 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') |
246 name = name_attr.value | 250 name = name_attr.value |
247 end | 251 end |
248 name =~ (/(.*)(\s+\w+)?/) | 252 name =~ (/(\w+)(\s+\w+)?/) |
249 fn = $1.strip | 253 fn = ($1 || "-").strip |
250 ln = ($2 || '-').strip | 254 ln = ($2 || '-').strip |
251 | 255 |
252 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), | 256 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), |
253 :firstname => fn[0, limit_for(User, 'firstname')], | 257 :firstname => fn[0, limit_for(User, 'firstname')], |
254 :lastname => ln[0, limit_for(User, 'lastname')] | 258 :lastname => ln[0, limit_for(User, 'lastname')] |
255 | 259 |
256 u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') | 260 u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') |
257 u.password = 'trac' | 261 u.password = 'trac' |
258 u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') | 262 u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') |
259 # finally, a default user is used if the new user is not valid | 263 # finally, a default user is used if the new user is not valid |
260 u = User.find(:first) unless u.save | 264 u = User.first unless u.save |
261 end | 265 end |
262 # Make sure he is a member of the project | 266 # Make sure user is a member of the project |
263 if project_member && !u.member_of?(@target_project) | 267 if project_member && !u.member_of?(@target_project) |
264 role = DEFAULT_ROLE | 268 role = DEFAULT_ROLE |
265 if u.admin | 269 if u.admin |
266 role = ROLE_MAPPING['admin'] | 270 role = ROLE_MAPPING['admin'] |
267 elsif TracPermission.find_by_username_and_action(username, 'developer') | 271 elsif TracPermission.find_by_username_and_action(username, 'developer') |
388 wiki_edit_count = 0 | 392 wiki_edit_count = 0 |
389 | 393 |
390 # Components | 394 # Components |
391 print "Migrating components" | 395 print "Migrating components" |
392 issues_category_map = {} | 396 issues_category_map = {} |
393 TracComponent.find(:all).each do |component| | 397 TracComponent.all.each do |component| |
394 print '.' | 398 print '.' |
395 STDOUT.flush | 399 STDOUT.flush |
396 c = IssueCategory.new :project => @target_project, | 400 c = IssueCategory.new :project => @target_project, |
397 :name => encode(component.name[0, limit_for(IssueCategory, 'name')]) | 401 :name => encode(component.name[0, limit_for(IssueCategory, 'name')]) |
398 next unless c.save | 402 next unless c.save |
402 puts | 406 puts |
403 | 407 |
404 # Milestones | 408 # Milestones |
405 print "Migrating milestones" | 409 print "Migrating milestones" |
406 version_map = {} | 410 version_map = {} |
407 TracMilestone.find(:all).each do |milestone| | 411 TracMilestone.all.each do |milestone| |
408 print '.' | 412 print '.' |
409 STDOUT.flush | 413 STDOUT.flush |
410 # First we try to find the wiki page... | 414 # First we try to find the wiki page... |
411 p = wiki.find_or_new_page(milestone.name.to_s) | 415 p = wiki.find_or_new_page(milestone.name.to_s) |
412 p.content = WikiContent.new(:page => p) if p.new_record? | 416 p.content = WikiContent.new(:page => p) if p.new_record? |
441 # Or create a new one | 445 # Or create a new one |
442 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, | 446 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, |
443 :field_format => 'string') | 447 :field_format => 'string') |
444 | 448 |
445 next if f.new_record? | 449 next if f.new_record? |
446 f.trackers = Tracker.find(:all) | 450 f.trackers = Tracker.all |
447 f.projects << @target_project | 451 f.projects << @target_project |
448 custom_field_map[field.name] = f | 452 custom_field_map[field.name] = f |
449 end | 453 end |
450 puts | 454 puts |
451 | 455 |
452 # Trac 'resolution' field as a Redmine custom field | 456 # Trac 'resolution' field as a Redmine custom field |
453 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) | 457 r = IssueCustomField.where(:name => "Resolution").first |
454 r = IssueCustomField.new(:name => 'Resolution', | 458 r = IssueCustomField.new(:name => 'Resolution', |
455 :field_format => 'list', | 459 :field_format => 'list', |
456 :is_filter => true) if r.nil? | 460 :is_filter => true) if r.nil? |
457 r.trackers = Tracker.find(:all) | 461 r.trackers = Tracker.all |
458 r.projects << @target_project | 462 r.projects << @target_project |
459 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq | 463 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq |
460 r.save! | 464 r.save! |
461 custom_field_map['resolution'] = r | 465 custom_field_map['resolution'] = r |
462 | 466 |
547 puts | 551 puts |
548 | 552 |
549 # Wiki | 553 # Wiki |
550 print "Migrating wiki" | 554 print "Migrating wiki" |
551 if wiki.save | 555 if wiki.save |
552 TracWikiPage.find(:all, :order => 'name, version').each do |page| | 556 TracWikiPage.order('name, version').all.each do |page| |
553 # Do not migrate Trac manual wiki pages | 557 # Do not migrate Trac manual wiki pages |
554 next if TRAC_WIKI_PAGES.include?(page.name) | 558 next if TRAC_WIKI_PAGES.include?(page.name) |
555 wiki_edit_count += 1 | 559 wiki_edit_count += 1 |
556 print '.' | 560 print '.' |
557 STDOUT.flush | 561 STDOUT.flush |
601 def self.limit_for(klass, attribute) | 605 def self.limit_for(klass, attribute) |
602 klass.columns_hash[attribute.to_s].limit | 606 klass.columns_hash[attribute.to_s].limit |
603 end | 607 end |
604 | 608 |
605 def self.encoding(charset) | 609 def self.encoding(charset) |
606 @ic = Iconv.new('UTF-8', charset) | 610 @charset = charset |
607 rescue Iconv::InvalidEncoding | |
608 puts "Invalid encoding!" | |
609 return false | |
610 end | 611 end |
611 | 612 |
612 def self.set_trac_directory(path) | 613 def self.set_trac_directory(path) |
613 @@trac_directory = path | 614 @@trac_directory = path |
614 raise "This directory doesn't exist!" unless File.directory?(path) | 615 raise "This directory doesn't exist!" unless File.directory?(path) |
711 next unless klass.respond_to? 'establish_connection' | 712 next unless klass.respond_to? 'establish_connection' |
712 klass.establish_connection connection_params | 713 klass.establish_connection connection_params |
713 end | 714 end |
714 end | 715 end |
715 | 716 |
716 private | |
717 def self.encode(text) | 717 def self.encode(text) |
718 @ic.iconv text | 718 if RUBY_VERSION < '1.9' |
719 rescue | 719 @ic ||= Iconv.new('UTF-8', @charset) |
720 text | 720 @ic.iconv text |
721 else | |
722 text.to_s.force_encoding(@charset).encode('UTF-8') | |
723 end | |
721 end | 724 end |
722 end | 725 end |
723 | 726 |
724 puts | 727 puts |
725 if Redmine::DefaultData::Loader.no_data? | 728 if Redmine::DefaultData::Loader.no_data? |
761 end | 764 end |
762 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} | 765 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding} |
763 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} | 766 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier} |
764 puts | 767 puts |
765 | 768 |
766 # Turn off email notifications | 769 old_notified_events = Setting.notified_events |
767 Setting.notified_events = [] | 770 old_password_min_length = Setting.password_min_length |
768 | 771 begin |
769 TracMigrate.migrate | 772 # Turn off email notifications temporarily |
773 Setting.notified_events = [] | |
774 Setting.password_min_length = 4 | |
775 # Run the migration | |
776 TracMigrate.migrate | |
777 ensure | |
778 # Restore previous settings | |
779 Setting.notified_events = old_notified_events | |
780 Setting.password_min_length = old_password_min_length | |
781 end | |
770 end | 782 end |
771 end | 783 end |
772 | 784 |