branch: elpa/git-commit
commit 7d4092f00bab34d46c4e07d6d6c55658e413b3b6
Author: Jonas Bernoulli <jo...@bernoul.li>
Commit: Jonas Bernoulli <jo...@bernoul.li>

    Apply stash even if "git stash apply" cannot do it
---
 docs/magit.org      | 56 +++++++++++++++++++++++++++++++++++------
 docs/magit.texi     | 57 ++++++++++++++++++++++++++++++++++++------
 lisp/magit-base.el  |  8 ++++++
 lisp/magit-stash.el | 72 ++++++++++++++++++++++++++++++++++++++++++++---------
 4 files changed, 167 insertions(+), 26 deletions(-)

diff --git a/docs/magit.org b/docs/magit.org
index c2aa7328cc..e16223ca2f 100644
--- a/docs/magit.org
+++ b/docs/magit.org
@@ -1525,6 +1525,13 @@ telling Magit to ask fewer questions.
 
   - Various:
 
+    - ~stash-apply-3way~  When a stash cannot be applied using
+      ~git stash apply~, then Magit uses ~git apply~ instead.
+      If doing so is safe, then it uses ~--3way~, when it is not
+      because doing so requires that some files are first staged,
+      then by default it prompts the user whether to use ~--3way~
+      or ~--reject~.  Add this symbol to always use ~--3way~.
+
     - ~kill-process~ There seldom is a reason to kill a process.
 
   - Global settings:
@@ -6396,16 +6403,51 @@ Also see [[man:git-stash]]
 
 - Key: z a (magit-stash-apply) ::
 
-  Apply a stash to the working tree.  Try to preserve the stash index.
-  If that fails because there are staged changes, apply without
-  preserving the stash index.
+  Apply a stash to the working tree.
+
+  First try ~git stash apply --index~, which tries to preserve
+  the index stored in the stash, if any.  This may fail because
+  applying the stash could result in conflicts and those have to
+  be stored in the index, making it impossible to also store the
+  stash's index there as well.
+
+  If the above failed, then try ~git stash apply~.  This fails
+  (with or without ~--index~) if there are any uncommitted
+  changes to files that are also modified in the stash.
+
+  If both of the above failed, then apply using ~git apply~.
+  If there are no conflicting files, use ~--3way~.  If there are
+  conflicting files, then using ~--3way~ requires that those
+  files are staged first, which may be undesirable, so prompt
+  the user whether to use ~--3way~ or ~--reject~.
+
+  Customize ~magit-no-confirm~ if you want to always use ~--3way~,
+  without being prompted.
 
 - Key: z p (magit-stash-pop) ::
 
