##// END OF EJS Templates
Trac importer improvements (by Mat Trudel):...
Jean-Philippe Lang -
r918:3937caeb3c8f
parent child
Show More
@@ -24,6 +24,7 namespace :redmine do
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
28
28 DEFAULT_STATUS = IssueStatus.default
29 DEFAULT_STATUS = IssueStatus.default
29 assigned_status = IssueStatus.find_by_position(2)
30 assigned_status = IssueStatus.find_by_position(2)
@@ -181,11 +182,38 namespace :redmine do
181 # Basic wiki syntax conversion
182 # Basic wiki syntax conversion
182 def self.convert_wiki_text(text)
183 def self.convert_wiki_text(text)
183 # Titles
184 # Titles
184 text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "h#{$1.length}. #{$2}\n"}
185 text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
185 # Links
186 # External Links
186 text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
187 text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
188 # Internal Links
189 text = text.gsub(/[[BR]]/, "\n") # This has to go before the rules below
190 text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
191 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
192 text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
193 text = text.gsub(/\[wiki:([^\s\]]+).*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
187 # Revisions links
194 # Revisions links
188 text = text.gsub(/\[(\d+)\]/, 'r\1')
195 text = text.gsub(/\[(\d+)\]/, 'r\1')
196 # Ticket number re-writing
197 text = text.gsub(/#(\d+)/) do |s|
198 TICKET_MAP[$1.to_i] ||= $1
199 "\##{TICKET_MAP[$1.to_i]}"
200 end
201 # Preformatted blocks
202 text = text.gsub(/\{\{\{/, '<pre>')
203 text = text.gsub(/\}\}\}/, '</pre>')
204 # Highlighting
205 text = text.gsub(/'''''([^\s])/, '_*\1')
206 text = text.gsub(/([^\s])'''''/, '\1*_')
207 text = text.gsub(/'''/, '*')
208 text = text.gsub(/''/, '_')
209 text = text.gsub(/__/, '+')
210 text = text.gsub(/~~/, '-')
211 text = text.gsub(/`/, '@')
212 text = text.gsub(/,,/, '~')
213 # Lists
214 text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
215
216
189 text
217 text
190 end
218 end
191
219
@@ -193,16 +221,9 namespace :redmine do
193 establish_connection({:adapter => trac_adapter,
221 establish_connection({:adapter => trac_adapter,
194 :database => trac_db_path})
222 :database => trac_db_path})
195
223
196 # Quick database test before clearing Redmine data
224 # Quick database test
197 TracComponent.count
225 TracComponent.count
198
226
199 puts "Deleting data"
200 CustomField.destroy_all
201 Issue.destroy_all
202 IssueCategory.destroy_all
203 Version.destroy_all
204 User.destroy_all "login <> 'admin'"
205
206 migrated_components = 0
227 migrated_components = 0
207 migrated_milestones = 0
228 migrated_milestones = 0
208 migrated_tickets = 0
229 migrated_tickets = 0
@@ -215,6 +236,7 namespace :redmine do
215 issues_category_map = {}
236 issues_category_map = {}
216 TracComponent.find(:all).each do |component|
237 TracComponent.find(:all).each do |component|
217 print '.'
238 print '.'
239 STDOUT.flush
218 c = IssueCategory.new :project => @target_project,
240 c = IssueCategory.new :project => @target_project,
219 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
241 :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
220 next unless c.save
242 next unless c.save
@@ -228,9 +250,10 namespace :redmine do
228 version_map = {}
250 version_map = {}
229 TracMilestone.find(:all).each do |milestone|
251 TracMilestone.find(:all).each do |milestone|
230 print '.'
252 print '.'
253 STDOUT.flush
231 v = Version.new :project => @target_project,
254 v = Version.new :project => @target_project,
232 :name => encode(milestone.name[0, limit_for(Version, 'name')]),
255 :name => encode(milestone.name[0, limit_for(Version, 'name')]),
233 :description => encode(milestone.description[0, limit_for(Version, 'description')]),
256 :description => encode(milestone.description.to_s[0, limit_for(Version, 'description')]),
234 :effective_date => milestone.due
257 :effective_date => milestone.due
235 next unless v.save
258 next unless v.save
236 version_map[milestone.name] = v
259 version_map[milestone.name] = v
@@ -244,9 +267,16 namespace :redmine do
244 custom_field_map = {}
267 custom_field_map = {}
245 TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
268 TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
246 print '.'
269 print '.'
247 f = IssueCustomField.new :name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
270 STDOUT.flush
248 :field_format => 'string'
271 # Redmine custom field name
249 next unless f.save
272 field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
273 # Find if the custom already exists in Redmine
274 f = IssueCustomField.find_by_name(field_name)
275 # Or create a new one
276 f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
277 :field_format => 'string')
278
279 next if f.new_record?
250 f.trackers = Tracker.find(:all)
280 f.trackers = Tracker.find(:all)
251 f.projects << @target_project
281 f.projects << @target_project
252 custom_field_map[field.name] = f
282 custom_field_map[field.name] = f
@@ -254,9 +284,10 namespace :redmine do
254 puts
284 puts
255
285
256 # Trac 'resolution' field as a Redmine custom field
286 # Trac 'resolution' field as a Redmine custom field
257 r = IssueCustomField.new :name => 'Resolution',
287 r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
288 r = IssueCustomField.new(:name => 'Resolution',
258 :field_format => 'list',
289 :field_format => 'list',
259 :is_filter => true
290 :is_filter => true) if r.nil?
260 r.trackers = Tracker.find(:all)
291 r.trackers = Tracker.find(:all)
261 r.projects << @target_project
292 r.projects << @target_project
262 r.possible_values = %w(fixed invalid wontfix duplicate worksforme)
293 r.possible_values = %w(fixed invalid wontfix duplicate worksforme)
@@ -264,8 +295,9 namespace :redmine do
264
295
265 # Tickets
296 # Tickets
266 print "Migrating tickets"
297 print "Migrating tickets"
267 TracTicket.find(:all).each do |ticket|
298 TracTicket.find(:all, :order => 'id ASC').each do |ticket|
268 print '.'
299 print '.'
300 STDOUT.flush
269 i = Issue.new :project => @target_project,
301 i = Issue.new :project => @target_project,
270 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
302 :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
271 :description => convert_wiki_text(encode(ticket.description)),
303 :description => convert_wiki_text(encode(ticket.description)),
@@ -276,9 +308,10 namespace :redmine do
276 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
308 i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
277 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
309 i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
278 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
310 i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
279 i.id = ticket.id
280 i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
311 i.custom_values << CustomValue.new(:custom_field => custom_field_map['resolution'], :value => ticket.resolution) unless ticket.resolution.blank?
312 i.id = ticket.id unless Issue.exists?(ticket.id)
281 next unless i.save
313 next unless i.save
314 TICKET_MAP[ticket.id] = i.id
282 migrated_tickets += 1
315 migrated_tickets += 1
283
316
284 # Owner
317 # Owner
@@ -327,6 +360,7 namespace :redmine do
327
360
328 # Custom fields
361 # Custom fields
329 ticket.customs.each do |custom|
362 ticket.customs.each do |custom|
363 next if custom_field_map[custom.name].nil?
330 v = CustomValue.new :custom_field => custom_field_map[custom.name],
364 v = CustomValue.new :custom_field => custom_field_map[custom.name],
331 :value => custom.value
365 :value => custom.value
332 v.customized = i
366 v.customized = i
@@ -344,6 +378,7 namespace :redmine do
344 if wiki.save
378 if wiki.save
345 TracWikiPage.find(:all, :order => 'name, version').each do |page|
379 TracWikiPage.find(:all, :order => 'name, version').each do |page|
346 print '.'
380 print '.'
381 STDOUT.flush
347 p = wiki.find_or_new_page(page.name)
382 p = wiki.find_or_new_page(page.name)
348 p.content = WikiContent.new(:page => p) if p.new_record?
383 p.content = WikiContent.new(:page => p) if p.new_record?
349 p.content.text = page.text
384 p.content.text = page.text
@@ -415,6 +450,8 namespace :redmine do
415 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
450 puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
416 # enable issues and wiki for the created project
451 # enable issues and wiki for the created project
417 project.enabled_module_names = ['issue_tracking', 'wiki']
452 project.enabled_module_names = ['issue_tracking', 'wiki']
453 project.trackers << TRACKER_BUG
454 project.trackers << TRACKER_FEATURE
418 end
455 end
419 @target_project = project.new_record? ? nil : project
456 @target_project = project.new_record? ? nil : project
420 end
457 end
@@ -436,7 +473,7 namespace :redmine do
436 end
473 end
437
474
438 puts
475 puts
439 puts "WARNING: Your Redmine data will be deleted during this process."
476 puts "WARNING: Your Redmine install will have a new project added during this process."
440 print "Are you sure you want to continue ? [y/N] "
477 print "Are you sure you want to continue ? [y/N] "
441 break unless STDIN.gets.match(/^y$/i)
478 break unless STDIN.gets.match(/^y$/i)
442 puts
479 puts
General Comments 0
You need to be logged in to leave comments. Login now