Project

General

Profile

RE: Trac Importer Patch Coordination ยป migrate_from_trac.rake.Callegaro-Cornu.patch

Changes proposed after patch from Anthony Callegaro - Gilles Cornu, 2011-07-03 01:45

View differences:

/Users/gilles/Documents/Home/pocafeina/open source/redmine.org/migrate_from_trac.rake.Callegaro 2011-07-02 12:07:49.000000000 +0200
1
# Redmine - project management software
1
# redMine - project management software
2 2
# Copyright (C) 2006-2007  Jean-Philippe Lang
3
# Copyright (C) 2007-2011  Trac/Redmine Community
4
# References:
5
#  - http://www.redmine.org/boards/1/topics/12273 (Trac Importer Patch Coordination)
6
#  - http://github.com/landy2005/Redmine-migrate-from-Trac
7 3
#
8 4
# This program is free software; you can redistribute it and/or
9 5
# modify it under the terms of the GNU General Public License
......
56 52
                            'blocker' => priorities[4]
57 53
                            }
58 54

  
59
        TRACKER_BUG = Tracker.find_by_name('Bug')
60
        TRACKER_FEATURE = Tracker.find_by_name('Feature')
61
        TRACKER_SUPPORT = Tracker.find_by_name('Support')
55
        TRACKER_BUG = Tracker.find_by_position(1)
56
        TRACKER_FEATURE = Tracker.find_by_position(2)
57
        # Add a fourth issue type for tasks as we use them heavily
58
        t = Tracker.find_by_name('Task')
59
        if !t
60
          t = Tracker.create(:name => 'Task',     :is_in_chlog => true,  :is_in_roadmap => false, :position => 4)
61
          t.workflows.copy(Tracker.find(1))
62
        end
63
        TRACKER_TASK = t
62 64
        DEFAULT_TRACKER = TRACKER_BUG
63 65
        TRACKER_MAPPING = {'defect' => TRACKER_BUG,
64 66
                           'enhancement' => TRACKER_FEATURE,
65
                           'task' => TRACKER_SUPPORT,
67
                           'task' => TRACKER_TASK,
66 68
                           'patch' =>TRACKER_FEATURE
67 69
                           }
68 70

  
......
248 250
        set_table_name :session_attribute
249 251
      end
250 252

  
251
# TODO put your Login Mapping in this method and rename method below
252
#      def self.find_or_create_user(username, project_member = false)
253
#        TRAC_REDMINE_LOGIN_MAP = []
254
#        return TRAC_REDMINE_LOGIN_MAP[username]
255
# OR more hard-coded:
256
#        if username == 'TracX'
257
#          username = 'RedmineX'
258
#        elsif username == 'gilles'
259
#          username = 'gcornu'
260
#        #elseif ...
261
#        else
262
#          username = 'gcornu'
263
#        end
264
#        return User.find_by_login(username)  
265
#      end
266

  
267 253
      def self.find_or_create_user(username, project_member = false)
268 254
        return User.anonymous if username.blank?
269 255

  
......
370 356
                          :name => encode(milestone.name[0, limit_for(Version, 'name')]),
371 357
                          :description => nil,
372 358
                          :wiki_page_title => milestone.name.to_s,
373
                          :effective_date => (!milestone.completed.blank? ? milestone.completed : (!milestone.due.blank? ? milestone.due : nil))
359
                          :effective_date => milestone.completed
374 360

  
375 361
          next unless v.save
376 362
          version_map[milestone.name] = v
......
385 371
        #print "Migrating custom fields"
386 372
        custom_field_map = {}
387 373
        TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
388
# use line below and adapt the WHERE condifiton, if you want to skip some unused custom fields
389
#        TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name} WHERE name NOT IN ('duration', 'software')").each do |field|
390 374
          #print '.' # Maybe not needed this out?
391 375
          #STDOUT.flush
392 376
          # Redmine custom field name
393 377
          field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
394

  
395
#          # Ugly hack to skip custom field 'Browser', which is in 'list' format...
396
#          next if field_name == 'browser'
397

  
398 378
          # Find if the custom already exists in Redmine
