]> source.dussan.org Git - redmine.git/commitdiff
Trac importer improvements (by Mat Trudel):
authorJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 25 Nov 2007 12:11:40 +0000 (12:11 +0000)
committerJean-Philippe Lang <jp_lang@yahoo.fr>
Sun, 25 Nov 2007 12:11:40 +0000 (12:11 +0000)
* better support for wiki internal links (still not perfect, but much improved)
* support for unordered lists
* support for most of trac's highlighting tags (underline, bold, etc)
* import progress dots now flush to stdout on every dot, so the import doesn't look frozen
* support for migration of multiple trac instances into a single Redmine install (as separate projects)

git-svn-id: http://redmine.rubyforge.org/svn/trunk@931 e93f8b46-1217-0410-a6f0-8f06a7374b81

lib/tasks/migrate_from_trac.rake

index ba002cb6fc37e85294d50a5dbdb17fa7189187ab..6467a5430eadbe6abcae348a7d0d9b76475b813f 100644 (file)
@@ -24,6 +24,7 @@ namespace :redmine do
   task :migrate_from_trac => :environment do
     
     module TracMigrate
+        TICKET_MAP = [];
      
         DEFAULT_STATUS = IssueStatus.default
         assigned_status = IssueStatus.find_by_position(2)
@@ -181,11 +182,38 @@ namespace :redmine do
       # Basic wiki syntax conversion
       def self.convert_wiki_text(text)
         # Titles
-        text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "h#{$1.length}. #{$2}\n"}
-        # Links
+        text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
+        # External Links
         text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
+        # Internal Links
+        text = text.gsub(/[[BR]]/, "\n") # This has to go before the rules below
+        text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
+        text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
+        text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
+        text = text.gsub(/\[wiki:([^\s\]]+).*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
         # Revisions links
         text = text.gsub(/\[(\d+)\]/, 'r\1')
+        # Ticket number re-writing
+        text = text.gsub(/#(\d+)/) do |s|
+          TICKET_MAP[$1.to_i] ||= $1
+          "\##{TICKET_MAP[$1.to_i]}"
+        end
+        # Preformatted blocks
+        text = text.gsub(/\{\{\{/, '<pre>')
+        text = text.gsub(/\}\}\}/, '</pre>')          
+        # Highlighting
+        text = text.gsub(/'''''([^\s])/, '_*\1')
+        text = text.gsub(/([^\s])'''''/, '\1*_')
+        text = text.gsub(/'''/, '*')
+        text = text.gsub(/''/, '_')
+        text = text.gsub(/__/, '+')
+        text = text.gsub(/~~/, '-')
+        text = text.gsub(/`/, '@')
+        text = text.gsub(/,,/, '~')        
+        # Lists
+        text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
+        
+
         text
       end
     
@@ -193,16 +221,9 @@ namespace :redmine do
         establish_connection({:adapter => trac_adapter, 
                               :database => trac_db_path})
 
-        # Quick database test before clearing Redmine data
+        # Quick database test
         TracComponent.count
-        
-        puts "Deleting data"
-        CustomField.destroy_all
-        Issue.destroy_all
-        IssueCategory.destroy_all
-        Version.destroy_all
-        User.destroy_all "login <> 'admin'"
-        
+                
         migrated_components = 0
         migrated_milestones = 0
         migrated_tickets = 0
@@ -215,6 +236,7 @@ namespace :redmine do
         issues_category_map = {}
         TracComponent.find(:all).each do |component|
        print '.'
+       STDOUT.flush
           c = IssueCategory.new :project => @target_project,
                                 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
        next unless c.save
@@ -228,9 +250,10 @@ namespace :redmine do
         version_map = {}
         TracMilestone.find(:all).each do |milestone|
           print '.'
+          STDOUT.flush
           v = Version.new :project => @target_project,
                           :name => encode(milestone.name[0, limit_for(Version, 'name')]),
-                          :description => encode(milestone.description[0, limit_for(Version, 'description')]),
+                          :description => encode(milestone.description.to_s[0, limit_for(Version, 'description')]),
                           :effective_date => milestone.due
           next unless v.save
           version_map[milestone.name] = v
@@ -244,9 +267,16 @@ namespace :redmine do
         custom_field_map = {}
         TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
           print '.'
-          f = IssueCustomField.new :name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
-                                   :field_format => 'string'
-          next unless f.save
+          STDOUT.flush
+          # Redmine custom field name
+          field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
+          # Find if the custom already exists in Redmine
+          f = IssueCustomField.find_by_name(field_name)
+          # Or create a new one
+          f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
+                                        :field_format => 'string')
+                                   
+          next if f.new_record?
           f.trackers = Tracker.find(:all)
           f.projects << @target_project
           custom_field_map[field.name] = f
@@ -254,9 +284,10 @@ namespace :redmine do
         puts
         
         # Trac 'resolution' field as a Redmine custom field
-        r = IssueCustomField.new :name => 'Resolution',
+        r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
+        r = IssueCustomField.new(:name => 'Resolution',
                                  :field_format => 'list',
-                                 :is_filter => true
+                                 :is_filter => true) if r.nil?
         r.trackers = Tracker.find(:all)
         r.projects << @target_project
         r.possible_values = %w(fixed invalid wontfix duplicate worksforme)
@@ -264,8 +295,9 @@ namespace :redmine do
             
         # Tickets
         print "Migrating tickets"
-          TracTicket.find(:all).each do |ticket|
+          TracTicket.find(:all, :order => 'id ASC').each do |ticket|
                print '.'
+               STDOUT.flush
                i = Issue.new :project => @target_project, 
                           :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
                           :description => convert_wiki_text(encode(ticket.description)),
@@ -276,9 +308,10 @@ namespace :redmine do
                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
                i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
+               i.id = ticket.id unless Issue.exists?(ticket.id)
                next unless i.save
+               TICKET_MAP[ticket.id] = i.id
                migrated_tickets += 1
                
                # Owner
@@ -327,6 +360,7 @@ namespace :redmine do
                
                # Custom fields
                ticket.customs.each do |custom|
+                 next if custom_field_map[custom.name].nil?
               v = CustomValue.new :custom_field => custom_field_map[custom.name],
                                   :value => custom.value
               v.customized = i
@@ -344,6 +378,7 @@ namespace :redmine do
         if wiki.save
           TracWikiPage.find(:all, :order => 'name, version').each do |page|
             print '.'
+            STDOUT.flush
             p = wiki.find_or_new_page(page.name)
             p.content = WikiContent.new(:page => p) if p.new_record?
             p.content.text = page.text
@@ -415,6 +450,8 @@ namespace :redmine do
           puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
           # enable issues and wiki for the created project
           project.enabled_module_names = ['issue_tracking', 'wiki']
+          project.trackers << TRACKER_BUG
+          project.trackers << TRACKER_FEATURE          
         end        
         @target_project = project.new_record? ? nil : project
       end
@@ -436,7 +473,7 @@ namespace :redmine do
     end
     
     puts
-    puts "WARNING: Your Redmine data will be deleted during this process."
+    puts "WARNING: Your Redmine install will have a new project added during this process."
     print "Are you sure you want to continue ? [y/N] "
     break unless STDIN.gets.match(/^y$/i)  
     puts