Compare commits

...

31 Commits
2.1.5 ... 1.4.0

Author SHA1 Message Date
Jean-Philippe Lang
7578e485a5 tagged version 1.4.0
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/tags/1.4.0@9416 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 08:20:21 +00:00
Jean-Philippe Lang
a35b81b9fa Merged r9412 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9413 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 08:08:22 +00:00
Jean-Philippe Lang
fd450fd2da Merged r9404, r9405 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9411 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:54:02 +00:00
Jean-Philippe Lang
2c0ba78f70 Merged r9409 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9410 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:53:10 +00:00
Jean-Philippe Lang
f0f01d370e Merged r9406 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9408 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 06:32:18 +00:00
Jean-Philippe Lang
4c330a1241 Merged r9391 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9403 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:58:46 +00:00
Jean-Philippe Lang
baa4ebd05f Merged r9389 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9402 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:57:13 +00:00
Jean-Philippe Lang
59f14478ed Merged r9387 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9401 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:53:47 +00:00
Jean-Philippe Lang
8fefb7c05b Merged r9390 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9400 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:52:49 +00:00
Jean-Philippe Lang
1feb373c89 Merged r9378 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9399 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:51:49 +00:00
Jean-Philippe Lang
32fd503cbb Merged r9381 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9398 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:50:58 +00:00
Jean-Philippe Lang
cf31aeaf81 Merged r9380 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9397 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:45:44 +00:00
Jean-Philippe Lang
83ea66fd2c Merged r9379 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9395 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-14 04:43:41 +00:00
Jean-Philippe Lang
ef2c5cab2d Merged r9392 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9393 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-13 19:41:05 +00:00
Etienne Massip
dee6f6b138 Merged r9374 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9388 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 18:31:32 +00:00
Jean-Philippe Lang
a4c0c18e3d Merged r9384, r9385 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9386 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 17:27:44 +00:00
Jean-Philippe Lang
4c82fbb6f8 Merged r9382 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9383 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-11 17:21:35 +00:00
Jean-Philippe Lang
68ded50edc Merged r9372 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9377 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:28:04 +00:00
Jean-Philippe Lang
72ecb80dc7 Merged r9358 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9376 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:27:07 +00:00
Jean-Philippe Lang
86ee285eb4 Merged r9367 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9375 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-09 06:22:31 +00:00
Jean-Philippe Lang
c229ea6386 Merged r9371 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9373 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-08 21:01:20 +00:00
Jean-Philippe Lang
687fca170e Merged r9359 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9360 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 13:51:55 +00:00
Toshi MARUYAMA
26564b06f7 Merged r9355 from trunk
remove 1.3-stable merged issues from CHANGELOG 1.4.0 list

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9356 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 13:01:12 +00:00
Jean-Philippe Lang
34016c38bd Merged r9349 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9354 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 12:39:51 +00:00
Jean-Philippe Lang
15ff361894 Merged r9350 and r9351 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9352 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 12:23:07 +00:00
Jean-Philippe Lang
b45b5f4322 Merged r9346 and r9347 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9348 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 11:17:31 +00:00
Toshi MARUYAMA
8addbc537a Merged r9343 from trunk
Simplified Chinese translation updated by fangzheng (#10611)

git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9344 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:45:24 +00:00
Jean-Philippe Lang
87eeacba80 Merged r9341 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9342 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:27:22 +00:00
Jean-Philippe Lang
a7250c41e2 Merged r9339 from trunk.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9340 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 10:25:03 +00:00
Jean-Philippe Lang
7c45396d92 Set version to stable.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9338 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 09:45:43 +00:00
Jean-Philippe Lang
c1f98c835c Adds 1.4-stable branch.
git-svn-id: svn+ssh://rubyforge.org/var/svn/redmine/branches/1.4-stable@9337 e93f8b46-1217-0410-a6f0-8f06a7374b81
2012-04-06 09:41:15 +00:00
39 changed files with 463 additions and 129 deletions

23
Gemfile
View File

@@ -2,7 +2,7 @@ source :rubygems
gem "rails", "2.3.14"
gem "i18n", "~> 0.4.2"
gem "coderay", "~> 1.0.0"
gem "coderay", "~> 1.0.6"
gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby]
gem "tzinfo", "~> 0.3.31"
@@ -16,18 +16,20 @@ group :openid do
gem "ruby-openid", "~> 2.1.4", :require => "openid"
end
# Optional gem for exporting the gantt to a PNG file
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
# Optional gem for exporting the gantt to a PNG file, not supported with jruby
platforms :mri, :mingw do
group :rmagick do
# RMagick 2 supports ruby 1.9
# RMagick 1 would be fine for ruby 1.8 but Bundler does not support
# different requirements for the same gem on different platforms
gem "rmagick", ">= 2.0.0"
end
end
# Database gems
platforms :mri, :mingw do
group :postgresql do
gem "pg", "~> 0.9.0"
gem "pg", ">= 0.11.0"
end
group :sqlite do
@@ -73,6 +75,11 @@ group :test do
gem "mocha"
end
if File.exists?('Gemfile.local')
puts "Loading Gemfile.local ..." if $DEBUG # `ruby -d` or `bundle -v`
instance_eval File.read('Gemfile.local')
end
# Load plugins' Gemfiles
Dir.glob File.expand_path("../vendor/plugins/*/Gemfile", __FILE__) do |file|
puts "Loading #{file} ..." if $DEBUG # `ruby -d` or `bundle -v`

View File