399 379
          f = IssueCustomField.find_by_name(field_name)
400 380
          # Ugly hack to handle billable checkbox. Would require to read the ini file to be cleaner
......
414 394
        end
415 395
        #puts
416 396

  
417
#        # Trac custom field 'Browser' field as a Redmine custom field
418
#        b = IssueCustomField.find(:first, :conditions => { :name => "Browser" })
419
#        b = IssueCustomField.new(:name => 'Browser',
420
#                                 :field_format => 'list',
421
#                                 :is_filter => true) if b.nil?
422
#        b.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT]
423
#        b.projects << @target_project
424
#        b.possible_values = (b.possible_values + %w(IE6 IE7 IE8 IE9 Firefox Chrome Safari Opera)).flatten.compact.uniq
425
#        b.save!
426
#        custom_field_map['browser'] = b
427

  
428 397
        # Trac 'resolution' field as a Redmine custom field
429 398
        r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
430 399
        r = IssueCustomField.new(:name => 'Resolution',
431 400
                                 :field_format => 'list',
432 401
                                 :is_filter => true) if r.nil?
433
        r.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT] 
402
        r.trackers = Tracker.find(:all)
434 403
        r.projects << @target_project
435 404
        r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
436 405
        r.save!
......
446 415
        k.save!
447 416
        custom_field_map['keywords'] = k
448 417

  
449
        # Trac 'version' field as a Redmine custom field, taking advantage of feature #2096 (available since Redmine 1.2.0)
450
        v = IssueCustomField.find(:first, :conditions => { :name => "Found in Version" })
451
        v = IssueCustomField.new(:name => 'Found in Version',
452
                                 :field_format => 'version',
453
                                 :is_filter => true) if v.nil?
454
        # Only apply to BUG tracker (?)
455
        v.trackers << TRACKER_BUG
456
        #v.trackers << [TRACKER_BUG, TRACKER_FEATURE]
457

  
458
        # Affect custom field to current Project
459
        v.projects << @target_project
460

  
461
        v.save!
462
        custom_field_map['found_in_version'] = v
463

  
464 418
        # Trac ticket id as a Redmine custom field
465 419
        tid = IssueCustomField.find(:first, :conditions => { :name => "TracID" })
466 420
        tid = IssueCustomField.new(:name => 'TracID',
467 421
                                 :field_format => 'string',
468 422
                                 :is_filter => true) if tid.nil?
469
        tid.trackers << [TRACKER_BUG, TRACKER_FEATURE, TRACKER_SUPPORT] 
423
        tid.trackers = Tracker.find(:all)
470 424
        tid.projects << @target_project
471 425
        tid.save!
472 426
        custom_field_map['tracid'] = tid
......
488 442
          i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
489 443
          i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
490 444
          i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
491
          # Use the Redmine-genereated new ticket ID anyway (no Ticket ID recycling)
492
          #i.id = ticket.id unless Issue.exists?(ticket.id)
445
          i.id = ticket.id unless Issue.exists?(ticket.id)
493 446
          next unless Time.fake(ticket.changetime) { i.save }
494 447
          TICKET_MAP[ticket.id] = i.id
495 448
          migrated_tickets += 1
......
500 453
              Time.fake(ticket.changetime) { i.save }
501 454
            end
502 455
          # Handle CC field
503
   # Feature disabled (CC field almost never used, No time to validate/test this recent improvments from A. Callegaro)
504
   #       ticket.cc.split(',').each do |email|
505
   #         w = Watcher.new :watchable_type => 'Issue',
506
   #                         :watchable_id => i.id,
507
   #                         :user_id => find_or_create_user(email.strip).id 
508
   #         w.save
509
   #       end
456
          ticket.cc.split(',').each do |email|
457
            w = Watcher.new :watchable_type => 'Issue',
458
                            :watchable_id => i.id,
459
                            :user_id => find_or_create_user(email.strip).id 
460
            w.save
461
          end
510 462

  
511 463
          # Necessary to handle direct link to note from timelogs and putting the right start time in issue