-  Apply a stash to the working tree and remove it from stash list.
-  Try to preserve the stash index.  If that fails because there are
-  staged changes, apply without preserving the stash index and forgo
-  removing the stash.
+  Apply a stash to the working tree.  On complete success (if the
+  stash can be applied without any conflicts, and while preserving
+  the stash's index) then remove the stash from stash list.
+
+  First try ~git stash pop --index~, which tries to preserve
+  the index stored in the stash, if any.  This may fail because
+  applying the stash could result in conflicts and those have to
+  be stored in the index, making it impossible to also store the
+  stash's index there as well.
+
+  If the above failed, then try ~git stash apply~.  This fails
+  (with or without ~--index~) if there are any uncommitted
+  changes to files that are also modified in the stash.
+
+  If both of the above failed, then apply using ~git apply~.
+  If there are no conflicting files, use ~--3way~.  If there are
+  conflicting files, then using ~--3way~ requires that those
+  files are staged first, which may be undesirable, so prompt
+  the user whether to use ~--3way~ or ~--reject~.
+
+  Customize ~magit-no-confirm~ if you want to always use ~--3way~,
+  without being prompted.
 
 - Key: z k (magit-stash-drop) ::
 
diff --git a/docs/magit.texi b/docs/magit.texi
index 543e615f9a..4fcf527fc5 100644
--- a/docs/magit.texi
+++ b/docs/magit.texi
@@ -2021,6 +2021,14 @@ set @code{magit-published-branches} to @code{nil}.
 Various:
 
 @itemize
+@item
+@code{stash-apply-3way}  When a stash cannot be applied using
+@code{git stash apply}, then Magit uses @code{git apply} instead.
+If doing so is safe, then it uses @code{--3way}, when it is not
+because doing so requires that some files are first staged,
+then by default it prompts the user whether to use @code{--3way}
+or @code{--reject}.  Add this symbol to always use @code{--3way}.
+
 @item
 @code{kill-process} There seldom is a reason to kill a process.
 @end itemize
@@ -7889,17 +7897,52 @@ prefix arguments are equivalent to @code{--all}-.
 @item @kbd{z a} (@code{magit-stash-apply})
 @kindex z a
 @findex magit-stash-apply
-Apply a stash to the working tree.  Try to preserve the stash index.
-If that fails because there are staged changes, apply without
-preserving the stash index.
+Apply a stash to the working tree.
+
+First try @code{git stash apply --index}, which tries to preserve
+the index stored in the stash, if any.  This may fail because
+applying the stash could result in conflicts and those have to
+be stored in the index, making it impossible to also store the
+stash's index there as well.
+
+If the above failed, then try @code{git stash apply}.  This fails
+(with or without @code{--index}) if there are any uncommitted
+changes to files that are also modified in the stash.
+
+If both of the above failed, then apply using @code{git apply}.
+If there are no conflicting files, use @code{--3way}.  If there are
+conflicting files, then using @code{--3way} requires that those
+files are staged first, which may be undesirable, so prompt
+the user whether to use @code{--3way} or @code{--reject}.
+
+Customize @code{magit-no-confirm} if you want to always use @code{--3way},
+without being prompted.
 
 @item @kbd{z p} (@code{magit-stash-pop})
 @kindex z p
 @findex magit-stash-pop
-Apply a stash to the working tree and remove it from stash list.
-Try to preserve the stash index.  If that fails because there are
-staged changes, apply without preserving the stash index and forgo
-removing the stash.
+Apply a stash to the working tree.  On complete success (if the
+stash can be applied without any conflicts, and while preserving
+the stash's index) then remove the stash from stash list.
+
+First try @code{git stash pop --index}, which tries to preserve
+the index stored in the stash, if any.  This may fail because
+applying the stash could result in conflicts and those have to
+be stored in the index, making it impossible to also store the
+stash's index there as well.
+
+If the above failed, then try @code{git stash apply}.  This fails
+(with or without @code{--index}) if there are any uncommitted
+changes to files that are also modified in the stash.
+
+If both of the above failed, then apply using @code{git apply}.
+If there are no conflicting files, use @code{--3way}.  If there are
+conflicting files, then using @code{--3way} requires that those
+files are staged first, which may be undesirable, so prompt
+the user whether to use @code{--3way} or @code{--reject}.
+
+Customize @code{magit-no-confirm} if you want to always use @code{--3way},
+without being prompted.
 
 @item @kbd{z k} (@code{magit-stash-drop})
 @kindex z k
diff --git a/lisp/magit-base.el b/lisp/magit-base.el
index 8aad2fb3af..64c0a99805 100644
--- a/lisp/magit-base.el
+++ b/lisp/magit-base.el
@@ -168,6 +168,7 @@ The value has the form ((COMMAND nil|PROMPT DEFAULT)...).
     (const remove-modules)
     (const remove-dirty-modules)
     (const trash-module-gitdirs)
+    (const stash-apply-3way)
     (const kill-process)
     (const safe-with-wip)))
 
@@ -317,6 +318,13 @@ Removing modules:
 
 Various:
 
+  `stash-apply-3way'  When a stash cannot be applied using
+  \"git stash apply\", then Magit uses \"git apply\" instead.
+  If doing so is safe, then it uses \"--3way\", when it is not
+  because doing so requires that some files are first staged,
+  then by default it prompts the user whether to use \"--3way\"
+  or \"--reject\".  Add this symbol to always use \"--3way\".
+
   `kill-process' There seldom is a reason to kill a process.
 
 Global settings:
