branch: elpa/git-commit
commit 7d4092f00bab34d46c4e07d6d6c55658e413b3b6
Author: Jonas Bernoulli <[email protected]>
Commit: Jonas Bernoulli <[email protected]>
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)