512 464
          noteid = 1
......
659 611
          if custom_field_map['tracid'] 
660 612
            custom_values[custom_field_map['tracid'].id] = ticket.id
661 613
          end
662

  
663
          if !ticket.version.blank? && custom_field_map['found_in_version']
664
            found_in = version_map[ticket.version]
665
            if !found_in.nil?
666
              puts "Issue #{i.id} found in #{found_in.name.to_s} (#{found_in.id.to_s}) - trac: #{ticket.version}"
667
            else
668
              #TODO: add better error management here...
669
              puts "Issue #{i.id} : ouch...  - trac: #{ticket.version}"  
670
            end 
671
            custom_values[custom_field_map['found_in_version'].id] = found_in.id.to_s
672
            STDOUT.flush
673
          end
674

  
675 614
          i.custom_field_values = custom_values
676 615
          i.save_custom_field_values
677 616
        end
......
738 677
        puts if wiki_pages_count < wiki_pages_total
739 678
        
740 679
        who = "   in Issues"
741
        #issues_total = TICKET_MAP.length #works with Ruby <= 1.8.6
742
        issues_total = TICKET_MAP.count #works with Ruby >= 1.8.7
680
        issues_total = TICKET_MAP.count
743 681
        TICKET_MAP.each do |newId|
744 682
          issues_count += 1
745 683
          simplebar(who, issues_count, issues_total)
......
759 697
        puts if issues_count < issues_total
760 698

  
761 699
        who = "   in Milestone descriptions"
762
        #milestone_wiki_total = milestone_wiki.length #works with Ruby <= 1.8.6
763
        milestone_wiki_total = milestone_wiki.count #works with Ruby >= 1.8.7
700
        milestone_wiki_total = milestone_wiki.count
764 701
        milestone_wiki.each do |name|
765 702
          milestone_wiki_count += 1
766 703
          simplebar(who, milestone_wiki_count, milestone_wiki_total)
......
865 802
          project.identifier = identifier
866 803
          puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
867 804
          # enable issues and wiki for the created project
868
          # Enable only a minimal set of modules by default
869
          project.enabled_module_names = ['issue_tracking', 'wiki']
805
          # Enable all project modules by default
806
          project.enabled_module_names = ['issue_tracking', 'wiki', 'time_tracking', 'news', 'documents', 'files', 'repository', 'boards', 'calendar', 'gantt']
870 807
        else
871 808
          puts
872 809
          puts "This project already exists in your Redmine database."
......
876 813
        end
877 814
        project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
878 815
        project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
879
        project.trackers << TRACKER_SUPPORT unless project.trackers.include?(TRACKER_SUPPORT)
816
        # Add Task type to the project
817
        project.trackers << TRACKER_TASK unless project.trackers.include?(TRACKER_TASK)
880 818
        @target_project = project.new_record? ? nil : project
881 819
        @target_project.reload
882 820
      end
......
952 890

  
953 891

  
954 892
  desc 'Subversion migration script'
955
  task :migrate_svn_commit_properties => :environment do
893
  task :migrate_from_trac_svn => :environment do
956 894

  
957 895
    require 'redmine/scm/adapters/abstract_adapter'
958 896
    require 'redmine/scm/adapters/subversion_adapter'
......
964 902
        TICKET_MAP = []
965 903

  
966 904
        class Commit
967
          attr_accessor :revision, :message, :author
905
          attr_accessor :revision, :message
968 906
          
969 907
          def initialize(attributes={})
970
            self.author = attributes[:author] || ""
971 908
            self.message = attributes[:message] || ""
972 909
            self.revision = attributes[:revision]
973 910
          end
974 911
        end
975 912
        
976 913
        class SvnExtendedAdapter < Redmine::Scm::Adapters::SubversionAdapter
977
        
978
            def set_author(path=nil, revision=nil, author=nil)
979
              path ||= ''
980

  
981
              cmd = "#{SVN_BIN} propset svn:author --quiet --revprop -r #{revision}  \"#{author}\" "
982
              cmd << credentials_string
983
              cmd << ' ' + target(URI.escape(path))
