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