@@ -75,9 +75,7 @@ class AdminController < ApplicationController
def info
@db_adapter_name = ActiveRecord::Base.connection.adapter_name
@checklist = [
[:text_default_administrator_account_changed,
User.find(:first,
:conditions => ["login=? and hashed_password=?", 'admin', User.hash_password('admin')]).nil?],
[:text_default_administrator_account_changed, User.default_admin_account_changed?],
[:text_file_repository_writable, File.writable?(Attachment.storage_path)],
[:text_plugin_assets_writable, File.writable?(Redmine::Plugin.public_directory)],
[:text_rmagick_available, Object.const_defined?(:Magick)]

View File

@@ -364,18 +364,6 @@ class ApplicationController < ActionController::Base
:content_type => 'application/atom+xml'
end
# TODO: remove in Redmine 1.4
def self.accept_key_auth(*actions)
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
accept_rss_auth(*actions)
end
# TODO: remove in Redmine 1.4
def accept_key_auth_actions
ActiveSupport::Deprecation.warn "ApplicationController.accept_key_auth_actions is deprecated and will be removed in Redmine 1.4. Use accept_rss_auth (or accept_api_auth) instead."
self.class.accept_rss_auth
end
def self.accept_rss_auth(*actions)
if actions.any?
write_inheritable_attribute('accept_rss_auth_actions', actions)

View File

@@ -47,7 +47,7 @@ class BoardsController < ApplicationController
:include => [:author, {:last_reply => :author}],
:limit => @topic_pages.items_per_page,
:offset => @topic_pages.current.offset
@message = Message.new
@message = Message.new(:board => @board)
render :action => 'show', :layout => !request.xhr?
}
format.atom {

View File

@@ -42,7 +42,7 @@ class IssueRelationsController < ApplicationController
def create
@relation = IssueRelation.new(params[:relation])
@relation.issue_from = @issue
if params[:relation] && m = params[:relation][:issue_to_id].to_s.match(/^#?(\d+)$/)
if params[:relation] && m = params[:relation][:issue_to_id].to_s.strip.match(/^#?(\d+)$/)
@relation.issue_to = Issue.visible.find_by_id(m[1].to_i)
end
saved = @relation.save

View File

@@ -225,12 +225,19 @@ class IssuesController < ApplicationController
end
target_projects ||= @projects
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
if @copy
@available_statuses = [IssueStatus.default]
else
@available_statuses = @issues.map(&:new_statuses_allowed_to).reduce(:&)
end
@custom_fields = target_projects.map{|p|p.all_issue_custom_fields}.reduce(:&)
@assignables = target_projects.map(&:assignable_users).reduce(:&)
@trackers = target_projects.map(&:trackers).reduce(:&)
@versions = target_projects.map {|p| p.shared_versions.open}.reduce(:&)
@categories = target_projects.map {|p| p.issue_categories}.reduce(:&)
if @copy
@attachments_present = @issues.detect {|i| i.attachments.any?}.present?
end
@safe_attributes = @issues.map(&:safe_attribute_names).reduce(:&)
render :layout => false if request.xhr?
@@ -246,7 +253,7 @@ class IssuesController < ApplicationController
@issues.each do |issue|
issue.reload
if @copy
issue = issue.copy
issue = issue.copy({}, :attachments => params[:copy_attachments].present?)
end
journal = issue.init_journal(User.current, params[:notes])
issue.safe_attributes = attributes
@@ -348,8 +355,6 @@ private
# from the params
# TODO: Refactor, not everything in here is needed by #edit
def update_issue_from_params
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
@priorities = IssuePriority.active
@edit_allowed = User.current.allowed_to?(:edit_issues, @project)
@time_entry = TimeEntry.new(:issue => @issue, :project => @issue.project)
@time_entry.attributes = params[:time_entry]
@@ -371,6 +376,8 @@ private
end
end
@issue.safe_attributes = issue_attributes
@priorities = IssuePriority.active
@allowed_statuses = @issue.new_statuses_allowed_to(User.current)
true
end
@@ -420,7 +427,16 @@ private
def parse_params_for_bulk_issue_attributes(params)
attributes = (params[:issue] || {}).reject {|k,v| v.blank?}
attributes.keys.each {|k| attributes[k] = '' if attributes[k] == 'none'}
attributes[:custom_field_values].reject! {|k,v| v.blank?} if attributes[:custom_field_values]
if custom = attributes[:custom_field_values]
custom.reject! {|k,v| v.blank?}
custom.keys.each do |k|
if custom[k].is_a?(Array)
custom[k] << '' if custom[k].delete('__none__')
else
custom[k] = '' if custom[k] == '__none__'
end
end
end
attributes
end
end

View File

@@ -100,6 +100,7 @@ module CustomFieldsHelper
when "list"
options = []
options << [l(:label_no_change_option), ''] unless custom_field.multiple?
options << [l(:label_none), '__none__'] unless custom_field.is_required?
options += custom_field.possible_values_options(projects)
select_tag(field_name, options_for_select(options),
:id => field_id, :multiple => custom_field.multiple?)

View File

@@ -332,7 +332,7 @@ module IssuesHelper
cv = issue.custom_field_values.detect {|v| v.custom_field_id == column.custom_field.id}
show_value(cv)
else
value = issue.send(column.name)
value = column.value(issue)
if value.is_a?(Date)
format_date(value)
elsif value.is_a?(Time)

View File

@@ -145,8 +145,8 @@ class Issue < ActiveRecord::Base
end
# Returns an unsaved copy of the issue
def copy(attributes=nil)
copy = self.class.new.copy_from(self)
def copy(attributes=nil, copy_options={})
copy = self.class.new.copy_from(self, copy_options)
copy.attributes = attributes if attributes
copy
end
@@ -509,18 +509,30 @@ class Issue < ActiveRecord::Base
!relations_to.detect {|ir| ir.relation_type == 'blocks' && !ir.issue_from.closed?}.nil?
end
# Returns an array of status that user is able to apply
# Returns an array of statuses that user is able to apply
def new_statuses_allowed_to(user=User.current, include_default=false)
statuses = status.find_new_statuses_allowed_to(
user.admin ? Role.all : user.roles_for_project(project),
tracker,
author == user,
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
)
statuses << status unless statuses.empty?
statuses << IssueStatus.default if include_default
statuses = statuses.uniq.sort
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
if new_record? && @copied_from
[IssueStatus.default, @copied_from.status].compact.uniq.sort
else
initial_status = nil
if new_record?
initial_status = IssueStatus.default
elsif status_id_was
initial_status = IssueStatus.find_by_id(status_id_was)
end
initial_status ||= status
statuses = initial_status.find_new_statuses_allowed_to(
user.admin ? Role.all : user.roles_for_project(project),
tracker,
author == user,
assigned_to_id_changed? ? assigned_to_id_was == user.id : assigned_to_id == user.id
)
statuses << initial_status unless statuses.empty?
statuses << IssueStatus.default if include_default
statuses = statuses.compact.uniq.sort
blocked? ? statuses.reject {|s| s.is_closed?} : statuses
end
end
def assigned_to_was

View File

@@ -42,6 +42,12 @@ class MailHandler < ActionMailer::Base
super email
end
cattr_accessor :ignored_emails_headers
@@ignored_emails_headers = {
'X-Auto-Response-Suppress' => 'OOF',
'Auto-Submitted' => 'auto-replied'
}
# Processes incoming emails
# Returns the created object (eg. an issue, a message) or false
def receive(email)
@@ -54,6 +60,16 @@ class MailHandler < ActionMailer::Base
end
return false
end
# Ignore auto generated emails
self.class.ignored_emails_headers.each do |key, ignored_value|
value = email.header_string(key)
if value && value.to_s.downcase == ignored_value.downcase
if logger && logger.info
logger.info "MailHandler: ignoring email with #{key}:#{value} header"
end
return false
end
end
@user = User.find_by_mail(sender_email) if sender_email.present?
if @user && !@user.active?
if logger && logger.info

View File

@@ -359,7 +359,7 @@ class Mailer < ActionMailer::Base
issues_by_assignee = scope.all(:include => [:status, :assigned_to, :project, :tracker]).group_by(&:assigned_to)
issues_by_assignee.each do |assignee, issues|
deliver_reminder(assignee, issues, days) if assignee && assignee.active?
deliver_reminder(assignee, issues, days) if assignee.is_a?(User) && assignee.active?
end
end
@@ -372,6 +372,17 @@ class Mailer < ActionMailer::Base
ActionMailer::Base.perform_deliveries = was_enabled
end
# Sends emails synchronously in the given block
def self.with_synched_deliveries(&block)
saved_method = ActionMailer::Base.delivery_method
if m = saved_method.to_s.match(%r{^async_(.+)$})
ActionMailer::Base.delivery_method = m[1].to_sym
end
yield
ensure
ActionMailer::Base.delivery_method = saved_method
end
private
def initialize_defaults(method_name)
super

View File

@@ -37,7 +37,6 @@ class Message < ActiveRecord::Base
:author_key => :author_id
acts_as_watchable
attr_protected :locked, :sticky
validates_presence_of :board, :subject, :content
validates_length_of :subject, :maximum => 255
validate :cannot_reply_to_locked_topic, :on => :create
@@ -50,7 +49,7 @@ class Message < ActiveRecord::Base
:conditions => Project.allowed_to_condition(args.shift || User.current, :view_messages, *args) } }
safe_attributes 'subject', 'content'
safe_attributes 'locked', 'sticky',
safe_attributes 'locked', 'sticky', 'board_id',
:if => lambda {|message, user|
user.allowed_to?(:edit_messages, message.project)
}

View File

@@ -348,6 +348,11 @@ class User < Principal
find(:first, :conditions => ["LOWER(mail) = ?", mail.to_s.downcase])
end
# Returns true if the default admin account can no longer be used
def self.default_admin_account_changed?
!User.active.find_by_login("admin").try(:check_password?, "admin")
end
def to_s
name
end

View File

@@ -60,6 +60,13 @@
<p><label><%= h(custom_field.name) %></label> <%= custom_field_tag_for_bulk_edit('issue', custom_field, @projects) %></p>
<% end %>
<% if @copy && @attachments_present %>
<p>
<label for='copy_attachments'><%= l(:label_copy_attachments) %></label>
<%= check_box_tag 'copy_attachments', '1', true %>
</p>
<% end %>
<%= call_hook(:view_issues_bulk_edit_details_bottom, { :issues => @issues }) %>
</div>

View File

@@ -5,6 +5,7 @@
<title><%=h html_title %></title>
<meta name="description" content="<%= Redmine::Info.app_name %>" />
<meta name="keywords" content="issue,bug,tracker" />
<meta http-equiv="X-UA-Compatible" content="IE=9; IE=8; IE=7; IE=EDGE" />
<%= csrf_meta_tag %>
<%= favicon %>
<%= stylesheet_link_tag 'application', :media => 'all' %>

View File

@@ -6,13 +6,17 @@
<p><label for="message_subject"><%= l(:field_subject) %></label><br />
<%= f.text_field :subject, :size => 120, :id => "message_subject" %>
<% if !replying && User.current.allowed_to?(:edit_messages, @project) %>
<% unless replying %>
<% if @message.safe_attribute? 'sticky' %>
<label><%= f.check_box :sticky %><%= l(:label_board_sticky) %></label>
<% end %>
<% if @message.safe_attribute? 'locked' %>
<label><%= f.check_box :locked %><%= l(:label_board_locked) %></label>
<% end %>
<% end %>
</p>
<% if !replying && !@message.new_record? && User.current.allowed_to?(:edit_messages, @project) %>
<% if !replying && !@message.new_record? && @message.safe_attribute?('board_id') %>
<p><label><%= l(:label_board) %></label><br />
<%= f.select :board_id, @project.boards.collect {|b| [b.name, b.id]} %></p>
<% end %>

View File

@@ -1,4 +1,6 @@
# MySQL (default setup).
# Default setup is given for MySQL with ruby1.8. If you're running Redmine
# with MySQL and ruby1.9, replace the adapter name with `mysql2`.
# Examples for PostgreSQL and SQLite3 can be found at the end.
production:
adapter: mysql

View File

@@ -1039,4 +1039,4 @@ fr:
label_child_revision: Enfant
error_scm_annotate_big_text_file: Cette entrée ne peut pas être annotée car elle excède la taille maximale.
setting_repositories_encodings: Encodages des fichiers et des dépôts
label_search_for_watchers: Search for watchers to add
label_search_for_watchers: Rechercher des observateurs

View File

@@ -1001,12 +1001,12 @@ zh:
label_child_revision: 子修订
error_scm_annotate_big_text_file: 输入文本内容超长,无法输入。
setting_default_issue_start_date_to_creation_date: 使用当前日期作为新问题的开始日期
button_edit_section: Edit this section
setting_repositories_encodings: Attachments and repositories encodings
description_all_columns: All Columns
button_export: Export
label_export_options: "%{export_format} export options"
error_attachment_too_big: This file cannot be uploaded because it exceeds the maximum allowed file size (%{max_size})
button_edit_section: 编辑此区域
setting_repositories_encodings: 附件和版本库编码
description_all_columns: 所有列
button_export: 导出
label_export_options: "%{export_format} 导出选项"
error_attachment_too_big: 该文件无法上传。超过文件大小限制 (%{max_size})
notice_failed_to_save_time_entries: "Failed to save %{count} time entrie(s) on %{total} selected: %{ids}."
label_x_issues:
zero: 0 问题

View File

@@ -339,6 +339,7 @@ ActionController::Routing::Routes.draw do |map|
map.resources :roles, :except => :show, :collection => {:permissions => [:get, :post]}
map.resources :enumerations, :except => :show
map.connect 'projects/:id/search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
map.connect 'search', :controller => 'search', :action => 'index', :conditions => {:method => :get}
map.connect 'mail_handler', :controller => 'mail_handler',

View File

@@ -4,13 +4,15 @@ Redmine - project management software
Copyright (C) 2006-2012 Jean-Philippe Lang
http://www.redmine.org/
== TBD v1.4.0
== 2012-04-14 v1.4.0
* Defect #2719: Increase username length limit from 30 to 60
* Defect #3087: Revision referring to issues across all projects
* Defect #4824: Unable to connect (can't convert Net::LDAP::LdapError into String)
* Defect #5058: reminder mails are not sent when delivery_method is :async_smtp
* Defect #6859: Moving issues to a tracker with different custom fields should let fill these fields
* Defect #7398: Error when trying to quick create a version with required custom field
* Defect #7495: Python multiline comments highlighting problem in Repository browser
* Defect #7826: bigdecimal-segfault-fix.rb must be removed for Oracle
* Defect #7920: Attempted to update a stale object when copying a project
* Defect #8857: Git: Too long in fetching repositories after upgrade from 1.1 or new branch at first time
@@ -20,6 +22,7 @@ http://www.redmine.org/
* Defect #9978: Japanese "permission_add_issue_watchers" is wrong
* Defect #10006: Email reminders are sent for closed issues
* Defect #10150: CSV export and spent time: rounding issue
* Defect #10168: CSV export breaks custom columns
* Defect #10181: Issue context menu and bulk edit form show irrelevant statuses
* Defect #10198: message_id regex in pop3.rb only recognizes Message-ID header (not Message-Id)
* Defect #10251: Description diff link in note details is relative when received by email
@@ -29,14 +32,12 @@ http://www.redmine.org/
* Defect #10410: [Localization] Grammar issue of Simplified Chinese in zh.yml
* Defect #10442: Ruby 1.9.3 Time Zone setting Internal error.
* Defect #10467: Confusing behavior while moving issue to a project with disabled Issues module
* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
* Defect #10554: Defect symbols when exporting tasks in pdf
* Defect #10575: Uploading of attachments which filename contains non-ASCII chars fails with Ruby 1.9
* Defect #10590: WikiContent::Version#text return string with #<Encoding:ASCII-8BIT> when uncompressed
* Defect #10591: Dutch "label_file_added" translation is wrong
* Defect #10593: Error: 'incompatible character encodings: UTF-8 and ASCII-8BIT' (old annoing issue) on ruby-1.9.3
* Defect #10600: Watchers search generates an Internal error
* Defect #10602: Attachment link has get parameter :class
* Defect #10605: Bulk edit selected issues does not allow selection of blank values for custom fields
* Defect #10619: When changing status before tracker, it shows improper status
* Feature #779: Multiple SCM per project
* Feature #971: Add "Spent time" column to query
* Feature #1060: Add a LDAP-filter using external auth sources
@@ -63,6 +64,7 @@ http://www.redmine.org/
* Feature #6296: Bulk-edit custom fields through context menu
* Feature #6386: Issue mail should render the HTML version of the issue details
* Feature #6449: Edit a wiki page's parent on the edit page
* Feature #6555: Double-click on "Submit" and "Save" buttons should not send two requests to server
* Feature #7361: Highlight active query in the side bar
* Feature #7420: Rest API for projects members
* Feature #7603: Please make editing issues more obvious than "Change properties (More)"
@@ -74,20 +76,32 @@ http://www.redmine.org/
* Feature #9995: Time entries insertion, "Create and continue" button
* Feature #10020: Enable global time logging at /time_entries/new
* Feature #10042: Bulk change private flag
* Feature #10046: Update installation doc on release
* Feature #10126: Add members of subprojects in the assignee and author filters
* Feature #10131: Include custom fiels in time entries API responses
* Feature #10207: Git: use default branch from HEAD
* Feature #10208: Estonian translation
* Feature #10253: Better handling of attachments when validation fails
* Feature #10350: Bulk copy should allow for changing the target version
* Feature #10607: Ignore out-of-office incoming emails
* Feature #10635: Adding time like "123 Min" is invalid
* Patch #9998: Make attachement "Optional Description" less wide
* Patch #10066: i18n not working with russian gem
* Patch #10128: Disable IE 8 compatibility mode to fix wrong div.autoscroll scroll bar behaviour
* Patch #10155: Russian translation changed
* Patch #10464: Enhanced PDF output for Issues list
* Patch #10470: Efficiently process new git revisions in a single batch
* Patch #10513: Dutch translation improvement
== 2012-04-14 v1.3.3
* Defect #10505: Error when exporting to PDF with NoMethodError (undefined method `downcase' for nil:NilClass)
* Defect #10554: Defect symbols when exporting tasks in pdf
* Defect #10564: Unable to change locked, sticky flags and board when editing a message
* Defect #10591: Dutch "label_file_added" translation is wrong
* Defect #10622: "Default administrator account changed" is always true
* Patch #10555: rake redmine:send_reminders aborted if issue assigned to group
* Patch #10611: Simplified Chinese translations for 1.3-stable
== 2012-03-11 v1.3.2
* Defect #8194: {{toc}} uses identical anchors for subsections with the same name

View File

@@ -31,6 +31,10 @@ Optional:
of the rmagick gem using:
bundle install --without development test rmagick
If you need to load some gems that are not required by Redmine core (eg. fcgi),
you can create a file named Gemfile.local at the root of your redmine directory.
It will be loaded automatically when running `bundle install`.
3. Create an empty utf8 encoded database: "redmine" for example
4. Configure the database parameters in config/database.yml

View File

@@ -30,7 +30,7 @@ module Redmine #:nodoc:
# 2:30 => 2.5
s.gsub!(%r{^(\d+):(\d+)$}) { $1.to_i + $2.to_i / 60.0 }
# 2h30, 2h, 30m => 2.5, 2, 0.5
s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
s.gsub!(%r{^((\d+)\s*(h|hours?))?\s*((\d+)\s*(m|min)?)?$}i) { |m| ($1 || $4) ? ($2.to_i + $5.to_i / 60.0) : m[0] }
end
# 2,5 => 2.5
s.gsub!(',', '.')

View File

@@ -40,7 +40,7 @@ module Redmine
# Should not return line numbers nor outer pre tag
def highlight_by_filename(text, filename)
language = ::CodeRay::FileType[filename]
language ? ::CodeRay.scan(text, language).html : ERB::Util.h(text)
language ? ::CodeRay.scan(text, language).html(:break_lines => true) : ERB::Util.h(text)
end
# Highlights +text+ using +language+ syntax

View File

@@ -10,7 +10,7 @@ module Redmine
# * official release: nil
# * stable branch: stable
# * trunk: devel
BRANCH = 'devel'
BRANCH = 'stable'
def self.revision
revision = nil

View File

@@ -23,7 +23,7 @@ module Redmine
end
def link_to(name, options={})
url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {})
url = { :format => name.to_s.downcase }.merge(options.delete(:url) || {}).except('page')
caption = options.delete(:caption) || name
html_options = { :class => name.to_s.downcase, :rel => 'nofollow' }.merge(options)
@view.content_tag('span', @view.link_to(caption, url, html_options))

