Defect #44299
openProject copy: an issue that cannot be copied makes the whole copy fail silently
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:
Project#copyreturns false.ProjectsController#copythen 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")- The final
saveinProject#copyis the one that persists whatever thecopy_*methods assigned to the project record itself, so those assignments are lost too. Redmine::Hook.call_hook(:model_project_copy_before_save)still runs, but anything it changed on the project is dropped by the failingsave.
How to reproduce:
- Create a project with two issues on two different trackers
- 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
- 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_issuesremoves from the association target the issue that could not be saved, so a skipped issue no longer keeps the project invalid.
Project#copyreturns true again and the remaining copy steps run normally.Project#copy_issuesrecords the source issues it had to skip, exposed asProject#issues_not_copied, andProjectsController#copyshows 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_fromof the first issue and again fromrelations_toof the second one. The second insert always failed on the uniqueness validation, and the rejectedIssueRelationstayed in the copied issue'srelations_to, leaving that issue invalid in memory. The patch fix that too.
Files
No data to display