##// END OF EJS Templates
Merged r2280 from trunk (#2506)....
Jean-Philippe Lang -
r2305:1c1755d2783e
parent child
Show More
@@ -5,12 +5,12
5 # modify it under the terms of the GNU General Public License
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
7 # of the License, or (at your option) any later version.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU General Public License
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
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.
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
@@ -22,10 +22,10 require 'pp'
22 namespace :redmine do
22 namespace :redmine do
23 desc 'Trac migration script'
23 desc 'Trac migration script'
24 task :migrate_from_trac => :environment do
24 task :migrate_from_trac => :environment do
25
25
26 module TracMigrate
26 module TracMigrate
27 TICKET_MAP = []
27 TICKET_MAP = []
28
28
29 DEFAULT_STATUS = IssueStatus.default
29 DEFAULT_STATUS = IssueStatus.default
30 assigned_status = IssueStatus.find_by_position(2)
30 assigned_status = IssueStatus.find_by_position(2)
31 resolved_status = IssueStatus.find_by_position(3)
31 resolved_status = IssueStatus.find_by_position(3)
@@ -36,7 +36,7 namespace :redmine do
36 'assigned' => assigned_status,
36 'assigned' => assigned_status,
37 'closed' => closed_status
37 'closed' => closed_status
38 }
38 }
39
39
40 priorities = Enumeration.get_values('IPRI')
40 priorities = Enumeration.get_values('IPRI')
41 DEFAULT_PRIORITY = priorities[0]
41 DEFAULT_PRIORITY = priorities[0]
42 PRIORITY_MAPPING = {'lowest' => priorities[0],
42 PRIORITY_MAPPING = {'lowest' => priorities[0],
@@ -51,7 +51,7 namespace :redmine do
51 'critical' => priorities[3],
51 'critical' => priorities[3],
52 'blocker' => priorities[4]
52 'blocker' => priorities[4]
53 }
53 }
54
54
55 TRACKER_BUG = Tracker.find_by_position(1)
55 TRACKER_BUG = Tracker.find_by_position(1)
56 TRACKER_FEATURE = Tracker.find_by_position(2)
56 TRACKER_FEATURE = Tracker.find_by_position(2)
57 DEFAULT_TRACKER = TRACKER_BUG
57 DEFAULT_TRACKER = TRACKER_BUG
@@ -60,7 +60,7 namespace :redmine do
60 'task' => TRACKER_FEATURE,
60 'task' => TRACKER_FEATURE,
61 'patch' =>TRACKER_FEATURE
61 'patch' =>TRACKER_FEATURE
62 }
62 }
63
63
64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
64 roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
65 manager_role = roles[0]
65 manager_role = roles[0]
66 developer_role = roles[1]
66 developer_role = roles[1]
@@ -68,7 +68,7 namespace :redmine do
68 ROLE_MAPPING = {'admin' => manager_role,
68 ROLE_MAPPING = {'admin' => manager_role,
69 'developer' => developer_role
69 'developer' => developer_role
70 }
70 }
71
71
72 class ::Time
72 class ::Time
73 class << self
73 class << self
74 alias :real_now :now
74 alias :real_now :now
@@ -87,10 +87,10 namespace :redmine do
87 class TracComponent < ActiveRecord::Base
87 class TracComponent < ActiveRecord::Base
88 set_table_name :component
88 set_table_name :component
89 end
89 end
90
90
91 class TracMilestone < ActiveRecord::Base
91 class TracMilestone < ActiveRecord::Base
92 set_table_name :milestone
92 set_table_name :milestone
93 # If this attribute is set a milestone has a defined target timepoint
93 # If this attribute is set a milestone has a defined target timepoint
94 def due
94 def due
95 if read_attribute(:due) && read_attribute(:due) > 0
95 if read_attribute(:due) && read_attribute(:due) > 0
96 Time.at(read_attribute(:due)).to_date
96 Time.at(read_attribute(:due)).to_date
@@ -112,37 +112,37 namespace :redmine do
112 has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
112 has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
113 end
113 end
114 end
114 end
115
115
116 class TracTicketCustom < ActiveRecord::Base
116 class TracTicketCustom < ActiveRecord::Base
117 set_table_name :ticket_custom
117 set_table_name :ticket_custom
118 end
118 end
119
119
120 class TracAttachment < ActiveRecord::Base
120 class TracAttachment < ActiveRecord::Base
121 set_table_name :attachment
121 set_table_name :attachment
122 set_inheritance_column :none
122 set_inheritance_column :none
123
123
124 def time; Time.at(read_attribute(:time)) end
124 def time; Time.at(read_attribute(:time)) end
125
125
126 def original_filename
126 def original_filename
127 filename
127 filename
128 end
128 end
129
129
130 def content_type
130 def content_type
131 Redmine::MimeType.of(filename) || ''
131 Redmine::MimeType.of(filename) || ''
132 end
132 end
133
133
134 def exist?
134 def exist?
135 File.file? trac_fullpath
135 File.file? trac_fullpath
136 end
136 end
137
137
138 def read
138 def read
139 File.open("#{trac_fullpath}", 'rb').read
139 File.open("#{trac_fullpath}", 'rb').read
140 end
140 end
141
141
142 def description
142 def description
143 read_attribute(:description).to_s.slice(0,255)
143 read_attribute(:description).to_s.slice(0,255)
144 end
144 end
145
145
146 private
146 private
147 def trac_fullpath
147 def trac_fullpath
148 attachment_type = read_attribute(:type)
148 attachment_type = read_attribute(:type)
@@ -150,11 +150,11 namespace :redmine do
150 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
150 "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
151 end
151 end
152 end
152 end
153
153
154 class TracTicket < ActiveRecord::Base
154 class TracTicket < ActiveRecord::Base
155 set_table_name :ticket
155 set_table_name :ticket
156 set_inheritance_column :none
156 set_inheritance_column :none
157
157
158 # ticket changes: only migrate status changes and comments
158 # ticket changes: only migrate status changes and comments
159 has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
159 has_many :changes, :class_name => "TracTicketChange", :foreign_key => :ticket
160 has_many :attachments, :class_name => "TracAttachment",
160 has_many :attachments, :class_name => "TracAttachment",
@@ -162,29 +162,29 namespace :redmine do
162 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
162 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'ticket'" +
163 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
163 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
164 has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
164 has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
165
165
166 def ticket_type
166 def ticket_type
167 read_attribute(:type)
167 read_attribute(:type)
168 end
168 end
169
169
170 def summary
170 def summary
171 read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
171 read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
172 end
172 end
173
173
174 def description
174 def description
175 read_attribute(:description).blank? ? summary : read_attribute(:description)
175 read_attribute(:description).blank? ? summary : read_attribute(:description)
176 end
176 end
177
177
178 def time; Time.at(read_attribute(:time)) end
178 def time; Time.at(read_attribute(:time)) end
179 def changetime; Time.at(read_attribute(:changetime)) end
179 def changetime; Time.at(read_attribute(:changetime)) end
180 end
180 end
181
181
182 class TracTicketChange < ActiveRecord::Base
182 class TracTicketChange < ActiveRecord::Base
183 set_table_name :ticket_change
183 set_table_name :ticket_change
184
184
185 def time; Time.at(read_attribute(:time)) end
185 def time; Time.at(read_attribute(:time)) end
186 end
186 end
187
187
188 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
188 TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
189 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
189 TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
190 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
190 TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
@@ -192,35 +192,35 namespace :redmine do
192 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
192 TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
193 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
193 WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
194 CamelCase TitleIndex)
194 CamelCase TitleIndex)
195
195
196 class TracWikiPage < ActiveRecord::Base
196 class TracWikiPage < ActiveRecord::Base
197 set_table_name :wiki
197 set_table_name :wiki
198 set_primary_key :name
198 set_primary_key :name
199
199
200 has_many :attachments, :class_name => "TracAttachment",
200 has_many :attachments, :class_name => "TracAttachment",
201 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
201 :finder_sql => "SELECT DISTINCT attachment.* FROM #{TracMigrate::TracAttachment.table_name}" +
202 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
202 " WHERE #{TracMigrate::TracAttachment.table_name}.type = 'wiki'" +
203 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
203 ' AND #{TracMigrate::TracAttachment.table_name}.id = \'#{id}\''
204
204
205 def self.columns
205 def self.columns
206 # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
206 # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
207 super.select {|column| column.name.to_s != 'readonly'}
207 super.select {|column| column.name.to_s != 'readonly'}
208 end
208 end
209
209
210 def time; Time.at(read_attribute(:time)) end
210 def time; Time.at(read_attribute(:time)) end
211 end
211 end
212
212
213 class TracPermission < ActiveRecord::Base
213 class TracPermission < ActiveRecord::Base
214 set_table_name :permission
214 set_table_name :permission
215 end
215 end
216
216
217 class TracSessionAttribute < ActiveRecord::Base
217 class TracSessionAttribute < ActiveRecord::Base
218 set_table_name :session_attribute
218 set_table_name :session_attribute
219 end
219 end
220
220
221 def self.find_or_create_user(username, project_member = false)
221 def self.find_or_create_user(username, project_member = false)
222 return User.anonymous if username.blank?
222 return User.anonymous if username.blank?
223
223
224 u = User.find_by_login(username)
224 u = User.find_by_login(username)
225 if !u
225 if !u
226 # Create a new user if not found
226 # Create a new user if not found
@@ -229,7 +229,7 namespace :redmine do
229 mail = mail_attr.value
229 mail = mail_attr.value
230 end
230 end
231 mail = "#{mail}@foo.bar" unless mail.include?("@")
231 mail = "#{mail}@foo.bar" unless mail.include?("@")
232
232
233 name = username
233 name = username
234 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
234 if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
235 name = name_attr.value
235 name = name_attr.value
@@ -237,7 +237,7 namespace :redmine do
237 name =~ (/(.*)(\s+\w+)?/)
237 name =~ (/(.*)(\s+\w+)?/)
238 fn = $1.strip
238 fn = $1.strip
239 ln = ($2 || '-').strip
239 ln = ($2 || '-').strip
240
240
241 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
241 u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
242 :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'),
242 :firstname => fn[0, limit_for(User, 'firstname')].gsub(/[^\w\s\'\-]/i, '-'),
243 :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-')
243 :lastname => ln[0, limit_for(User, 'lastname')].gsub(/[^\w\s\'\-]/i, '-')
@@ -261,7 +261,7 namespace :redmine do
261 end
261 end
262 u
262 u
263 end
263 end
264
264
265 # Basic wiki syntax conversion
265 # Basic wiki syntax conversion
266 def self.convert_wiki_text(text)
266 def self.convert_wiki_text(text)
267 # Titles
267 # Titles
@@ -282,7 +282,7 namespace :redmine do
282 # [milestone:"0.1.0 Mercury"]
282 # [milestone:"0.1.0 Mercury"]
283 text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
283 text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
284 text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
284 text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
285 # milestone:0.1.0
285 # milestone:0.1.0
286 text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
286 text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
287 text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
287 text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
288 # Internal Links
288 # Internal Links
@@ -293,11 +293,11 namespace :redmine do
293 text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
293 text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
294 text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
294 text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
295
295
296 # Links to pages UsingJustWikiCaps
296 # Links to pages UsingJustWikiCaps
297 text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
297 text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
298 # Normalize things that were supposed to not be links
298 # Normalize things that were supposed to not be links
299 # like !NotALink
299 # like !NotALink
300 text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
300 text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
301 # Revisions links
301 # Revisions links
302 text = text.gsub(/\[(\d+)\]/, 'r\1')
302 text = text.gsub(/\[(\d+)\]/, 'r\1')
303 # Ticket number re-writing
303 # Ticket number re-writing
@@ -318,7 +318,7 namespace :redmine do
318 shebang_re = /^\#\!([a-z]+)/
318 shebang_re = /^\#\!([a-z]+)/
319 # Regular expression for end of code
319 # Regular expression for end of code
320 pre_end_re = /\}\}\}/
320 pre_end_re = /\}\}\}/
321
321
322 # Go through the whole text..extract it line by line
322 # Go through the whole text..extract it line by line
323 text = text.gsub(/^(.*)$/) do |line|
323 text = text.gsub(/^(.*)$/) do |line|
324 m_pre = pre_re.match(line)
324 m_pre = pre_re.match(line)
@@ -338,7 +338,7 namespace :redmine do
338 end
338 end
339 end
339 end
340 end
340 end
341 line
341 line
342 end
342 end
343
343
344 # Highlighting
344 # Highlighting
@@ -349,25 +349,25 namespace :redmine do
349 text = text.gsub(/__/, '+')
349 text = text.gsub(/__/, '+')
350 text = text.gsub(/~~/, '-')
350 text = text.gsub(/~~/, '-')
351 text = text.gsub(/`/, '@')
351 text = text.gsub(/`/, '@')
352 text = text.gsub(/,,/, '~')
352 text = text.gsub(/,,/, '~')
353 # Lists
353 # Lists
354 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
354 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
355
355
356 text
356 text
357 end
357 end
358
358
359 def self.migrate
359 def self.migrate
360 establish_connection
360 establish_connection
361
361
362 # Quick database test
362 # Quick database test
363 TracComponent.count
363 TracComponent.count
364
364
365 migrated_components = 0
365 migrated_components = 0
366 migrated_milestones = 0
366 migrated_milestones = 0
367 migrated_tickets = 0
367 migrated_tickets = 0
368 migrated_custom_values = 0
368 migrated_custom_values = 0
369 migrated_ticket_attachments = 0
369 migrated_ticket_attachments = 0
370 migrated_wiki_edits = 0
370 migrated_wiki_edits = 0
371 migrated_wiki_attachments = 0
371 migrated_wiki_attachments = 0
372
372
373 #Wiki system initializing...
373 #Wiki system initializing...
@@ -375,21 +375,21 namespace :redmine do
375 @target_project.reload
375 @target_project.reload
376 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
376 wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
377 wiki_edit_count = 0
377 wiki_edit_count = 0
378
378
379 # Components
379 # Components
380 print "Migrating components"
380 print "Migrating components"
381 issues_category_map = {}
381 issues_category_map = {}
382 TracComponent.find(:all).each do |component|
382 TracComponent.find(:all).each do |component|
383 print '.'
383 print '.'
384 STDOUT.flush
384 STDOUT.flush
385 c = IssueCategory.new :project => @target_project,
385 c = IssueCategory.new :project => @target_project,
386 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
386 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
387 next unless c.save
387 next unless c.save
388 issues_category_map[component.name] = c
388 issues_category_map[component.name] = c
389 migrated_components += 1
389 migrated_components += 1
390 end
390 end
391 puts
391 puts
392
392
393 # Milestones
393 # Milestones
394 print "Migrating milestones"
394 print "Migrating milestones"
395 version_map = {}
395 version_map = {}
@@ -415,7 +415,7 namespace :redmine do
415 migrated_milestones += 1
415 migrated_milestones += 1
416 end
416 end
417 puts
417 puts
418
418
419 # Custom fields
419 # Custom fields
420 # TODO: read trac.ini instead
420 # TODO: read trac.ini instead
421 print "Migrating custom fields"
421 print "Migrating custom fields"
@@ -430,14 +430,14 namespace :redmine do
430 # Or create a new one
430 # Or create a new one
431 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
431 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
432 :field_format => 'string')
432 :field_format => 'string')
433
433
434 next if f.new_record?
434 next if f.new_record?
435 f.trackers = Tracker.find(:all)
435 f.trackers = Tracker.find(:all)
436 f.projects << @target_project
436 f.projects << @target_project
437 custom_field_map[field.name] = f
437 custom_field_map[field.name] = f
438 end
438 end
439 puts
439 puts
440
440
441 # Trac 'resolution' field as a Redmine custom field
441 # Trac 'resolution' field as a Redmine custom field
442 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
442 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
443 r = IssueCustomField.new(:name => 'Resolution',
443 r = IssueCustomField.new(:name => 'Resolution',
@@ -448,45 +448,44 namespace :redmine do
448 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
448 r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
449 r.save!
449 r.save!
450 custom_field_map['resolution'] = r
450 custom_field_map['resolution'] = r
451
451
452 # Tickets
452 # Tickets
453 print "Migrating tickets"
453 print "Migrating tickets"
454 TracTicket.find(:all, :order => 'id ASC').each do |ticket|
454 TracTicket.find(:all, :order => 'id ASC').each do |ticket|
455 print '.'
455 print '.'
456 STDOUT.flush
456 STDOUT.flush
457 i = Issue.new :project => @target_project,
457 i = Issue.new :project => @target_project,
458 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
458 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
459 :description => convert_wiki_text(encode(ticket.description)),
459 :description => convert_wiki_text(encode(ticket.description)),
460 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
460 :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
461 :created_on => ticket.time
461 :created_on => ticket.time
462 i.author = find_or_create_user(ticket.reporter)
462 i.author = find_or_create_user(ticket.reporter)
463 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
463 i.category = issues_category_map[ticket.component] unless ticket.component.blank?
464 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
464 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
465 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
465 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
466 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
466 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
467 i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
467 i.id = ticket.id unless Issue.exists?(ticket.id)
468 i.id = ticket.id unless Issue.exists?(ticket.id)
468 next unless Time.fake(ticket.changetime) { i.save }
469 next unless Time.fake(ticket.changetime) { i.save }
469 TICKET_MAP[ticket.id] = i.id
470 TICKET_MAP[ticket.id] = i.id
470 migrated_tickets += 1
471 migrated_tickets += 1
471
472
472 # Owner
473 # Owner
474 unless ticket.owner.blank?
473 unless ticket.owner.blank?
475 i.assigned_to = find_or_create_user(ticket.owner, true)
474 i.assigned_to = find_or_create_user(ticket.owner, true)
476 Time.fake(ticket.changetime) { i.save }
475 Time.fake(ticket.changetime) { i.save }
477 end
476 end
478
477
479 # Comments and status/resolution changes
478 # Comments and status/resolution changes
480 ticket.changes.group_by(&:time).each do |time, changeset|
479 ticket.changes.group_by(&:time).each do |time, changeset|
481 status_change = changeset.select {|change| change.field == 'status'}.first
480 status_change = changeset.select {|change| change.field == 'status'}.first
482 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
481 resolution_change = changeset.select {|change| change.field == 'resolution'}.first
483 comment_change = changeset.select {|change| change.field == 'comment'}.first
482 comment_change = changeset.select {|change| change.field == 'comment'}.first
484
483
485 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
484 n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
486 :created_on => time
485 :created_on => time
487 n.user = find_or_create_user(changeset.first.author)
486 n.user = find_or_create_user(changeset.first.author)
488 n.journalized = i
487 n.journalized = i
489 if status_change &&
488 if status_change &&
490 STATUS_MAPPING[status_change.oldvalue] &&
489 STATUS_MAPPING[status_change.oldvalue] &&
491 STATUS_MAPPING[status_change.newvalue] &&
490 STATUS_MAPPING[status_change.newvalue] &&
492 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
491 (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
@@ -502,35 +501,39 namespace :redmine do
502 :value => resolution_change.newvalue)
501 :value => resolution_change.newvalue)
503 end
502 end
504 n.save unless n.details.empty? && n.notes.blank?
503 n.save unless n.details.empty? && n.notes.blank?
505 end
504 end
506
505
507 # Attachments
506 # Attachments
508 ticket.attachments.each do |attachment|
507 ticket.attachments.each do |attachment|
509 next unless attachment.exist?
508 next unless attachment.exist?
510 a = Attachment.new :created_on => attachment.time
509 a = Attachment.new :created_on => attachment.time
511 a.file = attachment
510 a.file = attachment
512 a.author = find_or_create_user(attachment.author)
511 a.author = find_or_create_user(attachment.author)
513 a.container = i
512 a.container = i
514 a.description = attachment.description
513 a.description = attachment.description
515 migrated_ticket_attachments += 1 if a.save
514 migrated_ticket_attachments += 1 if a.save
516 end
515 end
517
516
518 # Custom fields
517 # Custom fields
519 ticket.customs.each do |custom|
518 custom_values = ticket.customs.inject({}) do |h, custom|
520 next if custom_field_map[custom.name].nil?
519 if custom_field = custom_field_map[custom.name]
521 v = CustomValue.new :custom_field => custom_field_map[custom.name],
520 h[custom_field.id] = custom.value
522 :value => custom.value
523 v.customized = i
524 next unless v.save
525 migrated_custom_values += 1
521 migrated_custom_values += 1
526 end
522 end
523 h
524 end
525 if custom_field_map['resolution'] && !ticket.resolution.blank?
526 custom_values[custom_field_map['resolution'].id] = ticket.resolution
527 end
528 i.custom_field_values = custom_values
529 i.save_custom_field_values
527 end
530 end
528
531
529 # update issue id sequence if needed (postgresql)
532 # update issue id sequence if needed (postgresql)
530 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
533 Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
531 puts
534 puts
532
535
533 # Wiki
536 # Wiki
534 print "Migrating wiki"
537 print "Migrating wiki"
535 if wiki.save
538 if wiki.save
536 TracWikiPage.find(:all, :order => 'name, version').each do |page|
539 TracWikiPage.find(:all, :order => 'name, version').each do |page|
@@ -545,10 +548,10 namespace :redmine do
545 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
548 p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
546 p.content.comments = page.comment
549 p.content.comments = page.comment
547 Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
550 Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
548
551
549 next if p.content.new_record?
552 next if p.content.new_record?
550 migrated_wiki_edits += 1
553 migrated_wiki_edits += 1
551
554
552 # Attachments
555 # Attachments
553 page.attachments.each do |attachment|
556 page.attachments.each do |attachment|
554 next unless attachment.exist?
557 next unless attachment.exist?
@@ -561,7 +564,7 namespace :redmine do
561 migrated_wiki_attachments += 1 if a.save
564 migrated_wiki_attachments += 1 if a.save
562 end
565 end
563 end
566 end
564
567
565 wiki.reload
568 wiki.reload
566 wiki.pages.each do |page|
569 wiki.pages.each do |page|
567 page.content.text = convert_wiki_text(page.content.text)
570 page.content.text = convert_wiki_text(page.content.text)
@@ -569,7 +572,7 namespace :redmine do
569 end
572 end
570 end
573 end
571 puts
574 puts
572
575
573 puts
576 puts
574 puts "Components: #{migrated_components}/#{TracComponent.count}"
577 puts "Components: #{migrated_components}/#{TracComponent.count}"
575 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
578 puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
@@ -579,18 +582,18 namespace :redmine do
579 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
582 puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
580 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
583 puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
581 end
584 end
582
585
583 def self.limit_for(klass, attribute)
586 def self.limit_for(klass, attribute)
584 klass.columns_hash[attribute.to_s].limit
587 klass.columns_hash[attribute.to_s].limit
585 end
588 end
586
589
587 def self.encoding(charset)
590 def self.encoding(charset)
588 @ic = Iconv.new('UTF-8', charset)
591 @ic = Iconv.new('UTF-8', charset)
589 rescue Iconv::InvalidEncoding
592 rescue Iconv::InvalidEncoding
590 puts "Invalid encoding!"
593 puts "Invalid encoding!"
591 return false
594 return false
592 end
595 end
593
596
594 def self.set_trac_directory(path)
597 def self.set_trac_directory(path)
595 @@trac_directory = path
598 @@trac_directory = path
596 raise "This directory doesn't exist!" unless File.directory?(path)
599 raise "This directory doesn't exist!" unless File.directory?(path)
@@ -615,7 +618,7 namespace :redmine do
615 puts e
618 puts e
616 return false
619 return false
617 end
620 end
618
621
619 def self.set_trac_db_host(host)
622 def self.set_trac_db_host(host)
620 return nil if host.blank?
623 return nil if host.blank?
621 @@trac_db_host = host
624 @@trac_db_host = host
@@ -625,7 +628,7 namespace :redmine do
625 return nil if port.to_i == 0
628 return nil if port.to_i == 0
626 @@trac_db_port = port.to_i
629 @@trac_db_port = port.to_i
627 end
630 end
628
631
629 def self.set_trac_db_name(name)
632 def self.set_trac_db_name(name)
630 return nil if name.blank?
633 return nil if name.blank?
631 @@trac_db_name = name
634 @@trac_db_name = name
@@ -634,22 +637,22 namespace :redmine do
634 def self.set_trac_db_username(username)
637 def self.set_trac_db_username(username)
635 @@trac_db_username = username
638 @@trac_db_username = username
636 end
639 end
637
640
638 def self.set_trac_db_password(password)
641 def self.set_trac_db_password(password)
639 @@trac_db_password = password
642 @@trac_db_password = password
640 end
643 end
641
644
642 def self.set_trac_db_schema(schema)
645 def self.set_trac_db_schema(schema)
643 @@trac_db_schema = schema
646 @@trac_db_schema = schema
644 end
647 end
645
648
646 mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
649 mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
647
650
648 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
651 def self.trac_db_path; "#{trac_directory}/db/trac.db" end
649 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
652 def self.trac_attachments_directory; "#{trac_directory}/attachments" end
650
653
651 def self.target_project_identifier(identifier)
654 def self.target_project_identifier(identifier)
652 project = Project.find_by_identifier(identifier)
655 project = Project.find_by_identifier(identifier)
653 if !project
656 if !project
654 # create the target project
657 # create the target project
655 project = Project.new :name => identifier.humanize,
658 project = Project.new :name => identifier.humanize,
@@ -662,16 +665,16 namespace :redmine do
662 puts
665 puts
663 puts "This project already exists in your Redmine database."
666 puts "This project already exists in your Redmine database."
664 print "Are you sure you want to append data to this project ? [Y/n] "
667 print "Are you sure you want to append data to this project ? [Y/n] "
665 exit if STDIN.gets.match(/^n$/i)
668 exit if STDIN.gets.match(/^n$/i)
666 end
669 end
667 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
670 project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
668 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
671 project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
669 @target_project = project.new_record? ? nil : project
672 @target_project = project.new_record? ? nil : project
670 end
673 end
671
674
672 def self.connection_params
675 def self.connection_params
673 if %w(sqlite sqlite3).include?(trac_adapter)
676 if %w(sqlite sqlite3).include?(trac_adapter)
674 {:adapter => trac_adapter,
677 {:adapter => trac_adapter,
675 :database => trac_db_path}
678 :database => trac_db_path}
676 else
679 else
677 {:adapter => trac_adapter,
680 {:adapter => trac_adapter,
@@ -684,7 +687,7 namespace :redmine do
684 }
687 }
685 end
688 end
686 end
689 end
687
690
688 def self.establish_connection
691 def self.establish_connection
689 constants.each do |const|
692 constants.each do |const|
690 klass = const_get(const)
693 klass = const_get(const)
@@ -692,7 +695,7 namespace :redmine do
692 klass.establish_connection connection_params
695 klass.establish_connection connection_params
693 end
696 end
694 end
697 end
695
698
696 private
699 private
697 def self.encode(text)
700 def self.encode(text)
698 @ic.iconv text
701 @ic.iconv text
@@ -700,7 +703,7 namespace :redmine do
700 text
703 text
701 end
704 end
702 end
705 end
703
706
704 puts
707 puts
705 if Redmine::DefaultData::Loader.no_data?
708 if Redmine::DefaultData::Loader.no_data?
706 puts "Redmine configuration need to be loaded before importing data."
709 puts "Redmine configuration need to be loaded before importing data."
@@ -709,10 +712,10 namespace :redmine do
709 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
712 puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
710 exit
713 exit
711 end
714 end
712
715
713 puts "WARNING: a new project will be added to Redmine during this process."
716 puts "WARNING: a new project will be added to Redmine during this process."
714 print "Are you sure you want to continue ? [y/N] "
717 print "Are you sure you want to continue ? [y/N] "
715 break unless STDIN.gets.match(/^y$/i)
718 break unless STDIN.gets.match(/^y$/i)
716 puts
719 puts
717
720
718 def prompt(text, options = {}, &block)
721 def prompt(text, options = {}, &block)
@@ -724,9 +727,9 namespace :redmine do
724 break if yield value
727 break if yield value
725 end
728 end
726 end
729 end
727
730
728 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
731 DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
729
732
730 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
733 prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
731 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
734 prompt('Trac database adapter (sqlite, sqlite3, mysql, postgresql)', :default => 'sqlite') {|adapter| TracMigrate.set_trac_adapter adapter}
732 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
735 unless %w(sqlite sqlite3).include?(TracMigrate.trac_adapter)
@@ -740,7 +743,7 namespace :redmine do
740 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
743 prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
741 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
744 prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
742 puts
745 puts
743
746
744 TracMigrate.migrate
747 TracMigrate.migrate
745 end
748 end
746 end
749 end
General Comments 0
You need to be logged in to leave comments. Login now