Project

General

Profile

Actions

Defect #44299

open

Project copy: an issue that cannot be copied makes the whole copy fail silently

Added by Vincent Robert 1 day ago.

Status:
New
Priority:
Normal
Assignee:
-
Category:
Projects
Target version:
-
Resolution:
Affected version:

Description

Project#copy_issues has always been deliberately tolerant: an issue that fails validation is logged ("Project#copy_issues: issue #X could not be copied") and the copy carries on.
But the issue that could not be saved is left in the issues association target as an unsaved record, so every subsequent project.save fails with "Issues is invalid".

Consequences, all reproducible on master without any plugin:

  1. Project#copy returns false. ProjectsController#copy then redirects without any flash message at all: the user gets a project that looks copied and no indication that anything was skipped. There is currently a comment in the controller about this issue ("TODO: inform about that")
  2. The final save in Project#copy is the one that persists whatever the copy_* methods assigned to the project record itself, so those assignments are lost too.
  3. Redmine::Hook.call_hook(:model_project_copy_before_save) still runs, but anything it changed on the project is dropped by the failing save .

How to reproduce:

  1. Create a project with two issues on two different trackers
  2. Add an issue custom field: format string, required, for all projects, for the tracker of one of those two issues. The existing issue now has a blank value for a required field, so it cannot be saved as is
  3. Copy the project with "Issues" checked

Result: the copy is created, one issue is skipped (as intended), but Project#copy returned false and the user is redirected with no message.

The impact is much bigger for instances using plugins. Project#copy is a common extension point, and the pattern plugins use is the one core itself uses:

def copy(project, options = {})
  super
  to_be_copied = %w(my_stuff)
  to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
  Project.transaction do
    if save
      reload
      to_be_copied.each {|name| send "copy_#{name}", project}
      save
    else
      false
    end
  end
end

Since the project can no longer be saved, if save is false and none of the plugin's copy_* steps run at all.
On one of our projects, where a required custom field had been added long after the issues were created, lots of issues could not be copied, and as a result none of the data contributed by our plugins was copied either, with no error shown anywhere but the log.

Proposed patch (attached):

  • Project#copy_issues removes from the association target the issue that could not be saved, so a skipped issue no longer keeps the project invalid.
    Project#copy returns true again and the remaining copy steps run normally.
  • Project#copy_issues records the source issues it had to skip, exposed as Project#issues_not_copied, and ProjectsController#copy shows a warning ("N issue(s) could not be copied."). This address the "TODO: inform about that" comment (added 16 years ago :), and makes sure the copy does not report plain success while silently dropping issues.
  • Also in copy_issues: a relation between two copied issues was created twice, once from relations_from of the first issue and again from relations_to of the second one. The second insert always failed on the uniqueness validation, and the rejected IssueRelation stayed in the copied issue's relations_to, leaving that issue invalid in memory. The patch fix that too.

Files

project_copy_skipped_issues.patch (10.3 KB) project_copy_skipped_issues.patch Vincent Robert, 2026-07-30 15:15

No data to display

Actions

Also available in: Atom PDF