984

  
985
              shellout(cmd) do |io|
986
                begin
987
                  loop do 
988
                    line = io.readline
989
                    puts line
990
                  end
991
                rescue EOFError
992
                end  
993
              end
994

  
995
              raise if $? && $?.exitstatus != 0
996

  
997
            end
998 914

  
999 915
            def set_message(path=nil, revision=nil, msg=nil)
1000 916
              path ||= ''
......
1044 960
                    commits << Commit.new(
1045 961
                                                {
1046 962
                                                  :revision => logentry.attributes['revision'].to_i,
1047
                                                  :message => logentry.elements['msg'].text,
1048
                                                  :author => logentry.elements['author'].text
963
                                                  :message => logentry.elements['msg'].text
1049 964
                                                })
1050 965
                  end
1051 966
                rescue => e
......
1059 974
          
1060 975
        end
1061 976
        
1062
        def self.migrate_authors
1063
          svn = self.scm          
1064
          commits = svn.messages(@svn_url)
1065
          commits.each do |commit| 
1066
            orig_author_name = commit.author
1067
            new_author_name = orig_author_name
1068
            
1069
            # TODO put your Trac/SVN/Redmine username mapping here:
1070
            if (commit.author == 'TracX')
1071
               new_author_name = 'RedmineY'
1072
            elsif (commit.author == 'gilles')
1073
               new_author_name = 'gcornu'
1074
            #elsif (commit.author == 'seco')
1075
            #...
1076
            else
1077
               new_author_name = 'RedmineY'
1078
            end
1079
            
1080
            if (new_author_name != orig_author_name)
1081
              scm.set_author(@svn_url, commit.revision, new_author_name)
1082
              puts "r#{commit.revision} - Author replaced: #{orig_author_name} -> #{new_author_name}"
1083
            else
1084
              puts "r#{commit.revision} - Author kept: #{orig_author_name} unchanged "
1085
            end
1086
          end
1087
        end
1088
        
1089
        def self.migrate_messages
977
        def self.migrate
1090 978

  
1091 979
          project = Project.find(@@redmine_project)
1092 980
          if !project
......
1120 1008
            
1121 1009
            if newText != commit.message             
1122 1010
              puts "Updating message #{commit.revision}"
1123
              
1124
              # Marcel Nadje enhancement, see http://www.redmine.org/issues/2748#note-3
1125
              # Hint: enable charset conversion if needed...
1126
              #newText = Iconv.conv('CP1252', 'UTF-8', newText)
1127
              
1128 1011
              scm.set_message(@svn_url, commit.revision, newText)
1129 1012
            end
1130 1013
          end
......
1154 1037
        end
1155 1038
      
1156 1039
        def self.scm
1157
          # Thomas Recloux fix, see http://www.redmine.org/issues/2748#note-1
1158
          # The constructor of the SvnExtendedAdapter has ony got four parameters, 
1159
          # => parameters 5,6 and 7 removed
1040
          # Bugfix, with redmine 1.0.1 (Debian's) it wasn't working anymore
1160 1041
          @scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password
1161
          #@scm ||= SvnExtendedAdapter.new @@svn_url, @@svn_url, @@svn_username, @@svn_password, 0, "", nil
1162 1042
          @scm
1163 1043
        end
1164 1044
    end
1165 1045

  
1166 1046
    puts
1047
    if Redmine::DefaultData::Loader.no_data?
1048
      puts "Redmine configuration need to be loaded before importing data."
1049
      puts "Please, run this first:"
1050
      puts
1051
      puts "  rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
1052
      exit
1053
    end
1054

  
1055
    puts "WARNING: all commit messages with references to trac pages will be modified"
1056
    print "Are you sure you want to continue ? [y/N] "
1057
    break unless STDIN.gets.match(/^y$/i)
1058
    puts
1059

  
1167 1060
    prompt('Subversion repository url') {|repository| SvnMigrate.set_svn_url repository.strip}
1168 1061
    prompt('Subversion repository username') {|username| SvnMigrate.set_svn_username username}
