branch: elpa/git-commit commit 46685b1c76d06792594dcf91e9972369e61abf95 Author: Jonas Bernoulli <jo...@bernoul.li> Commit: Jonas Bernoulli <jo...@bernoul.li>
magit-ediff-resolve-all: New command --- docs/magit.org | 74 ++++++++++++++++++++++----------------------- docs/magit.texi | 78 ++++++++++++++++++++++++------------------------ lisp/magit-ediff.el | 84 +++++++++++++++++++++++++++++++++++++++++++++++----- lisp/magit-extras.el | 3 +- lisp/magit-git.el | 8 +++++ 5 files changed, 161 insertions(+), 86 deletions(-) diff --git a/docs/magit.org b/docs/magit.org index 8f7b9bb44e..d649f7ffe8 100644 --- a/docs/magit.org +++ b/docs/magit.org @@ -3498,16 +3498,48 @@ information on how to use Ediff itself, see info:ediff. - Key: E m (magit-ediff-resolve-rest) :: - Resolve outstanding conflicts in a file using Ediff, defaulting to - the file at point. + This command allows you to resolve outstanding conflicts in a file + using Ediff, defaulting to the file at point. Provided that the value of ~merge.conflictstyle~ is ~diff3~, you can view the file's merge-base revision using ~/~ in the Ediff control buffer. - In the rare event that you want to manually resolve all conflicts, - including those already resolved by Git, use - ~ediff-merge-revisions-with-ancestor~. + The A, B and Ancestor buffers are constructed from the conflict + markers in the worktree file. Because you and/or Git may have + already resolved some conflicts, that means that these buffers + to not contain the actual versions from the respective blobs. + +- Key: E m (magit-ediff-resolve-all) :: + + This command allows you to resolve all conflicts in FILE using + Ediff, defaulting to the file at point. + + Provided that the value of ~merge.conflictstyle~ is ~diff3~, you can + view the file's merge-base revision using ~/~ in the Ediff control + buffer. + + First the file in the worktree is moved aside, appending the suffix + =.ORIG=, so that you could later go back to that version. Then it is + reconstructed from the two sides of the conflict and the merge-base, + if available. + + It would be nice if the worktree file were just used as-is, but + Ediff does not support that. This means that all conflicts, that + Git has already resolved, are restored. On the other hand Ediff + also tries to resolve conflicts, and in many cases Ediff and Git + should produce similar results. + + However if you have already resolved some conflicts manually, then + those changes are discarded (though you can recover them from the + backup file). In such cases ~magit-ediff-resolve-rest~ might be more + suitable. + + The advantage that this command has over ~magit-ediff-resolve-rest~ + is that the A, B and Ancestor buffers correspond to blobs from the + respective commits, allowing you to inspect a side in context and + to use Magit commands in these buffers to do so. Blame and log + commands are particularly useful here. - Key: E t (magit-git-mergetool) :: @@ -8807,38 +8839,6 @@ also affects the diffs displayed inside Magit. Please see [[*Branching]] and http://emacsair.me/2016/01/18/magit-2.4 -*** Can Magit be used as ~ediff-version-control-package~? - -No, it cannot. For that to work the functions ~ediff-magit-internal~ -and ~ediff-magit-merge-internal~ would have to be implemented, and they -are not. These two functions are only used by the three commands -~ediff-revision~, ~ediff-merge-revisions-with-ancestor~, and -~ediff-merge-revisions~. - -These commands only delegate the task of populating buffers with -certain revisions to the "internal" functions. The equally important -task of determining which revisions are to be compared/merged is not -delegated. Instead this is done without any support whatsoever from -the version control package/system - meaning that the user has to -enter the revisions explicitly. Instead of implementing -~ediff-magit-internal~ we provide ~magit-ediff-compare~, which handles -both tasks like it is 2005. - -The other commands ~ediff-merge-revisions~ and -~ediff-merge-revisions-with-ancestor~ are normally not what you want -when using a modern version control system like Git. Instead of -letting the user resolve only those conflicts which Git could not -resolve on its own, they throw away all work done by Git and then -expect the user to manually merge all conflicts, including those that -had already been resolved. That made sense back in the days when -version control systems couldn't merge (or so I have been told), but -not anymore. Once in a blue moon you might actually want to see all -conflicts, in which case you *can* use these commands, which then use -~ediff-vc-merge-internal~. So we don't actually have to implement -~ediff-magit-merge-internal~. Instead we provide the more useful -command ~magit-ediff-resolve~ which only shows yet-to-be resolved -conflicts. - *** Should I disable VC? If you don't use VC (the built-in version control interface) then diff --git a/docs/magit.texi b/docs/magit.texi index d36993b70c..ea7d1873a2 100644 --- a/docs/magit.texi +++ b/docs/magit.texi @@ -329,7 +329,6 @@ FAQ - How to @dots{}? * How to install the gitman info manual?:: * How to show diffs for gpg-encrypted files?:: * How does branching and pushing work?:: -* Can Magit be used as @code{ediff-version-control-package}?:: * Should I disable VC@?:: @@ -4369,16 +4368,49 @@ common ancestor of both revisions (i.e., use a "@dots{}" range). @item @kbd{E m} (@code{magit-ediff-resolve-rest}) @kindex E m @findex magit-ediff-resolve-rest -Resolve outstanding conflicts in a file using Ediff, defaulting to -the file at point. +This command allows you to resolve outstanding conflicts in a file +using Ediff, defaulting to the file at point. Provided that the value of @code{merge.conflictstyle} is @code{diff3}, you can view the file's merge-base revision using @code{/} in the Ediff control buffer. -In the rare event that you want to manually resolve all conflicts, -including those already resolved by Git, use -@code{ediff-merge-revisions-with-ancestor}. +The A, B and Ancestor buffers are constructed from the conflict +markers in the worktree file. Because you and/or Git may have +already resolved some conflicts, that means that these buffers +to not contain the actual versions from the respective blobs. + +@item @kbd{E m} (@code{magit-ediff-resolve-all}) +@kindex E m +@findex magit-ediff-resolve-all +This command allows you to resolve all conflicts in FILE using +Ediff, defaulting to the file at point. + +Provided that the value of @code{merge.conflictstyle} is @code{diff3}, you can +view the file's merge-base revision using @code{/} in the Ediff control +buffer. + +First the file in the worktree is moved aside, appending the suffix +@samp{.ORIG}, so that you could later go back to that version. Then it is +reconstructed from the two sides of the conflict and the merge-base, +if available. + +It would be nice if the worktree file were just used as-is, but +Ediff does not support that. This means that all conflicts, that +Git has already resolved, are restored. On the other hand Ediff +also tries to resolve conflicts, and in many cases Ediff and Git +should produce similar results. + +However if you have already resolved some conflicts manually, then +those changes are discarded (though you can recover them from the +backup file). In such cases @code{magit-ediff-resolve-rest} might be more +suitable. + +The advantage that this command has over @code{magit-ediff-resolve-rest} +is that the A, B and Ancestor buffers correspond to blobs from the +respective commits, allowing you to inspect a side in context and +to use Magit commands in these buffers to do so. Blame and log +commands are particularly useful here. @item @kbd{E t} (@code{magit-git-mergetool}) @kindex E t @@ -10695,7 +10727,6 @@ Please also see @ref{Debugging Tools}. * How to install the gitman info manual?:: * How to show diffs for gpg-encrypted files?:: * How does branching and pushing work?:: -* Can Magit be used as @code{ediff-version-control-package}?:: * Should I disable VC@?:: @end menu @@ -10769,39 +10800,6 @@ echo "*.gpg filter=gpg diff=gpg" > .gitattributes Please see @ref{Branching} and @uref{http://emacsair.me/2016/01/18/magit-2.4} -@node Can Magit be used as @code{ediff-version-control-package}? -@appendixsubsec Can Magit be used as @code{ediff-version-control-package}? - -No, it cannot. For that to work the functions @code{ediff-magit-internal} -and @code{ediff-magit-merge-internal} would have to be implemented, and they -are not. These two functions are only used by the three commands -@code{ediff-revision}, @code{ediff-merge-revisions-with-ancestor}, and -@code{ediff-merge-revisions}. - -These commands only delegate the task of populating buffers with -certain revisions to the "internal" functions. The equally important -task of determining which revisions are to be compared/merged is not -delegated. Instead this is done without any support whatsoever from -the version control package/system - meaning that the user has to -enter the revisions explicitly. Instead of implementing -@code{ediff-magit-internal} we provide @code{magit-ediff-compare}, which handles -both tasks like it is 2005. - -The other commands @code{ediff-merge-revisions} and -@code{ediff-merge-revisions-with-ancestor} are normally not what you want -when using a modern version control system like Git. Instead of -letting the user resolve only those conflicts which Git could not -resolve on its own, they throw away all work done by Git and then -expect the user to manually merge all conflicts, including those that -had already been resolved. That made sense back in the days when -version control systems couldn't merge (or so I have been told), but -not anymore. Once in a blue moon you might actually want to see all -conflicts, in which case you @strong{can} use these commands, which then use -@code{ediff-vc-merge-internal}. So we don't actually have to implement -@code{ediff-magit-merge-internal}. Instead we provide the more useful -command @code{magit-ediff-resolve} which only shows yet-to-be resolved -conflicts. - @node Should I disable VC@? @appendixsubsec Should I disable VC@? diff --git a/lisp/magit-ediff.el b/lisp/magit-ediff.el index a3bf6c477b..54f6d2ffdf 100644 --- a/lisp/magit-ediff.el +++ b/lisp/magit-ediff.el @@ -113,8 +113,9 @@ recommend you do not further complicate that by enabling this.") :info-manual "(ediff)" ["Ediff" [("E" "Dwim" magit-ediff-dwim) - ("s" "Stage" magit-ediff-stage) - ("m" "Resolve" magit-ediff-resolve-rest) + ("s" "Stage" magit-ediff-stage)] + [("m" "Resolve rest" magit-ediff-resolve-rest) + ("M" "Resolve all conflicts" magit-ediff-resolve-all) ("t" "Resolve using mergetool" magit-git-mergetool)] [("u" "Show unstaged" magit-ediff-show-unstaged) ("i" "Show staged" magit-ediff-show-staged) @@ -198,14 +199,78 @@ is put in FILE." ('(t t) 'ediff-merge-buffers-with-ancestor)) file))))) +;;;###autoload +(defun magit-ediff-resolve-all (file) + "Resolve all conflicts in FILE using Ediff. +See info node `(magit) Ediffing' for more information about this +and alternative commands." + (interactive (list (magit-read-unmerged-file))) + (magit-with-toplevel + (let* ((revA (or (magit-name-branch "HEAD") + (magit-commit-p "HEAD"))) + (revB (cl-find-if (lambda (head) (file-exists-p (magit-git-dir head))) + '("MERGE_HEAD" "CHERRY_PICK_HEAD" "REVERT_HEAD"))) + (revB (or (magit-name-branch revB) + (magit-commit-p revB))) + (revC (magit-commit-p (magit-git-string "merge-base" revA revB))) + (fileA (magit--rev-file-name file revA revB)) + (fileB (magit--rev-file-name file revB revA)) + (fileC (or (magit--rev-file-name file revC revA) + (magit--rev-file-name file revC revB)))) + ;; Ediff assumes that the FILE where it is going to store the merge + ;; result does not exist yet, so move the existing file out of the + ;; way. If a buffer visits FILE, then we have to kill that upfront. + (when-let ((buffer (find-buffer-visiting file))) + (when (and (buffer-modified-p buffer) + (not (y-or-n-p (format "Save buffer %s %s? " + (buffer-name buffer) + "(cannot continue otherwise)")))) + (user-error "Abort")) + (kill-buffer buffer)) + (let ((orig (concat file ".ORIG"))) + (when (file-exists-p orig) + (rename-file orig (make-temp-name (concat orig "_")))) + (rename-file file orig)) + (let ((setup (lambda () + ;; Use the same conflict marker style as Git uses. + (setq-local ediff-combination-pattern + '("<<<<<<< HEAD" A + ,(format "||||||| %s" revC) Ancestor + "=======" B + ,(format ">>>>>>> %s" revB))))) + (quit (lambda () + ;; For merge jobs Ediff switches buffer names around. + ;; At this point `ediff-buffer-C' no longer refer to + ;; the ancestor buffer but to the merge result buffer. + ;; See (if ediff-merge-job ...) in `ediff-setup'. + (when (buffer-live-p ediff-buffer-C) + (with-current-buffer ediff-buffer-C + (save-buffer) + (save-excursion + (goto-char (point-min)) + (unless (re-search-forward "^<<<<<<< " nil t) + (magit-stage-file file)))))))) + (if fileC + (magit-ediff-buffers + ((magit-get-revision-buffer revA fileA) + (magit-find-file-noselect revA fileA)) + ((magit-get-revision-buffer revB fileB) + (magit-find-file-noselect revB fileB)) + ((magit-get-revision-buffer revC fileC) + (magit-find-file-noselect revC fileC)) + setup quit file) + (magit-ediff-buffers + ((magit-get-revision-buffer revA fileA) + (magit-find-file-noselect revA fileA)) + ((magit-get-revision-buffer revB fileB) + (magit-find-file-noselect revB fileB)) + nil setup quit file)))))) + ;;;###autoload (defun magit-ediff-resolve-rest (file) "Resolve outstanding conflicts in FILE using Ediff. -FILE has to be relative to the top directory of the repository. - -In the rare event that you want to manually resolve all -conflicts, including those already resolved by Git, use -`ediff-merge-revisions-with-ancestor'." +See info node `(magit) Ediffing' for more information about this +and alternative commands." (interactive (list (magit-read-unmerged-file))) (magit-with-toplevel (with-current-buffer (find-file-noselect file) @@ -390,7 +455,10 @@ mind at all, then it asks the user for a command to run." (?c "[c]ommit" #'magit-ediff-show-commit) (?r "[r]ange" #'magit-ediff-compare) (?s "[s]tage" #'magit-ediff-stage) - (?v "resol[v]e" #'magit-ediff-resolve-rest)))) + (?m "[m] resolve remaining conflicts" + #'magit-ediff-resolve-rest) + (?M "[M] resolve all conflicts" + #'magit-ediff-resolve-all)))) ((eq command #'magit-ediff-compare) (apply #'magit-ediff-compare revA revB (magit-ediff-read-files revA revB file))) diff --git a/lisp/magit-extras.el b/lisp/magit-extras.el index c1a11d8b45..8308cd8404 100644 --- a/lisp/magit-extras.el +++ b/lisp/magit-extras.el @@ -50,7 +50,8 @@ (transient-define-prefix magit-git-mergetool (file args &optional transient) "Resolve conflicts in FILE using \"git mergetool --gui\". With a prefix argument allow changing ARGS using a transient -popup." +popup. See info node `(magit) Ediffing' for information about +alternative commands." :man-page "git-mergetool" ["Settings" ("-t" magit-git-mergetool:--tool) diff --git a/lisp/magit-git.el b/lisp/magit-git.el index 5cb4b5e02c..d3b88d8461 100644 --- a/lisp/magit-git.el +++ b/lisp/magit-git.el @@ -1108,6 +1108,14 @@ range. Otherwise, it can be any revision or range accepted by "--diff-filter=R" revA revB) 3))) +(defun magit--rev-file-name (file rev other-rev) + "For FILE, potentially renamed between REV and OTHER-REV, return name in REV. +Return nil, if FILE appears neither in REV nor OTHER-REV, +or if no rename is detected." + (or (car (member file (magit-revision-files rev))) + (and-let* ((renamed (magit-renamed-files rev other-rev))) + (car (rassoc file renamed))))) + (defun magit-file-status (&rest args) (magit--with-temp-process-buffer (save-excursion (magit-git-insert "status" "-z" args))