123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772 |
- # Redmine - project management software
- # Copyright (C) 2006-2012 Jean-Philippe Lang
- #
- # This program is free software; you can redistribute it and/or
- # modify it under the terms of the GNU General Public License
- # as published by the Free Software Foundation; either version 2
- # of the License, or (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- require 'active_record'
- require 'iconv'
- require 'pp'
-
- namespace :redmine do
- desc 'Trac migration script'
- task :migrate_from_trac => :environment do
-
- module TracMigrate
- TICKET_MAP = []
-
- DEFAULT_STATUS = IssueStatus.default
- assigned_status = IssueStatus.find_by_position(2)
- resolved_status = IssueStatus.find_by_position(3)
- feedback_status = IssueStatus.find_by_position(4)
- closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
- STATUS_MAPPING = {'new' => DEFAULT_STATUS,
- 'reopened' => feedback_status,
- 'assigned' => assigned_status,
- 'closed' => closed_status
- }
-
- priorities = IssuePriority.all
- DEFAULT_PRIORITY = priorities[0]
- PRIORITY_MAPPING = {'lowest' => priorities[0],
- 'low' => priorities[0],
- 'normal' => priorities[1],
- 'high' => priorities[2],
- 'highest' => priorities[3],
- # ---
- 'trivial' => priorities[0],
- 'minor' => priorities[1],
- 'major' => priorities[2],
- 'critical' => priorities[3],
- 'blocker' => priorities[4]
- }
-
- TRACKER_BUG = Tracker.find_by_position(1)
- TRACKER_FEATURE = Tracker.find_by_position(2)
- DEFAULT_TRACKER = TRACKER_BUG
- TRACKER_MAPPING = {'defect' => TRACKER_BUG,
- 'enhancement' => TRACKER_FEATURE,
- 'task' => TRACKER_FEATURE,
- 'patch' =>TRACKER_FEATURE
- }
-
- roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
- manager_role = roles[0]
- developer_role = roles[1]
- DEFAULT_ROLE = roles.last
- ROLE_MAPPING = {'admin' => manager_role,
- 'developer' => developer_role
- }
-
- class ::Time
- class << self
- alias :real_now :now
- def now
- real_now - @fake_diff.to_i
- end
- def fake(time)
- @fake_diff = real_now - time
- res = yield
- @fake_diff = 0
- res
- end
- end
- end
-
- class TracComponent < ActiveRecord::Base
- self.table_name = :component
- end
-
- class TracMilestone < ActiveRecord::Base
- self.table_name = :milestone
- # If this attribute is set a milestone has a defined target timepoint
- def due
- if read_attribute(:due) && read_attribute(:due) > 0
- Time.at(read_attribute(:due)).to_date
- else
- nil
- end
- end
- # This is the real timepoint at which the milestone has finished.
- def completed
- if read_attribute(:completed) && read_attribute(:completed) > 0
- Time.at(read_attribute(:completed)).to_date
- else
- nil
- end
- end
-
- def description
- # Attribute is named descr in Trac v0.8.x
- has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description)
- end
- end
-
- class TracTicketCustom < ActiveRecord::Base
- self.table_name = :ticket_custom
- end
-
- class TracAttachment < ActiveRecord::Base
- self.table_name = :attachment
- set_inheritance_column :none
-
- def time; Time.at(read_attribute(:time)) end
-
- def original_filename
- filename
- end
-
- def content_type
- ''
- end
-
- def exist?
- File.file? trac_fullpath
- end
-
- def open
- File.open("#{trac_fullpath}", 'rb') {|f|
- @file = f
- yield self
- }
- end
-
- def read(*args)
- @file.read(*args)
- end
-
- def description
- read_attribute(:description).to_s.slice(0,255)
- end
-
- private
- def trac_fullpath
- attachment_type = read_attribute(:type)
- trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
- "#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
- end
- end
-
- class TracTicket < ActiveRecord::Base
- self.table_name = :ticket
- set_inheritance_column :none
-
- # ticket changes: only migrate status changes and comments
- has_many :ticket_changes, :class_name => "TracTicketChange", :foreign_key => :ticket
- has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket
-
- def attachments
- TracMigrate::TracAttachment.all(:conditions => ["type = 'ticket' AND id = ?", self.id.to_s])
- end
-
- def ticket_type
- read_attribute(:type)
- end
-
- def summary
- read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary)
- end
-
- def description
- read_attribute(:description).blank? ? summary : read_attribute(:description)
- end
-
- def time; Time.at(read_attribute(:time)) end
- def changetime; Time.at(read_attribute(:changetime)) end
- end
-
- class TracTicketChange < ActiveRecord::Base
- self.table_name = :ticket_change
-
- def self.columns
- # Hides Trac field 'field' to prevent clash with AR field_changed? method (Rails 3.0)
- super.select {|column| column.name.to_s != 'field'}
- end
-
- def time; Time.at(read_attribute(:time)) end
- end
-
- TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \
- TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \
- TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \
- TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \
- TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \
- WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \
- CamelCase TitleIndex)
-
- class TracWikiPage < ActiveRecord::Base
- self.table_name = :wiki
- set_primary_key :name
-
- def self.columns
- # Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0)
- super.select {|column| column.name.to_s != 'readonly'}
- end
-
- def attachments
- TracMigrate::TracAttachment.all(:conditions => ["type = 'wiki' AND id = ?", self.id.to_s])
- end
-
- def time; Time.at(read_attribute(:time)) end
- end
-
- class TracPermission < ActiveRecord::Base
- self.table_name = :permission
- end
-
- class TracSessionAttribute < ActiveRecord::Base
- self.table_name = :session_attribute
- end
-
- def self.find_or_create_user(username, project_member = false)
- return User.anonymous if username.blank?
-
- u = User.find_by_login(username)
- if !u
- # Create a new user if not found
- mail = username[0, User::MAIL_LENGTH_LIMIT]
- if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email')
- mail = mail_attr.value
- end
- mail = "#{mail}@foo.bar" unless mail.include?("@")
-
- name = username
- if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name')
- name = name_attr.value
- end
- name =~ (/(.*)(\s+\w+)?/)
- fn = $1.strip
- ln = ($2 || '-').strip
-
- u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'),
- :firstname => fn[0, limit_for(User, 'firstname')],
- :lastname => ln[0, limit_for(User, 'lastname')]
-
- u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-')
- u.password = 'trac'
- u.admin = true if TracPermission.find_by_username_and_action(username, 'admin')
- # finally, a default user is used if the new user is not valid
- u = User.find(:first) unless u.save
- end
- # Make sure he is a member of the project
- if project_member && !u.member_of?(@target_project)
- role = DEFAULT_ROLE
- if u.admin
- role = ROLE_MAPPING['admin']
- elsif TracPermission.find_by_username_and_action(username, 'developer')
- role = ROLE_MAPPING['developer']
- end
- Member.create(:user => u, :project => @target_project, :roles => [role])
- u.reload
- end
- u
- end
-
- # Basic wiki syntax conversion
- def self.convert_wiki_text(text)
- # Titles
- text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
- # External Links
- text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
- # Ticket links:
- # [ticket:234 Text],[ticket:234 This is a test]
- text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1')
- # ticket:1234
- # #1 is working cause Redmine uses the same syntax.
- text = text.gsub(/ticket\:([^\ ]+)/, '#\1')
- # Milestone links:
- # [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)]
- # The text "Milestone 0.1.0 (Mercury)" is not converted,
- # cause Redmine's wiki does not support this.
- text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"')
- # [milestone:"0.1.0 Mercury"]
- text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"')
- text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"')
- # milestone:0.1.0
- text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1')
- text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1')
- # Internal Links
- text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below
- text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
- text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
- text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
- text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
- text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
-
- # Links to pages UsingJustWikiCaps
- text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]')
- # Normalize things that were supposed to not be links
- # like !NotALink
- text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2')
- # Revisions links
- text = text.gsub(/\[(\d+)\]/, 'r\1')
- # Ticket number re-writing
- text = text.gsub(/#(\d+)/) do |s|
- if $1.length < 10
- # TICKET_MAP[$1.to_i] ||= $1
- "\##{TICKET_MAP[$1.to_i] || $1}"
- else
- s
- end
- end
- # We would like to convert the Code highlighting too
- # This will go into the next line.
- shebang_line = false
- # Reguar expression for start of code
- pre_re = /\{\{\{/
- # Code hightlighing...
- shebang_re = /^\#\!([a-z]+)/
- # Regular expression for end of code
- pre_end_re = /\}\}\}/
-
- # Go through the whole text..extract it line by line
- text = text.gsub(/^(.*)$/) do |line|
- m_pre = pre_re.match(line)
- if m_pre
- line = '<pre>'
- else
- m_sl = shebang_re.match(line)
- if m_sl
- shebang_line = true
- line = '<code class="' + m_sl[1] + '">'
- end
- m_pre_end = pre_end_re.match(line)
- if m_pre_end
- line = '</pre>'
- if shebang_line
- line = '</code>' + line
- end
- end
- end
- line
- end
-
- # Highlighting
- text = text.gsub(/'''''([^\s])/, '_*\1')
- text = text.gsub(/([^\s])'''''/, '\1*_')
- text = text.gsub(/'''/, '*')
- text = text.gsub(/''/, '_')
- text = text.gsub(/__/, '+')
- text = text.gsub(/~~/, '-')
- text = text.gsub(/`/, '@')
- text = text.gsub(/,,/, '~')
- # Lists
- text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
-
- text
- end
-
- def self.migrate
- establish_connection
-
- # Quick database test
- TracComponent.count
-
- migrated_components = 0
- migrated_milestones = 0
- migrated_tickets = 0
- migrated_custom_values = 0
- migrated_ticket_attachments = 0
- migrated_wiki_edits = 0
- migrated_wiki_attachments = 0
-
- #Wiki system initializing...
- @target_project.wiki.destroy if @target_project.wiki
- @target_project.reload
- wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart')
- wiki_edit_count = 0
-
- # Components
- print "Migrating components"
- issues_category_map = {}
- TracComponent.find(:all).each do |component|
- print '.'
- STDOUT.flush
- c = IssueCategory.new :project => @target_project,
- :name => encode(component.name[0, limit_for(IssueCategory, 'name')])
- next unless c.save
- issues_category_map[component.name] = c
- migrated_components += 1
- end
- puts
-
- # Milestones
- print "Migrating milestones"
- version_map = {}
- TracMilestone.find(:all).each do |milestone|
- print '.'
- STDOUT.flush
- # First we try to find the wiki page...
- p = wiki.find_or_new_page(milestone.name.to_s)
- p.content = WikiContent.new(:page => p) if p.new_record?
- p.content.text = milestone.description.to_s
- p.content.author = find_or_create_user('trac')
- p.content.comments = 'Milestone'
- p.save
-
- v = Version.new :project => @target_project,
- :name => encode(milestone.name[0, limit_for(Version, 'name')]),
- :description => nil,
- :wiki_page_title => milestone.name.to_s,
- :effective_date => milestone.completed
-
- next unless v.save
- version_map[milestone.name] = v
- migrated_milestones += 1
- end
- puts
-
- # Custom fields
- # TODO: read trac.ini instead
- print "Migrating custom fields"
- custom_field_map = {}
- TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
- print '.'
- STDOUT.flush
- # Redmine custom field name
- field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize
- # Find if the custom already exists in Redmine
- f = IssueCustomField.find_by_name(field_name)
- # Or create a new one
- f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize,
- :field_format => 'string')
-
- next if f.new_record?
- f.trackers = Tracker.find(:all)
- f.projects << @target_project
- custom_field_map[field.name] = f
- end
- puts
-
- # Trac 'resolution' field as a Redmine custom field
- r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
- r = IssueCustomField.new(:name => 'Resolution',
- :field_format => 'list',
- :is_filter => true) if r.nil?
- r.trackers = Tracker.find(:all)
- r.projects << @target_project
- r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq
- r.save!
- custom_field_map['resolution'] = r
-
- # Tickets
- print "Migrating tickets"
- TracTicket.find_each(:batch_size => 200) do |ticket|
- print '.'
- STDOUT.flush
- i = Issue.new :project => @target_project,
- :subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]),
- :description => convert_wiki_text(encode(ticket.description)),
- :priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY,
- :created_on => ticket.time
- i.author = find_or_create_user(ticket.reporter)
- i.category = issues_category_map[ticket.component] unless ticket.component.blank?
- i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank?
- i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS
- i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER
- i.id = ticket.id unless Issue.exists?(ticket.id)
- next unless Time.fake(ticket.changetime) { i.save }
- TICKET_MAP[ticket.id] = i.id
- migrated_tickets += 1
-
- # Owner
- unless ticket.owner.blank?
- i.assigned_to = find_or_create_user(ticket.owner, true)
- Time.fake(ticket.changetime) { i.save }
- end
-
- # Comments and status/resolution changes
- ticket.ticket_changes.group_by(&:time).each do |time, changeset|
- status_change = changeset.select {|change| change.field == 'status'}.first
- resolution_change = changeset.select {|change| change.field == 'resolution'}.first
- comment_change = changeset.select {|change| change.field == 'comment'}.first
-
- n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''),
- :created_on => time
- n.user = find_or_create_user(changeset.first.author)
- n.journalized = i
- if status_change &&
- STATUS_MAPPING[status_change.oldvalue] &&
- STATUS_MAPPING[status_change.newvalue] &&
- (STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue])
- n.details << JournalDetail.new(:property => 'attr',
- :prop_key => 'status_id',
- :old_value => STATUS_MAPPING[status_change.oldvalue].id,
- :value => STATUS_MAPPING[status_change.newvalue].id)
- end
- if resolution_change
- n.details << JournalDetail.new(:property => 'cf',
- :prop_key => custom_field_map['resolution'].id,
- :old_value => resolution_change.oldvalue,
- :value => resolution_change.newvalue)
- end
- n.save unless n.details.empty? && n.notes.blank?
- end
-
- # Attachments
- ticket.attachments.each do |attachment|
- next unless attachment.exist?
- attachment.open {
- a = Attachment.new :created_on => attachment.time
- a.file = attachment
- a.author = find_or_create_user(attachment.author)
- a.container = i
- a.description = attachment.description
- migrated_ticket_attachments += 1 if a.save
- }
- end
-
- # Custom fields
- custom_values = ticket.customs.inject({}) do |h, custom|
- if custom_field = custom_field_map[custom.name]
- h[custom_field.id] = custom.value
- migrated_custom_values += 1
- end
- h
- end
- if custom_field_map['resolution'] && !ticket.resolution.blank?
- custom_values[custom_field_map['resolution'].id] = ticket.resolution
- end
- i.custom_field_values = custom_values
- i.save_custom_field_values
- end
-
- # update issue id sequence if needed (postgresql)
- Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
- puts
-
- # Wiki
- print "Migrating wiki"
- if wiki.save
- TracWikiPage.find(:all, :order => 'name, version').each do |page|
- # Do not migrate Trac manual wiki pages
- next if TRAC_WIKI_PAGES.include?(page.name)
- wiki_edit_count += 1
- print '.'
- STDOUT.flush
- p = wiki.find_or_new_page(page.name)
- p.content = WikiContent.new(:page => p) if p.new_record?
- p.content.text = page.text
- p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac'
- p.content.comments = page.comment
- Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
-
- next if p.content.new_record?
- migrated_wiki_edits += 1
-
- # Attachments
- page.attachments.each do |attachment|
- next unless attachment.exist?
- next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page
- attachment.open {
- a = Attachment.new :created_on => attachment.time
- a.file = attachment
- a.author = find_or_create_user(attachment.author)
- a.description = attachment.description
- a.container = p
- migrated_wiki_attachments += 1 if a.save
- }
- end
- end
-
- wiki.reload
- wiki.pages.each do |page|
- page.content.text = convert_wiki_text(page.content.text)
- Time.fake(page.content.updated_on) { page.content.save }
- end
- end
- puts
-
- puts
- puts "Components: #{migrated_components}/#{TracComponent.count}"
- puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
- puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
- puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
- puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
- puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
- puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
- end
-
- def self.limit_for(klass, attribute)
- klass.columns_hash[attribute.to_s].limit
- end
-
- def self.encoding(charset)
- @ic = Iconv.new('UTF-8', charset)
- rescue Iconv::InvalidEncoding
- puts "Invalid encoding!"
- return false
- end
-
- def self.set_trac_directory(path)
- @@trac_directory = path
- raise "This directory doesn't exist!" unless File.directory?(path)
- raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
- @@trac_directory
- rescue Exception => e
- puts e
- return false
- end
-
- def self.trac_directory
- @@trac_directory
- end
-
- def self.set_trac_adapter(adapter)
- return false if adapter.blank?
- raise "Unknown adapter: #{adapter}!" unless %w(sqlite3 mysql postgresql).include?(adapter)
- # If adapter is sqlite or sqlite3, make sure that trac.db exists
- raise "#{trac_db_path} doesn't exist!" if %w(sqlite3).include?(adapter) && !File.exist?(trac_db_path)
- @@trac_adapter = adapter
- rescue Exception => e
- puts e
- return false
- end
-
- def self.set_trac_db_host(host)
- return nil if host.blank?
- @@trac_db_host = host
- end
-
- def self.set_trac_db_port(port)
- return nil if port.to_i == 0
- @@trac_db_port = port.to_i
- end
-
- def self.set_trac_db_name(name)
- return nil if name.blank?
- @@trac_db_name = name
- end
-
- def self.set_trac_db_username(username)
- @@trac_db_username = username
- end
-
- def self.set_trac_db_password(password)
- @@trac_db_password = password
- end
-
- def self.set_trac_db_schema(schema)
- @@trac_db_schema = schema
- end
-
- mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password
-
- def self.trac_db_path; "#{trac_directory}/db/trac.db" end
- def self.trac_attachments_directory; "#{trac_directory}/attachments" end
-
- def self.target_project_identifier(identifier)
- project = Project.find_by_identifier(identifier)
- if !project
- # create the target project
- project = Project.new :name => identifier.humanize,
- :description => ''
- project.identifier = identifier
- puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
- # enable issues and wiki for the created project
- project.enabled_module_names = ['issue_tracking', 'wiki']
- else
- puts
- puts "This project already exists in your Redmine database."
- print "Are you sure you want to append data to this project ? [Y/n] "
- STDOUT.flush
- exit if STDIN.gets.match(/^n$/i)
- end
- project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG)
- project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE)
- @target_project = project.new_record? ? nil : project
- @target_project.reload
- end
-
- def self.connection_params
- if trac_adapter == 'sqlite3'
- {:adapter => 'sqlite3',
- :database => trac_db_path}
- else
- {:adapter => trac_adapter,
- :database => trac_db_name,
- :host => trac_db_host,
- :port => trac_db_port,
- :username => trac_db_username,
- :password => trac_db_password,
- :schema_search_path => trac_db_schema
- }
- end
- end
-
- def self.establish_connection
- constants.each do |const|
- klass = const_get(const)
- next unless klass.respond_to? 'establish_connection'
- klass.establish_connection connection_params
- end
- end
-
- private
- def self.encode(text)
- @ic.iconv text
- rescue
- text
- end
- end
-
- puts
- if Redmine::DefaultData::Loader.no_data?
- puts "Redmine configuration need to be loaded before importing data."
- puts "Please, run this first:"
- puts
- puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
- exit
- end
-
- puts "WARNING: a new project will be added to Redmine during this process."
- print "Are you sure you want to continue ? [y/N] "
- STDOUT.flush
- break unless STDIN.gets.match(/^y$/i)
- puts
-
- def prompt(text, options = {}, &block)
- default = options[:default] || ''
- while true
- print "#{text} [#{default}]: "
- STDOUT.flush
- value = STDIN.gets.chomp!
- value = default if value.blank?
- break if yield value
- end
- end
-
- DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
-
- prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
- prompt('Trac database adapter (sqlite3, mysql2, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter}
- unless %w(sqlite3).include?(TracMigrate.trac_adapter)
- prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
- prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
- prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
- prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
- prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
- prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
- end
- prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
- prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
- puts
-
- # Turn off email notifications
- Setting.notified_events = []
-
- TracMigrate.migrate
- end
- end
|