Revision 1297:0a574315af3e .svn/pristine/bc
| .svn/pristine/bc/bc30ce4931d78e6c793accaf2d15ef5a8dea2204.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 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 |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require 'cgi' |
|
| 19 |
|
|
| 20 |
module Redmine |
|
| 21 |
module Scm |
|
| 22 |
module Adapters |
|
| 23 |
class CommandFailed < StandardError #:nodoc: |
|
| 24 |
end |
|
| 25 |
|
|
| 26 |
class AbstractAdapter #:nodoc: |
|
| 27 |
|
|
| 28 |
# raised if scm command exited with error, e.g. unknown revision. |
|
| 29 |
class ScmCommandAborted < CommandFailed; end |
|
| 30 |
|
|
| 31 |
class << self |
|
| 32 |
def client_command |
|
| 33 |
"" |
|
| 34 |
end |
|
| 35 |
|
|
| 36 |
def shell_quote_command |
|
| 37 |
if Redmine::Platform.mswin? && RUBY_PLATFORM == 'java' |
|
| 38 |
client_command |
|
| 39 |
else |
|
| 40 |
shell_quote(client_command) |
|
| 41 |
end |
|
| 42 |
end |
|
| 43 |
|
|
| 44 |
# Returns the version of the scm client |
|
| 45 |
# Eg: [1, 5, 0] or [] if unknown |
|
| 46 |
def client_version |
|
| 47 |
[] |
|
| 48 |
end |
|
| 49 |
|
|
| 50 |
# Returns the version string of the scm client |
|
| 51 |
# Eg: '1.5.0' or 'Unknown version' if unknown |
|
| 52 |
def client_version_string |
|
| 53 |
v = client_version || 'Unknown version' |
|
| 54 |
v.is_a?(Array) ? v.join('.') : v.to_s
|
|
| 55 |
end |
|
| 56 |
|
|
| 57 |
# Returns true if the current client version is above |
|
| 58 |
# or equals the given one |
|
| 59 |
# If option is :unknown is set to true, it will return |
|
| 60 |
# true if the client version is unknown |
|
| 61 |
def client_version_above?(v, options={})
|
|
| 62 |
((client_version <=> v) >= 0) || (client_version.empty? && options[:unknown]) |
|
| 63 |
end |
|
| 64 |
|
|
| 65 |
def client_available |
|
| 66 |
true |
|
| 67 |
end |
|
| 68 |
|
|
| 69 |
def shell_quote(str) |
|
| 70 |
if Redmine::Platform.mswin? |
|
| 71 |
'"' + str.gsub(/"/, '\\"') + '"' |
|
| 72 |
else |
|
| 73 |
"'" + str.gsub(/'/, "'\"'\"'") + "'" |
|
| 74 |
end |
|
| 75 |
end |
|
| 76 |
end |
|
| 77 |
|
|
| 78 |
def initialize(url, root_url=nil, login=nil, password=nil, |
|
| 79 |
path_encoding=nil) |
|
| 80 |
@url = url |
|
| 81 |
@login = login if login && !login.empty? |
|
| 82 |
@password = (password || "") if @login |
|
| 83 |
@root_url = root_url.blank? ? retrieve_root_url : root_url |
|
| 84 |
end |
|
| 85 |
|
|
| 86 |
def adapter_name |
|
| 87 |
'Abstract' |
|
| 88 |
end |
|
| 89 |
|
|
| 90 |
def supports_cat? |
|
| 91 |
true |
|
| 92 |
end |
|
| 93 |
|
|
| 94 |
def supports_annotate? |
|
| 95 |
respond_to?('annotate')
|
|
| 96 |
end |
|
| 97 |
|
|
| 98 |
def root_url |
|
| 99 |
@root_url |
|
| 100 |
end |
|
| 101 |
|
|
| 102 |
def url |
|
| 103 |
@url |
|
| 104 |
end |
|
| 105 |
|
|
| 106 |
def path_encoding |
|
| 107 |
nil |
|
| 108 |
end |
|
| 109 |
|
|
| 110 |
# get info about the svn repository |
|
| 111 |
def info |
|
| 112 |
return nil |
|
| 113 |
end |
|
| 114 |
|
|
| 115 |
# Returns the entry identified by path and revision identifier |
|
| 116 |
# or nil if entry doesn't exist in the repository |
|
| 117 |
def entry(path=nil, identifier=nil) |
|
| 118 |
parts = path.to_s.split(%r{[\/\\]}).select {|n| !n.blank?}
|
|
| 119 |
search_path = parts[0..-2].join('/')
|
|
| 120 |
search_name = parts[-1] |
|
| 121 |
if search_path.blank? && search_name.blank? |
|
| 122 |
# Root entry |
|
| 123 |
Entry.new(:path => '', :kind => 'dir') |
|
| 124 |
else |
|
| 125 |
# Search for the entry in the parent directory |
|
| 126 |
es = entries(search_path, identifier) |
|
| 127 |
es ? es.detect {|e| e.name == search_name} : nil
|
|
| 128 |
end |
|
| 129 |
end |
|
| 130 |
|
|
| 131 |
# Returns an Entries collection |
|
| 132 |
# or nil if the given path doesn't exist in the repository |
|
| 133 |
def entries(path=nil, identifier=nil, options={})
|
|
| 134 |
return nil |
|
| 135 |
end |
|
| 136 |
|
|
| 137 |
def branches |
|
| 138 |
return nil |
|
| 139 |
end |
|
| 140 |
|
|
| 141 |
def tags |
|
| 142 |
return nil |
|
| 143 |
end |
|
| 144 |
|
|
| 145 |
def default_branch |
|
| 146 |
return nil |
|
| 147 |
end |
|
| 148 |
|
|
| 149 |
def properties(path, identifier=nil) |
|
| 150 |
return nil |
|
| 151 |
end |
|
| 152 |
|
|
| 153 |
def revisions(path=nil, identifier_from=nil, identifier_to=nil, options={})
|
|
| 154 |
return nil |
|
| 155 |
end |
|
| 156 |
|
|
| 157 |
def diff(path, identifier_from, identifier_to=nil) |
|
| 158 |
return nil |
|
| 159 |
end |
|
| 160 |
|
|
| 161 |
def cat(path, identifier=nil) |
|
| 162 |
return nil |
|
| 163 |
end |
|
| 164 |
|
|
| 165 |
def with_leading_slash(path) |
|
| 166 |
path ||= '' |
|
| 167 |
(path[0,1]!="/") ? "/#{path}" : path
|
|
| 168 |
end |
|
| 169 |
|
|
| 170 |
def with_trailling_slash(path) |
|
| 171 |
path ||= '' |
|
| 172 |
(path[-1,1] == "/") ? path : "#{path}/"
|
|
| 173 |
end |
|
| 174 |
|
|
| 175 |
def without_leading_slash(path) |
|
| 176 |
path ||= '' |
|
| 177 |
path.gsub(%r{^/+}, '')
|
|
| 178 |
end |
|
| 179 |
|
|
| 180 |
def without_trailling_slash(path) |
|
| 181 |
path ||= '' |
|
| 182 |
(path[-1,1] == "/") ? path[0..-2] : path |
|
| 183 |
end |
|
| 184 |
|
|
| 185 |
def shell_quote(str) |
|
| 186 |
self.class.shell_quote(str) |
|
| 187 |
end |
|
| 188 |
|
|
| 189 |
private |
|
| 190 |
def retrieve_root_url |
|
| 191 |
info = self.info |
|
| 192 |
info ? info.root_url : nil |
|
| 193 |
end |
|
| 194 |
|
|
| 195 |
def target(path, sq=true) |
|
| 196 |
path ||= '' |
|
| 197 |
base = path.match(/^\//) ? root_url : url |
|
| 198 |
str = "#{base}/#{path}".gsub(/[?<>\*]/, '')
|
|
| 199 |
if sq |
|
| 200 |
str = shell_quote(str) |
|
| 201 |
end |
|
| 202 |
str |
|
| 203 |
end |
|
| 204 |
|
|
| 205 |
def logger |
|
| 206 |
self.class.logger |
|
| 207 |
end |
|
| 208 |
|
|
| 209 |
def shellout(cmd, options = {}, &block)
|
|
| 210 |
self.class.shellout(cmd, options, &block) |
|
| 211 |
end |
|
| 212 |
|
|
| 213 |
def self.logger |
|
| 214 |
Rails.logger |
|
| 215 |
end |
|
| 216 |
|
|
| 217 |
def self.shellout(cmd, options = {}, &block)
|
|
| 218 |
if logger && logger.debug? |
|
| 219 |
logger.debug "Shelling out: #{strip_credential(cmd)}"
|
|
| 220 |
end |
|
| 221 |
if Rails.env == 'development' |
|
| 222 |
# Capture stderr when running in dev environment |
|
| 223 |
cmd = "#{cmd} 2>>#{shell_quote(Rails.root.join('log/scm.stderr.log').to_s)}"
|
|
| 224 |
end |
|
| 225 |
begin |
|
| 226 |
mode = "r+" |
|
| 227 |
IO.popen(cmd, mode) do |io| |
|
| 228 |
io.set_encoding("ASCII-8BIT") if io.respond_to?(:set_encoding)
|
|
| 229 |
io.close_write unless options[:write_stdin] |
|
| 230 |
block.call(io) if block_given? |
|
| 231 |
end |
|
| 232 |
## If scm command does not exist, |
|
| 233 |
## Linux JRuby 1.6.2 (ruby-1.8.7-p330) raises java.io.IOException |
|
| 234 |
## in production environment. |
|
| 235 |
# rescue Errno::ENOENT => e |
|
| 236 |
rescue Exception => e |
|
| 237 |
msg = strip_credential(e.message) |
|
| 238 |
# The command failed, log it and re-raise |
|
| 239 |
logmsg = "SCM command failed, " |
|
| 240 |
logmsg += "make sure that your SCM command (e.g. svn) is " |
|
| 241 |
logmsg += "in PATH (#{ENV['PATH']})\n"
|
|
| 242 |
logmsg += "You can configure your scm commands in config/configuration.yml.\n" |
|
| 243 |
logmsg += "#{strip_credential(cmd)}\n"
|
|
| 244 |
logmsg += "with: #{msg}"
|
|
| 245 |
logger.error(logmsg) |
|
| 246 |
raise CommandFailed.new(msg) |
|
| 247 |
end |
|
| 248 |
end |
|
| 249 |
|
|
| 250 |
# Hides username/password in a given command |
|
| 251 |
def self.strip_credential(cmd) |
|
| 252 |
q = (Redmine::Platform.mswin? ? '"' : "'") |
|
| 253 |
cmd.to_s.gsub(/(\-\-(password|username))\s+(#{q}[^#{q}]+#{q}|[^#{q}]\S+)/, '\\1 xxxx')
|
|
| 254 |
end |
|
| 255 |
|
|
| 256 |
def strip_credential(cmd) |
|
| 257 |
self.class.strip_credential(cmd) |
|
| 258 |
end |
|
| 259 |
|
|
| 260 |
def scm_iconv(to, from, str) |
|
| 261 |
return nil if str.nil? |
|
| 262 |
return str if to == from |
|
| 263 |
if str.respond_to?(:force_encoding) |
|
| 264 |
str.force_encoding(from) |
|
| 265 |
begin |
|
| 266 |
str.encode(to) |
|
| 267 |
rescue Exception => err |
|
| 268 |
logger.error("failed to convert from #{from} to #{to}. #{err}")
|
|
| 269 |
nil |
|
| 270 |
end |
|
| 271 |
else |
|
| 272 |
begin |
|
| 273 |
Iconv.conv(to, from, str) |
|
| 274 |
rescue Iconv::Failure => err |
|
| 275 |
logger.error("failed to convert from #{from} to #{to}. #{err}")
|
|
| 276 |
nil |
|
| 277 |
end |
|
| 278 |
end |
|
| 279 |
end |
|
| 280 |
|
|
| 281 |
def parse_xml(xml) |
|
| 282 |
if RUBY_PLATFORM == 'java' |
|
| 283 |
xml = xml.sub(%r{<\?xml[^>]*\?>}, '')
|
|
| 284 |
end |
|
| 285 |
ActiveSupport::XmlMini.parse(xml) |
|
| 286 |
end |
|
| 287 |
end |
|
| 288 |
|
|
| 289 |
class Entries < Array |
|
| 290 |
def sort_by_name |
|
| 291 |
dup.sort! {|x,y|
|
|
| 292 |
if x.kind == y.kind |
|
| 293 |
x.name.to_s <=> y.name.to_s |
|
| 294 |
else |
|
| 295 |
x.kind <=> y.kind |
|
| 296 |
end |
|
| 297 |
} |
|
| 298 |
end |
|
| 299 |
|
|
| 300 |
def revisions |
|
| 301 |
revisions ||= Revisions.new(collect{|entry| entry.lastrev}.compact)
|
|
| 302 |
end |
|
| 303 |
end |
|
| 304 |
|
|
| 305 |
class Info |
|
| 306 |
attr_accessor :root_url, :lastrev |
|
| 307 |
def initialize(attributes={})
|
|
| 308 |
self.root_url = attributes[:root_url] if attributes[:root_url] |
|
| 309 |
self.lastrev = attributes[:lastrev] |
|
| 310 |
end |
|
| 311 |
end |
|
| 312 |
|
|
| 313 |
class Entry |
|
| 314 |
attr_accessor :name, :path, :kind, :size, :lastrev, :changeset |
|
| 315 |
|
|
| 316 |
def initialize(attributes={})
|
|
| 317 |
self.name = attributes[:name] if attributes[:name] |
|
| 318 |
self.path = attributes[:path] if attributes[:path] |
|
| 319 |
self.kind = attributes[:kind] if attributes[:kind] |
|
| 320 |
self.size = attributes[:size].to_i if attributes[:size] |
|
| 321 |
self.lastrev = attributes[:lastrev] |
|
| 322 |
end |
|
| 323 |
|
|
| 324 |
def is_file? |
|
| 325 |
'file' == self.kind |
|
| 326 |
end |
|
| 327 |
|
|
| 328 |
def is_dir? |
|
| 329 |
'dir' == self.kind |
|
| 330 |
end |
|
| 331 |
|
|
| 332 |
def is_text? |
|
| 333 |
Redmine::MimeType.is_type?('text', name)
|
|
| 334 |
end |
|
| 335 |
|
|
| 336 |
def author |
|
| 337 |
if changeset |
|
| 338 |
changeset.author.to_s |
|
| 339 |
elsif lastrev |
|
| 340 |
Redmine::CodesetUtil.replace_invalid_utf8(lastrev.author.to_s.split('<').first)
|
|
| 341 |
end |
|
| 342 |
end |
|
| 343 |
end |
|
| 344 |
|
|
| 345 |
class Revisions < Array |
|
| 346 |
def latest |
|
| 347 |
sort {|x,y|
|
|
| 348 |
unless x.time.nil? or y.time.nil? |
|
| 349 |
x.time <=> y.time |
|
| 350 |
else |
|
| 351 |
0 |
|
| 352 |
end |
|
| 353 |
}.last |
|
| 354 |
end |
|
| 355 |
end |
|
| 356 |
|
|
| 357 |
class Revision |
|
| 358 |
attr_accessor :scmid, :name, :author, :time, :message, |
|
| 359 |
:paths, :revision, :branch, :identifier, |
|
| 360 |
:parents |
|
| 361 |
|
|
| 362 |
def initialize(attributes={})
|
|
| 363 |
self.identifier = attributes[:identifier] |
|
| 364 |
self.scmid = attributes[:scmid] |
|
| 365 |
self.name = attributes[:name] || self.identifier |
|
| 366 |
self.author = attributes[:author] |
|
| 367 |
self.time = attributes[:time] |
|
| 368 |
self.message = attributes[:message] || "" |
|
| 369 |
self.paths = attributes[:paths] |
|
| 370 |
self.revision = attributes[:revision] |
|
| 371 |
self.branch = attributes[:branch] |
|
| 372 |
self.parents = attributes[:parents] |
|
| 373 |
end |
|
| 374 |
|
|
| 375 |
# Returns the readable identifier. |
|
| 376 |
def format_identifier |
|
| 377 |
self.identifier.to_s |
|
| 378 |
end |
|
| 379 |
|
|
| 380 |
def ==(other) |
|
| 381 |
if other.nil? |
|
| 382 |
false |
|
| 383 |
elsif scmid.present? |
|
| 384 |
scmid == other.scmid |
|
| 385 |
elsif identifier.present? |
|
| 386 |
identifier == other.identifier |
|
| 387 |
elsif revision.present? |
|
| 388 |
revision == other.revision |
|
| 389 |
end |
|
| 390 |
end |
|
| 391 |
end |
|
| 392 |
|
|
| 393 |
class Annotate |
|
| 394 |
attr_reader :lines, :revisions |
|
| 395 |
|
|
| 396 |
def initialize |
|
| 397 |
@lines = [] |
|
| 398 |
@revisions = [] |
|
| 399 |
end |
|
| 400 |
|
|
| 401 |
def add_line(line, revision) |
|
| 402 |
@lines << line |
|
| 403 |
@revisions << revision |
|
| 404 |
end |
|
| 405 |
|
|
| 406 |
def content |
|
| 407 |
content = lines.join("\n")
|
|
| 408 |
end |
|
| 409 |
|
|
| 410 |
def empty? |
|
| 411 |
lines.empty? |
|
| 412 |
end |
|
| 413 |
end |
|
| 414 |
|
|
| 415 |
class Branch < String |
|
| 416 |
attr_accessor :revision, :scmid |
|
| 417 |
end |
|
| 418 |
end |
|
| 419 |
end |
|
| 420 |
end |
|
| .svn/pristine/bc/bc5154e6e901520f6590eecc5a9c5e2a04a8c2f1.svn-base | ||
|---|---|---|
| 1 |
/* Turkish initialisation for the jQuery UI date picker plugin. */ |
|
| 2 |
/* Written by Izzet Emre Erkan (kara@karalamalar.net). */ |
|
| 3 |
jQuery(function($){
|
|
| 4 |
$.datepicker.regional['tr'] = {
|
|
| 5 |
closeText: 'kapat', |
|
| 6 |
prevText: '<geri', |
|
| 7 |
nextText: 'ileri>', |
|
| 8 |
currentText: 'bugün', |
|
| 9 |
monthNames: ['Ocak','Şubat','Mart','Nisan','Mayıs','Haziran', |
|
| 10 |
'Temmuz','Ağustos','Eylül','Ekim','Kasım','Aralık'], |
|
| 11 |
monthNamesShort: ['Oca','Åžub','Mar','Nis','May','Haz', |
|
| 12 |
'Tem','AÄŸu','Eyl','Eki','Kas','Ara'], |
|
| 13 |
dayNames: ['Pazar','Pazartesi','Salı','Çarşamba','Perşembe','Cuma','Cumartesi'], |
|
| 14 |
dayNamesShort: ['Pz','Pt','Sa','Ça','Pe','Cu','Ct'], |
|
| 15 |
dayNamesMin: ['Pz','Pt','Sa','Ça','Pe','Cu','Ct'], |
|
| 16 |
weekHeader: 'Hf', |
|
| 17 |
dateFormat: 'dd.mm.yy', |
|
| 18 |
firstDay: 1, |
|
| 19 |
isRTL: false, |
|
| 20 |
showMonthAfterYear: false, |
|
| 21 |
yearSuffix: ''}; |
|
| 22 |
$.datepicker.setDefaults($.datepicker.regional['tr']); |
|
| 23 |
}); |
|
| .svn/pristine/bc/bc8bccaed2502b05fe9e07abbe378d2850bf83c3.svn-base | ||
|---|---|---|
| 1 |
desc "Run the Continous Integration tests for Redmine" |
|
| 2 |
task :ci do |
|
| 3 |
# RAILS_ENV and ENV[] can diverge so force them both to test |
|
| 4 |
ENV['RAILS_ENV'] = 'test' |
|
| 5 |
RAILS_ENV = 'test' |
|
| 6 |
Rake::Task["ci:setup"].invoke |
|
| 7 |
Rake::Task["ci:build"].invoke |
|
| 8 |
Rake::Task["ci:teardown"].invoke |
|
| 9 |
end |
|
| 10 |
|
|
| 11 |
# Tasks can be hooked into by redefining them in a plugin |
|
| 12 |
namespace :ci do |
|
| 13 |
desc "Setup Redmine for a new build." |
|
| 14 |
task :setup do |
|
| 15 |
Rake::Task["ci:dump_environment"].invoke |
|
| 16 |
Rake::Task["db:create"].invoke |
|
| 17 |
Rake::Task["db:migrate"].invoke |
|
| 18 |
Rake::Task["db:schema:dump"].invoke |
|
| 19 |
Rake::Task["test:scm:update"].invoke |
|
| 20 |
end |
|
| 21 |
|
|
| 22 |
desc "Build Redmine" |
|
| 23 |
task :build do |
|
| 24 |
Rake::Task["test"].invoke |
|
| 25 |
end |
|
| 26 |
|
|
| 27 |
# Use this to cleanup after building or run post-build analysis. |
|
| 28 |
desc "Finish the build" |
|
| 29 |
task :teardown do |
|
| 30 |
end |
|
| 31 |
|
|
| 32 |
desc "Creates and configures the databases for the CI server" |
|
| 33 |
task :database do |
|
| 34 |
path = 'config/database.yml' |
|
| 35 |
unless File.exists?(path) |
|
| 36 |
database = ENV['DATABASE_ADAPTER'] |
|
| 37 |
ruby = ENV['RUBY_VER'].gsub('.', '').gsub('-', '')
|
|
| 38 |
branch = ENV['BRANCH'].gsub('.', '').gsub('-', '')
|
|
| 39 |
dev_db_name = "ci_#{branch}_#{ruby}_dev"
|
|
| 40 |
test_db_name = "ci_#{branch}_#{ruby}_test"
|
|
| 41 |
|
|
| 42 |
case database |
|
| 43 |
when 'mysql' |
|
| 44 |
raise "Error creating databases" unless |
|
| 45 |
system(%|mysql -u jenkins --password=jenkins -e 'create database #{dev_db_name} character set utf8;'|) &&
|
|
| 46 |
system(%|mysql -u jenkins --password=jenkins -e 'create database #{test_db_name} character set utf8;'|)
|
|
| 47 |
dev_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' }
|
|
| 48 |
test_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' }
|
|
| 49 |
when 'postgresql' |
|
| 50 |
raise "Error creating databases" unless |
|
| 51 |
system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';"|) &&
|
|
| 52 |
system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';"|)
|
|
| 53 |
dev_conf = { 'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
|
|
| 54 |
test_conf = { 'adapter' => 'postgresql', 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
|
|
| 55 |
when 'sqlite3' |
|
| 56 |
dev_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{dev_db_name}.sqlite3" }
|
|
| 57 |
test_conf = { 'adapter' => 'sqlite3', 'database' => "db/#{test_db_name}.sqlite3" }
|
|
| 58 |
else |
|
| 59 |
raise "Unknown database" |
|
| 60 |
end |
|
| 61 |
|
|
| 62 |
File.open(path, 'w') do |f| |
|
| 63 |
f.write YAML.dump({'development' => dev_conf, 'test' => test_conf})
|
|
| 64 |
end |
|
| 65 |
end |
|
| 66 |
end |
|
| 67 |
|
|
| 68 |
desc "Dump the environment information to a BUILD_ENVIRONMENT ENV variable for debugging" |
|
| 69 |
task :dump_environment do |
|
| 70 |
|
|
| 71 |
ENV['BUILD_ENVIRONMENT'] = ['ruby -v', 'gem -v', 'gem list'].collect do |command| |
|
| 72 |
result = `#{command}`
|
|
| 73 |
"$ #{command}\n#{result}"
|
|
| 74 |
end.join("\n")
|
|
| 75 |
|
|
| 76 |
end |
|
| 77 |
end |
|
| 78 |
|
|
| .svn/pristine/bc/bcc554e7744ab29cc1c8ca57d857bed00d539a54.svn-base | ||
|---|---|---|
| 1 |
<div class="contextual"> |
|
| 2 |
<%= link_to(l(:label_news_new), |
|
| 3 |
new_project_news_path(@project), |
|
| 4 |
:class => 'icon icon-add', |
|
| 5 |
:onclick => 'showAndScrollTo("add-news", "news_title"); return false;') if @project && User.current.allowed_to?(:manage_news, @project) %>
|
|
| 6 |
</div> |
|
| 7 |
|
|
| 8 |
<div id="add-news" style="display:none;"> |
|
| 9 |
<h2><%=l(:label_news_new)%></h2> |
|
| 10 |
<%= labelled_form_for @news, :url => project_news_index_path(@project), |
|
| 11 |
:html => { :id => 'news-form', :multipart => true } do |f| %>
|
|
| 12 |
<%= render :partial => 'news/form', :locals => { :f => f } %>
|
|
| 13 |
<%= submit_tag l(:button_create) %> |
|
| 14 |
<%= preview_link preview_news_path(:project_id => @project), 'news-form' %> | |
|
| 15 |
<%= link_to l(:button_cancel), "#", :onclick => '$("#add-news").hide()' %>
|
|
| 16 |
<% end if @project %> |
|
| 17 |
<div id="preview" class="wiki"></div> |
|
| 18 |
</div> |
|
| 19 |
|
|
| 20 |
<h2><%=l(:label_news_plural)%></h2> |
|
| 21 |
|
|
| 22 |
<% if @newss.empty? %> |
|
| 23 |
<p class="nodata"><%= l(:label_no_data) %></p> |
|
| 24 |
<% else %> |
|
| 25 |
<% @newss.each do |news| %> |
|
| 26 |
<h3><%= avatar(news.author, :size => "24") %><%= link_to_project(news.project) + ': ' unless news.project == @project %> |
|
| 27 |
<%= link_to h(news.title), news_path(news) %> |
|
| 28 |
<%= "(#{l(:label_x_comments, :count => news.comments_count)})" if news.comments_count > 0 %></h3>
|
|
| 29 |
<p class="author"><%= authoring news.created_on, news.author %></p> |
|
| 30 |
<div class="wiki"> |
|
| 31 |
<%= textilizable(news, :description) %> |
|
| 32 |
</div> |
|
| 33 |
<% end %> |
|
| 34 |
<% end %> |
|
| 35 |
<p class="pagination"><%= pagination_links_full @news_pages %></p> |
|
| 36 |
|
|
| 37 |
<% other_formats_links do |f| %> |
|
| 38 |
<%= f.link_to 'Atom', :url => {:project_id => @project, :key => User.current.rss_key} %>
|
|
| 39 |
<% end %> |
|
| 40 |
|
|
| 41 |
<% content_for :header_tags do %> |
|
| 42 |
<%= auto_discovery_link_tag(:atom, params.merge({:format => 'atom', :page => nil, :key => User.current.rss_key})) %>
|
|
| 43 |
<%= stylesheet_link_tag 'scm' %> |
|
| 44 |
<% end %> |
|
| 45 |
|
|
| 46 |
<% html_title(l(:label_news_plural)) -%> |
|
| .svn/pristine/bc/bcd855d1f37773ea6cbe38e91cc6db056d7aef2e.svn-base | ||
|---|---|---|
| 1 |
<div class="contextual"> |
|
| 2 |
<%= link_to(l(:button_edit), edit_user_path(@user), :class => 'icon icon-edit') if User.current.admin? %> |
|
| 3 |
</div> |
|
| 4 |
|
|
| 5 |
<h2><%= avatar @user, :size => "50" %> <%=h @user.name %></h2> |
|
| 6 |
|
|
| 7 |
<div class="splitcontentleft"> |
|
| 8 |
<ul> |
|
| 9 |
<% unless @user.pref.hide_mail %> |
|
| 10 |
<li><%=l(:field_mail)%>: <%= mail_to(h(@user.mail), nil, :encode => 'javascript') %></li> |
|
| 11 |
<% end %> |
|
| 12 |
<% @user.visible_custom_field_values.each do |custom_value| %> |
|
| 13 |
<% if !custom_value.value.blank? %> |
|
| 14 |
<li><%=h custom_value.custom_field.name%>: <%=h show_value(custom_value) %></li> |
|
| 15 |
<% end %> |
|
| 16 |
<% end %> |
|
| 17 |
<li><%=l(:label_registered_on)%>: <%= format_date(@user.created_on) %></li> |
|
| 18 |
<% unless @user.last_login_on.nil? %> |
|
| 19 |
<li><%=l(:field_last_login_on)%>: <%= format_date(@user.last_login_on) %></li> |
|
| 20 |
<% end %> |
|
| 21 |
</ul> |
|
| 22 |
|
|
| 23 |
<% unless @memberships.empty? %> |
|
| 24 |
<h3><%=l(:label_project_plural)%></h3> |
|
| 25 |
<ul> |
|
| 26 |
<% for membership in @memberships %> |
|
| 27 |
<li><%= link_to_project(membership.project) %> |
|
| 28 |
(<%=h membership.roles.sort.collect(&:to_s).join(', ') %>, <%= format_date(membership.created_on) %>)</li>
|
|
| 29 |
<% end %> |
|
| 30 |
</ul> |
|
| 31 |
<% end %> |
|
| 32 |
<%= call_hook :view_account_left_bottom, :user => @user %> |
|
| 33 |
</div> |
|
| 34 |
|
|
| 35 |
<div class="splitcontentright"> |
|
| 36 |
|
|
| 37 |
<% unless @events_by_day.empty? %> |
|
| 38 |
<h3><%= link_to l(:label_activity), :controller => 'activities', |
|
| 39 |
:action => 'index', :id => nil, :user_id => @user, |
|
| 40 |
:from => @events_by_day.keys.first %></h3> |
|
| 41 |
|
|
| 42 |
<p> |
|
| 43 |
<%=l(:label_reported_issues)%>: <%= Issue.count(:conditions => ["author_id=?", @user.id]) %> |
|
| 44 |
</p> |
|
| 45 |
|
|
| 46 |
<div id="activity"> |
|
| 47 |
<% @events_by_day.keys.sort.reverse.each do |day| %> |
|
| 48 |
<h4><%= format_activity_day(day) %></h4> |
|
| 49 |
<dl> |
|
| 50 |
<% @events_by_day[day].sort {|x,y| y.event_datetime <=> x.event_datetime }.each do |e| -%>
|
|
| 51 |
<dt class="<%= e.event_type %>"> |
|
| 52 |
<span class="time"><%= format_time(e.event_datetime, false) %></span> |
|
| 53 |
<%= content_tag('span', h(e.project), :class => 'project') %>
|
|
| 54 |
<%= link_to format_activity_title(e.event_title), e.event_url %></dt> |
|
| 55 |
<dd><span class="description"><%= format_activity_description(e.event_description) %></span></dd> |
|
| 56 |
<% end -%> |
|
| 57 |
</dl> |
|
| 58 |
<% end -%> |
|
| 59 |
</div> |
|
| 60 |
|
|
| 61 |
<% other_formats_links do |f| %> |
|
| 62 |
<%= f.link_to 'Atom', :url => {:controller => 'activities', :action => 'index', :id => nil, :user_id => @user, :key => User.current.rss_key} %>
|
|
| 63 |
<% end %> |
|
| 64 |
|
|
| 65 |
<% content_for :header_tags do %> |
|
| 66 |
<%= auto_discovery_link_tag(:atom, :controller => 'activities', :action => 'index', :user_id => @user, :format => :atom, :key => User.current.rss_key) %> |
|
| 67 |
<% end %> |
|
| 68 |
<% end %> |
|
| 69 |
<%= call_hook :view_account_right_bottom, :user => @user %> |
|
| 70 |
</div> |
|
| 71 |
|
|
| 72 |
<% html_title @user.name %> |
|
| .svn/pristine/bc/bcefb9a2bbe2d9c2d605eef1915f4d25e465456c.svn-base | ||
|---|---|---|
| 1 |
# Redmine - project management software |
|
| 2 |
# Copyright (C) 2006-2012 Jean-Philippe Lang |
|
| 3 |
# |
|
| 4 |
# This program is free software; you can redistribute it and/or |
|
| 5 |
# modify it under the terms of the GNU General Public License |
|
| 6 |
# as published by the Free Software Foundation; either version 2 |
|
| 7 |
# of the License, or (at your option) any later version. |
|
| 8 |
# |
|
| 9 |
# This program is distributed in the hope that it will be useful, |
|
| 10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
| 12 |
# GNU General Public License for more details. |
|
| 13 |
# |
|
| 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 |
|
| 16 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
| 17 |
|
|
| 18 |
require 'active_record' |
|
| 19 |
require 'iconv' |
|
| 20 |
require 'pp' |
|
| 21 |
|
|
| 22 |
namespace :redmine do |
|
| 23 |
desc 'Trac migration script' |
|
| 24 |
task :migrate_from_trac => :environment do |
|
| 25 |
|
|
| 26 |
module TracMigrate |
|
| 27 |
TICKET_MAP = [] |
|
| 28 |
|
|
| 29 |
DEFAULT_STATUS = IssueStatus.default |
|
| 30 |
assigned_status = IssueStatus.find_by_position(2) |
|
| 31 |
resolved_status = IssueStatus.find_by_position(3) |
|
| 32 |
feedback_status = IssueStatus.find_by_position(4) |
|
| 33 |
closed_status = IssueStatus.find :first, :conditions => { :is_closed => true }
|
|
| 34 |
STATUS_MAPPING = {'new' => DEFAULT_STATUS,
|
|
| 35 |
'reopened' => feedback_status, |
|
| 36 |
'assigned' => assigned_status, |
|
| 37 |
'closed' => closed_status |
|
| 38 |
} |
|
| 39 |
|
|
| 40 |
priorities = IssuePriority.all |
|
| 41 |
DEFAULT_PRIORITY = priorities[0] |
|
| 42 |
PRIORITY_MAPPING = {'lowest' => priorities[0],
|
|
| 43 |
'low' => priorities[0], |
|
| 44 |
'normal' => priorities[1], |
|
| 45 |
'high' => priorities[2], |
|
| 46 |
'highest' => priorities[3], |
|
| 47 |
# --- |
|
| 48 |
'trivial' => priorities[0], |
|
| 49 |
'minor' => priorities[1], |
|
| 50 |
'major' => priorities[2], |
|
| 51 |
'critical' => priorities[3], |
|
| 52 |
'blocker' => priorities[4] |
|
| 53 |
} |
|
| 54 |
|
|
| 55 |
TRACKER_BUG = Tracker.find_by_position(1) |
|
| 56 |
TRACKER_FEATURE = Tracker.find_by_position(2) |
|
| 57 |
DEFAULT_TRACKER = TRACKER_BUG |
|
| 58 |
TRACKER_MAPPING = {'defect' => TRACKER_BUG,
|
|
| 59 |
'enhancement' => TRACKER_FEATURE, |
|
| 60 |
'task' => TRACKER_FEATURE, |
|
| 61 |
'patch' =>TRACKER_FEATURE |
|
| 62 |
} |
|
| 63 |
|
|
| 64 |
roles = Role.find(:all, :conditions => {:builtin => 0}, :order => 'position ASC')
|
|
| 65 |
manager_role = roles[0] |
|
| 66 |
developer_role = roles[1] |
|
| 67 |
DEFAULT_ROLE = roles.last |
|
| 68 |
ROLE_MAPPING = {'admin' => manager_role,
|
|
| 69 |
'developer' => developer_role |
|
| 70 |
} |
|
| 71 |
|
|
| 72 |
class ::Time |
|
| 73 |
class << self |
|
| 74 |
alias :real_now :now |
|
| 75 |
def now |
|
| 76 |
real_now - @fake_diff.to_i |
|
| 77 |
end |
|
| 78 |
def fake(time) |
|
| 79 |
@fake_diff = real_now - time |
|
| 80 |
res = yield |
|
| 81 |
@fake_diff = 0 |
|
| 82 |
res |
|
| 83 |
end |
|
| 84 |
end |
|
| 85 |
end |
|
| 86 |
|
|
| 87 |
class TracComponent < ActiveRecord::Base |
|
| 88 |
self.table_name = :component |
|
| 89 |
end |
|
| 90 |
|
|
| 91 |
class TracMilestone < ActiveRecord::Base |
|
| 92 |
self.table_name = :milestone |
|
| 93 |
# If this attribute is set a milestone has a defined target timepoint |
|
| 94 |
def due |
|
| 95 |
if read_attribute(:due) && read_attribute(:due) > 0 |
|
| 96 |
Time.at(read_attribute(:due)).to_date |
|
| 97 |
else |
|
| 98 |
nil |
|
| 99 |
end |
|
| 100 |
end |
|
| 101 |
# This is the real timepoint at which the milestone has finished. |
|
| 102 |
def completed |
|
| 103 |
if read_attribute(:completed) && read_attribute(:completed) > 0 |
|
| 104 |
Time.at(read_attribute(:completed)).to_date |
|
| 105 |
else |
|
| 106 |
nil |
|
| 107 |
end |
|
| 108 |
end |
|
| 109 |
|
|
| 110 |
def description |
|
| 111 |
# Attribute is named descr in Trac v0.8.x |
|
| 112 |
has_attribute?(:descr) ? read_attribute(:descr) : read_attribute(:description) |
|
| 113 |
end |
|
| 114 |
end |
|
| 115 |
|
|
| 116 |
class TracTicketCustom < ActiveRecord::Base |
|
| 117 |
self.table_name = :ticket_custom |
|
| 118 |
end |
|
| 119 |
|
|
| 120 |
class TracAttachment < ActiveRecord::Base |
|
| 121 |
self.table_name = :attachment |
|
| 122 |
set_inheritance_column :none |
|
| 123 |
|
|
| 124 |
def time; Time.at(read_attribute(:time)) end |
|
| 125 |
|
|
| 126 |
def original_filename |
|
| 127 |
filename |
|
| 128 |
end |
|
| 129 |
|
|
| 130 |
def content_type |
|
| 131 |
'' |
|
| 132 |
end |
|
| 133 |
|
|
| 134 |
def exist? |
|
| 135 |
File.file? trac_fullpath |
|
| 136 |
end |
|
| 137 |
|
|
| 138 |
def open |
|
| 139 |
File.open("#{trac_fullpath}", 'rb') {|f|
|
|
| 140 |
@file = f |
|
| 141 |
yield self |
|
| 142 |
} |
|
| 143 |
end |
|
| 144 |
|
|
| 145 |
def read(*args) |
|
| 146 |
@file.read(*args) |
|
| 147 |
end |
|
| 148 |
|
|
| 149 |
def description |
|
| 150 |
read_attribute(:description).to_s.slice(0,255) |
|
| 151 |
end |
|
| 152 |
|
|
| 153 |
private |
|
| 154 |
def trac_fullpath |
|
| 155 |
attachment_type = read_attribute(:type) |
|
| 156 |
trac_file = filename.gsub( /[^a-zA-Z0-9\-_\.!~*']/n ) {|x| sprintf('%%%02x', x[0]) }
|
|
| 157 |
"#{TracMigrate.trac_attachments_directory}/#{attachment_type}/#{id}/#{trac_file}"
|
|
| 158 |
end |
|
| 159 |
end |
|
| 160 |
|
|
| 161 |
class TracTicket < ActiveRecord::Base |
|
| 162 |
self.table_name = :ticket |
|
| 163 |
set_inheritance_column :none |
|
| 164 |
|
|
| 165 |
# ticket changes: only migrate status changes and comments |
|
| 166 |
has_many :ticket_changes, :class_name => "TracTicketChange", :foreign_key => :ticket |
|
| 167 |
has_many :customs, :class_name => "TracTicketCustom", :foreign_key => :ticket |
|
| 168 |
|
|
| 169 |
def attachments |
|
| 170 |
TracMigrate::TracAttachment.all(:conditions => ["type = 'ticket' AND id = ?", self.id.to_s]) |
|
| 171 |
end |
|
| 172 |
|
|
| 173 |
def ticket_type |
|
| 174 |
read_attribute(:type) |
|
| 175 |
end |
|
| 176 |
|
|
| 177 |
def summary |
|
| 178 |
read_attribute(:summary).blank? ? "(no subject)" : read_attribute(:summary) |
|
| 179 |
end |
|
| 180 |
|
|
| 181 |
def description |
|
| 182 |
read_attribute(:description).blank? ? summary : read_attribute(:description) |
|
| 183 |
end |
|
| 184 |
|
|
| 185 |
def time; Time.at(read_attribute(:time)) end |
|
| 186 |
def changetime; Time.at(read_attribute(:changetime)) end |
|
| 187 |
end |
|
| 188 |
|
|
| 189 |
class TracTicketChange < ActiveRecord::Base |
|
| 190 |
self.table_name = :ticket_change |
|
| 191 |
|
|
| 192 |
def self.columns |
|
| 193 |
# Hides Trac field 'field' to prevent clash with AR field_changed? method (Rails 3.0) |
|
| 194 |
super.select {|column| column.name.to_s != 'field'}
|
|
| 195 |
end |
|
| 196 |
|
|
| 197 |
def time; Time.at(read_attribute(:time)) end |
|
| 198 |
end |
|
| 199 |
|
|
| 200 |
TRAC_WIKI_PAGES = %w(InterMapTxt InterTrac InterWiki RecentChanges SandBox TracAccessibility TracAdmin TracBackup TracBrowser TracCgi TracChangeset \ |
|
| 201 |
TracEnvironment TracFastCgi TracGuide TracImport TracIni TracInstall TracInterfaceCustomization \ |
|
| 202 |
TracLinks TracLogging TracModPython TracNotification TracPermissions TracPlugins TracQuery \ |
|
| 203 |
TracReports TracRevisionLog TracRoadmap TracRss TracSearch TracStandalone TracSupport TracSyntaxColoring TracTickets \ |
|
| 204 |
TracTicketsCustomFields TracTimeline TracUnicode TracUpgrade TracWiki WikiDeletePage WikiFormatting \ |
|
| 205 |
WikiHtml WikiMacros WikiNewPage WikiPageNames WikiProcessors WikiRestructuredText WikiRestructuredTextLinks \ |
|
| 206 |
CamelCase TitleIndex) |
|
| 207 |
|
|
| 208 |
class TracWikiPage < ActiveRecord::Base |
|
| 209 |
self.table_name = :wiki |
|
| 210 |
set_primary_key :name |
|
| 211 |
|
|
| 212 |
def self.columns |
|
| 213 |
# Hides readonly Trac field to prevent clash with AR readonly? method (Rails 2.0) |
|
| 214 |
super.select {|column| column.name.to_s != 'readonly'}
|
|
| 215 |
end |
|
| 216 |
|
|
| 217 |
def attachments |
|
| 218 |
TracMigrate::TracAttachment.all(:conditions => ["type = 'wiki' AND id = ?", self.id.to_s]) |
|
| 219 |
end |
|
| 220 |
|
|
| 221 |
def time; Time.at(read_attribute(:time)) end |
|
| 222 |
end |
|
| 223 |
|
|
| 224 |
class TracPermission < ActiveRecord::Base |
|
| 225 |
self.table_name = :permission |
|
| 226 |
end |
|
| 227 |
|
|
| 228 |
class TracSessionAttribute < ActiveRecord::Base |
|
| 229 |
self.table_name = :session_attribute |
|
| 230 |
end |
|
| 231 |
|
|
| 232 |
def self.find_or_create_user(username, project_member = false) |
|
| 233 |
return User.anonymous if username.blank? |
|
| 234 |
|
|
| 235 |
u = User.find_by_login(username) |
|
| 236 |
if !u |
|
| 237 |
# Create a new user if not found |
|
| 238 |
mail = username[0, User::MAIL_LENGTH_LIMIT] |
|
| 239 |
if mail_attr = TracSessionAttribute.find_by_sid_and_name(username, 'email') |
|
| 240 |
mail = mail_attr.value |
|
| 241 |
end |
|
| 242 |
mail = "#{mail}@foo.bar" unless mail.include?("@")
|
|
| 243 |
|
|
| 244 |
name = username |
|
| 245 |
if name_attr = TracSessionAttribute.find_by_sid_and_name(username, 'name') |
|
| 246 |
name = name_attr.value |
|
| 247 |
end |
|
| 248 |
name =~ (/(.*)(\s+\w+)?/) |
|
| 249 |
fn = $1.strip |
|
| 250 |
ln = ($2 || '-').strip |
|
| 251 |
|
|
| 252 |
u = User.new :mail => mail.gsub(/[^-@a-z0-9\.]/i, '-'), |
|
| 253 |
:firstname => fn[0, limit_for(User, 'firstname')], |
|
| 254 |
:lastname => ln[0, limit_for(User, 'lastname')] |
|
| 255 |
|
|
| 256 |
u.login = username[0, User::LOGIN_LENGTH_LIMIT].gsub(/[^a-z0-9_\-@\.]/i, '-') |
|
| 257 |
u.password = 'trac' |
|
| 258 |
u.admin = true if TracPermission.find_by_username_and_action(username, 'admin') |
|
| 259 |
# finally, a default user is used if the new user is not valid |
|
| 260 |
u = User.find(:first) unless u.save |
|
| 261 |
end |
|
| 262 |
# Make sure he is a member of the project |
|
| 263 |
if project_member && !u.member_of?(@target_project) |
|
| 264 |
role = DEFAULT_ROLE |
|
| 265 |
if u.admin |
|
| 266 |
role = ROLE_MAPPING['admin'] |
|
| 267 |
elsif TracPermission.find_by_username_and_action(username, 'developer') |
|
| 268 |
role = ROLE_MAPPING['developer'] |
|
| 269 |
end |
|
| 270 |
Member.create(:user => u, :project => @target_project, :roles => [role]) |
|
| 271 |
u.reload |
|
| 272 |
end |
|
| 273 |
u |
|
| 274 |
end |
|
| 275 |
|
|
| 276 |
# Basic wiki syntax conversion |
|
| 277 |
def self.convert_wiki_text(text) |
|
| 278 |
# Titles |
|
| 279 |
text = text.gsub(/^(\=+)\s(.+)\s(\=+)/) {|s| "\nh#{$1.length}. #{$2}\n"}
|
|
| 280 |
# External Links |
|
| 281 |
text = text.gsub(/\[(http[^\s]+)\s+([^\]]+)\]/) {|s| "\"#{$2}\":#{$1}"}
|
|
| 282 |
# Ticket links: |
|
| 283 |
# [ticket:234 Text],[ticket:234 This is a test] |
|
| 284 |
text = text.gsub(/\[ticket\:([^\ ]+)\ (.+?)\]/, '"\2":/issues/show/\1') |
|
| 285 |
# ticket:1234 |
|
| 286 |
# #1 is working cause Redmine uses the same syntax. |
|
| 287 |
text = text.gsub(/ticket\:([^\ ]+)/, '#\1') |
|
| 288 |
# Milestone links: |
|
| 289 |
# [milestone:"0.1.0 Mercury" Milestone 0.1.0 (Mercury)] |
|
| 290 |
# The text "Milestone 0.1.0 (Mercury)" is not converted, |
|
| 291 |
# cause Redmine's wiki does not support this. |
|
| 292 |
text = text.gsub(/\[milestone\:\"([^\"]+)\"\ (.+?)\]/, 'version:"\1"') |
|
| 293 |
# [milestone:"0.1.0 Mercury"] |
|
| 294 |
text = text.gsub(/\[milestone\:\"([^\"]+)\"\]/, 'version:"\1"') |
|
| 295 |
text = text.gsub(/milestone\:\"([^\"]+)\"/, 'version:"\1"') |
|
| 296 |
# milestone:0.1.0 |
|
| 297 |
text = text.gsub(/\[milestone\:([^\ ]+)\]/, 'version:\1') |
|
| 298 |
text = text.gsub(/milestone\:([^\ ]+)/, 'version:\1') |
|
| 299 |
# Internal Links |
|
| 300 |
text = text.gsub(/\[\[BR\]\]/, "\n") # This has to go before the rules below |
|
| 301 |
text = text.gsub(/\[\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
| 302 |
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
| 303 |
text = text.gsub(/\[wiki:\"(.+)\".*\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
| 304 |
text = text.gsub(/\[wiki:([^\s\]]+)\]/) {|s| "[[#{$1.delete(',./?;|:')}]]"}
|
|
| 305 |
text = text.gsub(/\[wiki:([^\s\]]+)\s(.*)\]/) {|s| "[[#{$1.delete(',./?;|:')}|#{$2.delete(',./?;|:')}]]"}
|
|
| 306 |
|
|
| 307 |
# Links to pages UsingJustWikiCaps |
|
| 308 |
text = text.gsub(/([^!]|^)(^| )([A-Z][a-z]+[A-Z][a-zA-Z]+)/, '\\1\\2[[\3]]') |
|
| 309 |
# Normalize things that were supposed to not be links |
|
| 310 |
# like !NotALink |
|
| 311 |
text = text.gsub(/(^| )!([A-Z][A-Za-z]+)/, '\1\2') |
|
| 312 |
# Revisions links |
|
| 313 |
text = text.gsub(/\[(\d+)\]/, 'r\1') |
|
| 314 |
# Ticket number re-writing |
|
| 315 |
text = text.gsub(/#(\d+)/) do |s| |
|
| 316 |
if $1.length < 10 |
|
| 317 |
# TICKET_MAP[$1.to_i] ||= $1 |
|
| 318 |
"\##{TICKET_MAP[$1.to_i] || $1}"
|
|
| 319 |
else |
|
| 320 |
s |
|
| 321 |
end |
|
| 322 |
end |
|
| 323 |
# We would like to convert the Code highlighting too |
|
| 324 |
# This will go into the next line. |
|
| 325 |
shebang_line = false |
|
| 326 |
# Reguar expression for start of code |
|
| 327 |
pre_re = /\{\{\{/
|
|
| 328 |
# Code hightlighing... |
|
| 329 |
shebang_re = /^\#\!([a-z]+)/ |
|
| 330 |
# Regular expression for end of code |
|
| 331 |
pre_end_re = /\}\}\}/ |
|
| 332 |
|
|
| 333 |
# Go through the whole text..extract it line by line |
|
| 334 |
text = text.gsub(/^(.*)$/) do |line| |
|
| 335 |
m_pre = pre_re.match(line) |
|
| 336 |
if m_pre |
|
| 337 |
line = '<pre>' |
|
| 338 |
else |
|
| 339 |
m_sl = shebang_re.match(line) |
|
| 340 |
if m_sl |
|
| 341 |
shebang_line = true |
|
| 342 |
line = '<code class="' + m_sl[1] + '">' |
|
| 343 |
end |
|
| 344 |
m_pre_end = pre_end_re.match(line) |
|
| 345 |
if m_pre_end |
|
| 346 |
line = '</pre>' |
|
| 347 |
if shebang_line |
|
| 348 |
line = '</code>' + line |
|
| 349 |
end |
|
| 350 |
end |
|
| 351 |
end |
|
| 352 |
line |
|
| 353 |
end |
|
| 354 |
|
|
| 355 |
# Highlighting |
|
| 356 |
text = text.gsub(/'''''([^\s])/, '_*\1') |
|
| 357 |
text = text.gsub(/([^\s])'''''/, '\1*_') |
|
| 358 |
text = text.gsub(/'''/, '*') |
|
| 359 |
text = text.gsub(/''/, '_') |
|
| 360 |
text = text.gsub(/__/, '+') |
|
| 361 |
text = text.gsub(/~~/, '-') |
|
| 362 |
text = text.gsub(/`/, '@') |
|
| 363 |
text = text.gsub(/,,/, '~') |
|
| 364 |
# Lists |
|
| 365 |
text = text.gsub(/^([ ]+)\* /) {|s| '*' * $1.length + " "}
|
|
| 366 |
|
|
| 367 |
text |
|
| 368 |
end |
|
| 369 |
|
|
| 370 |
def self.migrate |
|
| 371 |
establish_connection |
|
| 372 |
|
|
| 373 |
# Quick database test |
|
| 374 |
TracComponent.count |
|
| 375 |
|
|
| 376 |
migrated_components = 0 |
|
| 377 |
migrated_milestones = 0 |
|
| 378 |
migrated_tickets = 0 |
|
| 379 |
migrated_custom_values = 0 |
|
| 380 |
migrated_ticket_attachments = 0 |
|
| 381 |
migrated_wiki_edits = 0 |
|
| 382 |
migrated_wiki_attachments = 0 |
|
| 383 |
|
|
| 384 |
#Wiki system initializing... |
|
| 385 |
@target_project.wiki.destroy if @target_project.wiki |
|
| 386 |
@target_project.reload |
|
| 387 |
wiki = Wiki.new(:project => @target_project, :start_page => 'WikiStart') |
|
| 388 |
wiki_edit_count = 0 |
|
| 389 |
|
|
| 390 |
# Components |
|
| 391 |
print "Migrating components" |
|
| 392 |
issues_category_map = {}
|
|
| 393 |
TracComponent.find(:all).each do |component| |
|
| 394 |
print '.' |
|
| 395 |
STDOUT.flush |
|
| 396 |
c = IssueCategory.new :project => @target_project, |
|
| 397 |
:name => encode(component.name[0, limit_for(IssueCategory, 'name')]) |
|
| 398 |
next unless c.save |
|
| 399 |
issues_category_map[component.name] = c |
|
| 400 |
migrated_components += 1 |
|
| 401 |
end |
|
| 402 |
puts |
|
| 403 |
|
|
| 404 |
# Milestones |
|
| 405 |
print "Migrating milestones" |
|
| 406 |
version_map = {}
|
|
| 407 |
TracMilestone.find(:all).each do |milestone| |
|
| 408 |
print '.' |
|
| 409 |
STDOUT.flush |
|
| 410 |
# First we try to find the wiki page... |
|
| 411 |
p = wiki.find_or_new_page(milestone.name.to_s) |
|
| 412 |
p.content = WikiContent.new(:page => p) if p.new_record? |
|
| 413 |
p.content.text = milestone.description.to_s |
|
| 414 |
p.content.author = find_or_create_user('trac')
|
|
| 415 |
p.content.comments = 'Milestone' |
|
| 416 |
p.save |
|
| 417 |
|
|
| 418 |
v = Version.new :project => @target_project, |
|
| 419 |
:name => encode(milestone.name[0, limit_for(Version, 'name')]), |
|
| 420 |
:description => nil, |
|
| 421 |
:wiki_page_title => milestone.name.to_s, |
|
| 422 |
:effective_date => milestone.completed |
|
| 423 |
|
|
| 424 |
next unless v.save |
|
| 425 |
version_map[milestone.name] = v |
|
| 426 |
migrated_milestones += 1 |
|
| 427 |
end |
|
| 428 |
puts |
|
| 429 |
|
|
| 430 |
# Custom fields |
|
| 431 |
# TODO: read trac.ini instead |
|
| 432 |
print "Migrating custom fields" |
|
| 433 |
custom_field_map = {}
|
|
| 434 |
TracTicketCustom.find_by_sql("SELECT DISTINCT name FROM #{TracTicketCustom.table_name}").each do |field|
|
|
| 435 |
print '.' |
|
| 436 |
STDOUT.flush |
|
| 437 |
# Redmine custom field name |
|
| 438 |
field_name = encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize |
|
| 439 |
# Find if the custom already exists in Redmine |
|
| 440 |
f = IssueCustomField.find_by_name(field_name) |
|
| 441 |
# Or create a new one |
|
| 442 |
f ||= IssueCustomField.create(:name => encode(field.name[0, limit_for(IssueCustomField, 'name')]).humanize, |
|
| 443 |
:field_format => 'string') |
|
| 444 |
|
|
| 445 |
next if f.new_record? |
|
| 446 |
f.trackers = Tracker.find(:all) |
|
| 447 |
f.projects << @target_project |
|
| 448 |
custom_field_map[field.name] = f |
|
| 449 |
end |
|
| 450 |
puts |
|
| 451 |
|
|
| 452 |
# Trac 'resolution' field as a Redmine custom field |
|
| 453 |
r = IssueCustomField.find(:first, :conditions => { :name => "Resolution" })
|
|
| 454 |
r = IssueCustomField.new(:name => 'Resolution', |
|
| 455 |
:field_format => 'list', |
|
| 456 |
:is_filter => true) if r.nil? |
|
| 457 |
r.trackers = Tracker.find(:all) |
|
| 458 |
r.projects << @target_project |
|
| 459 |
r.possible_values = (r.possible_values + %w(fixed invalid wontfix duplicate worksforme)).flatten.compact.uniq |
|
| 460 |
r.save! |
|
| 461 |
custom_field_map['resolution'] = r |
|
| 462 |
|
|
| 463 |
# Tickets |
|
| 464 |
print "Migrating tickets" |
|
| 465 |
TracTicket.find_each(:batch_size => 200) do |ticket| |
|
| 466 |
print '.' |
|
| 467 |
STDOUT.flush |
|
| 468 |
i = Issue.new :project => @target_project, |
|
| 469 |
:subject => encode(ticket.summary[0, limit_for(Issue, 'subject')]), |
|
| 470 |
:description => convert_wiki_text(encode(ticket.description)), |
|
| 471 |
:priority => PRIORITY_MAPPING[ticket.priority] || DEFAULT_PRIORITY, |
|
| 472 |
:created_on => ticket.time |
|
| 473 |
i.author = find_or_create_user(ticket.reporter) |
|
| 474 |
i.category = issues_category_map[ticket.component] unless ticket.component.blank? |
|
| 475 |
i.fixed_version = version_map[ticket.milestone] unless ticket.milestone.blank? |
|
| 476 |
i.status = STATUS_MAPPING[ticket.status] || DEFAULT_STATUS |
|
| 477 |
i.tracker = TRACKER_MAPPING[ticket.ticket_type] || DEFAULT_TRACKER |
|
| 478 |
i.id = ticket.id unless Issue.exists?(ticket.id) |
|
| 479 |
next unless Time.fake(ticket.changetime) { i.save }
|
|
| 480 |
TICKET_MAP[ticket.id] = i.id |
|
| 481 |
migrated_tickets += 1 |
|
| 482 |
|
|
| 483 |
# Owner |
|
| 484 |
unless ticket.owner.blank? |
|
| 485 |
i.assigned_to = find_or_create_user(ticket.owner, true) |
|
| 486 |
Time.fake(ticket.changetime) { i.save }
|
|
| 487 |
end |
|
| 488 |
|
|
| 489 |
# Comments and status/resolution changes |
|
| 490 |
ticket.ticket_changes.group_by(&:time).each do |time, changeset| |
|
| 491 |
status_change = changeset.select {|change| change.field == 'status'}.first
|
|
| 492 |
resolution_change = changeset.select {|change| change.field == 'resolution'}.first
|
|
| 493 |
comment_change = changeset.select {|change| change.field == 'comment'}.first
|
|
| 494 |
|
|
| 495 |
n = Journal.new :notes => (comment_change ? convert_wiki_text(encode(comment_change.newvalue)) : ''), |
|
| 496 |
:created_on => time |
|
| 497 |
n.user = find_or_create_user(changeset.first.author) |
|
| 498 |
n.journalized = i |
|
| 499 |
if status_change && |
|
| 500 |
STATUS_MAPPING[status_change.oldvalue] && |
|
| 501 |
STATUS_MAPPING[status_change.newvalue] && |
|
| 502 |
(STATUS_MAPPING[status_change.oldvalue] != STATUS_MAPPING[status_change.newvalue]) |
|
| 503 |
n.details << JournalDetail.new(:property => 'attr', |
|
| 504 |
:prop_key => 'status_id', |
|
| 505 |
:old_value => STATUS_MAPPING[status_change.oldvalue].id, |
|
| 506 |
:value => STATUS_MAPPING[status_change.newvalue].id) |
|
| 507 |
end |
|
| 508 |
if resolution_change |
|
| 509 |
n.details << JournalDetail.new(:property => 'cf', |
|
| 510 |
:prop_key => custom_field_map['resolution'].id, |
|
| 511 |
:old_value => resolution_change.oldvalue, |
|
| 512 |
:value => resolution_change.newvalue) |
|
| 513 |
end |
|
| 514 |
n.save unless n.details.empty? && n.notes.blank? |
|
| 515 |
end |
|
| 516 |
|
|
| 517 |
# Attachments |
|
| 518 |
ticket.attachments.each do |attachment| |
|
| 519 |
next unless attachment.exist? |
|
| 520 |
attachment.open {
|
|
| 521 |
a = Attachment.new :created_on => attachment.time |
|
| 522 |
a.file = attachment |
|
| 523 |
a.author = find_or_create_user(attachment.author) |
|
| 524 |
a.container = i |
|
| 525 |
a.description = attachment.description |
|
| 526 |
migrated_ticket_attachments += 1 if a.save |
|
| 527 |
} |
|
| 528 |
end |
|
| 529 |
|
|
| 530 |
# Custom fields |
|
| 531 |
custom_values = ticket.customs.inject({}) do |h, custom|
|
|
| 532 |
if custom_field = custom_field_map[custom.name] |
|
| 533 |
h[custom_field.id] = custom.value |
|
| 534 |
migrated_custom_values += 1 |
|
| 535 |
end |
|
| 536 |
h |
|
| 537 |
end |
|
| 538 |
if custom_field_map['resolution'] && !ticket.resolution.blank? |
|
| 539 |
custom_values[custom_field_map['resolution'].id] = ticket.resolution |
|
| 540 |
end |
|
| 541 |
i.custom_field_values = custom_values |
|
| 542 |
i.save_custom_field_values |
|
| 543 |
end |
|
| 544 |
|
|
| 545 |
# update issue id sequence if needed (postgresql) |
|
| 546 |
Issue.connection.reset_pk_sequence!(Issue.table_name) if Issue.connection.respond_to?('reset_pk_sequence!')
|
|
| 547 |
puts |
|
| 548 |
|
|
| 549 |
# Wiki |
|
| 550 |
print "Migrating wiki" |
|
| 551 |
if wiki.save |
|
| 552 |
TracWikiPage.find(:all, :order => 'name, version').each do |page| |
|
| 553 |
# Do not migrate Trac manual wiki pages |
|
| 554 |
next if TRAC_WIKI_PAGES.include?(page.name) |
|
| 555 |
wiki_edit_count += 1 |
|
| 556 |
print '.' |
|
| 557 |
STDOUT.flush |
|
| 558 |
p = wiki.find_or_new_page(page.name) |
|
| 559 |
p.content = WikiContent.new(:page => p) if p.new_record? |
|
| 560 |
p.content.text = page.text |
|
| 561 |
p.content.author = find_or_create_user(page.author) unless page.author.blank? || page.author == 'trac' |
|
| 562 |
p.content.comments = page.comment |
|
| 563 |
Time.fake(page.time) { p.new_record? ? p.save : p.content.save }
|
|
| 564 |
|
|
| 565 |
next if p.content.new_record? |
|
| 566 |
migrated_wiki_edits += 1 |
|
| 567 |
|
|
| 568 |
# Attachments |
|
| 569 |
page.attachments.each do |attachment| |
|
| 570 |
next unless attachment.exist? |
|
| 571 |
next if p.attachments.find_by_filename(attachment.filename.gsub(/^.*(\\|\/)/, '').gsub(/[^\w\.\-]/,'_')) #add only once per page |
|
| 572 |
attachment.open {
|
|
| 573 |
a = Attachment.new :created_on => attachment.time |
|
| 574 |
a.file = attachment |
|
| 575 |
a.author = find_or_create_user(attachment.author) |
|
| 576 |
a.description = attachment.description |
|
| 577 |
a.container = p |
|
| 578 |
migrated_wiki_attachments += 1 if a.save |
|
| 579 |
} |
|
| 580 |
end |
|
| 581 |
end |
|
| 582 |
|
|
| 583 |
wiki.reload |
|
| 584 |
wiki.pages.each do |page| |
|
| 585 |
page.content.text = convert_wiki_text(page.content.text) |
|
| 586 |
Time.fake(page.content.updated_on) { page.content.save }
|
|
| 587 |
end |
|
| 588 |
end |
|
| 589 |
puts |
|
| 590 |
|
|
| 591 |
puts |
|
| 592 |
puts "Components: #{migrated_components}/#{TracComponent.count}"
|
|
| 593 |
puts "Milestones: #{migrated_milestones}/#{TracMilestone.count}"
|
|
| 594 |
puts "Tickets: #{migrated_tickets}/#{TracTicket.count}"
|
|
| 595 |
puts "Ticket files: #{migrated_ticket_attachments}/" + TracAttachment.count(:conditions => {:type => 'ticket'}).to_s
|
|
| 596 |
puts "Custom values: #{migrated_custom_values}/#{TracTicketCustom.count}"
|
|
| 597 |
puts "Wiki edits: #{migrated_wiki_edits}/#{wiki_edit_count}"
|
|
| 598 |
puts "Wiki files: #{migrated_wiki_attachments}/" + TracAttachment.count(:conditions => {:type => 'wiki'}).to_s
|
|
| 599 |
end |
|
| 600 |
|
|
| 601 |
def self.limit_for(klass, attribute) |
|
| 602 |
klass.columns_hash[attribute.to_s].limit |
|
| 603 |
end |
|
| 604 |
|
|
| 605 |
def self.encoding(charset) |
|
| 606 |
@ic = Iconv.new('UTF-8', charset)
|
|
| 607 |
rescue Iconv::InvalidEncoding |
|
| 608 |
puts "Invalid encoding!" |
|
| 609 |
return false |
|
| 610 |
end |
|
| 611 |
|
|
| 612 |
def self.set_trac_directory(path) |
|
| 613 |
@@trac_directory = path |
|
| 614 |
raise "This directory doesn't exist!" unless File.directory?(path) |
|
| 615 |
raise "#{trac_attachments_directory} doesn't exist!" unless File.directory?(trac_attachments_directory)
|
|
| 616 |
@@trac_directory |
|
| 617 |
rescue Exception => e |
|
| 618 |
puts e |
|
| 619 |
return false |
|
| 620 |
end |
|
| 621 |
|
|
| 622 |
def self.trac_directory |
|
| 623 |
@@trac_directory |
|
| 624 |
end |
|
| 625 |
|
|
| 626 |
def self.set_trac_adapter(adapter) |
|
| 627 |
return false if adapter.blank? |
|
| 628 |
raise "Unknown adapter: #{adapter}!" unless %w(sqlite3 mysql postgresql).include?(adapter)
|
|
| 629 |
# If adapter is sqlite or sqlite3, make sure that trac.db exists |
|
| 630 |
raise "#{trac_db_path} doesn't exist!" if %w(sqlite3).include?(adapter) && !File.exist?(trac_db_path)
|
|
| 631 |
@@trac_adapter = adapter |
|
| 632 |
rescue Exception => e |
|
| 633 |
puts e |
|
| 634 |
return false |
|
| 635 |
end |
|
| 636 |
|
|
| 637 |
def self.set_trac_db_host(host) |
|
| 638 |
return nil if host.blank? |
|
| 639 |
@@trac_db_host = host |
|
| 640 |
end |
|
| 641 |
|
|
| 642 |
def self.set_trac_db_port(port) |
|
| 643 |
return nil if port.to_i == 0 |
|
| 644 |
@@trac_db_port = port.to_i |
|
| 645 |
end |
|
| 646 |
|
|
| 647 |
def self.set_trac_db_name(name) |
|
| 648 |
return nil if name.blank? |
|
| 649 |
@@trac_db_name = name |
|
| 650 |
end |
|
| 651 |
|
|
| 652 |
def self.set_trac_db_username(username) |
|
| 653 |
@@trac_db_username = username |
|
| 654 |
end |
|
| 655 |
|
|
| 656 |
def self.set_trac_db_password(password) |
|
| 657 |
@@trac_db_password = password |
|
| 658 |
end |
|
| 659 |
|
|
| 660 |
def self.set_trac_db_schema(schema) |
|
| 661 |
@@trac_db_schema = schema |
|
| 662 |
end |
|
| 663 |
|
|
| 664 |
mattr_reader :trac_directory, :trac_adapter, :trac_db_host, :trac_db_port, :trac_db_name, :trac_db_schema, :trac_db_username, :trac_db_password |
|
| 665 |
|
|
| 666 |
def self.trac_db_path; "#{trac_directory}/db/trac.db" end
|
|
| 667 |
def self.trac_attachments_directory; "#{trac_directory}/attachments" end
|
|
| 668 |
|
|
| 669 |
def self.target_project_identifier(identifier) |
|
| 670 |
project = Project.find_by_identifier(identifier) |
|
| 671 |
if !project |
|
| 672 |
# create the target project |
|
| 673 |
project = Project.new :name => identifier.humanize, |
|
| 674 |
:description => '' |
|
| 675 |
project.identifier = identifier |
|
| 676 |
puts "Unable to create a project with identifier '#{identifier}'!" unless project.save
|
|
| 677 |
# enable issues and wiki for the created project |
|
| 678 |
project.enabled_module_names = ['issue_tracking', 'wiki'] |
|
| 679 |
else |
|
| 680 |
puts |
|
| 681 |
puts "This project already exists in your Redmine database." |
|
| 682 |
print "Are you sure you want to append data to this project ? [Y/n] " |
|
| 683 |
STDOUT.flush |
|
| 684 |
exit if STDIN.gets.match(/^n$/i) |
|
| 685 |
end |
|
| 686 |
project.trackers << TRACKER_BUG unless project.trackers.include?(TRACKER_BUG) |
|
| 687 |
project.trackers << TRACKER_FEATURE unless project.trackers.include?(TRACKER_FEATURE) |
|
| 688 |
@target_project = project.new_record? ? nil : project |
|
| 689 |
@target_project.reload |
|
| 690 |
end |
|
| 691 |
|
|
| 692 |
def self.connection_params |
|
| 693 |
if trac_adapter == 'sqlite3' |
|
| 694 |
{:adapter => 'sqlite3',
|
|
| 695 |
:database => trac_db_path} |
|
| 696 |
else |
|
| 697 |
{:adapter => trac_adapter,
|
|
| 698 |
:database => trac_db_name, |
|
| 699 |
:host => trac_db_host, |
|
| 700 |
:port => trac_db_port, |
|
| 701 |
:username => trac_db_username, |
|
| 702 |
:password => trac_db_password, |
|
| 703 |
:schema_search_path => trac_db_schema |
|
| 704 |
} |
|
| 705 |
end |
|
| 706 |
end |
|
| 707 |
|
|
| 708 |
def self.establish_connection |
|
| 709 |
constants.each do |const| |
|
| 710 |
klass = const_get(const) |
|
| 711 |
next unless klass.respond_to? 'establish_connection' |
|
| 712 |
klass.establish_connection connection_params |
|
| 713 |
end |
|
| 714 |
end |
|
| 715 |
|
|
| 716 |
private |
|
| 717 |
def self.encode(text) |
|
| 718 |
@ic.iconv text |
|
| 719 |
rescue |
|
| 720 |
text |
|
| 721 |
end |
|
| 722 |
end |
|
| 723 |
|
|
| 724 |
puts |
|
| 725 |
if Redmine::DefaultData::Loader.no_data? |
|
| 726 |
puts "Redmine configuration need to be loaded before importing data." |
|
| 727 |
puts "Please, run this first:" |
|
| 728 |
puts |
|
| 729 |
puts " rake redmine:load_default_data RAILS_ENV=\"#{ENV['RAILS_ENV']}\""
|
|
| 730 |
exit |
|
| 731 |
end |
|
| 732 |
|
|
| 733 |
puts "WARNING: a new project will be added to Redmine during this process." |
|
| 734 |
print "Are you sure you want to continue ? [y/N] " |
|
| 735 |
STDOUT.flush |
|
| 736 |
break unless STDIN.gets.match(/^y$/i) |
|
| 737 |
puts |
|
| 738 |
|
|
| 739 |
def prompt(text, options = {}, &block)
|
|
| 740 |
default = options[:default] || '' |
|
| 741 |
while true |
|
| 742 |
print "#{text} [#{default}]: "
|
|
| 743 |
STDOUT.flush |
|
| 744 |
value = STDIN.gets.chomp! |
|
| 745 |
value = default if value.blank? |
|
| 746 |
break if yield value |
|
| 747 |
end |
|
| 748 |
end |
|
| 749 |
|
|
| 750 |
DEFAULT_PORTS = {'mysql' => 3306, 'postgresql' => 5432}
|
|
| 751 |
|
|
| 752 |
prompt('Trac directory') {|directory| TracMigrate.set_trac_directory directory.strip}
|
|
| 753 |
prompt('Trac database adapter (sqlite3, mysql2, postgresql)', :default => 'sqlite3') {|adapter| TracMigrate.set_trac_adapter adapter}
|
|
| 754 |
unless %w(sqlite3).include?(TracMigrate.trac_adapter) |
|
| 755 |
prompt('Trac database host', :default => 'localhost') {|host| TracMigrate.set_trac_db_host host}
|
|
| 756 |
prompt('Trac database port', :default => DEFAULT_PORTS[TracMigrate.trac_adapter]) {|port| TracMigrate.set_trac_db_port port}
|
|
| 757 |
prompt('Trac database name') {|name| TracMigrate.set_trac_db_name name}
|
|
| 758 |
prompt('Trac database schema', :default => 'public') {|schema| TracMigrate.set_trac_db_schema schema}
|
|
| 759 |
prompt('Trac database username') {|username| TracMigrate.set_trac_db_username username}
|
|
| 760 |
prompt('Trac database password') {|password| TracMigrate.set_trac_db_password password}
|
|
| 761 |
end |
|
| 762 |
prompt('Trac database encoding', :default => 'UTF-8') {|encoding| TracMigrate.encoding encoding}
|
|
| 763 |
prompt('Target project identifier') {|identifier| TracMigrate.target_project_identifier identifier}
|
|
| 764 |
puts |
|
| 765 |
|
|
| 766 |
# Turn off email notifications |
|
| 767 |
Setting.notified_events = [] |
|
| 768 |
|
|
| 769 |
TracMigrate.migrate |
|
| 770 |
end |
|
| 771 |
end |
|
| 772 |
|
|
| .svn/pristine/bc/bcf601e4c2eab267682f5366299899e20f6d08d2.svn-base | ||
|---|---|---|
| 1 |
OpenIdAuthentication |
|
| 2 |
==================== |
|
| 3 |
|
|
| 4 |
Provides a thin wrapper around the excellent ruby-openid gem from JanRan. Be sure to install that first: |
|
| 5 |
|
|
| 6 |
gem install ruby-openid |
|
| 7 |
|
|
| 8 |
To understand what OpenID is about and how it works, it helps to read the documentation for lib/openid/consumer.rb |
|
| 9 |
from that gem. |
|
| 10 |
|
|
| 11 |
The specification used is http://openid.net/specs/openid-authentication-2_0.html. |
|
| 12 |
|
|
| 13 |
|
|
| 14 |
Prerequisites |
|
| 15 |
============= |
|
| 16 |
|
|
| 17 |
OpenID authentication uses the session, so be sure that you haven't turned that off. |
|
| 18 |
|
|
| 19 |
Alternatively, you can use the file-based store, which just relies on on tmp/openids being present in RAILS_ROOT. But be aware that this store only works if you have a single application server. And it's not safe to use across NFS. It's recommended that you use the database store if at all possible. To use the file-based store, you'll also have to add this line to your config/environment.rb: |
|
| 20 |
|
|
| 21 |
OpenIdAuthentication.store = :file |
|
| 22 |
|
|
| 23 |
This particular plugin also relies on the fact that the authentication action allows for both POST and GET operations. |
|
| 24 |
If you're using RESTful authentication, you'll need to explicitly allow for this in your routes.rb. |
|
| 25 |
|
|
| 26 |
The plugin also expects to find a root_url method that points to the home page of your site. You can accomplish this by using a root route in config/routes.rb: |
|
| 27 |
|
|
| 28 |
map.root :controller => 'articles' |
|
| 29 |
|
|
| 30 |
This plugin relies on Rails Edge revision 6317 or newer. |
|
| 31 |
|
|
| 32 |
|
|
| 33 |
Example |
|
| 34 |
======= |
|
| 35 |
|
|
| 36 |
This example is just to meant to demonstrate how you could use OpenID authentication. You might well want to add |
|
| 37 |
salted hash logins instead of plain text passwords and other requirements on top of this. Treat it as a starting point, |
|
| 38 |
not a destination. |
|
| 39 |
|
|
| 40 |
Note that the User model referenced in the simple example below has an 'identity_url' attribute. You will want to add the same or similar field to whatever |
|
| 41 |
model you are using for authentication. |
|
| 42 |
|
|
| 43 |
Also of note is the following code block used in the example below: |
|
| 44 |
|
|
| 45 |
authenticate_with_open_id do |result, identity_url| |
|
| 46 |
... |
|
| 47 |
end |
|
| 48 |
|
|
| 49 |
In the above code block, 'identity_url' will need to match user.identity_url exactly. 'identity_url' will be a string in the form of 'http://example.com' - |
|
| 50 |
If you are storing just 'example.com' with your user, the lookup will fail. |
|
| 51 |
|
|
| 52 |
There is a handy method in this plugin called 'normalize_url' that will help with validating OpenID URLs. |
|
| 53 |
|
|
| 54 |
OpenIdAuthentication.normalize_url(user.identity_url) |
|
| 55 |
|
|
| 56 |
The above will return a standardized version of the OpenID URL - the above called with 'example.com' will return 'http://example.com/' |
|
| 57 |
It will also raise an InvalidOpenId exception if the URL is determined to not be valid. |
|
| 58 |
Use the above code in your User model and validate OpenID URLs before saving them. |
|
| 59 |
|
|
| 60 |
config/routes.rb |
|
| 61 |
|
|
Also available in: Unified diff