| 
      1
     | 
    
      # redMine - project management software
 
     | 
  
  
    | 
      2
     | 
    
      # Copyright (C) 2006-2007  Jean-Philippe Lang
 
     | 
  
  
    | 
      3
     | 
    
      #
 
     | 
  
  
    | 
      4
     | 
    
      # This program is free software; you can redistribute it and/or
 
     | 
  
  
    | 
      5
     | 
    
      # modify it under the terms of the GNU General Public License
 
     | 
  
  
    | 
      6
     | 
    
      # as published by the Free Software Foundation; either version 2
 
     | 
  
  
    | 
      7
     | 
    
      # of the License, or (at your option) any later version.
 
     | 
  
  
    | 
      8
     | 
    
      # 
 
     | 
  
  
    | 
      9
     | 
    
      # This program is distributed in the hope that it will be useful,
 
     | 
  
  
    | 
      10
     | 
    
      # but WITHOUT ANY WARRANTY; without even the implied warranty of
 
     | 
  
  
    | 
      11
     | 
    
      # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
     | 
  
  
    | 
      12
     | 
    
      # GNU General Public License for more details.
 
     | 
  
  
    | 
      13
     | 
    
      # 
 
     | 
  
  
    | 
      14
     | 
    
      # You should have received a copy of the GNU General Public License
 
     | 
  
  
    | 
      15
     | 
    
      # along with this program; if not, write to the Free Software
 
     | 
  
  
    | 
      16
     | 
    
      # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 
     | 
  
  
    | 
      17
     | 
    
      
 
     | 
  
  
    | 
      18
     | 
    
      require 'active_record'
 
     | 
  
  
    | 
      19
     | 
    
      require 'iconv'
 
     | 
  
  
    | 
      20
     | 
    
      require 'pp'
 
     | 
  
  
    | 
      21
     | 
    
      require 'lib/tasks/trac/trac_migration'
 
     | 
  
  
    | 
      22
     | 
    
      
 
     | 
  
  
    | 
      23
     | 
    
      namespace :redmine do
 
     | 
  
  
    | 
      24
     | 
    
        desc 'Trac migration script'
 
     | 
  
  
    | 
      25
     | 
    
        task :migrate_from_trac => :environment do
 
     | 
  
  
    | 
      26
     | 
    
          
 
     | 
  
  
    | 
      27
     | 
    
          module TracMigrate
 
     | 
  
  
    | 
      28
     | 
    
              TICKET_MAP = []
 
     | 
  
  
    | 
      29
     | 
    
           
 
     | 
  
  
    | 
      30
     | 
    
              DEFAULT_STATUS = IssueStatus.default
 
     | 
  
  
    | 
      31
     | 
    
              assigned_status = IssueStatus.find_by_position(2)
 
     | 
  
  
    | 
      32
     | 
    
              resolved_status = IssueStatus.find_by_position(3)
 
     | 
  
  
    | 
      33
     | 
    
              feedback_status = IssueStatus.find_by_position(4)
 
     | 
  
  
    | 
      34
     | 
    
              closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
     | 
  
  
    | 
      35
     | 
    
              STATUS_MAPPING = {'new' => DEFAULT_STATUS,
     | 
  
  
    | 
      36
     | 
    
                                'reopened' => feedback_status,
 
     | 
  
  
    | 
      37
     | 
    
                                'assigned' => assigned_status,
 
     | 
  
  
    | 
      38
     | 
    
                                'closed' => closed_status
 
     | 
  
  
    | 
      39
     | 
    
                                }
 
     | 
  
  
    | 
      40
     | 
    
                                
 
     | 
  
  
    | 
      41
     | 
    
              priorities = Enumeration.get_values('IPRI')
     | 
  
  
    | 
      42
     | 
    
              DEFAULT_PRIORITY = priorities[0]
 
     | 
  
  
    | 
      43
     | 
    
              PRIORITY_MAPPING = {'lowest' => priorities[0],
     | 
  
  
    | 
      44
     | 
    
                                  'low' => priorities[0],
 
     | 
  
  
    | 
      45
     | 
    
                                  'normal' => priorities[1],
 
     | 
  
  
    | 
      46
     | 
    
                                  'high' => priorities[2],
 
     | 
  
  
    | 
      47
     | 
    
                                  'highest' => priorities[3],
 
     | 
  
  
    | 
      48
     | 
    
                                  # ---
 
     | 
  
  
    | 
      49
     | 
    
                                  'trivial' => priorities[0],
 
     | 
  
  
    | 
      50
     | 
    
                                  'minor' => priorities[1],
 
     | 
  
  
    | 
      51
     | 
    
                                  'major' => priorities[2],
 
     | 
  
  
    | 
      52
     | 
    
                                  'critical' => priorities[3],
 
     | 
  
  
    | 
      53
     | 
    
                                  'blocker' => priorities[4]
 
     | 
  
  
    | 
      54
     | 
    
                                  }
 
     | 
  
  
    | 
      55
     | 
    
            
 
     | 
  
  
    | 
      56
     | 
    
              TRACKER_BUG = Tracker.find_by_position(1)
 
     | 
  
  
    | 
      57
     | 
    
              TRACKER_FEATURE = Tracker.find_by_position(2)
 
     | 
  
  
    | 
      58
     | 
    
              DEFAULT_TRACKER = TRACKER_BUG
 
     | 
  
  
    | 
      59
     | 
    
              TRACKER_MAPPING = {'defect' => TRACKER_BUG,
     | 
  
  
    | 
      60
     | 
    
                                 'enhancement' => TRACKER_FEATURE,
 
     | 
  
  
    | 
      61
     | 
    
                                 'task' => TRACKER_FEATURE,
 
     | 
  
  
    | 
      62
     | 
    
                                 'patch' =>TRACKER_FEATURE
 
     | 
  
  
    | 
      63
     | 
    
                                 }
 
     | 
  
  
    | 
      64
     | 
    
              
 
     | 
  
  
    | 
      65
     | 
    
              roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
     | 
  
  
    | 
      66
     | 
    
              manager_role = roles[0]
 
     | 
  
  
    | 
      67
     | 
    
              developer_role = roles[1]
 
     | 
  
  
    | 
      68
     | 
    
              DEFAULT_ROLE = roles.last
 
     | 
  
  
    | 
      69
     | 
    
              ROLE_MAPPING = {'admin' => manager_role,
     | 
  
  
    | 
      70
     | 
    
                              'developer' => developer_role
 
     | 
  
  
    | 
      71
     | 
    
                              }
 
     | 
  
  
    | 
      72
     | 
    
                              
 
     | 
  
  
    | 
      73
     | 
    
            class ::Time
 
     | 
  
  
    | 
      74
     | 
    
              class << self
 
     | 
  
  
    | 
      75
     | 
    
                alias :real_now :now
 
     | 
  
  
    | 
      76
     | 
    
                def now
 
     | 
  
  
    | 
      77
     | 
    
                  real_now - @fake_diff.to_i
 
     | 
  
  
    | 
      78
     | 
    
                end
 
     | 
  
  
    | 
      79
     | 
    
                def fake(time)
 
     | 
  
  
    | 
      80
     | 
    
                  @fake_diff = real_now - time
 
     | 
  
  
    | 
      81
     | 
    
                  res = yield
 
     | 
  
  
    | 
      82
     | 
    
                  @fake_diff = 0
 
     | 
  
  
    | 
      83
     | 
    
                 res
 
     | 
  
  
    | 
      84
     | 
    
                end
 
     | 
  
  
    | 
      85
     | 
    
              end
 
     | 
  
  
    | 
      86
     | 
    
            end
 
     | 
  
  
    | 
      87
     | 
    
      
 
     | 
  
  
    | 
      88
     | 
    
            class TracComponent < ActiveRecord::Base
 
     | 
  
  
    | 
      89
     | 
    
              set_table_name :component
 
     | 
  
  
    | 
      90
     | 
    
            end
 
     | 
  
  
    | 
      91
     | 
    
        
 
     | 
  
  
    | 
      92
     | 
    
            class TracMilestone < ActiveRecord::Base
 
     | 
  
  
    | 
      93
     | 
    
              set_table_name :milestone
 
     | 
  
  
    | 
      94
     | 
    
              
 
     | 
  
  
    | 
      95
     | 
    
              def due
 
     | 
  
  
    | 
      96
     | 
    
                if read_attribute(:due) && read_attribute(:due) > 0
 
     | 
  
  
    | 
      97
     | 
    
                  Time.at(read_attribute(:due)).to_date
 
     | 
  
  
    | 
      98
     | 
    
                else
 
     | 
  
  
    | 
      99
     | 
    
                  nil
 
     | 
  
  
    | 
      100
     | 
    
                end
 
     | 
  
  
    | 
      101
     | 
    
              end
 
     | 
  
  
    | 
      102
     | 
    
              
 
     | 
  
  
    | 
      103
     | 
    
              # This is the real timepoint at which the milestone has finished.
 
     | 
  
  
    | 
      104
     | 
    
              def completed
 
     | 
  
  
    | 
      105
     | 
    
                if read_attribute(:completed) && read_attribute(:completed) > 0
 
     | 
  
  
    | 
      106
     | 
    
                  Time.at(read_attribute(:completed)).to_date
 
     | 
  
  
    | 
      107
     | 
    
                else
 
     | 
  
  
    | 
      108
     | 
    
                  nil
 
     | 
  
  
    | 
      109
     | 
    
                end
 
     | 
  
  
    | 
      110
     | 
    
              end
 
     | 
  
  
    | 
      111
     | 
    
      
 
     | 
  
  
    | 
      112
     | 
    
              def description
 
     | 
  
  
    | 
      113
     | 
    
                # Attribute is named descr in Trac v0.8.x
 
     | 
  
  
    | 
      114
     | 
    
                has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
 
     | 
  
  
    | 
      115
     | 
    
              end
 
     | 
  
  
    | 
      116
     | 
    
            end
 
     | 
  
  
    | 
      117
     | 
    
            
 
     | 
  
  
    | 
      118
     | 
    
            class TracTicketCustom < ActiveRecord::Base
 
     | 
  
  
    | 
      119
     | 
    
              set_table_name :ticket_custom
 
     | 
  
  
    | 
      120
     | 
    
            end
 
     | 
  
  
    | 
      121
     | 
    
      
 
     | 
  
  
    | 
      122
     | 
    
            class TracAttachment < ActiveRecord::Base
 
     | 
  
  
    | 
      123
     | 
    
              set_table_name :attachment
 
     | 
  
  
    | 
      124
     | 
    
              set_inheritance_column :none
 
     | 
  
  
    | 
      125
     | 
    
              
 
     | 
  
  
    | 
      126
     | 
    
              def time; Time.at(read_attribute(:time)) end
 
     | 
  
  
    | 
      127
     | 
    
              
 
     | 
  
  
    | 
      128
     | 
    
              def original_filename
 
     | 
  
  
    | 
      129
     | 
    
                filename
 
     | 
  
  
    | 
      130
     | 
    
              end
 
     | 
  
  
    | 
      131
     | 
    
              
 
     | 
  
  
    | 
      132
     | 
    
              def content_type
 
     | 
  
  
    | 
      133
     | 
    
                Redmine::MimeType.of(filename) || ''
 
     | 
  
  
    | 
      134
     | 
    
              end
 
     | 
  
  
    | 
      135
     | 
    
              
 
     | 
  
  
    | 
      136
     | 
    
              def exist?
 
     | 
  
  
    | 
      137
     | 
    
                File.file? trac_fullpath
 
     | 
  
  
    | 
      138
     | 
    
              end
 
     | 
  
  
    | 
      139
     | 
    
              
 
     | 
  
  
    | 
      140
     | 
    
              def read
 
     | 
  
  
    | 
      141
     | 
    
                File.open("#{trac_fullpath}", 'rb').read
     | 
  
  
    | 
      142
     | 
    
              end
 
     | 
  
  
    | 
      143
     | 
    
              
 
     | 
  
  
    | 
      144
     | 
    
              def description
 
     | 
  
  
    | 
      145
     | 
    
                read_attribute(:description).to_s.slice(0,255)
 
     | 
  
  
    | 
      146
     | 
    
              end
 
     | 
  
  
    | 
      147
     | 
    
              
 
     | 
  
  
    | 
      148
     | 
    
            private
 
     | 
  
  
    | 
      149
     | 
    
              def trac_fullpath
 
     | 
  
  
    | 
      150
     | 
    
                attachment_type = read_attribute(:type)
 
     | 
  
  
    | 
      151
     | 
    
                trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
     | 
  
  
    | 
      152
     | 
    
                "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
     | 
  
  
    | 
      153
     | 
    
              end
 
     | 
  
  
    | 
      154
     | 
    
            end
 
     | 
  
  
    | 
      155
     | 
    
            
 
     | 
  
  
    | 
      156
     | 
    
            class TracTicket < ActiveRecord::Base
 
     | 
  
  
    | 
      157
     | 
    
              set_table_name :ticket
 
     | 
  
  
    | 
      158
     | 
    
              set_inheritance_column :none
 
     | 
  
  
    | 
      159
     | 
    
              
 
     | 
  
  
    | 
      160
     | 
    
              # ticket changes: only migrate status changes and comments
 
     | 
  
  
    | 
      161
     | 
    
              has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
 
     | 
  
  
    | 
      162
     | 
    
              has_many :attachments, :class_name => "TracAttachment",
 
     | 
  
  
    | 
      163
     | 
    
                                     :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
     | 
  
  
    | 
      164
     | 
    
                                                    " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
     | 
  
  
    | 
      165
     | 
    
                                                    ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
     | 
  
  
    | 
      166
     | 
    
              has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
 
     | 
  
  
    | 
      167
     | 
    
              
 
     | 
  
  
    | 
      168
     | 
    
              def ticket_type
 
     | 
  
  
    | 
      169
     | 
    
                read_attribute(:type)
 
     | 
  
  
    | 
      170
     | 
    
              end
 
     | 
  
  
    | 
      171
     | 
    
              
 
     | 
  
  
    | 
      172
     | 
    
              def summary
 
     | 
  
  
    | 
      173
     | 
    
                read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
 
     | 
  
  
    | 
      174
     | 
    
              end
 
     | 
  
  
    | 
      175
     | 
    
              
 
     | 
  
  
    | 
      176
     | 
    
              def description
 
     | 
  
  
    | 
      177
     | 
    
                read_attribute(:description).blank? ? summary : read_attribute(:description)
 
     | 
  
  
    | 
      178
     | 
    
              end
 
     | 
  
  
    | 
      179
     | 
    
              
 
     | 
  
  
    | 
      180
     | 
    
              def time; Time.at(read_attribute(:time)) end
 
     | 
  
  
    | 
      181
     | 
    
              def changetime; Time.at(read_attribute(:changetime)) end
 
     | 
  
  
    | 
      182
     | 
    
            end
 
     | 
  
  
    | 
      183
     | 
    
            
 
     | 
  
  
    | 
      184
     | 
    
            class TracTicketChange < ActiveRecord::Base
 
     | 
  
  
    | 
      185
     | 
    
              set_table_name :ticket_change
 
     | 
  
  
    | 
      186
     | 
    
              
 
     | 
  
  
    | 
      187
     | 
    
              def time; Time.at(read_attribute(:time)) end
 
     | 
  
  
    | 
      188
     | 
    
            end
 
     | 
  
  
    | 
      189
     | 
    
            
 
     | 
  
  
    | 
      190
     | 
    
            TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
 
     | 
  
  
    | 
      191
     | 
    
                                 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
 
     | 
  
  
    | 
      192
     | 
    
                                 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
 
     | 
  
  
    | 
      193
     | 
    
                                 TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
 
     | 
  
  
    | 
      194
     | 
    
                                 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
 
     | 
  
  
    | 
      195
     | 
    
                                 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
 
     | 
  
  
    | 
      196
     | 
    
                                 CamelCase TitleIndex)
 
     | 
  
  
    | 
      197
     | 
    
            
 
     | 
  
  
    | 
      198
     | 
    
            class TracWikiPage < ActiveRecord::Base
 
     | 
  
  
    | 
      199
     | 
    
              set_table_name :wiki
 
     | 
  
  
    | 
      200
     | 
    
              set_primary_key :name
 
     | 
  
  
    | 
      201
     | 
    
              
 
     | 
  
  
    | 
      202
     | 
    
              has_many :attachments, :class_name => "TracAttachment",
 
     | 
  
  
    | 
      203
     | 
    
                                     :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
     | 
  
  
    | 
      204
     | 
    
                                            " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
     | 
  
  
    | 
      205
     | 
    
                                            ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
     | 
  
  
    | 
      206
     | 
    
              
 
     | 
  
  
    | 
      207
     | 
    
              def self.columns
 
     | 
  
  
    | 
      208
     | 
    
                # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
 
     | 
  
  
    | 
      209
     | 
    
                super.select {|column| column.name.to_s != 'readonly'}
     | 
  
  
    | 
      210
     | 
    
              end
 
     | 
  
  
    | 
      211
     | 
    
              
 
     | 
  
  
    | 
      212
     | 
    
              def time; Time.at(read_attribute(:time)) end
 
     | 
  
  
    | 
      213
     | 
    
            end
 
     | 
  
  
    | 
      214
     | 
    
            
 
     | 
  
  
    | 
      215
     | 
    
            class TracPermission < ActiveRecord::Base
 
     | 
  
  
    | 
      216
     | 
    
              set_table_name :permission  
 
     | 
  
  
    | 
      217
     | 
    
            end
 
     | 
  
  
    | 
      218
     | 
    
            
 
     | 
  
  
    | 
      219
     | 
    
            class TracSessionAttribute < ActiveRecord::Base
 
     | 
  
  
    | 
      220
     | 
    
              set_table_name :session_attribute
 
     | 
  
  
    | 
      221
     | 
    
            end
 
     | 
  
  
    | 
      222
     | 
    
             
 
     | 
  
  
    | 
      223
     | 
    
            def self.find_or_create_user(username, project_member = false)
 
     | 
  
  
    | 
      224
     | 
    
              return User.anonymous if username.blank?
 
     | 
  
  
    | 
      225
     | 
    
              
 
     | 
  
  
    | 
      226
     | 
    
              u = User.find_by_login(username)
 
     | 
  
  
    | 
      227
     | 
    
              if !u
 
     | 
  
  
    | 
      228
     | 
    
                # Create a new user if not found
 
     | 
  
  
    | 
      229
     | 
    
                mail = username[0,limit_for(User, 'mail')]
 
     | 
  
  
    | 
      230
     | 
    
                if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email')
 
     | 
  
  
    | 
      231
     | 
    
                  mail = mail_attr.value
 
     | 
  
  
    | 
      232
     | 
    
                end
 
     | 
  
  
    | 
      233
     | 
    
                mail = "#{mail}@foo.bar" unless mail.include?("@")
     | 
  
  
    | 
      234
     | 
    
                
 
     | 
  
  
    | 
      235
     | 
    
                name = username
 
     | 
  
  
    | 
      236
     | 
    
                if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
 
     | 
  
  
    | 
      237
     | 
    
                  name = name_attr.value
 
     | 
  
  
    | 
      238
     | 
    
                end
 
     | 
  
  
    | 
      239
     | 
    
                name =~ (/(.*)(\s+\w+)?/)
 
     | 
  
  
    | 
      240
     | 
    
                fn = $1.strip
 
     | 
  
  
    | 
      241
     | 
    
                ln = ($2 || '-').strip
 
     | 
  
  
    | 
      242
     | 
    
                
 
     | 
  
  
    | 
      243
     | 
    
                u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
 
     | 
  
  
    | 
      244
     | 
    
                             :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'),
 
     | 
  
  
    | 
      245
     | 
    
                             :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-')
 
     | 
  
  
    | 
      246
     | 
    
      
 
     | 
  
  
    | 
      247
     | 
    
                u.login = username[0,limit_for(User, 'login')].gsub(/[^a-z0-9_\-@\.]/i, '-')
 
     | 
  
  
    | 
      248
     | 
    
                u.password = 'trac'
 
     | 
  
  
    | 
      249
     | 
    
                u.admin = true if TracPermission.find_by_username_and_action(username, 'admin')
 
     | 
  
  
    | 
      250
     | 
    
                # finally, a default user is used if the new user is not valid
 
     | 
  
  
    | 
      251
     | 
    
                u = User.find(:first) unless u.save
 
     | 
  
  
    | 
      252
     | 
    
              end
 
     | 
  
  
    | 
      253
     | 
    
              # Make sure he is a member of the project
 
     | 
  
  
    | 
      254
     | 
    
              if project_member && !u.member_of?(@target_project)
 
     | 
  
  
    | 
      255
     | 
    
                role = DEFAULT_ROLE
 
     | 
  
  
    | 
      256
     | 
    
                if u.admin
 
     | 
  
  
    | 
      257
     | 
    
                  role = ROLE_MAPPING['admin']
 
     | 
  
  
    | 
      258
     | 
    
                elsif TracPermission.find_by_username_and_action(username, 'developer')
 
     | 
  
  
    | 
      259
     | 
    
                  role = ROLE_MAPPING['developer']
 
     | 
  
  
    | 
      260
     | 
    
                end
 
     | 
  
  
    | 
      261
     | 
    
                Member.create(:user => u, :project => @target_project, :role => role)
 
     | 
  
  
    | 
      262
     | 
    
                u.reload
 
     | 
  
  
    | 
      263
     | 
    
              end
 
     | 
  
  
    | 
      264
     | 
    
              u
 
     | 
  
  
    | 
      265
     | 
    
            end
 
     | 
  
  
    | 
      266
     | 
    
           
 
     | 
  
  
    | 
      267
     | 
    
          
 
     | 
  
  
    | 
      268
     | 
    
            def self.migrate
 
     | 
  
  
    | 
      269
     | 
    
              establish_connection
 
     | 
  
  
    | 
      270
     | 
    
      
 
     | 
  
  
    | 
      271
     | 
    
              # Quick database test
 
     | 
  
  
    | 
      272
     | 
    
              TracComponent.count
 
     | 
  
  
    | 
      273
     | 
    
                      
 
     | 
  
  
    | 
      274
     | 
    
              migrated_components = 0
 
     | 
  
  
    | 
      275
     | 
    
              migrated_milestones = 0
 
     | 
  
  
    | 
      276
     | 
    
              migrated_tickets = 0
 
     | 
  
  
    | 
      277
     | 
    
              migrated_custom_values = 0
 
     | 
  
  
    | 
      278
     | 
    
              migrated_ticket_attachments = 0
 
     | 
  
  
    | 
      279
     | 
    
              migrated_wiki_edits = 0      
 
     | 
  
  
    | 
      280
     | 
    
              migrated_wiki_attachments = 0
 
     | 
  
  
    | 
      281
     | 
    
      
 
     | 
  
  
    | 
      282
     | 
    
              #Wiki system initializing
 
     | 
  
  
    | 
      283
     | 
    
              @target_project.wiki.destroy if @target_project.wiki
 
     | 
  
  
    | 
      284
     | 
    
              @target_project.reload
 
     | 
  
  
    | 
      285
     | 
    
              wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
 
     | 
  
  
    | 
      286
     | 
    
              wiki_edit_count = 0
 
     | 
  
  
    | 
      287
     | 
    
        
 
     | 
  
  
    | 
      288
     | 
    
              # Components
 
     | 
  
  
    | 
      289
     | 
    
              print "Migrating components"
 
     | 
  
  
    | 
      290
     | 
    
              issues_category_map = {}
     | 
  
  
    | 
      291
     | 
    
              TracComponent.find(:all).each do |component|
 
     | 
  
  
    | 
      292
     | 
    
            	print '.'
 
     | 
  
  
    | 
      293
     | 
    
            	STDOUT.flush
 
     | 
  
  
    | 
      294
     | 
    
                c = IssueCategory.new :project => @target_project,
 
     | 
  
  
    | 
      295
     | 
    
                                      :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
 
     | 
  
  
    | 
      296
     | 
    
            	next unless c.save
 
     | 
  
  
    | 
      297
     | 
    
            	issues_category_map[component.name] = c
 
     | 
  
  
    | 
      298
     | 
    
            	migrated_components += 1
 
     | 
  
  
    | 
      299
     | 
    
              end
 
     | 
  
  
    | 
      300
     | 
    
              puts
 
     | 
  
  
    | 
      301
     | 
    
              
 
     | 
  
  
    | 
      302
     | 
    
              # Milestones
 
     | 
  
  
    | 
      303
     | 
    
              print "Migrating milestones"
 
     | 
  
  
    | 
      304
     | 
    
              version_map = {}
     | 
  
  
    | 
      305
     | 
    
              TracMilestone.find(:all).each do |milestone|
 
     | 
  
  
    | 
      306
     | 
    
                print '.'
 
     | 
  
  
    | 
      307
     | 
    
                STDOUT.flush
 
     | 
  
  
    | 
      308
     | 
    
                # First we try to find the wiki page...
 
     | 
  
  
    | 
      309
     | 
    
                p = wiki.find_or_new_page(milestone.name.to_s)
 
     | 
  
  
    | 
      310
     | 
    
                p.content = WikiContent.new(:page => p) if p.new_record?
 
     | 
  
  
    | 
      311
     | 
    
                p.content.text = milestone.description.to_s
 
     | 
  
  
    | 
      312
     | 
    
                p.content.author = find_or_create_user('trac')
     | 
  
  
    | 
      313
     | 
    
                p.content.comments = 'Milestone'
 
     | 
  
  
    | 
      314
     | 
    
                p.save
 
     | 
  
  
    | 
      315
     | 
    
                
 
     | 
  
  
    | 
      316
     | 
    
                v = Version.new :project => @target_project,
 
     | 
  
  
    | 
      317
     | 
    
                                :name => encode(milestone.name[0, limit_for(Version, 'name')]),
 
     | 
  
  
    | 
      318
     | 
    
                                :description => nil,
 
     | 
  
  
    | 
      319
     | 
    
                                :wiki_page_title => encode(milestone.name.to_s),
 
     | 
  
  
    | 
      320
     | 
    
                                :effective_date => milestone.completed
 
     | 
  
  
    | 
      321
     | 
    
                next unless v.save
 
     | 
  
  
    | 
      322
     | 
    
                version_map[milestone.name] = v
 
     | 
  
  
    | 
      323
     | 
    
                migrated_milestones += 1
 
     | 
  
  
    | 
      324
     | 
    
              end
 
     | 
  
  
    | 
      325
     | 
    
              puts
 
     | 
  
  
    | 
      326
     | 
    
              
 
     | 
  
  
    | 
      327
     | 
    
              # Custom fields
 
     | 
  
  
    | 
      328
     | 
    
              # TODO: read trac.ini instead
 
     | 
  
  
    | 
      329
     | 
    
              print "Migrating custom fields"
 
     | 
  
  
    | 
      330
     | 
    
              custom_field_map = {}
     | 
  
  
    | 
      331
     | 
    
              TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
     | 
  
  
    | 
      332
     | 
    
                print '.'
 
     | 
  
  
    | 
      333
     | 
    
                STDOUT.flush
 
     | 
  
  
    | 
      334
     | 
    
                # Redmine custom field name
 
     | 
  
  
    | 
      335
     | 
    
                field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
 
     | 
  
  
    | 
      336
     | 
    
                # Find if the custom already exists in Redmine
 
     | 
  
  
    | 
      337
     | 
    
                f = IssueCustomField.find_by_name(field_name)
 
     | 
  
  
    | 
      338
     | 
    
                # Or create a new one
 
     | 
  
  
    | 
      339
     | 
    
                f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
 
     | 
  
  
    | 
      340
     | 
    
                                              :field_format => 'string')
 
     | 
  
  
    | 
      341
     | 
    
                                         
 
     | 
  
  
    | 
      342
     | 
    
                next if f.new_record?
 
     | 
  
  
    | 
      343
     | 
    
                f.trackers = Tracker.find(:all)
 
     | 
  
  
    | 
      344
     | 
    
                f.projects << @target_project
 
     | 
  
  
    | 
      345
     | 
    
                custom_field_map[field.name] = f
 
     | 
  
  
    | 
      346
     | 
    
              end
 
     | 
  
  
    | 
      347
     | 
    
              puts
 
     | 
  
  
    | 
      348
     | 
    
              
 
     | 
  
  
    | 
      349
     | 
    
              # Trac 'resolution' field as a Redmine custom field
 
     | 
  
  
    | 
      350
     | 
    
              r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
     | 
  
  
    | 
      351
     | 
    
              r = IssueCustomField.new(:name => 'Resolution',
 
     | 
  
  
    | 
      352
     | 
    
                                       :field_format => 'list',
 
     | 
  
  
    | 
      353
     | 
    
                                       :is_filter => true) if r.nil?
 
     | 
  
  
    | 
      354
     | 
    
              r.trackers = Tracker.find(:all)
 
     | 
  
  
    | 
      355
     | 
    
              r.projects << @target_project
 
     | 
  
  
    | 
      356
     | 
    
              r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
 
     | 
  
  
    | 
      357
     | 
    
              r.save!
 
     | 
  
  
    | 
      358
     | 
    
              custom_field_map['resolution'] = r
 
     | 
  
  
    | 
      359
     | 
    
                  
 
     | 
  
  
    | 
      360
     | 
    
              # Tickets
 
     | 
  
  
    | 
      361
     | 
    
              print "Migrating tickets"
 
     | 
  
  
    | 
      362
     | 
    
                TracTicket.find(:all, :order => 'id ASC').each do |ticket|
 
     | 
  
  
    | 
      363
     | 
    
              	print '.'
 
     | 
  
  
    | 
      364
     | 
    
              	STDOUT.flush
 
     | 
  
  
    | 
      365
     | 
    
              	i = Issue.new :project => @target_project, 
 
     | 
  
  
    | 
      366
     | 
    
                                :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
 
     | 
  
  
    | 
      367
     | 
    
                                :description => TracMigrate::TracWiki.wiki(encode(ticket.description)),
 
     | 
  
  
    | 
      368
     | 
    
                                :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
 
     | 
  
  
    | 
      369
     | 
    
                                :created_on => ticket.time
 
     | 
  
  
    | 
      370
     | 
    
              	i.author = find_or_create_user(ticket.reporter)    	
 
     | 
  
  
    | 
      371
     | 
    
              	i.category = issues_category_map[ticket.component] unless ticket.component.blank?
 
     | 
  
  
    | 
      372
     | 
    
              	i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
 
     | 
  
  
    | 
      373
     | 
    
              	i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
 
     | 
  
  
    | 
      374
     | 
    
              	i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
 
     | 
  
  
    | 
      375
     | 
    
              	i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
 
     | 
  
  
    | 
      376
     | 
    
              	i.id = ticket.id unless Issue.exists?(ticket.id)
 
     | 
  
  
    | 
      377
     | 
    
              	next unless Time.fake(ticket.changetime) { i.save }
     | 
  
  
    | 
      378
     | 
    
              	TICKET_MAP[ticket.id] = i.id
 
     | 
  
  
    | 
      379
     | 
    
              	migrated_tickets += 1
 
     | 
  
  
    | 
      380
     | 
    
              	
 
     | 
  
  
    | 
      381
     | 
    
              	# Owner
 
     | 
  
  
    | 
      382
     | 
    
                  unless ticket.owner.blank?
 
     | 
  
  
    | 
      383
     | 
    
                    i.assigned_to = find_or_create_user(ticket.owner, true)
 
     | 
  
  
    | 
      384
     | 
    
                    Time.fake(ticket.changetime) { i.save }
     | 
  
  
    | 
      385
     | 
    
                  end
 
     | 
  
  
    | 
      386
     | 
    
            	
 
     | 
  
  
    | 
      387
     | 
    
              	# Comments and status/resolution changes
 
     | 
  
  
    | 
      388
     | 
    
              	ticket.changes.group_by(&:time).each do |time, changeset|
 
     | 
  
  
    | 
      389
     | 
    
                    status_change = changeset.select {|change| change.field == 'status'}.first
     | 
  
  
    | 
      390
     | 
    
                    resolution_change = changeset.select {|change| change.field == 'resolution'}.first
     | 
  
  
    | 
      391
     | 
    
                    comment_change = changeset.select {|change| change.field == 'comment'}.first
     | 
  
  
    | 
      392
     | 
    
                    
 
     | 
  
  
    | 
      393
     | 
    
                    n = Journal.new :notes => (comment_change ? TracMigrate::TracWiki.wiki(encode(comment_change.newvalue)) : ''),
 
     | 
  
  
    | 
      394
     | 
    
                                    :created_on => time
 
     | 
  
  
    | 
      395
     | 
    
                    n.user = find_or_create_user(changeset.first.author)
 
     | 
  
  
    | 
      396
     | 
    
                    n.journalized = i
 
     | 
  
  
    | 
      397
     | 
    
                    if status_change && 
 
     | 
  
  
    | 
      398
     | 
    
                         STATUS_MAPPING[status_change.oldvalue] &&
 
     | 
  
  
    | 
      399
     | 
    
                         STATUS_MAPPING[status_change.newvalue] &&
 
     | 
  
  
    | 
      400
     | 
    
                         (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
 
     | 
  
  
    | 
      401
     | 
    
                      n.details << JournalDetail.new(:property => 'attr',
 
     | 
  
  
    | 
      402
     | 
    
                                                     :prop_key => 'status_id',
 
     | 
  
  
    | 
      403
     | 
    
                                                     :old_value => STATUS_MAPPING[status_change.oldvalue].id,
 
     | 
  
  
    | 
      404
     | 
    
                                                     :value => STATUS_MAPPING[status_change.newvalue].id)
 
     | 
  
  
    | 
      405
     | 
    
                    end
 
     | 
  
  
    | 
      406
     | 
    
                    if resolution_change
 
     | 
  
  
    | 
      407
     | 
    
                      n.details << JournalDetail.new(:property => 'cf',
 
     | 
  
  
    | 
      408
     | 
    
                                                     :prop_key => custom_field_map['resolution'].id,
 
     | 
  
  
    | 
      409
     | 
    
                                                     :old_value => resolution_change.oldvalue,
 
     | 
  
  
    | 
      410
     | 
    
                                                     :value => resolution_change.newvalue)
 
     | 
  
  
    | 
      411
     | 
    
                    end
 
     | 
  
  
    | 
      412
     | 
    
                    n.save unless n.details.empty? && n.notes.blank?
 
     | 
  
  
    | 
      413
     | 
    
              	end
 
     | 
  
  
    | 
      414
     | 
    
              	
 
     | 
  
  
    | 
      415
     | 
    
              	# Attachments
 
     | 
  
  
    | 
      416
     | 
    
              	ticket.attachments.each do |attachment|
 
     | 
  
  
    | 
      417
     | 
    
              	  next unless attachment.exist?
 
     | 
  
  
    | 
      418
     | 
    
                    a = Attachment.new :created_on => attachment.time
 
     | 
  
  
    | 
      419
     | 
    
                    a.file = attachment
 
     | 
  
  
    | 
      420
     | 
    
                    a.author = find_or_create_user(attachment.author)
 
     | 
  
  
    | 
      421
     | 
    
                    a.container = i
 
     | 
  
  
    | 
      422
     | 
    
                    a.description = attachment.description
 
     | 
  
  
    | 
      423
     | 
    
                    migrated_ticket_attachments += 1 if a.save
 
     | 
  
  
    | 
      424
     | 
    
              	end
 
     | 
  
  
    | 
      425
     | 
    
              	
 
     | 
  
  
    | 
      426
     | 
    
              	# Custom fields
 
     | 
  
  
    | 
      427
     | 
    
              	ticket.customs.each do |custom|
 
     | 
  
  
    | 
      428
     | 
    
              	  next if custom_field_map[custom.name].nil?
 
     | 
  
  
    | 
      429
     | 
    
                    v = CustomValue.new :custom_field => custom_field_map[custom.name],
 
     | 
  
  
    | 
      430
     | 
    
                                        :value => custom.value
 
     | 
  
  
    | 
      431
     | 
    
                    v.customized = i
 
     | 
  
  
    | 
      432
     | 
    
                    next unless v.save
 
     | 
  
  
    | 
      433
     | 
    
                    migrated_custom_values += 1
 
     | 
  
  
    | 
      434
     | 
    
              	end
 
     | 
  
  
    | 
      435
     | 
    
              end
 
     | 
  
  
    | 
      436
     | 
    
              
 
     | 
  
  
    | 
      437
     | 
    
              # update issue id sequence if needed (postgresql)
 
     | 
  
  
    | 
      438
     | 
    
              Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
     | 
  
  
    | 
      439
     | 
    
              puts
 
     | 
  
  
    | 
      440
     | 
    
              
 
     | 
  
  
    | 
      441
     | 
    
              # Wiki      
 
     | 
  
  
    | 
      442
     | 
    
              print "Migrating wiki"
 
     | 
  
  
    | 
      443
     | 
    
              if wiki.save
 
     | 
  
  
    | 
      444
     | 
    
                TracWikiPage.find(:all, :order => 'name, version').each do |page|
 
     | 
  
  
    | 
      445
     | 
    
                  # Do not migrate Trac manual wiki pages
 
     | 
  
  
    | 
      446
     | 
    
                  next if TRAC_WIKI_PAGES.include?(page.name)
 
     | 
  
  
    | 
      447
     | 
    
                  wiki_edit_count += 1
 
     | 
  
  
    | 
      448
     | 
    
                  print '.'
 
     | 
  
  
    | 
      449
     | 
    
                  STDOUT.flush
 
     | 
  
  
    | 
      450
     | 
    
                  p = wiki.find_or_new_page(page.name)
 
     | 
  
  
    | 
      451
     | 
    
                  p.content = WikiContent.new(:page => p) if p.new_record?
 
     | 
  
  
    | 
      452
     | 
    
                  p.content.text = page.text
 
     | 
  
  
    | 
      453
     | 
    
                  p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
 
     | 
  
  
    | 
      454
     | 
    
                  p.content.comments = page.comment
 
     | 
  
  
    | 
      455
     | 
    
                  Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
     | 
  
  
    | 
      456
     | 
    
                  
 
     | 
  
  
    | 
      457
     | 
    
                  next if p.content.new_record?
 
     | 
  
  
    | 
      458
     | 
    
                  migrated_wiki_edits += 1 
 
     | 
  
  
    | 
      459
     | 
    
                  
 
     | 
  
  
    | 
      460
     | 
    
                  # Attachments
 
     | 
  
  
    | 
      461
     | 
    
                  page.attachments.each do |attachment|
 
     | 
  
  
    | 
      462
     | 
    
                    next unless attachment.exist?
 
     | 
  
  
    | 
      463
     | 
    
                    next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
 
     | 
  
  
    | 
      464
     | 
    
                    a = Attachment.new :created_on => attachment.time
 
     | 
  
  
    | 
      465
     | 
    
                    a.file = attachment
 
     | 
  
  
    | 
      466
     | 
    
                    a.author = find_or_create_user(attachment.author)
 
     | 
  
  
    | 
      467
     | 
    
                    a.description = attachment.description
 
     | 
  
  
    | 
      468
     | 
    
                    a.container = p
 
     | 
  
  
    | 
      469
     | 
    
                    migrated_wiki_attachments += 1 if a.save
 
     | 
  
  
    | 
      470
     | 
    
                  end
 
     | 
  
  
    | 
      471
     | 
    
                end
 
     | 
  
  
    | 
      472
     | 
    
                
 
     | 
  
  
    | 
      473
     | 
    
                wiki.reload
 
     | 
  
  
    | 
      474
     | 
    
                wiki.pages.each do |page|
 
     | 
  
  
    | 
      475
     | 
    
                  page.content.text = TracMigrate::TracWiki.wiki(page.content.text)
 
     | 
  
  
    | 
      476
     | 
    
                  Time.fake(page.content.updated_on) { page.content.save }
     | 
  
  
    | 
      477
     | 
    
                end
 
     | 
  
  
    | 
      478
     | 
    
              end
 
     | 
  
  
    | 
      479
     | 
    
              puts
 
     | 
  
  
    | 
      480
     | 
    
              
 
     | 
  
  
    | 
      481
     | 
    
              puts
 
     | 
  
  
    | 
      482
     | 
    
              puts "Components:      #{migrated_components}/#{TracComponent.count}"
     | 
  
  
    | 
      483
     | 
    
              puts "Milestones:      #{migrated_milestones}/#{TracMilestone.count}"
     | 
  
  
    | 
      484
     | 
    
              puts "Tickets:         #{migrated_tickets}/#{TracTicket.count}"
     | 
  
  
    | 
      485
     | 
    
              puts "Ticket files:    #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
     | 
  
  
    | 
      486
     | 
    
              puts "Custom values:   #{migrated_custom_values}/#{TracTicketCustom.count}"
     | 
  
  
    | 
      487
     | 
    
              puts "Wiki edits:      #{migrated_wiki_edits}/#{wiki_edit_count}"
     | 
  
  
    | 
      488
     | 
    
              puts "Wiki files:      #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
     | 
  
  
    | 
      489
     | 
    
            end
 
     | 
  
  
    | 
      490
     | 
    
            
 
     | 
  
  
    | 
      491
     | 
    
            def self.limit_for(klass, attribute)
 
     | 
  
  
    | 
      492
     | 
    
              klass.columns_hash[attribute.to_s].limit
 
     | 
  
  
    | 
      493
     | 
    
            end
 
     | 
  
  
    | 
      494
     | 
    
            
 
     | 
  
  
    | 
      495
     | 
    
            def self.encoding(charset)
 
     | 
  
  
    | 
      496
     | 
    
              @ic = Iconv.new('UTF-8', charset)
     | 
  
  
    | 
      497
     | 
    
            rescue Iconv::InvalidEncoding
 
     | 
  
  
    | 
      498
     | 
    
              puts "Invalid encoding!"
 
     | 
  
  
    | 
      499
     | 
    
              return false
 
     | 
  
  
    | 
      500
     | 
    
            end
 
     | 
  
  
    | 
      501
     | 
    
            
 
     | 
  
  
    | 
      502
     | 
    
            def self.set_trac_directory(path)
 
     | 
  
  
    | 
      503
     | 
    
              @@trac_directory = path
 
     | 
  
  
    | 
      504
     | 
    
              raise "This directory doesn't exist!" unless File.directory?(path)
 
     | 
  
  
    | 
      505
     | 
    
              raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
     | 
  
  
    | 
      506
     | 
    
              @@trac_directory
 
     | 
  
  
    | 
      507
     | 
    
            rescue Exception => e
 
     | 
  
  
    | 
      508
     | 
    
              puts e
 
     | 
  
  
    | 
      509
     | 
    
              return false
 
     | 
  
  
    | 
      510
     | 
    
            end
 
     | 
  
  
    | 
      511
     | 
    
      
 
     | 
  
  
    | 
      512
     | 
    
            def self.trac_directory
 
     | 
  
  
    | 
      513
     | 
    
              @@trac_directory
 
     | 
  
  
    | 
      514
     | 
    
            end
 
     | 
  
  
    | 
      515
     | 
    
      
 
     | 
  
  
    | 
      516
     | 
    
            def self.set_trac_adapter(adapter)
 
     | 
  
  
    | 
      517
     | 
    
              return false if adapter.blank?
 
     | 
  
  
    | 
      518
     | 
    
              raise "Unknown adapter: #{adapter}!" unless %w(sqlite sqlite3 mysql postgresql).include?(adapter)
     | 
  
  
    | 
      519
     | 
    
              # If adapter is sqlite or sqlite3, make sure that trac.db exists
 
     | 
  
  
    | 
      520
     | 
    
              raise "#{trac_db_path} doesn't exist!" if %w(sqlite sqlite3).include?(adapter) && !File.exist?(trac_db_path)
     | 
  
  
    | 
      521
     | 
    
              @@trac_adapter = adapter
 
     | 
  
  
    | 
      522
     | 
    
            rescue Exception => e
 
     | 
  
  
    | 
      523
     | 
    
              puts e
 
     | 
  
  
    | 
      524
     | 
    
              return false
 
     | 
  
  
    | 
      525
     | 
    
            end
 
     | 
  
  
    | 
      526
     | 
    
            
 
     | 
  
  
    | 
      527
     | 
    
            def self.set_trac_db_host(host)
 
     | 
  
  
    | 
      528
     | 
    
              return nil if host.blank?
 
     | 
  
  
    | 
      529
     | 
    
              @@trac_db_host = host
 
     | 
  
  
    | 
      530
     | 
    
            end
 
     | 
  
  
    | 
      531
     | 
    
      
 
     | 
  
  
    | 
      532
     | 
    
            def self.set_trac_db_port(port)
 
     | 
  
  
    | 
      533
     | 
    
              return nil if port.to_i == 0
 
     | 
  
  
    | 
      534
     | 
    
              @@trac_db_port = port.to_i
 
     | 
  
  
    | 
      535
     | 
    
            end
 
     | 
  
  
    | 
      536
     | 
    
            
 
     | 
  
  
    | 
      537
     | 
    
            def self.set_trac_db_name(name)
 
     | 
  
  
    | 
      538
     | 
    
              return nil if name.blank?
 
     | 
  
  
    | 
      539
     | 
    
              @@trac_db_name = name
 
     | 
  
  
    | 
      540
     | 
    
            end
 
     | 
  
  
    | 
      541
     | 
    
      
 
     | 
  
  
    | 
      542
     | 
    
            def self.set_trac_db_username(username)
 
     | 
  
  
    | 
      543
     | 
    
              @@trac_db_username = username
 
     | 
  
  
    | 
      544
     | 
    
            end
 
     | 
  
  
    | 
      545
     | 
    
            
 
     | 
  
  
    | 
      546
     | 
    
            def self.set_trac_db_password(password)
 
     | 
  
  
    | 
      547
     | 
    
              @@trac_db_password = password
 
     | 
  
  
    | 
      548
     | 
    
            end
 
     | 
  
  
    | 
      549
     | 
    
            
 
     | 
  
  
    | 
      550
     | 
    
            def self.set_trac_db_schema(schema)
 
     | 
  
  
    | 
      551
     | 
    
              @@trac_db_schema = schema
 
     | 
  
  
    | 
      552
     | 
    
            end
 
     | 
  
  
    | 
      553
     | 
    
      
 
     | 
  
  
    | 
      554
     | 
    
            mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
 
     | 
  
  
    | 
      555
     | 
    
            
 
     | 
  
  
    | 
      556
     | 
    
            def self.trac_db_path; "#{trac_directory}/db/trac.db" end
     | 
  
  
    | 
      557
     | 
    
            def self.trac_attachments_directory; "#{trac_directory}/attachments" end
     | 
  
  
    | 
      558
     | 
    
            
 
     | 
  
  
    | 
      559
     | 
    
            def self.target_project_identifier(identifier)
 
     | 
  
  
    | 
      560
     | 
    
              project = Project.find_by_identifier(identifier)        
 
     | 
  
  
    | 
      561
     | 
    
              if !project
 
     | 
  
  
    | 
      562
     | 
    
                # create the target project
 
     | 
  
  
    | 
      563
     | 
    
                project = Project.new :name => identifier.humanize,
 
     | 
  
  
    | 
      564
     | 
    
                                      :description => ''
 
     | 
  
  
    | 
      565
     | 
    
                project.identifier = identifier
 
     | 
  
  
    | 
      566
     | 
    
                puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
     | 
  
  
    | 
      567
     | 
    
                # enable issues and wiki for the created project
 
     | 
  
  
    | 
      568
     | 
    
                project.enabled_module_names = ['issue_tracking', 'wiki']
 
     | 
  
  
    | 
      569
     | 
    
              else
 
     | 
  
  
    | 
      570
     | 
    
                puts
 
     | 
  
  
    | 
      571
     | 
    
                puts "This project already exists in your Redmine database."
 
     | 
  
  
    | 
      572
     | 
    
                print "Are you sure you want to append data to this project ? [Y/n] "
 
     | 
  
  
    | 
      573
     | 
    
                exit if STDIN.gets.match(/^n$/i)  
 
     | 
  
  
    | 
      574
     | 
    
              end
 
     | 
  
  
    | 
      575
     | 
    
              project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
 
     | 
  
  
    | 
      576
     | 
    
              project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
 
     | 
  
  
    | 
      577
     | 
    
              @target_project = project.new_record? ? nil : project
 
     | 
  
  
    | 
      578
     | 
    
            end
 
     | 
  
  
    | 
      579
     | 
    
            
 
     | 
  
  
    | 
      580
     | 
    
            def self.connection_params
 
     | 
  
  
    | 
      581
     | 
    
              if %w(sqlite sqlite3).include?(trac_adapter)
 
     | 
  
  
    | 
      582
     | 
    
                {:adapter => trac_adapter, 
     | 
  
  
    | 
      583
     | 
    
                 :database => trac_db_path}
 
     | 
  
  
    | 
      584
     | 
    
              else
 
     | 
  
  
    | 
      585
     | 
    
                {:adapter => trac_adapter,
     | 
  
  
    | 
      586
     | 
    
                 :database => trac_db_name,
 
     | 
  
  
    | 
      587
     | 
    
                 :host => trac_db_host,
 
     | 
  
  
    | 
      588
     | 
    
                 :port => trac_db_port,
 
     | 
  
  
    | 
      589
     | 
    
                 :username => trac_db_username,
 
     | 
  
  
    | 
      590
     | 
    
                 :password => trac_db_password,
 
     | 
  
  
    | 
      591
     | 
    
                 :schema_search_path => trac_db_schema
 
     | 
  
  
    | 
      592
     | 
    
                }
 
     | 
  
  
    | 
      593
     | 
    
              end
 
     | 
  
  
    | 
      594
     | 
    
            end
 
     | 
  
  
    | 
      595
     | 
    
            
 
     | 
  
  
    | 
      596
     | 
    
            def self.establish_connection
 
     | 
  
  
    | 
      597
     | 
    
              constants.each do |const|
 
     | 
  
  
    | 
      598
     | 
    
                klass = const_get(const)
 
     | 
  
  
    | 
      599
     | 
    
                next unless klass.respond_to? 'establish_connection'
 
     | 
  
  
    | 
      600
     | 
    
                klass.establish_connection connection_params
 
     | 
  
  
    | 
      601
     | 
    
              end
 
     | 
  
  
    | 
      602
     | 
    
            end
 
     | 
  
  
    | 
      603
     | 
    
            
 
     | 
  
  
    | 
      604
     | 
    
          private
 
     | 
  
  
    | 
      605
     | 
    
            def self.encode(text)
 
     | 
  
  
    | 
      606
     | 
    
              @ic.iconv text
 
     | 
  
  
    | 
      607
     | 
    
            rescue
 
     | 
  
  
    | 
      608
     | 
    
              text
 
     | 
  
  
    | 
      609
     | 
    
            end
 
     | 
  
  
    | 
      610
     | 
    
          end
 
     | 
  
  
    | 
      611
     | 
    
          
 
     | 
  
  
    | 
      612
     | 
    
          puts
 
     | 
  
  
    | 
      613
     | 
    
          if Redmine::DefaultData::Loader.no_data?
 
     | 
  
  
    | 
      614
     | 
    
            puts "Redmine configuration need to be loaded before importing data."
 
     | 
  
  
    | 
      615
     | 
    
            puts "Please, run this first:"
 
     | 
  
  
    | 
      616
     | 
    
            puts
 
     | 
  
  
    | 
      617
     | 
    
            puts "  rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
     | 
  
  
    | 
      618
     | 
    
            exit
 
     | 
  
  
    | 
      619
     | 
    
          end
 
     | 
  
  
    | 
      620
     | 
    
          
 
     | 
  
  
    | 
      621
     | 
    
          puts "WARNING: a new project will be added to Redmine during this process."
 
     | 
  
  
    | 
      622
     | 
    
          print "Are you sure you want to continue ? [y/N] "
 
     | 
  
  
    | 
      623
     | 
    
          break unless STDIN.gets.match(/^y$/i)  
 
     | 
  
  
    | 
      624
     | 
    
          puts
 
     | 
  
  
    | 
      625
     | 
    
      
 
     | 
  
  
    | 
      626
     | 
    
          def prompt(text, options = {}, &block)
     | 
  
  
    | 
      627
     | 
    
            default = options[:default] || ''
 
     | 
  
  
    | 
      628
     | 
    
            while true
 
     | 
  
  
    | 
      629
     | 
    
              print "#{text} [#{default}]: "
     | 
  
  
    | 
      630
     | 
    
              value = STDIN.gets.chomp!
 
     | 
  
  
    | 
      631
     | 
    
              value = default if value.blank?
 
     | 
  
  
    | 
      632
     | 
    
              break if yield value
 
     | 
  
  
    | 
      633
     | 
    
            end
 
     | 
  
  
    | 
      634
     | 
    
          end
 
     | 
  
  
    | 
      635
     | 
    
          
 
     | 
  
  
    | 
      636
     | 
    
          DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
     | 
  
  
    | 
      637
     | 
    
          
 
     | 
  
  
    | 
      638
     | 
    
          prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
     | 
  
  
    | 
      639
     | 
    
          prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
     | 
  
  
    | 
      640
     | 
    
          unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
 
     | 
  
  
    | 
      641
     | 
    
            prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
     | 
  
  
    | 
      642
     | 
    
            prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
     | 
  
  
    | 
      643
     | 
    
            prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
     | 
  
  
    | 
      644
     | 
    
            prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
     | 
  
  
    | 
      645
     | 
    
            prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
     | 
  
  
    | 
      646
     | 
    
            prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
     | 
  
  
    | 
      647
     | 
    
          end
 
     | 
  
  
    | 
      648
     | 
    
          prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
     | 
  
  
    | 
      649
     | 
    
          prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
     | 
  
  
    | 
      650
     | 
    
          puts
 
     | 
  
  
    | 
      651
     | 
    
          
 
     | 
  
  
    | 
      652
     | 
    
          TracMigrate.migrate
 
     | 
  
  
    | 
      653
     | 
    
        end
 
     | 
  
  
    | 
      654
     | 
    
      end
 
     |