1169 1062
    prompt('Subversion repository password') {|password| SvnMigrate.set_svn_password password}
1063
    prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
1170 1064
    puts
1171
        
1172
    author_migration_enabled = unsafe_prompt('1) Start Migration of SVN Commit Authors (y,n)?', {:default => 'n'}) == 'y'
1173
    puts
1174
    if author_migration_enabled
1175
      puts "WARNING: Some (maybe all) commit authors will be replaced"
1176
      print "Are you sure you want to continue ? [y/N] "
1177
      break unless STDIN.gets.match(/^y$/i)
1178
      
1179
      SvnMigrate.migrate_authors
1180
    end
1181 1065

  
1182
    message_migration_enabled = unsafe_prompt('2) Start Migration of SVN Commit Messages (y,n)?', {:default => 'n'}) == 'y'
1183
    puts
1184
    if message_migration_enabled
1185
      if Redmine::DefaultData::Loader.no_data?
1186
        puts "Redmine configuration need to be loaded before importing data."
1187
        puts "Please, run this first:"
1188
        puts
1189
        puts "  rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
1190
        exit
1191
      end
1192
  
1193
      puts "WARNING: all commit messages with references to trac pages will be modified"
1194
      print "Are you sure you want to continue ? [y/N] "
1195
      break unless STDIN.gets.match(/^y$/i)
1196
      puts
1197
  
1198
      prompt('Redmine project identifier') {|identifier| SvnMigrate.set_redmine_project_identifier identifier}
1199
      puts
1200
  
1201
      SvnMigrate.migrate_messages
1202
    end
1066
    SvnMigrate.migrate
1067
    
1203 1068
  end
1204 1069

  
1205 1070
  # Prompt
......
1214 1079
    end
1215 1080
  end
1216 1081

  
1217
  # Sorry, I had troubles to intagrate 'prompt' and quickly went this way...
1218
  def unsafe_prompt(text, options = {})
1219
    default = options[:default] || ''
1220
    print "#{text} [#{default}]: "
1221
    value = STDIN.gets.chomp!
1222
    value = default if value.blank?
1223
    value
1224
  end
1225

  
1226 1082
  # Basic wiki syntax conversion
1227 1083
  def convert_wiki_text_mapping(text, ticket_map = [])
1228 1084
        # Hide links
......
1385 1241
        text = text.gsub(/''/, '_')
1386 1242
        text = text.gsub(/__/, '+')
1387 1243
        text = text.gsub(/~~/, '-')
1388
        text = text.gsub(/`/, '@')
1389 1244
        text = text.gsub(/,,/, '~')
1390 1245
        # Tables
1391 1246
        text = text.gsub(/\|\|/, '|')
......
1403 1258
        # TOC (is right-aligned, because that in Trac)
1404 1259
        text = text.gsub(/\[\[TOC(?:\((.*?)\))?\]\]/m) {|s| "{{>toc}}\n"}
1405 1260

  
1406
        # Thomas Recloux enhancements, see http://www.redmine.org/issues/2748#note-1
1407
        # Redmine needs a space between keywords "refs,ref,fix" and the issue number (#1234) in subversion commit messages.
1408
        # TODO: rewrite it in a more regex-style way
1409

  
1410
        text = text.gsub("refs#", "refs #")
1411
        text = text.gsub("Refs#", "refs #")
1412
        text = text.gsub("REFS#", "refs #")
1413
        text = text.gsub("ref#", "refs #")
1414
        text = text.gsub("Ref#", "refs #")
1415
        text = text.gsub("REF#", "refs #")
1416

  
1417
        text = text.gsub("fix#", "fixes #")
1418
        text = text.gsub("Fix#", "fixes #")
1419
        text = text.gsub("FIX#", "fixes #")
1420
        text = text.gsub("fixes#", "fixes #")
1421
        text = text.gsub("Fixes#", "fixes #")
1422
        text = text.gsub("FIXES#", "fixes #")
1423
        
1424 1261
        # Restore and convert code blocks
1425 1262
        text = code_convert(text)
1426 1263

  
    (1-1/1)