diff --git a/lisp/magit-stash.el b/lisp/magit-stash.el
index b784658b67..beed295563 100644
--- a/lisp/magit-stash.el
+++ b/lisp/magit-stash.el
@@ -237,23 +237,71 @@ specifying a list of files to be stashed."
 ;;;###autoload
 (defun magit-stash-apply (stash)
   "Apply a stash to the working tree.
-Try to preserve the stash index.  If that fails because there
-are staged changes, apply without preserving the stash index."
+
+First try \"git stash apply --index\", which tries to preserve
+the index stored in the stash, if any.  This may fail because
+applying the stash could result in conflicts and those have to
+be stored in the index, making it impossible to also store the
+stash's index there as well.
+
+If the above failed, then try \"git stash apply\".  This fails
+\(with or without \"--index\") if there are any uncommitted
+changes to files that are also modified in the stash.
+
+If both of the above failed, then apply using \"git apply\".
+If there are no conflicting files, use \"--3way\".  If there are
+conflicting files, then using \"--3way\" requires that those
+files are staged first, which may be undesirable, so prompt
+the user whether to use \"--3way\" or \"--reject\"."
   (interactive (list (magit-read-stash "Apply stash")))
-  (if (= (magit-call-git "stash" "apply" "--index" stash) 0)
-      (magit-refresh)
-    (magit-run-git "stash" "apply" stash)))
+  (magit-stash--apply "apply" stash))
 
 ;;;###autoload
 (defun magit-stash-pop (stash)
-  "Apply a stash to the working tree and remove it from stash list.
-Try to preserve the stash index.  If that fails because there
-are staged changes, apply without preserving the stash index
-and forgo removing the stash."
+  "Apply a stash to the working tree, on success remove it from stash list.
+
+First try \"git stash pop --index\", which tries to preserve
+the index stored in the stash, if any.  This may fail because
+applying the stash could result in conflicts and those have to
+be stored in the index, making it impossible to also store the
+stash's index there as well.
+
+If the above failed, then try \"git stash apply\".  This fails
+\(with or without \"--index\") if there are any uncommitted
+changes to files that are also modified in the stash.
+
+If both of the above failed, then apply using \"git apply\".
+If there are no conflicting files, use \"--3way\".  If there are
+conflicting files, then using \"--3way\" requires that those
+files are staged first, which may be undesirable, so prompt
+the user whether to use \"--3way\" or \"--reject\"."
   (interactive (list (magit-read-stash "Pop stash")))
-  (if (= (magit-call-git "stash" "apply" "--index" stash) 0)
-      (magit-stash-drop stash)
-    (magit-run-git "stash" "apply" stash)))
+  (magit-stash--apply "pop" stash))
+
+(defun magit-stash--apply (action stash)
+  (or (= (magit-call-git "stash" action "--index" stash) 0)
+      ;; The stash's index could not be applied, so always keep the stash.
+      (= (magit-call-git "stash" "apply" stash) 0)
+      (let* ((range (format "%s^..%s" stash stash))
+             (stashed (magit-git-items "diff" "-z" "--name-only" range "--"))
+             (conflicts (cl-sort (cl-union (magit-unstaged-files t stashed)
+                                           (magit-untracked-files t stashed)
+                                           :test #'equal)
+                                 #'string<))
+             (arg (cond
+                   ((not conflicts) "--3way")
+                   ((magit-confirm-files
+                     'stash-apply-3way conflicts
+                     "Apply stash using `--3way', which requires first staging"
+                     "(else use `--reject')"
+                     t)
+                    (magit-stage-1 nil conflicts)
+                    "--3way")
+                   ("--reject"))))
+        (with-temp-buffer
+          (magit-git-insert "diff" range)
+          (magit-run-git-with-input "apply" arg "-"))))
+  (magit-refresh))
 
 ;;;###autoload
 (defun magit-stash-drop (stash)

Reply via email to