Project

General



Profile

« Previous | Next » 

Revision 931

Trac importer improvements (by Mat Trudel):
  • 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)

View differences:

trunk/lib/tasks/migrate_from_trac.rake
24 24
  task :migrate_from_trac => :environment do
25 25
    
26 26
    module TracMigrate
27
        TICKET_MAP = [];
27 28
     
28 29
        DEFAULT_STATUS = IssueStatus.default
29 30
        assigned_status = IssueStatus.find_by_position(2)
......
181 182
      # Basic wiki syntax conversion
182 183
      def self.convert_wiki_text(text)
183 184
        # Titles
184
        text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "h#{$1.length}. #{$2}\n"}
185
        # Links
185
        text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
186
        # External Links
186 187
        text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
188
        # Internal Links
189
        text = text.gsub(/[[BR]]/, "\n") # This has to go before the rules below
190
        text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
191
        text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
192
        text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
193
        text = text.gsub(/\[wiki:([^\s\]]+).*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
187 194
        # Revisions links
188 195
        text = text.gsub(/\[(\d+)\]/, 'r\1')
196
        # Ticket number re-writing
197
        text = text.gsub(/#(\d+)/) do |s|
198
          TICKET_MAP[$1.to_i] ||= $1
199
          "\##{TICKET_MAP[$1.to_i]}"
200
        end
201
        # Preformatted blocks
202
        text = text.gsub(/\{\{\{/, '<pre>')
203
        text = text.gsub(/\}\}\}/, '</pre>')          
204
        # Highlighting
205
        text = text.gsub(/'''''([^\s])/, '_*\1')
206
        text = text.gsub(/([^\s])'''''/, '\1*_')
207
        text = text.gsub(/'''/, '*')
208
        text = text.gsub(/''/, '_')
209
        text = text.gsub(/__/, '+')
210
        text = text.gsub(/~~/, '-')
211
        text = text.gsub(/`/, '@')
212
        text = text.gsub(/,,/, '~')        
213
        # Lists
214
        text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
215
        
216

  
189 217
        text
190 218
      end
191 219
    
......
193 221
        establish_connection({:adapter => trac_adapter, 
194 222
                              :database => trac_db_path})
195 223

  
196
        # Quick database test before clearing Redmine data
224
        # Quick database test
197 225
        TracComponent.count
198
        
199
        puts "Deleting data"
200
        CustomField.destroy_all
201
        Issue.destroy_all
202
        IssueCategory.destroy_all
203
        Version.destroy_all
204
        User.destroy_all "login <> 'admin'"
205
        
226
                
206 227
        migrated_components = 0
207 228
        migrated_milestones = 0
208 229
        migrated_tickets = 0
......
215 236
        issues_category_map = {}
216 237
        TracComponent.find(:all).each do |component|
217 238
      	print '.'
239
      	STDOUT.flush
218 240
          c = IssueCategory.new :project => @target_project,
219 241
                                :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
220 242
      	next unless c.save
......
228 250
        version_map = {}
229 251
        TracMilestone.find(:all).each do |milestone|
230 252
          print '.'
253
          STDOUT.flush
231 254
          v = Version.new :project => @target_project,
232 255
                          :name => encode(milestone.name[0, limit_for(Version, 'name')]),
233
                          :description => encode(milestone.description[0, limit_for(Version, 'description')]),
256
                          :description => encode(milestone.description.to_s[0, limit_for(Version, 'description')]),
234 257
                          :effective_date => milestone.due
235 258
          next unless v.save
236 259
          version_map[milestone.name] = v
......
244 267
        custom_field_map = {}
245 268
        TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
246 269
          print '.'
247
          f = IssueCustomField.new :name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
248
                                   :field_format => 'string'
249
          next unless f.save
270
          STDOUT.flush
271
          # Redmine custom field name
272
          field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
273
          # Find if the custom already exists in Redmine
274
          f = IssueCustomField.find_by_name(field_name)
275
          # Or create a new one
276
          f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
277
                                        :field_format => 'string')
278
                                   
279
          next if f.new_record?
250 280
          f.trackers = Tracker.find(:all)
251 281
          f.projects << @target_project
252 282
          custom_field_map[field.name] = f
......
254 284
        puts
255 285
        
256 286
        # Trac 'resolution' field as a Redmine custom field
257
        r = IssueCustomField.new :name => 'Resolution',
287
        r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
288
        r = IssueCustomField.new(:name => 'Resolution',
258 289
                                 :field_format => 'list',
259
                                 :is_filter => true
290
                                 :is_filter => true) if r.nil?
260 291
        r.trackers = Tracker.find(:all)
261 292
        r.projects << @target_project
262 293
        r.possible_values = %w(fixed invalid wontfix duplicate worksforme)
......
264 295
            
265 296
        # Tickets
266 297
        print "Migrating tickets"
267
          TracTicket.find(:all).each do |ticket|
298
          TracTicket.find(:all, :order => 'id ASC').each do |ticket|
268 299
        	print '.'
300
        	STDOUT.flush
269 301
        	i = Issue.new :project => @target_project, 
270 302
                          :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
271 303
                          :description => convert_wiki_text(encode(ticket.description)),
......
276 308
        	i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
277 309
        	i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
278 310
        	i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
279
        	i.id = ticket.id
280 311
        	i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
312
        	i.id = ticket.id unless Issue.exists?(ticket.id)
281 313
        	next unless i.save
314
        	TICKET_MAP[ticket.id] = i.id
282 315
        	migrated_tickets += 1
283 316
        	
284 317
        	# Owner
......
327 360
        	
328 361
        	# Custom fields
329 362
        	ticket.customs.each do |custom|
363
        	  next if custom_field_map[custom.name].nil?
330 364
              v = CustomValue.new :custom_field => custom_field_map[custom.name],
331 365
                                  :value => custom.value
332 366
              v.customized = i
......
344 378
        if wiki.save
345 379
          TracWikiPage.find(:all, :order => 'name, version').each do |page|
346 380
            print '.'
381
            STDOUT.flush
347 382
            p = wiki.find_or_new_page(page.name)
348 383
            p.content = WikiContent.new(:page => p) if p.new_record?
349 384
            p.content.text = page.text
......
415 450
          puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
416 451
          # enable issues and wiki for the created project
417 452
          project.enabled_module_names = ['issue_tracking', 'wiki']
453
          project.trackers << TRACKER_BUG
454
          project.trackers << TRACKER_FEATURE          
418 455
        end        
419 456
        @target_project = project.new_record? ? nil : project
420 457
      end
......
436 473
    end
437 474
    
438 475
    puts
439
    puts "WARNING: Your Redmine data will be deleted during this process."
476
    puts "WARNING: Your Redmine install will have a new project added during this process."
440 477
    print "Are you sure you want to continue ? [y/N] "
441 478
    break unless STDIN.gets.match(/^y$/i)  
442 479
    puts

Also available in: Unified diff