diff --git a/app/models/issue.rb b/app/models/issue.rb index 8c3146137..21aed394b 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -1848,6 +1848,14 @@ class Issue < ActiveRecord::Base # Closes duplicates if the issue is being closed def close_duplicates if Setting.close_duplicate_issues? && closing? + # Check that there are no circular dependency of relationships + relations_to.where(:relation_type => IssueRelation::TYPE_DUPLICATES).each do |relation| + unless relation.valid? + errors.add :base, relation.errors.full_messages.first + throw :abort + end + end + duplicates.each do |duplicate| # Reload is needed in case the duplicate was updated by a previous duplicate duplicate.reload diff --git a/app/models/issue_relation.rb b/app/models/issue_relation.rb index d0a66ba40..e0075cb0a 100644 --- a/app/models/issue_relation.rb +++ b/app/models/issue_relation.rb @@ -239,6 +239,10 @@ class IssueRelation < ActiveRecord::Base issue_from.blocks? issue_to when 'blocks' issue_to.blocks? issue_from + when 'duplicated' + self.class.where(issue_from_id: issue_from, issue_to_id: issue_to, relation_type: TYPE_DUPLICATES).exists? + when 'duplicates' + self.class.where(issue_from_id: issue_to, issue_to_id: issue_from, relation_type: TYPE_DUPLICATES).exists? when 'relates' self.class.where(issue_from_id: issue_to, issue_to_id: issue_from).present? else diff --git a/test/unit/issue_relation_test.rb b/test/unit/issue_relation_test.rb index 35a81ffb2..7188febab 100644 --- a/test/unit/issue_relation_test.rb +++ b/test/unit/issue_relation_test.rb @@ -189,6 +189,24 @@ class IssueRelationTest < ActiveSupport::TestCase assert_not_equal [], r.errors[:base] end + def test_validates_circular_dependency_on_reverse_relations_using_duplicates + with_locale 'en' do + IssueRelation.delete_all + issue1 = issues(:issues_001) + issue2 = issues(:issues_002) + assert IssueRelation.create!( + :issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_DUPLICATES + ) + r = IssueRelation.new( + :issue_from => issue2, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_DUPLICATES + ) + assert !r.save + assert_include 'This relation would create a circular dependency', r.errors.full_messages + end + end + def test_create_with_initialized_journals_should_create_journals from = Issue.find(1) to = Issue.find(2) diff --git a/test/unit/issue_test.rb b/test/unit/issue_test.rb index 6c3ae4ccc..fb45194a1 100644 --- a/test/unit/issue_test.rb +++ b/test/unit/issue_test.rb @@ -1605,6 +1605,27 @@ class IssueTest < ActiveSupport::TestCase assert !issue1.reload.closed? end + def test_should_not_close_duplicated_issue_with_circular_dependency + IssueRelation.delete_all + issue1 = issues(:issues_001) + issue2 = issues(:issues_002) + + IssueRelation.create(:issue_from => issue1, :issue_to => issue2, + :relation_type => IssueRelation::TYPE_DUPLICATES).save!(:validate => false) + IssueRelation.create(:issue_from => issue2, :issue_to => issue1, + :relation_type => IssueRelation::TYPE_DUPLICATES).save!(:validate => false) + + assert_equal 1, issue1.duplicates.count + assert_equal issue2, issue1.duplicates.first + assert_equal 1, issue2.duplicates.count + assert_equal issue1, issue2.duplicates.first + + issue1.init_journal(users(:users_002), "Closing issue") + issue1.status = IssueStatus.where(:is_closed => true).first + assert !issue1.save + assert_include 'This relation would create a circular dependency', issue1.errors.full_messages + end + def test_assignable_versions issue = Issue.new(:project_id => 1, :tracker_id => 1, :author_id => 1, :status_id => 1, :fixed_version_id => 1,