diff --git a/thunar/thunar-dialogs.c b/thunar/thunar-dialogs.c
index b87ac1e0a488fe3f17c21421d895b033bf64320e..2cb1ce50348d9b5316648a262ad5f3336ff94f8e 100644
--- a/thunar/thunar-dialogs.c
+++ b/thunar/thunar-dialogs.c
@@ -474,6 +474,14 @@ thunar_dialogs_show_job_ask (GtkWindow        *parent,
           mnemonic = _("S_kip All");
           break;
 
+        case THUNAR_JOB_RESPONSE_RENAME:
+          mnemonic = _("Re_name");
+          break;
+
+        case THUNAR_JOB_RESPONSE_RENAME_ALL:
+          mnemonic = _("Rena_me All");
+          break;
+
         case THUNAR_JOB_RESPONSE_NO:
           mnemonic = _("_No");
           break;
@@ -579,6 +587,8 @@ thunar_dialogs_show_job_ask_replace (GtkWindow  *parent,
   GtkWidget         *skip_button;
   GtkWidget         *replaceall_button;
   GtkWidget         *replace_button;
+  GtkWidget         *renameall_button;
+  GtkWidget         *rename_button;
   GdkPixbuf         *icon;
   gchar             *date_custom_style;
   gchar             *date_string;
@@ -626,24 +636,32 @@ thunar_dialogs_show_job_ask_replace (GtkWindow  *parent,
   skip_button       = gtk_button_new_with_mnemonic (_("_Skip"));
   replaceall_button = gtk_button_new_with_mnemonic (_("Replace _All"));
   replace_button    = gtk_button_new_with_mnemonic (_("_Replace"));
+  renameall_button  = gtk_button_new_with_mnemonic (_("Rena_me All"));
+  rename_button     = gtk_button_new_with_mnemonic (_("Re_name"));
 
   g_signal_connect (cancel_button,      "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
   g_signal_connect (skipall_button,     "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
   g_signal_connect (skip_button,        "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
   g_signal_connect (replaceall_button,  "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
   g_signal_connect (replace_button,     "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
+  g_signal_connect (renameall_button,   "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
+  g_signal_connect (rename_button,      "clicked", G_CALLBACK (thunar_dialogs_show_job_ask_replace_callback), dialog);
 
   g_object_set_data (G_OBJECT (cancel_button),     "response-id", GINT_TO_POINTER (GTK_RESPONSE_CANCEL));
   g_object_set_data (G_OBJECT (skipall_button),    "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_SKIP_ALL));
   g_object_set_data (G_OBJECT (skip_button),       "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_SKIP));
   g_object_set_data (G_OBJECT (replaceall_button), "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_REPLACE_ALL));
   g_object_set_data (G_OBJECT (replace_button),    "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_REPLACE));
+  g_object_set_data (G_OBJECT (renameall_button),  "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_RENAME_ALL));
+  g_object_set_data (G_OBJECT (rename_button),     "response-id", GINT_TO_POINTER (THUNAR_JOB_RESPONSE_RENAME));
 
   gtk_container_add (GTK_CONTAINER (button_box), cancel_button);
   gtk_container_add (GTK_CONTAINER (button_box), skipall_button);
   gtk_container_add (GTK_CONTAINER (button_box), skip_button);
   gtk_container_add (GTK_CONTAINER (button_box), replaceall_button);
   gtk_container_add (GTK_CONTAINER (button_box), replace_button);
+  gtk_container_add (GTK_CONTAINER (button_box), renameall_button);
+  gtk_container_add (GTK_CONTAINER (button_box), rename_button);
   gtk_container_add (GTK_CONTAINER (content_area), button_box);
   gtk_widget_set_halign (button_box, GTK_ALIGN_CENTER);
   gtk_box_set_spacing (GTK_BOX (button_box), 5);
diff --git a/thunar/thunar-enum-types.c b/thunar/thunar-enum-types.c
index aaa877e4e1b106cc5654608c806081822a53ab23..d3d94278a26a2503163898d8a1c327de72e19b59 100644
--- a/thunar/thunar-enum-types.c
+++ b/thunar/thunar-enum-types.c
@@ -372,6 +372,8 @@ thunar_job_response_get_type (void)
 	      { THUNAR_JOB_RESPONSE_REPLACE_ALL, "THUNAR_JOB_RESPONSE_REPLACE_ALL", "replace-all" },
 	      { THUNAR_JOB_RESPONSE_SKIP,        "THUNAR_JOB_RESPONSE_SKIP",        "skip"        },
 	      { THUNAR_JOB_RESPONSE_SKIP_ALL,    "THUNAR_JOB_RESPONSE_SKIP_ALL",    "skip-all"    },
+	      { THUNAR_JOB_RESPONSE_RENAME,      "THUNAR_JOB_RESPONSE_RENAME",      "rename"      },
+	      { THUNAR_JOB_RESPONSE_RENAME_ALL,  "THUNAR_JOB_RESPONSE_RENAME_ALL",  "rename-all " },
 	      { 0,                               NULL,                              NULL          }
 	    };
 
diff --git a/thunar/thunar-enum-types.h b/thunar/thunar-enum-types.h
index bbd4a23116ac62044660ab213130c68812b69838..df25c955bed4da778a2415ce01573ea668b005c8 100644
--- a/thunar/thunar-enum-types.h
+++ b/thunar/thunar-enum-types.h
@@ -240,6 +240,8 @@ ThunarThumbnailSize thunar_zoom_level_to_thumbnail_size   (ThunarZoomLevel zoom_
  * @THUNAR_JOB_RESPONSE_REPLACE_ALL :
  * @THUNAR_JOB_RESPONSE_SKIP        :
  * @THUNAR_JOB_RESPONSE_SKIP_ALL    :
+ * @THUNAR_JOB_RESPONSE_RENAME      :
+ * @THUNAR_JOB_RESPONSE_RENAME_ALL  :
  *
  * Possible responses for the ThunarJob::ask signal.
  **/
@@ -256,8 +258,10 @@ typedef enum /*< flags >*/
   THUNAR_JOB_RESPONSE_REPLACE_ALL = 1 << 8,
   THUNAR_JOB_RESPONSE_SKIP        = 1 << 9,
   THUNAR_JOB_RESPONSE_SKIP_ALL    = 1 << 10,
+  THUNAR_JOB_RESPONSE_RENAME      = 1 << 11,
+  THUNAR_JOB_RESPONSE_RENAME_ALL  = 1 << 12,
 } ThunarJobResponse;
-#define THUNAR_JOB_RESPONSE_MAX_INT 10
+#define THUNAR_JOB_RESPONSE_MAX_INT 12
 
 GType thunar_job_response_get_type (void) G_GNUC_CONST;
 
diff --git a/thunar/thunar-io-jobs-util.c b/thunar/thunar-io-jobs-util.c
index 0032985e92a5df1c2f3dbaf6ef813679585606e1..e875f60dd4bdc77a03bc5818f4153ffe3ef11c46 100644
--- a/thunar/thunar-io-jobs-util.c
+++ b/thunar/thunar-io-jobs-util.c
@@ -141,3 +141,91 @@ thunar_io_jobs_util_next_duplicate_file (ThunarJob *job,
 
 
 
+/**
+ * thunar_io_jobs_util_next_renamed_file:
+ * @job       : a #ThunarJob.
+ * @src_file  : the source #GFile.
+ * @tgt_file  : the target #GFile.
+ * @n         : the @n<!---->th copy/move to create the #GFile for.
+ * @error     : return location for errors or %NULL.
+ *
+ * Determines the #GFile for the next copy/move to @tgt_file.
+ *
+ * File named X will be renamed to "X (copy 1)".
+ *
+ * If there are errors or the job was cancelled, the return value
+ * will be %NULL and @error will be set.
+ *
+ * Return value: the #GFile referencing the @n<!---->th copy/move
+ *               of @tgt_file or %NULL on error/cancellation.
+ **/
+GFile *
+thunar_io_jobs_util_next_renamed_file (ThunarJob *job,
+                                       GFile     *src_file,
+                                       GFile     *tgt_file,
+                                       guint      n,
+                                       GError   **error)
+{
+  GFileInfo   *info;
+  GError      *err = NULL;
+  GFile       *renamed_file = NULL;
+  GFile       *parent_file = NULL;
+  const gchar *old_display_name;
+  gchar       *display_name;
+  gchar       *file_basename;
+  gchar       *dot = NULL;
+
+  _thunar_return_val_if_fail (THUNAR_IS_JOB (job), NULL);
+  _thunar_return_val_if_fail (G_IS_FILE (src_file), NULL);
+  _thunar_return_val_if_fail (G_IS_FILE (tgt_file), NULL);
+  _thunar_return_val_if_fail (0 < n, NULL);
+  _thunar_return_val_if_fail (error == NULL || *error == NULL, NULL);
+  _thunar_return_val_if_fail (!thunar_g_file_is_root (src_file), NULL);
+  _thunar_return_val_if_fail (!thunar_g_file_is_root (tgt_file), NULL);
+
+  /* abort on cancellation */
+  if (exo_job_set_error_if_cancelled (EXO_JOB (job), error))
+    return NULL;
+
+  /* query the source file info / display name */
+  info = g_file_query_info (src_file, G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+                            G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+                            G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+                            exo_job_get_cancellable (EXO_JOB (job)), &err);
+
+  /* abort on error */
+  if (info == NULL)
+    {
+      g_propagate_error (error, err);
+      return NULL;
+    }
+
+  old_display_name = g_file_info_get_display_name (info);
+  /* get file extension if file is not a directory */
+  if (g_file_info_get_file_type (info) != G_FILE_TYPE_DIRECTORY)
+    dot = thunar_util_str_get_extension (old_display_name);
+
+  if (dot != NULL)
+    {
+      file_basename = g_strndup (old_display_name, dot - old_display_name);
+      /* I18N: put " (copy #)" between basename and extension */
+      display_name = g_strdup_printf (_("%s (copy %u)%s"), file_basename, n, dot);
+      g_free(file_basename);
+    }
+  else
+    {
+      /* I18N: put " (copy #)" after filename (for files without extension) */
+      display_name = g_strdup_printf (_("%s (copy %u)"), old_display_name, n);
+    }
+
+  /* create the GFile for the copy/move */
+  parent_file = g_file_get_parent (tgt_file);
+  renamed_file = g_file_get_child (parent_file, display_name);
+  g_object_unref (parent_file);
+
+  /* free resources */
+  g_object_unref (info);
+  g_free (display_name);
+
+  return renamed_file;
+}
diff --git a/thunar/thunar-io-jobs-util.h b/thunar/thunar-io-jobs-util.h
index 2ff28960b0970ba6e592f66503901d494aedcffc..b79687e9030e8630f3976ecb95be1335ea9bb4fb 100644
--- a/thunar/thunar-io-jobs-util.h
+++ b/thunar/thunar-io-jobs-util.h
@@ -31,6 +31,12 @@ GFile *thunar_io_jobs_util_next_duplicate_file (ThunarJob *job,
                                                 guint      n,
                                                 GError   **error) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
 
+GFile *thunar_io_jobs_util_next_renamed_file (ThunarJob *job,
+                                              GFile     *src_file,
+                                              GFile     *tgt_file,
+                                              guint      n,
+                                              GError   **error) G_GNUC_MALLOC G_GNUC_WARN_UNUSED_RESULT;
+
 G_END_DECLS
 
 #endif /* !__THUNAR_IO_JOBS_UITL_H__ */
diff --git a/thunar/thunar-job.c b/thunar/thunar-job.c
index 7f9f8de1fbc13facd8b6aa22011822a225ad91b5..33f6c43e3860b34a1a8150827d482c8d6d889787 100644
--- a/thunar/thunar-job.c
+++ b/thunar/thunar-job.c
@@ -259,6 +259,8 @@ thunar_job_real_ask_replace (ThunarJob  *job,
   g_signal_emit (job, job_signals[ASK], 0, message,
                  THUNAR_JOB_RESPONSE_REPLACE
                  | THUNAR_JOB_RESPONSE_REPLACE_ALL
+                 | THUNAR_JOB_RESPONSE_RENAME
+                 | THUNAR_JOB_RESPONSE_RENAME_ALL
                  | THUNAR_JOB_RESPONSE_SKIP
                  | THUNAR_JOB_RESPONSE_SKIP_ALL
                  | THUNAR_JOB_RESPONSE_CANCEL,
@@ -484,6 +486,10 @@ thunar_job_ask_replace (ThunarJob *job,
   if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_REPLACE_ALL))
     return THUNAR_JOB_RESPONSE_REPLACE;
 
+  /* check if the user said "Rename All" earlier */
+  if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_RENAME_ALL))
+    return THUNAR_JOB_RESPONSE_RENAME;
+
   /* check if the user said "Overwrite None" earlier */
   if (G_UNLIKELY (job->priv->earlier_ask_overwrite_response == THUNAR_JOB_RESPONSE_SKIP_ALL))
     return THUNAR_JOB_RESPONSE_SKIP;
@@ -513,6 +519,8 @@ thunar_job_ask_replace (ThunarJob *job,
   /* translate the response */
   if (response == THUNAR_JOB_RESPONSE_REPLACE_ALL)
     response = THUNAR_JOB_RESPONSE_REPLACE;
+  else if (response == THUNAR_JOB_RESPONSE_RENAME_ALL)
+    response = THUNAR_JOB_RESPONSE_RENAME;
   else if (response == THUNAR_JOB_RESPONSE_SKIP_ALL)
     response = THUNAR_JOB_RESPONSE_SKIP;
   else if (response == THUNAR_JOB_RESPONSE_CANCEL)
diff --git a/thunar/thunar-transfer-job.c b/thunar/thunar-transfer-job.c
index bb299ab857b12b85e317b390676e3137f4c080c0..40b4d72036eed7fecd24509b9c5a912d1a406164 100644
--- a/thunar/thunar-transfer-job.c
+++ b/thunar/thunar-transfer-job.c
@@ -104,6 +104,7 @@ struct _ThunarTransferNode
   ThunarTransferNode *children;
   GFile              *source_file;
   gboolean            replace_confirmed;
+  gboolean            rename_confirmed;
 };
 
 
@@ -336,6 +337,7 @@ thunar_transfer_job_collect_node (ThunarTransferJob  *job,
           child_node = g_slice_new0 (ThunarTransferNode);
           child_node->source_file = g_object_ref (lp->data);
           child_node->replace_confirmed = node->replace_confirmed;
+          child_node->rename_confirmed = FALSE;
 
           /* hook the child node into the child list */
           child_node->next = node->children;
@@ -493,14 +495,15 @@ ttj_copy_file (ThunarTransferJob *job,
  * @source_file        : the source #GFile to copy.
  * @target_file        : the destination #GFile to copy to.
  * @replace_confirmed  : whether the user has already confirmed that this file should replace an existing one
+ * @rename_confirmed   : whether the user has already confirmed that this file should be renamed to a new unique file name
  * @error              : return location for errors or %NULL.
  *
  * Tries to copy @source_file to @target_file. The real destination is the
  * return value and may differ from @target_file (e.g. if you try to copy
  * the file "/foo/bar" into the same directory you'll end up with something
  * like "/foo/copy of bar" instead of "/foo/bar"). If an existing file would
- * be replaced, the user is asked to confirm this unless @replace_confirmed
- * is TRUE.
+ * be replaced, the user is asked to confirm replace or rename it unless
+ * @replace_confirmed or @rename_confirmed is TRUE.
  *
  * The return value is guaranteed to be %NULL on errors and @error will
  * always be set in those cases. If the file is skipped, the return value
@@ -517,12 +520,15 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
                                GFile             *source_file,
                                GFile             *target_file,
                                gboolean           replace_confirmed,
+                               gboolean           rename_confirmed,
                                GError           **error)
 {
   ThunarJobResponse response;
+  GFile            *dest_file = target_file;
   GFileCopyFlags    copy_flags = G_FILE_COPY_NOFOLLOW_SYMLINKS;
   GError           *err = NULL;
   gint              n;
+  gint              n_rename = 0;
 
   _thunar_return_val_if_fail (THUNAR_IS_TRANSFER_JOB (job), NULL);
   _thunar_return_val_if_fail (G_IS_FILE (source_file), NULL);
@@ -537,13 +543,13 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
   while (err == NULL)
     {
       thunar_transfer_job_check_pause (job);
-      if (G_LIKELY (!g_file_equal (source_file, target_file)))
+      if (G_LIKELY (!g_file_equal (source_file, dest_file)))
         {
-          /* try to copy the file from source_file to the target_file */
-          if (ttj_copy_file (job, source_file, target_file, copy_flags, TRUE, &err))
+          /* try to copy the file from source_file to the dest_file */
+          if (ttj_copy_file (job, source_file, dest_file, copy_flags, TRUE, &err))
             {
               /* return the real target file */
-              return g_object_ref (target_file);
+              return g_object_ref (dest_file);
             }
         }
       else
@@ -581,12 +587,14 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
           /* reset the error */
           g_clear_error (&err);
 
-          /* if necessary, ask the user whether to replace the target file */
+          /* if necessary, ask the user whether to replace or rename the target file */
           if (replace_confirmed)
             response = THUNAR_JOB_RESPONSE_REPLACE;
+          else if (rename_confirmed)
+            response = THUNAR_JOB_RESPONSE_RENAME;
           else
             response = thunar_job_ask_replace (THUNAR_JOB (job), source_file,
-                                               target_file, &err);
+                                               dest_file, &err);
 
           if (err != NULL)
             break;
@@ -597,6 +605,24 @@ thunar_transfer_job_copy_file (ThunarTransferJob *job,
               copy_flags |= G_FILE_COPY_OVERWRITE;
               continue;
             }
+          else if (response == THUNAR_JOB_RESPONSE_RENAME)
+            {
+              GFile *renamed_file;
+              renamed_file = thunar_io_jobs_util_next_renamed_file (THUNAR_JOB (job),
+                                                                    source_file,
+                                                                    dest_file,
+                                                                    ++n_rename, &err);
+              if (renamed_file != NULL)
+                {
+                  if (err != NULL)
+                    g_object_unref (renamed_file);
+                  else
+                    {
+                      dest_file = renamed_file;
+                      rename_confirmed = TRUE;
+                    }
+                }
+            }
 
           /* tell the caller we skipped the file if the user
            * doesn't want to retry/overwrite */
@@ -681,6 +707,7 @@ retry_copy:
       real_target_file = thunar_transfer_job_copy_file (job, node->source_file,
                                                         target_file,
                                                         node->replace_confirmed,
+                                                        node->rename_confirmed,
                                                         &err);
       if (G_LIKELY (real_target_file != NULL))
         {
@@ -962,6 +989,44 @@ thunar_transfer_job_prepare_untrash_file (ExoJob     *job,
 }
 
 
+static gboolean
+thunar_transfer_job_move_file_with_rename (ExoJob             *job,
+                                           ThunarTransferNode *node,
+                                           GList              *tp,
+                                           GFileCopyFlags      flags,
+                                           GError            **error)
+{
+  gboolean  move_rename_successful = FALSE;
+  gint      n_rename = 1;
+  GFile    *renamed_file;
+
+  node->rename_confirmed = TRUE;
+  while (TRUE)
+    {
+      g_clear_error (error);
+      renamed_file = thunar_io_jobs_util_next_renamed_file (THUNAR_JOB (job),
+                                                            node->source_file,
+                                                            tp->data,
+                                                            n_rename++, error);
+      if (renamed_file == NULL)
+          return FALSE;
+
+      /* Try to move it again to the new renamed file.
+       * Directly try to move, because it is racy to first check for file existence
+       * and then execute something based on the outcome of that. */
+      move_rename_successful = g_file_move (node->source_file,
+                                            renamed_file,
+                                            flags,
+                                            exo_job_get_cancellable (job),
+                                            NULL, NULL, error);
+      if (!move_rename_successful && !exo_job_is_cancelled (job) && ((*error)->code == G_IO_ERROR_EXISTS))
+        continue;
+
+      return move_rename_successful;
+    }
+}
+
+
 static gboolean
 thunar_transfer_job_move_file (ExoJob                *job,
                                GFileInfo             *info,
@@ -986,7 +1051,7 @@ thunar_transfer_job_move_file (ExoJob                *job,
                                  move_flags,
                                  exo_job_get_cancellable (job),
                                  NULL, NULL, error);
-  /* if the file already exists, ask the user if they want to overwrite or skip it */
+  /* if the file already exists, ask the user if they want to overwrite, rename or skip it */
   if (!move_successful && (*error)->code == G_IO_ERROR_EXISTS)
     {
       g_clear_error (error);
@@ -1002,6 +1067,11 @@ thunar_transfer_job_move_file (ExoJob                *job,
                                          exo_job_get_cancellable (job),
                                          NULL, NULL, error);
         }
+      /* if the user chose to rename then try to do so */
+      else if (response == THUNAR_JOB_RESPONSE_RENAME)
+        {
+          move_successful = thunar_transfer_job_move_file_with_rename (job, node, tp, move_flags, error);
+        }
       /* if the user chose to cancel then abort all remaining file moves */
       else if (response == THUNAR_JOB_RESPONSE_CANCEL)
         {
@@ -1012,7 +1082,7 @@ thunar_transfer_job_move_file (ExoJob                *job,
           transfer_job->target_file_list= NULL;
           return FALSE;
         }
-      /* if the user chose not to replace the file, so that response == THUNAR_JOB_RESPONSE_SKIP,
+      /* if the user chose not to replace nor rename the file, so that response == THUNAR_JOB_RESPONSE_SKIP,
        * then *error will be NULL but move_successful will be FALSE, so that the source and target
        * files will be released and the matching list items will be dropped below
        */
@@ -1249,6 +1319,7 @@ thunar_transfer_job_new (GList                *source_node_list,
           node = g_slice_new0 (ThunarTransferNode);
           node->source_file = g_object_ref (sp->data);
           node->replace_confirmed = FALSE;
+          node->rename_confirmed = FALSE;
           job->source_node_list = g_list_append (job->source_node_list, node);
 
           /* append target file */