diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 2a42c99ed..bd5b3c9ed 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -149,13 +149,16 @@ class ProjectsController < ApplicationController
       Mailer.with_deliveries(params[:notifications] == '1') do
         @project = Project.new
         @project.safe_attributes = params[:project]
-        if @project.copy(@source_project, :only => params[:only])
+        copied = @project.copy(@source_project, :only => params[:only])
+        if @project.issues_not_copied.present?
+          flash[:warning] = l(:warning_issues_not_copied, @project.issues_not_copied.size)
+        end
+        if copied
           flash[:notice] = l(:notice_successful_create)
           redirect_to settings_project_path(@project)
         elsif !@project.new_record?
           # Project was created
           # But some objects were not copied due to validation failures
-          # (eg. issues from disabled trackers)
           # TODO: inform about that
           redirect_to settings_project_path(@project)
         end
diff --git a/app/models/project.rb b/app/models/project.rb
index d15c29882..b703fa7ba 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -917,6 +917,10 @@ class Project < ApplicationRecord
     p.nil? ? nil : p.identifier.to_s.succ
   end
 
+  # Returns the issues of the source project that could not be copied
+  # by the last call to #copy
+  attr_reader :issues_not_copied
+
   # Copies and saves the Project instance based on the +project+.
   # Duplicates the source project's:
   # * Wiki
@@ -934,6 +938,7 @@ class Project < ApplicationRecord
   #   project.copy(1, :only => ['members', 'versions'])  # => copies members and versions
   def copy(project, options={})
     project = Project.find(project) unless project.is_a?(Project)
+    @issues_not_copied = []
 
     to_be_copied = %w(members wiki versions issue_categories issues queries boards documents)
     to_be_copied = to_be_copied & Array.wrap(options[:only]) unless options[:only].nil?
@@ -1199,6 +1204,10 @@ class Project < ApplicationRecord
               "#{new_issue.errors.full_messages}"
           )
         end
+        @issues_not_copied << issue
+        # Remove the issue that could not be saved from the association target,
+        # otherwise it would make the project invalid and prevent any further save
+        self.issues.target.delete(new_issue)
       else
         issues_map[issue.id] = new_issue unless new_issue.new_record?
       end
@@ -1230,6 +1239,9 @@ class Project < ApplicationRecord
       end
 
       issue.relations_to.each do |source_relation|
+        # Relations between two copied issues have already been copied above
+        next if issues_map[source_relation.issue_from_id]
+
         new_issue_relation = IssueRelation.new
         new_issue_relation.attributes =
           source_relation.attributes.dup.except("id", "issue_from_id", "issue_to_id")
diff --git a/config/locales/de.yml b/config/locales/de.yml
index 0b3e0eb03..725723c35 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -1159,6 +1159,7 @@ de:
   version_status_open: offen
 
   warning_attachments_not_saved: "%{count} Datei(en) konnten nicht gespeichert werden."
+  warning_issues_not_copied: "%{count} Ticket(s) konnten nicht kopiert werden."
   label_search_attachments_yes: Namen und Beschreibungen von Anhängen durchsuchen
   label_search_attachments_no: Keine Anhänge suchen
   label_search_attachments_only: Nur Anhänge suchen
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6ebe23d46..fa5a2f73d 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -227,6 +227,7 @@ en:
   error_session_expired: "Your session has expired. Please login again."
   error_token_expired: "This password recovery link has expired, please try again."
   warning_attachments_not_saved: "%{count} file(s) could not be saved."
+  warning_issues_not_copied: "%{count} issue(s) could not be copied."
   error_password_expired: "Your password has expired or the administrator requires you to change it."
   error_invalid_file_encoding: "The file is not a valid %{encoding} encoded file"
   error_invalid_csv_file_or_settings: "The file is not a CSV file or does not match the settings below (%{value})"
diff --git a/config/locales/fr.yml b/config/locales/fr.yml
index 30612a0b3..f6b8d358d 100644
--- a/config/locales/fr.yml
+++ b/config/locales/fr.yml
@@ -231,6 +231,7 @@ fr:
   error_attachment_too_big: Ce fichier ne peut pas être attaché car il excède la taille maximale autorisée (%{max_size})
   error_session_expired: "Votre session a expiré. Veuillez vous reconnecter."
   warning_attachments_not_saved: "%{count} fichier(s) n'ont pas pu être sauvegardés."
+  warning_issues_not_copied: "%{count} demande(s) n'ont pas pu être copiées."
   error_password_expired: "Votre mot de passe a expiré ou nécessite d'être changé."
   error_invalid_file_encoding: "Le fichier n'est pas un fichier %{encoding} valide"
   error_invalid_csv_file_or_settings: "Le fichier n'est pas un fichier CSV ou n'est pas conforme aux paramètres sélectionnés (%{value})"