View File

@@ -48,8 +48,8 @@ namespace :ci do
test_conf = { 'adapter' => (RUBY_VERSION >= '1.9' ? 'mysql2' : 'mysql'), 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins', 'encoding' => 'utf8' }
when 'postgresql'
raise "Error creating databases" unless
system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';|) &&
system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';|)
system(%|psql -U jenkins -d postgres -c "create database #{dev_db_name} owner jenkins encoding 'UTF8';"|) &&
system(%|psql -U jenkins -d postgres -c "create database #{test_db_name} owner jenkins encoding 'UTF8';"|)
dev_conf = { 'adapter' => 'postgresql', 'database' => dev_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
test_conf = { 'adapter' => 'postgresql', 'database' => test_db_name, 'host' => 'localhost', 'username' => 'jenkins', 'password' => 'jenkins' }
when 'sqlite3'

View File

@@ -35,7 +35,9 @@ namespace :redmine do
options[:project] = ENV['project'] if ENV['project']
options[:tracker] = ENV['tracker'].to_i if ENV['tracker']
options[:users] = (ENV['users'] || '').split(',').each(&:strip!)
Mailer.reminders(options)
Mailer.with_synched_deliveries do
Mailer.reminders(options)
end
end
end

View File

@@ -520,4 +520,16 @@ function hideOnLoad() {
});
}
function addFormObserversForDoubleSubmit() {
$$('form[method=post]').each(function(el) {
Event.observe(el, 'submit', function(e) {
var form = Event.element(e);
form.select('input[type=submit]').each(function(btn) {
btn.disable();
});
});
});
}
Event.observe(window, 'load', hideOnLoad);
Event.observe(window, 'load', addFormObserversForDoubleSubmit);

