diff --git a/app/controllers/settings_controller.rb b/app/controllers/settings_controller.rb index 05550bc65..fdd6bf4f0 100644 --- a/app/controllers/settings_controller.rb +++ b/app/controllers/settings_controller.rb @@ -47,7 +47,7 @@ class SettingsController < ApplicationController @guessed_host_and_path << ('/'+ Redmine::Utils.relative_url_root.gsub(%r{^\/}, '')) unless Redmine::Utils.relative_url_root.blank? @commit_update_keywords = Setting.commit_update_keywords.dup - @commit_update_keywords[''] = {} if @commit_update_keywords.blank? + @commit_update_keywords << {} if @commit_update_keywords.blank? Redmine::Themes.rescan end diff --git a/app/models/changeset.rb b/app/models/changeset.rb index 2c574f75e..4198e7e02 100644 --- a/app/models/changeset.rb +++ b/app/models/changeset.rb @@ -118,7 +118,7 @@ class Changeset < ActiveRecord::Base ref_keywords = Setting.commit_ref_keywords.downcase.split(",").collect(&:strip) ref_keywords_any = ref_keywords.delete('*') # keywords used to fix issues - fix_keywords = Setting.commit_update_by_keyword.keys + fix_keywords = Setting.commit_update_keywords_array.map {|r| r['keywords']}.flatten.compact kw_regexp = (ref_keywords + fix_keywords).collect{|kw| Regexp.escape(kw)}.join("|") @@ -215,16 +215,18 @@ class Changeset < ActiveRecord::Base # Updates the +issue+ according to +action+ def fix_issue(issue, action) - updates = Setting.commit_update_by_keyword[action] - return unless updates.is_a?(Hash) - # the issue may have been updated by the closure of another one (eg. duplicate) issue.reload # don't change the status is the issue is closed return if issue.status && issue.status.is_closed? journal = issue.init_journal(user || User.anonymous, ll(Setting.default_language, :text_status_changed_by_changeset, text_tag(issue.project))) - issue.assign_attributes updates.slice(*Issue.attribute_names) + rule = Setting.commit_update_keywords_array.detect do |rule| + rule['keywords'].include?(action) && (rule['if_tracker_id'].blank? || rule['if_tracker_id'] == issue.tracker_id.to_s) + end + if rule + issue.assign_attributes rule.slice(*Issue.attribute_names) + end Redmine::Hook.call_hook(:model_changeset_scan_commit_for_issue_ids_pre_issue_update, { :changeset => self, :issue => issue, :action => action }) unless issue.save diff --git a/app/models/setting.rb b/app/models/setting.rb index 71ef15208..444f45e20 100644 --- a/app/models/setting.rb +++ b/app/models/setting.rb @@ -154,18 +154,18 @@ END_SRC # Example: # params = {:keywords => ['fixes', 'closes'], :status_id => ["3", "5"], :done_ratio => ["", "100"]} # Setting.commit_update_keywords_from_params(params) - # # => {'fixes' => {'status_id' => "3"}, 'closes' => {'status_id' => "5", 'done_ratio' => "100"}} + # # => [{'keywords => 'fixes', 'status_id' => "3"}, {'keywords => 'closes', 'status_id' => "5", 'done_ratio' => "100"}] def self.commit_update_keywords_from_params(params) - s = {} + s = [] if params.is_a?(Hash) && params.key?(:keywords) && params.values.all? {|v| v.is_a? Array} attributes = params.except(:keywords).keys params[:keywords].each_with_index do |keywords, i| next if keywords.blank? - s[keywords] = attributes.inject({}) {|h, a| + s << attributes.inject({}) {|h, a| value = params[a][i].to_s h[a.to_s] = value if value.present? h - } + }.merge('keywords' => keywords) end end s @@ -177,39 +177,39 @@ END_SRC end # Helper that returns a Hash with single update keywords as keys - def self.commit_update_by_keyword - h = {} - if commit_update_keywords.is_a?(Hash) - commit_update_keywords.each do |keywords, attribute_updates| - next unless attribute_updates.is_a?(Hash) - attribute_updates = attribute_updates.dup - attribute_updates.delete_if {|k, v| v.blank?} - keywords.to_s.split(",").map(&:strip).reject(&:blank?).each do |keyword| - h[keyword.downcase] = attribute_updates - end + def self.commit_update_keywords_array + a = [] + if commit_update_keywords.is_a?(Array) + commit_update_keywords.each do |rule| + next unless rule.is_a?(Hash) + rule = rule.dup + rule.delete_if {|k, v| v.blank?} + keywords = rule['keywords'].to_s.downcase.split(",").map(&:strip).reject(&:blank?) + next if keywords.empty? + a << rule.merge('keywords' => keywords) end end - h + a end def self.commit_fix_keywords - ActiveSupport::Deprecation.warn "Setting.commit_fix_keywords is deprecated and will be removed in Redmine 3" - if commit_update_keywords.is_a?(Hash) - commit_update_keywords.keys.first + ActiveSupport::Deprecation.warn "Setting.commit_fix_keywords is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['keywords'] end end def self.commit_fix_status_id - ActiveSupport::Deprecation.warn "Setting.commit_fix_status_id is deprecated and will be removed in Redmine 3" - if commit_update_keywords.is_a?(Hash) - commit_update_keywords[commit_fix_keywords]['status_id'] + ActiveSupport::Deprecation.warn "Setting.commit_fix_status_id is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['status_id'] end end def self.commit_fix_done_ratio - ActiveSupport::Deprecation.warn "Setting.commit_fix_done_ratio is deprecated and will be removed in Redmine 3" - if commit_update_keywords.is_a?(Hash) - commit_update_keywords[commit_fix_keywords]['done_ratio'] + ActiveSupport::Deprecation.warn "Setting.commit_fix_done_ratio is deprecated and will be removed in Redmine 3" + if commit_update_keywords.is_a?(Array) + commit_update_keywords.first && commit_update_keywords.first['done_ratio'] end end diff --git a/app/views/settings/_repositories.html.erb b/app/views/settings/_repositories.html.erb index d90c212ec..e95a3fee1 100644 --- a/app/views/settings/_repositories.html.erb +++ b/app/views/settings/_repositories.html.erb @@ -80,6 +80,7 @@ + @@ -87,15 +88,17 @@ - <% @commit_update_keywords.each do |keywords, updates| %> + <% @commit_update_keywords.each do |rule| %> - - - + + + + <% end %> + diff --git a/config/settings.yml b/config/settings.yml index 05779accc..66c67bd01 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -108,7 +108,7 @@ commit_ref_keywords: default: 'refs,references,IssueID' commit_update_keywords: serialized: true - default: {} + default: [] commit_logtime_enabled: default: 0 commit_logtime_activity_id: diff --git a/test/functional/settings_controller_test.rb b/test/functional/settings_controller_test.rb index b3cf7bd5a..f5d4377f1 100644 --- a/test/functional/settings_controller_test.rb +++ b/test/functional/settings_controller_test.rb @@ -81,38 +81,41 @@ class SettingsControllerTest < ActionController::TestCase end def test_edit_commit_update_keywords - with_settings :commit_update_keywords => { - "fixes, resolves" => {"status_id" => "3"}, - "closes" => {"status_id" => "5", "done_ratio" => "100"} - } do + with_settings :commit_update_keywords => [ + {"keywords" => "fixes, resolves", "status_id" => "3"}, + {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} + ] do get :edit end assert_response :success assert_select 'tr.commit-keywords', 2 - assert_select 'tr.commit-keywords' do + assert_select 'tr.commit-keywords:nth-child(1)' do assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'fixes, resolves' assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do assert_select 'option[value=3][selected=selected]' end end - assert_select 'tr.commit-keywords' do + assert_select 'tr.commit-keywords:nth-child(2)' do assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', 'closes' assert_select 'select[name=?]', 'settings[commit_update_keywords][status_id][]' do - assert_select 'option[value=5][selected=selected]' + assert_select 'option[value=5][selected=selected]', :text => 'Closed' end assert_select 'select[name=?]', 'settings[commit_update_keywords][done_ratio][]' do - assert_select 'option[value=100][selected=selected]' + assert_select 'option[value=100][selected=selected]', :text => '100 %' + end + assert_select 'select[name=?]', 'settings[commit_update_keywords][if_tracker_id][]' do + assert_select 'option[value=2][selected=selected]', :text => 'Feature request' end end end def test_edit_without_commit_update_keywords_should_show_blank_line - with_settings :commit_update_keywords => {} do + with_settings :commit_update_keywords => [] do get :edit end assert_response :success assert_select 'tr.commit-keywords', 1 do - assert_select 'input[name=?][value=?]', 'settings[commit_update_keywords][keywords][]', '' + assert_select 'input[name=?]:not([value])', 'settings[commit_update_keywords][keywords][]' end end @@ -121,14 +124,15 @@ class SettingsControllerTest < ActionController::TestCase :commit_update_keywords => { :keywords => ["resolves", "closes"], :status_id => ["3", "5"], - :done_ratio => ["", "100"] + :done_ratio => ["", "100"], + :if_tracker_id => ["", "2"] } } assert_redirected_to '/settings' - assert_equal({ - "resolves" => {"status_id" => "3"}, - "closes" => {"status_id" => "5", "done_ratio" => "100"} - }, Setting.commit_update_keywords) + assert_equal([ + {"keywords" => "resolves", "status_id" => "3"}, + {"keywords" => "closes", "status_id" => "5", "done_ratio" => "100", "if_tracker_id" => "2"} + ], Setting.commit_update_keywords) end def test_get_plugin_settings diff --git a/test/object_helpers.rb b/test/object_helpers.rb index 2f7559249..9cb0916f7 100644 --- a/test/object_helpers.rb +++ b/test/object_helpers.rb @@ -166,4 +166,16 @@ module ObjectHelpers field.save! field end + + def Changeset.generate!(attributes={}) + @generated_changeset_rev ||= '123456' + @generated_changeset_rev.succ! + changeset = new(attributes) + changeset.repository ||= Project.find(1).repository + changeset.revision ||= @generated_changeset_rev + changeset.committed_on ||= Time.now + yield changeset if block_given? + changeset.save! + changeset + end end diff --git a/test/unit/changeset_test.rb b/test/unit/changeset_test.rb index 93bd7bba5..0eea5f88b 100644 --- a/test/unit/changeset_test.rb +++ b/test/unit/changeset_test.rb @@ -31,7 +31,7 @@ class ChangesetTest < ActiveSupport::TestCase def test_ref_keywords_any ActionMailer::Base.deliveries.clear Setting.commit_ref_keywords = '*' - Setting.commit_update_keywords = {'fixes , closes' => {'status_id' => '5', 'done_ratio' => '90'}} + Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => '5', 'done_ratio' => '90'}] c = Changeset.new(:repository => Project.find(1).repository, :committed_on => Time.now, @@ -111,11 +111,7 @@ class ChangesetTest < ActiveSupport::TestCase def test_ref_keywords_closing_with_timelog Setting.commit_ref_keywords = '*' - Setting.commit_update_keywords = { - 'fixes , closes' => { - 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s - } - } + Setting.commit_update_keywords = [{'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id.to_s}] Setting.commit_logtime_enabled = '1' c = Changeset.new(:repository => Project.find(1).repository, @@ -165,20 +161,45 @@ class ChangesetTest < ActiveSupport::TestCase end def test_update_keywords_with_multiple_rules - Setting.commit_update_keywords = { - 'fixes, closes' => {'status_id' => '5'}, - 'resolves' => {'status_id' => '3'} - } - issue1 = Issue.generate! - issue2 = Issue.generate! + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes, closes', 'status_id' => '5'}, + {'keywords' => 'resolves', 'status_id' => '3'} + ] do - c = Changeset.new(:repository => Project.find(1).repository, - :committed_on => Time.now, - :comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}", - :revision => '12345') - assert c.save - assert_equal 5, issue1.reload.status_id - assert_equal 3, issue2.reload.status_id + issue1 = Issue.generate! + issue2 = Issue.generate! + Changeset.generate!(:comments => "Closes ##{issue1.id}\nResolves ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 3, issue2.reload.status_id + end + end + + def test_update_keywords_with_multiple_rules_should_match_tracker + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'}, + {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => ''} + ] do + + issue1 = Issue.generate!(:tracker_id => 2) + issue2 = Issue.generate! + Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 3, issue2.reload.status_id + end + end + + def test_update_keywords_with_multiple_rules_and_no_match + with_settings :commit_update_keywords => [ + {'keywords' => 'fixes', 'status_id' => '5', 'if_tracker_id' => '2'}, + {'keywords' => 'fixes', 'status_id' => '3', 'if_tracker_id' => '3'} + ] do + + issue1 = Issue.generate!(:tracker_id => 2) + issue2 = Issue.generate! + Changeset.generate!(:comments => "Fixes ##{issue1.id}, ##{issue2.id}") + assert_equal 5, issue1.reload.status_id + assert_equal 1, issue2.reload.status_id # no updates + end end def test_commit_referencing_a_subproject_issue @@ -192,7 +213,7 @@ class ChangesetTest < ActiveSupport::TestCase end def test_commit_closing_a_subproject_issue - with_settings :commit_update_keywords => {'closes' => {'status_id' => '5'}}, + with_settings :commit_update_keywords => [{'keywords' => 'closes', 'status_id' => '5'}], :default_language => 'en' do issue = Issue.find(5) assert !issue.closed? diff --git a/test/unit/repository_test.rb b/test/unit/repository_test.rb index 3ff9fc51e..c269df7bb 100644 --- a/test/unit/repository_test.rb +++ b/test/unit/repository_test.rb @@ -183,9 +183,9 @@ class RepositoryTest < ActiveSupport::TestCase Setting.default_language = 'en' Setting.commit_ref_keywords = 'refs , references, IssueID' - Setting.commit_update_keywords = { - 'fixes , closes' => {'status_id' => IssueStatus.where(:is_closed => true).first.id, 'done_ratio' => '90'} - } + Setting.commit_update_keywords = [ + {'keywords' => 'fixes , closes', 'status_id' => IssueStatus.where(:is_closed => true).first.id, 'done_ratio' => '90'} + ] Setting.default_language = 'en' ActionMailer::Base.deliveries.clear
<%= l(:label_tracker) %> <%= l(:setting_commit_fix_keywords) %> <%= l(:label_applied_status) %> <%= l(:field_done_ratio) %>
<%= text_field_tag "settings[commit_update_keywords][keywords][]", keywords, :size => 30 %><%= select_tag "settings[commit_update_keywords][status_id][]", options_for_select([["", 0]] + IssueStatus.sorted.all.collect{|status| [status.name, status.id.to_s]}, updates['status_id']) %><%= select_tag "settings[commit_update_keywords][done_ratio][]", options_for_select([["", ""]] + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, updates['done_ratio']) %><%= select_tag "settings[commit_update_keywords][if_tracker_id][]", options_for_select([[l(:label_all), ""]] + Tracker.sorted.all.map {|t| [t.name, t.id.to_s]}, rule['if_tracker_id']) %><%= text_field_tag "settings[commit_update_keywords][keywords][]", rule['keywords'], :size => 30 %><%= select_tag "settings[commit_update_keywords][status_id][]", options_for_select([["", 0]] + IssueStatus.sorted.all.collect{|status| [status.name, status.id.to_s]}, rule['status_id']) %><%= select_tag "settings[commit_update_keywords][done_ratio][]", options_for_select([["", ""]] + (0..10).to_a.collect {|r| ["#{r*10} %", "#{r*10}"] }, rule['done_ratio']) %> <%= link_to image_tag('delete.png'), '#', :class => 'delete-commit-keywords' %>
<%= l(:text_comma_separated) %>