diff --git a/config/locales/ja.yml b/config/locales/ja.yml
index afa98fcaf..97d8ad822 100644
--- a/config/locales/ja.yml
+++ b/config/locales/ja.yml
@@ -223,6 +223,7 @@ ja:
   error_can_not_delete_tracker_html: 'このトラッカーは使用中です。削除できません。<p>以下のプロジェクトにこのトラッカーを使用しているチケットがあります:<br>%{projects}</p>'
 
   warning_attachments_not_saved: "%{count}個の添付ファイルが保存できませんでした。"
+  warning_issues_not_copied: "%{count}件のチケットをコピーできませんでした。"
 
   mail_subject_lost_password: "%{value} パスワード再設定"
   mail_body_lost_password: 'パスワードを変更するには、以下のリンクをクリックしてください:'
diff --git a/test/functional/projects_controller_test.rb b/test/functional/projects_controller_test.rb
index 496ab7dee..22fb161ef 100644
--- a/test/functional/projects_controller_test.rb
+++ b/test/functional/projects_controller_test.rb
@@ -1496,6 +1496,31 @@ class ProjectsControllerTest < Redmine::ControllerTest
     assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
   end
 
+  def test_post_copy_should_warn_about_issues_that_could_not_be_copied
+    @request.session[:user_id] = 1 # admin
+    # Issues of the source project can not be copied because this required
+    # custom field is blank
+    IssueCustomField.generate!(:field_format => 'string', :is_required => true,
+                               :is_for_all => true, :trackers => Tracker.all)
+    post(
+      :copy,
+      :params => {
+        :id => 1,
+        :project => {
+          :name => 'Copy',
+          :identifier => 'unique-copy',
+          :tracker_ids => ['1', '2', '3', ''],
+          :enabled_module_names => %w(issue_tracking)
+        },
+        :only => %w(issues)
+      }
+    )
+    assert_redirected_to :controller => 'projects', :action => 'settings', :id => 'unique-copy'
+    project = Project.find('unique-copy')
+    assert_equal 0, project.issues.count
+    assert_equal l(:warning_issues_not_copied, Project.find(1).issues.count), flash[:warning]
+  end
+
   def test_post_copy_with_failure
     @request.session[:user_id] = 1
     post(
diff --git a/test/unit/project_copy_test.rb b/test/unit/project_copy_test.rb
index 8cad4f020..f2f209cdd 100644
--- a/test/unit/project_copy_test.rb
+++ b/test/unit/project_copy_test.rb
@@ -66,6 +66,38 @@ class ProjectCopyTest < ActiveSupport::TestCase
     assert_equal "Closed", copied_issue.status.name
   end
 
+  test "#copy should keep copying when an issue cannot be copied" do
+    # This issue can not be copied because the required custom field below is blank
+    Issue.generate!(:project => @source_project, :tracker_id => 2,
+                    :subject => 'No required value')
+    IssueCustomField.generate!(:field_format => 'string', :is_required => true,
+                               :is_for_all => true, :trackers => [Tracker.find(2)])
+
+    assert @project.copy(@source_project)
+    assert @project.valid?
+    assert_nil @project.issues.find_by(:subject => 'No required value')
+    # The other issues and objects are copied even though an issue was skipped
+    assert @project.issues.any?
+    assert @project.versions.any?
+    assert @project.members.any?
+  end
+
+  test "#copy should report the issues that could not be copied" do
+    skipped_issue =
+      Issue.generate!(:project => @source_project, :tracker_id => 2,
+                      :subject => 'No required value')
+    IssueCustomField.generate!(:field_format => 'string', :is_required => true,
+                               :is_for_all => true, :trackers => [Tracker.find(2)])
+
+    assert @project.copy(@source_project)
+    assert_equal [skipped_issue], @project.issues_not_copied
+  end
+
+  test "#copy should not report any issue when they are all copied" do
+    assert @project.copy(@source_project)
+    assert_equal [], @project.issues_not_copied
+  end
+
   test "#copy should copy issues custom values" do
     field = IssueCustomField.generate!(:is_for_all => true, :trackers => Tracker.all)
     issue = Issue.generate!(:project => @source_project, :subject => 'Custom field copy')
@@ -187,6 +219,22 @@ class ProjectCopyTest < ActiveSupport::TestCase
     end
   end
 
+  test "#copy should copy a relation between two copied issues only once" do
+    issue = Issue.generate!(:project => @source_project, :subject => 'Relation from')
+    other_issue = Issue.generate!(:project => @source_project, :subject => 'Relation to')
+    IssueRelation.create!(:issue_from => issue, :issue_to => other_issue,
+                          :relation_type => 'relates')
+
+    assert @project.copy(@source_project)
+    # A second copy of the relation would be rejected by the uniqueness
+    # validation and would leave the copied issue invalid
+    assert @project.issues.all?(&:valid?)
+    assert_equal(
+      1,
+      IssueRelation.where(:issue_from_id => @project.issues, :issue_to_id => @project.issues).count
+    )
+  end
+
   test "#copy should copy issue attachments" do
     set_tmp_attachments_directory
     issue = Issue.generate!(:subject => "copy with attachment", :tracker_id => 1, :project_id => @source_project.id)
