--- 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 +1,5 @@ -# Redmine - project management software +# 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 @@ -56,13 +52,19 @@ 'blocker' => priorities[4] } - TRACKER_BUG = Tracker.find_by_name('Bug') - TRACKER_FEATURE = Tracker.find_by_name('Feature') - TRACKER_SUPPORT = Tracker.find_by_name('Support') + 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_SUPPORT, + 'task' => TRACKER_TASK, 'patch' =>TRACKER_FEATURE } @@ -248,22 +250,6 @@ 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? @@ -370,7 +356,7 @@ :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)) + :effective_date => milestone.completed next unless v.save version_map[milestone.name] = v @@ -385,16 +371,10 @@ #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 @@ -414,23 +394,12 @@ 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.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,27 +415,12 @@ 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.trackers = Tracker.find(:all) tid.projects << @target_project tid.save! custom_field_map['tracid'] = tid @@ -488,8 +442,7 @@ 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) + 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,13 +453,12 @@ 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 + 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,19 +611,6 @@ 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 @@ -738,8 +677,7 @@ 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 + issues_total = TICKET_MAP.count TICKET_MAP.each do |newId| issues_count += 1 simplebar(who, issues_count, issues_total) @@ -759,8 +697,7 @@ 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_total = milestone_wiki.count milestone_wiki.each do |name| milestone_wiki_count += 1 simplebar(who, milestone_wiki_count, milestone_wiki_total) @@ -865,8 +802,8 @@ 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'] + # 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,7 +813,8 @@ 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) + # 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,7 +890,7 @@ desc 'Subversion migration script' - task :migrate_svn_commit_properties => :environment do + task :migrate_from_trac_svn => :environment do require 'redmine/scm/adapters/abstract_adapter' require 'redmine/scm/adapters/subversion_adapter' @@ -964,37 +902,15 @@ TICKET_MAP = [] class Commit - attr_accessor :revision, :message, :author + attr_accessor :revision, :message 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 ||= '' @@ -1044,8 +960,7 @@ commits << Commit.new( { :revision => logentry.attributes['revision'].to_i, - :message => logentry.elements['msg'].text, - :author => logentry.elements['author'].text + :message => logentry.elements['msg'].text }) end rescue => e @@ -1059,34 +974,7 @@ 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 + def self.migrate project = Project.find(@@redmine_project) if !project @@ -1120,11 +1008,6 @@ 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 @@ -1154,52 +1037,34 @@ 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 + # 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 ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password, 0, "", nil @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 - - 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 + SvnMigrate.migrate + end # Prompt @@ -1214,15 +1079,6 @@ 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 @@ -1385,7 +1241,6 @@ text = text.gsub(/''/, '_') text = text.gsub(/__/, '+') text = text.gsub(/~~/, '-') - text = text.gsub(/`/, '@') text = text.gsub(/,,/, '~') # Tables text = text.gsub(/\|\|/, '|') @@ -1403,24 +1258,6 @@ # 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)