*** migrate_from_trac.rake 2011-07-02 23:25:13.000000000 +0200 --- /Users/gilles/Documents/Home/pocafeina/open source/redmine.org/migrate_from_trac.rake.Callegaro 2011-07-02 12:07:49.000000000 +0200 *************** *** 1,9 **** ! # Redmine - project management software # Copyright (C) 2006-2007 Jean-Philippe Lang - # Copyright (C) 2007-2011 Trac/Redmine Community - # References: - # - http://www.redmine.org/boards/1/topics/12273 (Trac Importer Patch Coordination) - # - http://github.com/landy2005/Redmine-migrate-from-Trac # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License --- 1,5 ---- ! # redMine - project management software # Copyright (C) 2006-2007 Jean-Philippe Lang # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License *************** *** 56,68 **** 'blocker' => priorities[4] } ! TRACKER_BUG = Tracker.find_by_name('Bug') ! TRACKER_FEATURE = Tracker.find_by_name('Feature') ! TRACKER_SUPPORT = Tracker.find_by_name('Support') DEFAULT_TRACKER = TRACKER_BUG TRACKER_MAPPING = {'defect' => TRACKER_BUG, 'enhancement' => TRACKER_FEATURE, ! 'task' => TRACKER_SUPPORT, 'patch' =>TRACKER_FEATURE } --- 52,70 ---- 'blocker' => priorities[4] } ! TRACKER_BUG = Tracker.find_by_position(1) ! TRACKER_FEATURE = Tracker.find_by_position(2) ! # Add a fourth issue type for tasks as we use them heavily ! t = Tracker.find_by_name('Task') ! if !t ! t = Tracker.create(:name => 'Task', :is_in_chlog => true, :is_in_roadmap => false, :position => 4) ! t.workflows.copy(Tracker.find(1)) ! end ! TRACKER_TASK = t DEFAULT_TRACKER = TRACKER_BUG TRACKER_MAPPING = {'defect' => TRACKER_BUG, 'enhancement' => TRACKER_FEATURE, ! 'task' => TRACKER_TASK, 'patch' =>TRACKER_FEATURE } *************** *** 248,269 **** set_table_name :session_attribute end - # TODO put your Login Mapping in this method and rename method below - # def self.find_or_create_user(username, project_member = false) - # TRAC_REDMINE_LOGIN_MAP = [] - # return TRAC_REDMINE_LOGIN_MAP[username] - # OR more hard-coded: - # if username == 'TracX' - # username = 'RedmineX' - # elsif username == 'gilles' - # username = 'gcornu' - # #elseif ... - # else - # username = 'gcornu' - # end - # return User.find_by_login(username) - # end - def self.find_or_create_user(username, project_member = false) return User.anonymous if username.blank? --- 250,255 ---- *************** *** 370,376 **** :name => encode(milestone.name[0, limit_for(Version, 'name')]), :description => nil, :wiki_page_title => milestone.name.to_s, ! :effective_date => (!milestone.completed.blank? ? milestone.completed : (!milestone.due.blank? ? milestone.due : nil)) next unless v.save version_map[milestone.name] = v --- 356,362 ---- :name => encode(milestone.name[0, limit_for(Version, 'name')]), :description => nil, :wiki_page_title => milestone.name.to_s, ! :effective_date => milestone.completed next unless v.save version_map[milestone.name] = v *************** *** 385,400 **** #print "Migrating custom fields" custom_field_map = {} TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field| - # use line below and adapt the WHERE condifiton, if you want to skip some unused custom fields - # TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name} WHERE name NOT IN ('duration', 'software')").each do |field| #print '.' # Maybe not needed this out? #STDOUT.flush # Redmine custom field name field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize - - # # Ugly hack to skip custom field 'Browser', which is in 'list' format... - # next if field_name == 'browser' - # Find if the custom already exists in Redmine f = IssueCustomField.find_by_name(field_name) # Ugly hack to handle billable checkbox. Would require to read the ini file to be cleaner --- 371,380 ---- *************** *** 414,436 **** end #puts - # # Trac custom field 'Browser' field as a Redmine custom field - # b = IssueCustomField.find(:first, :conditions => { :name => "Browser" }) - # b = IssueCustomField.new(:name => 'Browser', - # :field_format => 'list', - # :is_filter => true) if b.nil? - # b.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT] - # b.projects << @target_project - # b.possible_values = (b.possible_values + %w(IE6 IE7 IE8 IE9 Firefox Chrome Safari Opera)).flatten.compact.uniq - # b.save! - # custom_field_map['browser'] = b - # Trac 'resolution' field as a Redmine custom field r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) r = IssueCustomField.new(:name => 'Resolution', :field_format => 'list', :is_filter => true) if r.nil? ! r.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT] r.projects << @target_project r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.save! --- 394,405 ---- end #puts # Trac 'resolution' field as a Redmine custom field r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" }) r = IssueCustomField.new(:name => 'Resolution', :field_format => 'list', :is_filter => true) if r.nil? ! r.trackers = Tracker.find(:all) r.projects << @target_project r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq r.save! *************** *** 446,472 **** k.save! custom_field_map['keywords'] = k - # Trac 'version' field as a Redmine custom field, taking advantage of feature #2096 (available since Redmine 1.2.0) - v = IssueCustomField.find(:first, :conditions => { :name => "Found in Version" }) - v = IssueCustomField.new(:name => 'Found in Version', - :field_format => 'version', - :is_filter => true) if v.nil? - # Only apply to BUG tracker (?) - v.trackers << TRACKER_BUG - #v.trackers << [TRACKER_BUG, TRACKER_FEATURE] - - # Affect custom field to current Project - v.projects << @target_project - - v.save! - custom_field_map['found_in_version'] = v - # Trac ticket id as a Redmine custom field tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" }) tid = IssueCustomField.new(:name => 'TracID', :field_format => 'string', :is_filter => true) if tid.nil? ! tid.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT] tid.projects << @target_project tid.save! custom_field_map['tracid'] = tid --- 415,426 ---- k.save! custom_field_map['keywords'] = k # Trac ticket id as a Redmine custom field tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" }) tid = IssueCustomField.new(:name => 'TracID', :field_format => 'string', :is_filter => true) if tid.nil? ! tid.trackers = Tracker.find(:all) tid.projects << @target_project tid.save! custom_field_map['tracid'] = tid *************** *** 488,495 **** i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER ! # Use the Redmine-genereated new ticket ID anyway (no Ticket ID recycling) ! #i.id = ticket.id unless Issue.exists?(ticket.id) next unless Time.fake(ticket.changetime) { i.save } TICKET_MAP[ticket.id] = i.id migrated_tickets += 1 --- 442,448 ---- i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER ! i.id = ticket.id unless Issue.exists?(ticket.id) next unless Time.fake(ticket.changetime) { i.save } TICKET_MAP[ticket.id] = i.id migrated_tickets += 1 *************** *** 500,512 **** Time.fake(ticket.changetime) { i.save } end # Handle CC field ! # Feature disabled (CC field almost never used, No time to validate/test this recent improvments from A. Callegaro) ! # ticket.cc.split(',').each do |email| ! # w = Watcher.new :watchable_type => 'Issue', ! # :watchable_id => i.id, ! # :user_id => find_or_create_user(email.strip).id ! # w.save ! # end # Necessary to handle direct link to note from timelogs and putting the right start time in issue noteid = 1 --- 453,464 ---- Time.fake(ticket.changetime) { i.save } end # Handle CC field ! ticket.cc.split(',').each do |email| ! w = Watcher.new :watchable_type => 'Issue', ! :watchable_id => i.id, ! :user_id => find_or_create_user(email.strip).id ! w.save ! end # Necessary to handle direct link to note from timelogs and putting the right start time in issue noteid = 1 *************** *** 659,677 **** if custom_field_map['tracid'] custom_values[custom_field_map['tracid'].id] = ticket.id end - - if !ticket.version.blank? && custom_field_map['found_in_version'] - found_in = version_map[ticket.version] - if !found_in.nil? - puts "Issue #{i.id} found in #{found_in.name.to_s} (#{found_in.id.to_s}) - trac: #{ticket.version}" - else - #TODO: add better error management here... - puts "Issue #{i.id} : ouch... - trac: #{ticket.version}" - end - custom_values[custom_field_map['found_in_version'].id] = found_in.id.to_s - STDOUT.flush - end - i.custom_field_values = custom_values i.save_custom_field_values end --- 611,616 ---- *************** *** 738,745 **** puts if wiki_pages_count < wiki_pages_total who = " in Issues" ! #issues_total = TICKET_MAP.length #works with Ruby <= 1.8.6 ! issues_total = TICKET_MAP.count #works with Ruby >= 1.8.7 TICKET_MAP.each do |newId| issues_count += 1 simplebar(who, issues_count, issues_total) --- 677,683 ---- puts if wiki_pages_count < wiki_pages_total who = " in Issues" ! issues_total = TICKET_MAP.count TICKET_MAP.each do |newId| issues_count += 1 simplebar(who, issues_count, issues_total) *************** *** 759,766 **** puts if issues_count < issues_total who = " in Milestone descriptions" ! #milestone_wiki_total = milestone_wiki.length #works with Ruby <= 1.8.6 ! milestone_wiki_total = milestone_wiki.count #works with Ruby >= 1.8.7 milestone_wiki.each do |name| milestone_wiki_count += 1 simplebar(who, milestone_wiki_count, milestone_wiki_total) --- 697,703 ---- puts if issues_count < issues_total who = " in Milestone descriptions" ! milestone_wiki_total = milestone_wiki.count milestone_wiki.each do |name| milestone_wiki_count += 1 simplebar(who, milestone_wiki_count, milestone_wiki_total) *************** *** 865,872 **** project.identifier = identifier puts "Unable to create a project with identifier '#{identifier}'!" unless project.save # enable issues and wiki for the created project ! # Enable only a minimal set of modules by default ! project.enabled_module_names = ['issue_tracking', 'wiki'] else puts puts "This project already exists in your Redmine database." --- 802,809 ---- project.identifier = identifier puts "Unable to create a project with identifier '#{identifier}'!" unless project.save # enable issues and wiki for the created project ! # Enable all project modules by default ! project.enabled_module_names = ['issue_tracking', 'wiki', 'time_tracking', 'news', 'documents', 'files', 'repository', 'boards', 'calendar', 'gantt'] else puts puts "This project already exists in your Redmine database." *************** *** 876,882 **** end project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) ! project.trackers << TRACKER_SUPPORT unless project.trackers.include?(TRACKER_SUPPORT) @target_project = project.new_record? ? nil : project @target_project.reload end --- 813,820 ---- end project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) ! # Add Task type to the project ! project.trackers << TRACKER_TASK unless project.trackers.include?(TRACKER_TASK) @target_project = project.new_record? ? nil : project @target_project.reload end *************** *** 952,958 **** desc 'Subversion migration script' ! task :migrate_svn_commit_properties => :environment do require 'redmine/scm/adapters/abstract_adapter' require 'redmine/scm/adapters/subversion_adapter' --- 890,896 ---- desc 'Subversion migration script' ! task :migrate_from_trac_svn => :environment do require 'redmine/scm/adapters/abstract_adapter' require 'redmine/scm/adapters/subversion_adapter' *************** *** 964,1000 **** TICKET_MAP = [] class Commit ! attr_accessor :revision, :message, :author def initialize(attributes={}) - self.author = attributes[:author] || "" self.message = attributes[:message] || "" self.revision = attributes[:revision] end end class SvnExtendedAdapter < Redmine::Scm::Adapters::SubversionAdapter - - def set_author(path=nil, revision=nil, author=nil) - path ||= '' - - cmd = "#{SVN_BIN} propset svn:author --quiet --revprop -r #{revision} \"#{author}\" " - cmd << credentials_string - cmd << ' ' + target(URI.escape(path)) - - shellout(cmd) do |io| - begin - loop do - line = io.readline - puts line - end - rescue EOFError - end - end - - raise if $? && $?.exitstatus != 0 - - end def set_message(path=nil, revision=nil, msg=nil) path ||= '' --- 902,916 ---- TICKET_MAP = [] class Commit ! attr_accessor :revision, :message def initialize(attributes={}) self.message = attributes[:message] || "" self.revision = attributes[:revision] end end class SvnExtendedAdapter < Redmine::Scm::Adapters::SubversionAdapter def set_message(path=nil, revision=nil, msg=nil) path ||= '' *************** *** 1044,1051 **** commits << Commit.new( { :revision => logentry.attributes['revision'].to_i, ! :message => logentry.elements['msg'].text, ! :author => logentry.elements['author'].text }) end rescue => e --- 960,966 ---- commits << Commit.new( { :revision => logentry.attributes['revision'].to_i, ! :message => logentry.elements['msg'].text }) end rescue => e *************** *** 1059,1092 **** end ! def self.migrate_authors ! svn = self.scm ! commits = svn.messages(@svn_url) ! commits.each do |commit| ! orig_author_name = commit.author ! new_author_name = orig_author_name ! ! # TODO put your Trac/SVN/Redmine username mapping here: ! if (commit.author == 'TracX') ! new_author_name = 'RedmineY' ! elsif (commit.author == 'gilles') ! new_author_name = 'gcornu' ! #elsif (commit.author == 'seco') ! #... ! else ! new_author_name = 'RedmineY' ! end ! ! if (new_author_name != orig_author_name) ! scm.set_author(@svn_url, commit.revision, new_author_name) ! puts "r#{commit.revision} - Author replaced: #{orig_author_name} -> #{new_author_name}" ! else ! puts "r#{commit.revision} - Author kept: #{orig_author_name} unchanged " ! end ! end ! end ! ! def self.migrate_messages project = Project.find(@@redmine_project) if !project --- 974,980 ---- end ! def self.migrate project = Project.find(@@redmine_project) if !project *************** *** 1120,1130 **** if newText != commit.message puts "Updating message #{commit.revision}" - - # Marcel Nadje enhancement, see http://www.redmine.org/issues/2748#note-3 - # Hint: enable charset conversion if needed... - #newText = Iconv.conv('CP1252', 'UTF-8', newText) - scm.set_message(@svn_url, commit.revision, newText) end end --- 1008,1013 ---- *************** *** 1154,1205 **** end def self.scm ! # Thomas Recloux fix, see http://www.redmine.org/issues/2748#note-1 ! # The constructor of the SvnExtendedAdapter has ony got four parameters, ! # => parameters 5,6 and 7 removed @scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password - #@scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password, 0, "", nil @scm end end puts prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip} prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username} prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password} puts - - author_migration_enabled = unsafe_prompt('1) Start Migration of SVN Commit Authors (y,n)?', {:default => 'n'}) == 'y' - puts - if author_migration_enabled - puts "WARNING: Some (maybe all) commit authors will be replaced" - print "Are you sure you want to continue ? [y/N] " - break unless STDIN.gets.match(/^y$/i) - - SvnMigrate.migrate_authors - end ! message_migration_enabled = unsafe_prompt('2) Start Migration of SVN Commit Messages (y,n)?', {:default => 'n'}) == 'y' ! puts ! if message_migration_enabled ! if Redmine::DefaultData::Loader.no_data? ! puts "Redmine configuration need to be loaded before importing data." ! puts "Please, run this first:" ! puts ! puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" ! exit ! end ! ! puts "WARNING: all commit messages with references to trac pages will be modified" ! print "Are you sure you want to continue ? [y/N] " ! break unless STDIN.gets.match(/^y$/i) ! puts ! ! prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier} ! puts ! ! SvnMigrate.migrate_messages ! end end # Prompt --- 1037,1070 ---- end def self.scm ! # Bugfix, with redmine 1.0.1 (Debian's) it wasn't working anymore @scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password @scm end end puts + if Redmine::DefaultData::Loader.no_data? + puts "Redmine configuration need to be loaded before importing data." + puts "Please, run this first:" + puts + puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\"" + exit + end + + puts "WARNING: all commit messages with references to trac pages will be modified" + print "Are you sure you want to continue ? [y/N] " + break unless STDIN.gets.match(/^y$/i) + puts + prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip} prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username} prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password} + prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier} puts ! SvnMigrate.migrate ! end # Prompt *************** *** 1214,1228 **** end end - # Sorry, I had troubles to intagrate 'prompt' and quickly went this way... - def unsafe_prompt(text, options = {}) - default = options[:default] || '' - print "#{text} [#{default}]: " - value = STDIN.gets.chomp! - value = default if value.blank? - value - end - # Basic wiki syntax conversion def convert_wiki_text_mapping(text, ticket_map = []) # Hide links --- 1079,1084 ---- *************** *** 1385,1391 **** text = text.gsub(/''/, '_') text = text.gsub(/__/, '+') text = text.gsub(/~~/, '-') - text = text.gsub(/`/, '@') text = text.gsub(/,,/, '~') # Tables text = text.gsub(/\|\|/, '|') --- 1241,1246 ---- *************** *** 1403,1426 **** # TOC (is right-aligned, because that in Trac) text = text.gsub(/\[\[TOC(?:\((.*?)\))?\]\]/m) {|s| "{{>toc}}\n"} - # Thomas Recloux enhancements, see http://www.redmine.org/issues/2748#note-1 - # Redmine needs a space between keywords "refs,ref,fix" and the issue number (#1234) in subversion commit messages. - # TODO: rewrite it in a more regex-style way - - text = text.gsub("refs#", "refs #") - text = text.gsub("Refs#", "refs #") - text = text.gsub("REFS#", "refs #") - text = text.gsub("ref#", "refs #") - text = text.gsub("Ref#", "refs #") - text = text.gsub("REF#", "refs #") - - text = text.gsub("fix#", "fixes #") - text = text.gsub("Fix#", "fixes #") - text = text.gsub("FIX#", "fixes #") - text = text.gsub("fixes#", "fixes #") - text = text.gsub("Fixes#", "fixes #") - text = text.gsub("FIXES#", "fixes #") - # Restore and convert code blocks text = code_convert(text) --- 1258,1263 ----