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