View File

@@ -110,92 +110,89 @@ div.action_A { background: #bfb }
-webkit-user-select: none;
}
.syntaxhl .code pre { overflow: auto }
.syntaxhl .debug { color:white ! important; background:blue ! important; }
.syntaxhl .debug { color: white !important; background: blue !important; }
.syntaxhl .attribute-name { color:#b48 }
.syntaxhl .annotation { color:#007 }
.syntaxhl .attribute-name { color:#b48 }
.syntaxhl .attribute-value { color:#700 }
.syntaxhl .binary { color:#509 }
.syntaxhl .char .content { color:#D20 }
.syntaxhl .char .delimiter { color:#710 }
.syntaxhl .char { color:#D20 }
.syntaxhl .class { color:#B06; font-weight:bold }
.syntaxhl .class-variable { color:#369 }
.syntaxhl .color { color:#0A0 }
.syntaxhl .comment { color:#777 }
.syntaxhl .comment .char { color:#444 }
.syntaxhl .comment .delimiter { color:#444 }
.syntaxhl .char { color:#D20 }
.syntaxhl .char .content { color:#D20 }
.syntaxhl .char .delimiter { color:#710 }
.syntaxhl .class { color:#B06; font-weight:bold }
.syntaxhl .complex { color:#A08 }
.syntaxhl .constant { color:#036; font-weight:bold }
.syntaxhl .color { color:#0A0 }
.syntaxhl .class-variable { color:#369 }
.syntaxhl .decorator { color:#B0B }
.syntaxhl .definition { color:#099; font-weight:bold }
.syntaxhl .directive { color:#088; font-weight:bold }
.syntaxhl .delimiter { color:black }
.syntaxhl .directive { color:#088; font-weight:bold }
.syntaxhl .doc { color:#970 }
.syntaxhl .doctype { color:#34b }
.syntaxhl .doc-string { color:#D42; font-weight:bold }
.syntaxhl .escape { color:#666 }
.syntaxhl .doctype { color:#34b }
.syntaxhl .entity { color:#800; font-weight:bold }
.syntaxhl .error { color:#F00; background-color:#FAA }
.syntaxhl .escape { color:#666 }
.syntaxhl .exception { color:#C00; font-weight:bold }
.syntaxhl .float { color:#60E }
.syntaxhl .function { color:#06B; font-weight:bold }
.syntaxhl .global-variable { color:#d70 }
.syntaxhl .hex { color:#02b }
.syntaxhl .integer { color:#00D }
.syntaxhl .include { color:#B44; font-weight:bold }
.syntaxhl .imaginary { color:#f00 }
.syntaxhl .include { color:#B44; font-weight:bold }
.syntaxhl .inline { background-color: hsla(0,0%,0%,0.07); color: black }
.syntaxhl .inline-delimiter { font-weight: bold; color: #666 }
.syntaxhl .instance-variable { color:#33B }
.syntaxhl .integer { color:#00D }
.syntaxhl .key .char { color: #60f }
.syntaxhl .key .delimiter { color: #404 }
.syntaxhl .key { color: #606 }
.syntaxhl .keyword { color:#080; font-weight:bold }
.syntaxhl .label { color:#970; font-weight:bold }
.syntaxhl .local-variable { color:#963 }
.syntaxhl .namespace { color:#707; font-weight:bold }
.syntaxhl .octal { color:#40E }
.syntaxhl .operator { }
.syntaxhl .predefined-constant { color:#069 }
.syntaxhl .predefined { color:#369; font-weight:bold }
.syntaxhl .predefined-constant { color:#069 }
.syntaxhl .predefined-type { color:#0a5; font-weight:bold }
.syntaxhl .preprocessor { color:#579 }
.syntaxhl .pseudo-class { color:#00C; font-weight:bold }
.syntaxhl .reserved { color:#080; font-weight:bold }
.syntaxhl .key .char { color: #60f }
.syntaxhl .key .delimiter { color: #404 }
.syntaxhl .key { color: #606 }
.syntaxhl .keyword { color:#080; font-weight:bold }
.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
.syntaxhl .regexp .content { color:#808 }
.syntaxhl .regexp .delimiter { color:#404 }
.syntaxhl .regexp .modifier { color:#C2C }
.syntaxhl .string { background-color:hsla(0,100%,50%,0.05); }
.syntaxhl .string .content { color: #D20 }
.syntaxhl .string .char { color: #b0b }
.syntaxhl .string .delimiter { color: #710 }
.syntaxhl .string .modifier { color: #E40 }
.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
.syntaxhl .regexp { background-color:hsla(300,100%,50%,0.06); }
.syntaxhl .reserved { color:#080; font-weight:bold }
.syntaxhl .shell .content { color:#2B2 }
.syntaxhl .shell .delimiter { color:#161 }
.syntaxhl .symbol { color:#A60 }
.syntaxhl .shell { background-color:hsla(120,100%,50%,0.06); }
.syntaxhl .string .char { color: #b0b }
.syntaxhl .string .content { color: #D20 }
.syntaxhl .string .delimiter { color: #710 }
.syntaxhl .string .modifier { color: #E40 }
.syntaxhl .string { background-color:hsla(0,100%,50%,0.05); }
.syntaxhl .symbol .content { color:#A60 }
.syntaxhl .symbol .delimiter { color:#630 }
.syntaxhl .symbol { color:#A60 }
.syntaxhl .tag { color:#070 }
.syntaxhl .type { color:#339; font-weight:bold }
.syntaxhl .value { color: #088; }
.syntaxhl .variable { color:#037 }
.syntaxhl .insert { background: hsla(120,100%,50%,0.12) }
.syntaxhl .delete { background: hsla(0,100%,50%,0.12) }
.syntaxhl .change { color: #bbf; background: #007; }
.syntaxhl .head { color: #f8f; background: #505 }
.syntaxhl .head .filename { color: white; }
.syntaxhl .delete .eyecatcher { background-color: hsla(0,100%,50%,0.2); border: 1px solid hsla(0,100%,45%,0.5); margin: -1px; border-bottom: none; border-top-left-radius: 5px; border-top-right-radius: 5px; }
.syntaxhl .insert .eyecatcher { background-color: hsla(120,100%,50%,0.2); border: 1px solid hsla(120,100%,25%,0.5); margin: -1px; border-top: none; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; }
.syntaxhl .insert .insert { color: #0c0; background:transparent; font-weight:bold }
.syntaxhl .delete .delete { color: #c00; background:transparent; font-weight:bold }
.syntaxhl .change .change { color: #88f }
.syntaxhl .head .head { color: #f4f }

View File

@@ -48,6 +48,10 @@ class IssueRelationsControllerTest < ActionController::TestCase
post :create, :issue_id => 1,
:relation => {:issue_to_id => '2', :relation_type => 'relates', :delay => ''}
end
relation = IssueRelation.first(:order => 'id DESC')
assert_equal 1, relation.issue_from_id
assert_equal 2, relation.issue_to_id
assert_equal 'relates', relation.relation_type
end
def test_create_xhr
@@ -61,6 +65,9 @@ class IssueRelationsControllerTest < ActionController::TestCase
assert_select 'tr', 2 # relations
end
end
relation = IssueRelation.first(:order => 'id DESC')
assert_equal 3, relation.issue_from_id
assert_equal 1, relation.issue_to_id
end
def test_create_should_accept_id_with_hash
@@ -69,6 +76,18 @@ class IssueRelationsControllerTest < ActionController::TestCase
post :create, :issue_id => 1,
:relation => {:issue_to_id => '#2', :relation_type => 'relates', :delay => ''}
end
relation = IssueRelation.first(:order => 'id DESC')
assert_equal 2, relation.issue_to_id
end
def test_create_should_strip_id
assert_difference 'IssueRelation.count' do
@request.session[:user_id] = 3
post :create, :issue_id => 1,
:relation => {:issue_to_id => ' 2 ', :relation_type => 'relates', :delay => ''}
end
relation = IssueRelation.first(:order => 'id DESC')
assert_equal 2, relation.issue_to_id
end
def test_create_should_not_break_with_non_numerical_id

View File

@@ -305,6 +305,15 @@ class IssuesControllerTest < ActionController::TestCase
assert_response :success
end
def test_index_should_omit_page_param_in_export_links
get :index, :page => 2
assert_response :success
assert_select 'a.atom[href=/issues.atom]'
assert_select 'a.csv[href=/issues.csv]'
assert_select 'a.pdf[href=/issues.pdf]'
assert_select 'form#csv-export-form[action=/issues.csv]'
end
def test_index_csv
get :index, :format => 'csv'
assert_response :success
@@ -1363,6 +1372,22 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 'This is the test_new issue', issue.subject
end
def test_update_new_form_should_propose_transitions_based_on_initial_status
@request.session[:user_id] = 2
Workflow.delete_all
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 2)
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 1, :new_status_id => 5)
Workflow.create!(:role_id => 1, :tracker_id => 1, :old_status_id => 5, :new_status_id => 4)
xhr :post, :new, :project_id => 1,
:issue => {:tracker_id => 1,
:status_id => 5,
:subject => 'This is an issue'}
assert_equal 5, assigns(:issue).status_id
assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
end
def test_post_create
@request.session[:user_id] = 2
assert_difference 'Issue.count' do
@@ -2171,6 +2196,23 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 'This is the test_new issue', issue.subject
end
def test_update_edit_form_should_propose_transitions_based_on_initial_status
@request.session[:user_id] = 2
Workflow.delete_all
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 1)
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 2, :new_status_id => 5)
Workflow.create!(:role_id => 1, :tracker_id => 2, :old_status_id => 5, :new_status_id => 4)
xhr :put, :new, :project_id => 1,
:id => 2,
:issue => {:tracker_id => 2,
:status_id => 5,
:subject => 'This is an issue'}
assert_equal 5, assigns(:issue).status_id
assert_equal [1,2,5], assigns(:allowed_statuses).map(&:id).sort
end
def test_update_edit_form_with_project_change
@request.session[:user_id] = 2
xhr :put, :new, :project_id => 1,
@@ -2665,7 +2707,7 @@ class IssuesControllerTest < ActionController::TestCase
:attributes => {:name => "issue[custom_field_values][#{field.id}]"},
:children => {
:only => {:tag => 'option'},
:count => Project.find(1).users.count + 1
:count => Project.find(1).users.count + 2 # "no change" + "none" options
}
end
@@ -2681,7 +2723,7 @@ class IssuesControllerTest < ActionController::TestCase
:attributes => {:name => "issue[custom_field_values][#{field.id}]"},
:children => {
:only => {:tag => 'option'},
:count => Project.find(1).shared_versions.count + 1
:count => Project.find(1).shared_versions.count + 2 # "no change" + "none" options
}
end
@@ -2698,7 +2740,7 @@ class IssuesControllerTest < ActionController::TestCase
:attributes => {:name => "issue[custom_field_values][1][]"},
:children => {
:only => {:tag => 'option'},
:count => 3
:count => field.possible_values.size + 1 # "none" options
}
end
@@ -2924,6 +2966,17 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal '777', journal.details.first.value
end
def test_bulk_update_custom_field_to_blank
@request.session[:user_id] = 2
post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing custom field',
:issue => {:priority_id => '',
:assigned_to_id => '',
:custom_field_values => {'1' => '__none__'}}
assert_response 302
assert_equal '', Issue.find(1).custom_field_value(1)
assert_equal '', Issue.find(3).custom_field_value(1)
end
def test_bulk_update_multi_custom_field
field = CustomField.find(1)
field.update_attribute :multiple, true
@@ -2942,6 +2995,20 @@ class IssuesControllerTest < ActionController::TestCase
assert_nil Issue.find(2).custom_field_value(1)
end
def test_bulk_update_multi_custom_field_to_blank
field = CustomField.find(1)
field.update_attribute :multiple, true
@request.session[:user_id] = 2
post :bulk_update, :ids => [1, 3], :notes => 'Bulk editing multi custom field',
:issue => {:priority_id => '',
:assigned_to_id => '',
:custom_field_values => {'1' => ['__none__']}}
assert_response 302
assert_equal [''], Issue.find(1).custom_field_value(1)
assert_equal [''], Issue.find(3).custom_field_value(1)
end
def test_bulk_update_unassign
assert_not_nil Issue.find(2).assigned_to
@request.session[:user_id] = 2
@@ -2990,6 +3057,19 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 'Failed to save 1 issue(s) on 2 selected: #2.', flash[:error]
end
def test_get_bulk_copy
@request.session[:user_id] = 2
get :bulk_edit, :ids => [1, 2, 3], :copy => '1'
assert_response :success
assert_template 'bulk_edit'
issues = assigns(:issues)
assert_not_nil issues
assert_equal [1, 2, 3], issues.map(&:id).sort
assert_select 'input[name=copy_attachments]'
end
def test_bulk_copy_to_another_project
@request.session[:user_id] = 2
assert_difference 'Issue.count', 2 do
@@ -2998,27 +3078,41 @@ class IssuesControllerTest < ActionController::TestCase
end
end
assert_redirected_to '/projects/ecookbook/issues'
copies = Issue.all(:order => 'id DESC', :limit => issues.size)
copies.each do |copy|
assert_equal 2, copy.project_id
end
end
def test_bulk_copy_should_allow_not_changing_the_issue_attributes
@request.session[:user_id] = 2
issue_before_move = Issue.find(1)
assert_difference 'Issue.count', 1 do
assert_no_difference 'Project.find(1).issues.count' do
post :bulk_update, :ids => [1], :copy => '1',
:issue => {
:project_id => '2', :tracker_id => '', :assigned_to_id => '',
:status_id => '', :start_date => '', :due_date => ''
}
end
issues = [
Issue.create!(:project_id => 1, :tracker_id => 1, :status_id => 1, :priority_id => 2, :subject => 'issue 1', :author_id => 1, :assigned_to_id => nil),
Issue.create!(:project_id => 2, :tracker_id => 3, :status_id => 2, :priority_id => 1, :subject => 'issue 2', :author_id => 2, :assigned_to_id => 3)
]
assert_difference 'Issue.count', issues.size do
post :bulk_update, :ids => issues.map(&:id), :copy => '1',
:issue => {
:project_id => '', :tracker_id => '', :assigned_to_id => '',
:status_id => '', :start_date => '', :due_date => ''
}
end
copies = Issue.all(:order => 'id DESC', :limit => issues.size)
issues.each do |orig|
copy = copies.detect {|c| c.subject == orig.subject}
assert_not_nil copy
assert_equal orig.project_id, copy.project_id
assert_equal orig.tracker_id, copy.tracker_id
assert_equal orig.status_id, copy.status_id
assert_equal orig.assigned_to_id, copy.assigned_to_id
assert_equal orig.priority_id, copy.priority_id
end
issue_after_move = Issue.first(:order => 'id desc', :conditions => {:project_id => 2})
assert_equal issue_before_move.tracker_id, issue_after_move.tracker_id
assert_equal issue_before_move.status_id, issue_after_move.status_id
assert_equal issue_before_move.assigned_to_id, issue_after_move.assigned_to_id
end
def test_bulk_copy_should_allow_changing_the_issue_attributes
def test_bulk_copy_should_allow_changing_the_issue_attributes
# Fixes random test failure with Mysql
# where Issue.all(:limit => 2, :order => 'id desc', :conditions => {:project_id => 2})
# doesn't return the expected results
@@ -3030,7 +3124,7 @@ class IssuesControllerTest < ActionController::TestCase
post :bulk_update, :ids => [1, 2], :copy => '1',
:issue => {
:project_id => '2', :tracker_id => '', :assigned_to_id => '4',
:status_id => '3', :start_date => '2009-12-01', :due_date => '2009-12-31'
:status_id => '1', :start_date => '2009-12-01', :due_date => '2009-12-31'
}
end
end
@@ -3040,7 +3134,7 @@ class IssuesControllerTest < ActionController::TestCase
copied_issues.each do |issue|
assert_equal 2, issue.project_id, "Project is incorrect"
assert_equal 4, issue.assigned_to_id, "Assigned to is incorrect"
assert_equal 3, issue.status_id, "Status is incorrect"
assert_equal 1, issue.status_id, "Status is incorrect"
assert_equal '2009-12-01', issue.start_date.to_s, "Start date is incorrect"
assert_equal '2009-12-31', issue.due_date.to_s, "Due date is incorrect"
end
@@ -3064,6 +3158,36 @@ class IssuesControllerTest < ActionController::TestCase
assert_equal 'Copying one issue', journal.notes
end
def test_bulk_copy_should_allow_not_copying_the_attachments
attachment_count = Issue.find(3).attachments.size
assert attachment_count > 0
@request.session[:user_id] = 2
assert_difference 'Issue.count', 1 do
assert_no_difference 'Attachment.count' do
post :bulk_update, :ids => [3], :copy => '1',
:issue => {
:project_id => ''
}
end
end
end
def test_bulk_copy_should_allow_copying_the_attachments
attachment_count = Issue.find(3).attachments.size
assert attachment_count > 0
@request.session[:user_id] = 2
assert_difference 'Issue.count', 1 do
assert_difference 'Attachment.count', attachment_count do
post :bulk_update, :ids => [3], :copy => '1', :copy_attachments => '1',
:issue => {
:project_id => ''
}
end
end
end
def test_bulk_copy_to_another_project_should_follow_when_needed
@request.session[:user_id] = 2
post :bulk_update, :ids => [1], :copy => '1', :issue => {:project_id => 2}, :follow => '1'

View File

@@ -131,6 +131,30 @@ class MessagesControllerTest < ActionController::TestCase
assert_equal 'New body', message.content
end
def test_post_edit_sticky_and_locked
@request.session[:user_id] = 2
post :edit, :board_id => 1, :id => 1,
:message => { :subject => 'New subject',
:content => 'New body',
:locked => '1',
:sticky => '1'}
assert_redirected_to '/boards/1/topics/1'
message = Message.find(1)
assert_equal true, message.sticky?
assert_equal true, message.locked?
end
def test_post_edit_should_allow_to_change_board
@request.session[:user_id] = 2
post :edit, :board_id => 1, :id => 1,
:message => { :subject => 'New subject',
:content => 'New body',
:board_id => 2}
assert_redirected_to '/boards/2/topics/1'
message = Message.find(1)
assert_equal Board.find(2), message.board
end
def test_reply
@request.session[:user_id] = 2
post :reply, :board_id => 1, :id => 1, :reply => { :content => 'This is a test reply', :subject => 'Test reply' }

View File

@@ -53,4 +53,14 @@ class LayoutTest < ActionController::IntegrationTest
:attributes => {:src => %r{^/javascripts/jstoolbar/textile.js}},
:parent => {:tag => 'head'}
end
def test_search_field_outside_project_should_link_to_global_search
get '/'
assert_select 'div#quick-search form[action=/search]'
end
def test_search_field_inside_project_should_link_to_project_search
get '/projects/ecookbook'
assert_select 'div#quick-search form[action=/projects/ecookbook/search]'
end
end

View File

@@ -23,5 +23,9 @@ class RoutingSearchTest < ActionController::IntegrationTest
{ :method => 'get', :path => "/search" },
{ :controller => 'search', :action => 'index' }
)
assert_routing(
{ :method => 'get', :path => "/projects/foo/search" },
{ :controller => 'search', :action => 'index', :id => 'foo' }
)
end
end

View File

@@ -395,6 +395,14 @@ class IssueTest < ActiveSupport::TestCase
assert_equal expected_statuses, issue.new_statuses_allowed_to(admin)
end
def test_new_statuses_allowed_to_should_return_default_and_current_status_when_copying
issue = Issue.find(1).copy
assert_equal [1], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
issue = Issue.find(2).copy
assert_equal [1, 2], issue.new_statuses_allowed_to(User.find(2)).map(&:id)
end
def test_copy
issue = Issue.new.copy_from(1)
assert issue.copy?

View File

@@ -359,6 +359,21 @@ class MailHandlerTest < ActiveSupport::TestCase
end
end
def test_should_ignore_auto_replied_emails
[
"X-Auto-Response-Suppress: OOF",
"Auto-Submitted: auto-replied",
"Auto-Submitted: Auto-Replied"
].each do |header|
raw = IO.read(File.join(FIXTURES_PATH, 'ticket_on_given_project.eml'))
raw = header + "\n" + raw
assert_no_difference 'Issue.count' do
assert_equal false, MailHandler.receive(raw), "email with #{header} header was not ignored"
end
end
end
def test_add_issue_should_send_email_notification
Setting.notified_events = ['issue_added']
ActionMailer::Base.deliveries.clear

View File

@@ -45,6 +45,7 @@ class TimeEntryTest < ActiveSupport::TestCase
"3 h 15 m" => 3.25,
"3 hours" => 3.0,
"12min" => 0.2,
"12 Min" => 0.2,
}
assertions.each do |k, v|

View File

@@ -630,6 +630,38 @@ class UserTest < ActiveSupport::TestCase
end
end
def test_default_admin_account_changed_should_return_false_if_account_was_not_changed
user = User.find_by_login("admin")
user.password = "admin"
user.save!
assert_equal false, User.default_admin_account_changed?
end
def test_default_admin_account_changed_should_return_true_if_password_was_changed
user = User.find_by_login("admin")
user.password = "newpassword"
user.save!
assert_equal true, User.default_admin_account_changed?
end
def test_default_admin_account_changed_should_return_true_if_account_is_disabled
user = User.find_by_login("admin")
user.password = "admin"
user.status = User::STATUS_LOCKED
user.save!
assert_equal true, User.default_admin_account_changed?
end
def test_default_admin_account_changed_should_return_true_if_account_does_not_exist
user = User.find_by_login("admin")
user.destroy
assert_equal true, User.default_admin_account_changed?
end
def test_roles_for_project
# user with a role
roles = @jsmith.roles_for_project(Project.find(1))