aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Whitton2025-09-04 11:25:25 +0100
committerSean Whitton2025-09-16 14:30:02 +0100
commit208e80018a7cdc87b35c7f8a08a243600c54c9cc (patch)
tree8905aae6b0f9976d628345e083406f4aa02fdba5
parent0000d9b7b1a457793deecff2cb56ec82c04545f3 (diff)
downloademacs-208e80018a7cdc87b35c7f8a08a243600c54c9cc.tar.gz
emacs-208e80018a7cdc87b35c7f8a08a243600c54c9cc.zip
New commands to apply changes to other working trees
* lisp/vc/diff-mode.el (diff-apply-buffer): New TEST argument. * lisp/vc/vc.el (diff-apply-buffer): Declare. (vc-no-confirm-moving-changes): New user option. (vc-apply-to-other-working-tree) (vc-apply-root-to-other-working-tree): New commands. * lisp/vc/vc-hooks.el (vc-prefix-map): Bind them. * doc/emacs/vc1-xtra.texi (Other Working Trees): * etc/NEWS: Document them.
-rw-r--r--doc/emacs/vc1-xtra.texi52
-rw-r--r--etc/NEWS2
-rw-r--r--lisp/vc/diff-mode.el50
-rw-r--r--lisp/vc/vc-hooks.el4
-rw-r--r--lisp/vc/vc.el151
5 files changed, 227 insertions, 32 deletions
diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi
index b5a0f0c129b..15b7a73e5d7 100644
--- a/doc/emacs/vc1-xtra.texi
+++ b/doc/emacs/vc1-xtra.texi
@@ -397,15 +397,15 @@ want to disturb. So you type @kbd{C-x v w c} (see below) and create a
397new working tree, following the prompts to check out the version 2 397new working tree, following the prompts to check out the version 2
398branch there. 398branch there.
399 399
400You apply the patch to that working tree, build and test it. Satisfied, 400You apply the patch to that working tree using @w{@kbd{C-x v w a}} (see
401you use @kbd{C-x v P} (@pxref{Pulling / Pushing}) in the other working 401below), build and test it. Satisfied, you use @w{@kbd{C-x v P}}
402tree. In the course of testing the patch, you've realised that the bug 402(@pxref{Pulling / Pushing}) in the other working tree. In the course of
403exists in version 3 of the software, too. So you switch back to your 403testing the patch, you've realised that the bug exists in version 3 of
404first working tree, and use @kbd{C-x v m} (@pxref{Merging}) to merge the 404the software, too. So you switch back to your first working tree, and
405branch you have checked out in the other working tree. Now your version 405use @kbd{C-x v m} (@pxref{Merging}) to merge the branch you have checked
406of the trunk has all of version 2's fixes merged into it, but you 406out in the other working tree. Now your version of the trunk has all of
407haven't pushed it yet because you're still refactoring. You'll use 407version 2's fixes merged into it, but you haven't pushed it yet because
408@kbd{C-x v P} later. 408you're still refactoring. You'll use @kbd{C-x v P} later.
409@end indentedblock 409@end indentedblock
410 410
411Ordinary VC commands like @kbd{C-x v v} (@pxref{Basic VC Editing}) and 411Ordinary VC commands like @kbd{C-x v v} (@pxref{Basic VC Editing}) and
@@ -440,6 +440,12 @@ Visit this file or directory in another working tree.
440Invoke @kbd{C-x p p} (@code{project-switch-project}) but limited to 440Invoke @kbd{C-x p p} (@code{project-switch-project}) but limited to
441other working trees. 441other working trees.
442 442
443@item C-x v w a
444Copy or move fileset changes to another working tree.
445
446@item C-x v w A
447Copy or move all changes to another working tree.
448
443@item C-x v w x 449@item C-x v w x
444Delete a working tree you no longer need. 450Delete a working tree you no longer need.
445 451
@@ -500,6 +506,34 @@ to other working trees. The main difference between @kbd{C-x v w w} and
500buffer in the other working tree while the latter considers the other 506buffer in the other working tree while the latter considers the other
501working tree as a whole, independent project. 507working tree as a whole, independent project.
502 508
509@kindex C-x v w a
510@findex vc-apply-to-other-working-tree
511The command @kbd{C-x v w a} (@code{vc-apply-to-other-working-tree})
512prompts you to select a working tree, then copies changes from the
513current working tree to that other working tree. With a prefix
514argument, it moves changes instead of just copying them. Usually the
515command operates on local (uncommitted) changes to the current VC
516fileset. When invoked in a Diff mode (@pxref{Diff Mode}) buffer, it
517operates on the changes specified by the contents of that buffer. The
518command stops and does nothing if any of the changes don't apply.
519
520@kbd{C-x v w a} is useful to copy changes to a temporary working tree in
521order to test them. It is also useful to copy fixes back to your main
522working tree for checking in. For example, you might hack away at a bug
523in a temporary working tree, and fix it. You'd then want to copy or
524move the fix back to your main working tree to check it in and push it.
525
526@kindex C-x v w A
527@findex vc-apply-root-to-other-working-tree
528The command @kbd{C-x v w A} works similarly, except that it always
529copies or moves all local changes to the whole working tree, not just
530changes to the current VC fileset or changes represented by the contents
531of a Diff mode buffer. With two prefix arguments, this command shows a
532preview of changes to be copied, leaving you to apply them using
533standard Diff mode commands like @kbd{C-c C-a} and @w{@kbd{C-c <RET> a}}
534(@pxref{Diff Mode}). (@w{@kbd{C-u C-u C-x v w A}} is roughly equivalent
535to typing @w{@kbd{C-x v D}} followed by @w{@kbd{C-x v w w}}.)
536
503@kindex C-x v w x 537@kindex C-x v w x
504@kindex C-x v w R 538@kindex C-x v w R
505@findex vc-delete-working-tree 539@findex vc-delete-working-tree
diff --git a/etc/NEWS b/etc/NEWS
index 27e3f1e4ce0..736874c1a0a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2072,6 +2072,8 @@ other working trees:
2072- 'C-x v w c': Add a new working tree. 2072- 'C-x v w c': Add a new working tree.
2073- 'C-x v w w': Visit this file in another working tree. 2073- 'C-x v w w': Visit this file in another working tree.
2074- 'C-x v w s': Like 'C-x p p' but limited to other working trees. 2074- 'C-x v w s': Like 'C-x p p' but limited to other working trees.
2075- 'C-x v w a': Copy or move fileset changes to another working tree.
2076- 'C-x v w A': Copy or move all changes to another working tree.
2075- 'C-x v w x': Delete a working tree you no longer need. 2077- 'C-x v w x': Delete a working tree you no longer need.
2076- 'C-x v w R': Relocate a working tree to another file name. 2078- 'C-x v w R': Relocate a working tree to another file name.
2077 2079
diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index c58ad67ff52..f207f87811c 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -2213,17 +2213,21 @@ customize `diff-ask-before-revert-and-kill-hunk' to control that."
2213 (when (null (diff-apply-buffer beg end t)) 2213 (when (null (diff-apply-buffer beg end t))
2214 (diff-hunk-kill))))) 2214 (diff-hunk-kill)))))
2215 2215
2216(defun diff-apply-buffer (&optional beg end reverse) 2216(defun diff-apply-buffer (&optional beg end reverse test)
2217 "Apply the diff in the entire diff buffer. 2217 "Apply the diff in the entire diff buffer.
2218Interactively, if the region is active, apply all hunks that the region 2218Interactively, if the region is active, apply all hunks that the region
2219overlaps; otherwise, apply all hunks. 2219overlaps; otherwise, apply all hunks.
2220With a prefix argument, reverse-apply the hunks. 2220With a prefix argument, reverse-apply the hunks.
2221If applying all hunks succeeds, save the changed buffers. 2221If applying all hunks succeeds, save the changed buffers.
2222 2222
2223When called from Lisp with optional arguments, restrict the application 2223When called from Lisp, returns nil if buffers were successfully modified
2224to hunks lying between BEG and END, and reverse-apply them when REVERSE 2224and saved, or the number of failed hunk applications otherwise.
2225is non-nil. Returns nil if buffers were successfully modified and 2225Optional arguments BEG and END restrict the hunks to be applied to those
2226saved, or the number of failed hunk applications otherwise." 2226lying between BEG and END.
2227Optional argument REVERSE means to reverse-apply hunks.
2228Optional argument TEST means to not actually apply or reverse-apply any
2229hunks, but return the same information: nil if all hunks can be applied,
2230or the number of hunks that can't be applied."
2227 (interactive (list (use-region-beginning) 2231 (interactive (list (use-region-beginning)
2228 (use-region-end) 2232 (use-region-end)
2229 current-prefix-arg)) 2233 current-prefix-arg))
@@ -2234,7 +2238,7 @@ saved, or the number of failed hunk applications otherwise."
2234 (goto-char (or beg (point-min))) 2238 (goto-char (or beg (point-min)))
2235 (diff-beginning-of-hunk t) 2239 (diff-beginning-of-hunk t)
2236 (while (pcase-let ((`(,buf ,line-offset ,pos ,_src ,dst ,switched) 2240 (while (pcase-let ((`(,buf ,line-offset ,pos ,_src ,dst ,switched)
2237 (diff-find-source-location nil reverse))) 2241 (diff-find-source-location nil reverse test)))
2238 (cond ((and line-offset (not switched)) 2242 (cond ((and line-offset (not switched))
2239 (push (cons pos dst) 2243 (push (cons pos dst)
2240 (alist-get buf buffer-edits))) 2244 (alist-get buf buffer-edits)))
@@ -2244,23 +2248,25 @@ saved, or the number of failed hunk applications otherwise."
2244 (or (not end) (< (point) end)) 2248 (or (not end) (< (point) end))
2245 (looking-at-p diff-hunk-header-re))))) 2249 (looking-at-p diff-hunk-header-re)))))
2246 (cond ((zerop failures) 2250 (cond ((zerop failures)
2247 (dolist (buf-edits (reverse buffer-edits)) 2251 (unless test
2248 (with-current-buffer (car buf-edits) 2252 (dolist (buf-edits (reverse buffer-edits))
2249 (dolist (edit (cdr buf-edits)) 2253 (with-current-buffer (car buf-edits)
2250 (let ((pos (car edit)) 2254 (dolist (edit (cdr buf-edits))
2251 (dst (cdr edit)) 2255 (let ((pos (car edit))
2252 (inhibit-read-only t)) 2256 (dst (cdr edit))
2253 (goto-char (car pos)) 2257 (inhibit-read-only t))
2254 (delete-region (car pos) (cdr pos)) 2258 (goto-char (car pos))
2255 (insert (car dst)))) 2259 (delete-region (car pos) (cdr pos))
2256 (save-buffer))) 2260 (insert (car dst))))
2257 (message "Saved %d buffers" (length buffer-edits)) 2261 (save-buffer)))
2262 (message "Saved %d buffers" (length buffer-edits)))
2258 nil) 2263 nil)
2259 (t 2264 (t
2260 (message (ngettext "%d hunk failed; no buffers changed" 2265 (unless test
2261 "%d hunks failed; no buffers changed" 2266 (message (ngettext "%d hunk failed; no buffers changed"
2262 failures) 2267 "%d hunks failed; no buffers changed"
2263 failures) 2268 failures)
2269 failures))
2264 failures)))) 2270 failures))))
2265 2271
2266(defalias 'diff-mouse-goto-source #'diff-goto-source) 2272(defalias 'diff-mouse-goto-source #'diff-goto-source)
@@ -2616,7 +2622,7 @@ Call FUN with two args (BEG and END) for each hunk."
2616 (or (ignore-errors (diff-hunk-next) (point)) 2622 (or (ignore-errors (diff-hunk-next) (point))
2617 max))))))))) 2623 max)))))))))
2618 2624
2619;; This doesn't use `diff--iterate-hunks', since that assumes that 2625;; This doesn't use `diff--iterate-hunks' because that assumes that
2620;; hunks don't change size. 2626;; hunks don't change size.
2621(defun diff--ignore-whitespace-all-hunks () 2627(defun diff--ignore-whitespace-all-hunks ()
2622 "Re-diff all the hunks, ignoring whitespace-differences." 2628 "Re-diff all the hunks, ignoring whitespace-differences."
diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index ded24ada8a0..da67f7f1815 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -970,7 +970,9 @@ In the latter case, VC mode is deactivated for this buffer."
970 "w w" #'vc-switch-working-tree 970 "w w" #'vc-switch-working-tree
971 "w s" #'vc-working-tree-switch-project 971 "w s" #'vc-working-tree-switch-project
972 "w x" #'vc-delete-working-tree 972 "w x" #'vc-delete-working-tree
973 "w R" #'vc-move-working-tree) 973 "w R" #'vc-move-working-tree
974 "w a" #'vc-apply-to-other-working-tree
975 "w A" #'vc-apply-root-to-other-working-tree)
974(fset 'vc-prefix-map vc-prefix-map) 976(fset 'vc-prefix-map vc-prefix-map)
975(define-key ctl-x-map "v" 'vc-prefix-map) 977(define-key ctl-x-map "v" 'vc-prefix-map)
976 978
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index e0e9d0e2ac6..07b9ea8b951 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -4661,6 +4661,157 @@ BACKEND is the VC backend."
4661 (when-let* ((p (project-current nil to))) 4661 (when-let* ((p (project-current nil to)))
4662 (project-remember-project p))) 4662 (project-remember-project p)))
4663 4663
4664(declare-function diff-apply-buffer "diff-mode")
4665
4666;;;###autoload
4667(defun vc-apply-to-other-working-tree (directory &optional move)
4668 "Apply VC fileset's local changes to working tree under DIRECTORY.
4669Must be called from within an existing VC working tree.
4670When called interactively, prompts for DIRECTORY.
4671With a prefix argument (when called from Lisp, with optional argument
4672MOVE non-nil), don't just copy the changes, but move them, from the
4673current working tree to DIRECTORY.
4674
4675When called from a `diff-mode' buffer, move or copy the changes
4676specified by the contents of that buffer, only.
4677
4678If any changes to be moved or copied can't be applied to DIRECTORY, it
4679is an error, and no changes are applied.
4680If any changes to be moved can't be reverse-applied to this working
4681tree, it is an error, and no changes are moved."
4682 ;; The double prefix arg that `vc-apply-root-to-other-working-tree'
4683 ;; has is omitted here, for now, because it is probably less useful.
4684 (interactive
4685 (list
4686 (vc--prompt-other-working-tree
4687 (vc-responsible-backend default-directory)
4688 (format "%s changes to working tree"
4689 (if current-prefix-arg "Move" "Apply")))
4690 current-prefix-arg))
4691 (let* ((relative-dir (file-relative-name default-directory
4692 (vc-root-dir)))
4693 (mirror-dir (expand-file-name relative-dir directory)))
4694 (unless (file-directory-p mirror-dir)
4695 (user-error "`%s' not found in `%s'" relative-dir directory))
4696 (vc--apply-to-other-working-tree directory mirror-dir
4697 (vc-deduce-fileset)
4698 (and (derived-mode-p 'diff-mode)
4699 (buffer-string))
4700 move)))
4701
4702;;;###autoload
4703(defun vc-apply-root-to-other-working-tree (directory &optional move preview)
4704 "Apply all local changes to this working tree to the one under DIRECTORY.
4705Must be called from within an existing VC working tree.
4706When called interactively, prompts for DIRECTORY.
4707With a prefix argument (when called from Lisp, with optional argument
4708MOVE non-nil), don't just copy the changes, but move them, from the
4709current working tree to DIRECTORY.
4710
4711With a double prefix argument (\\[universal-argument] \\[universal-argument]; \
4712when called from Lisp, with
4713optional argument PREVIEW non-nil), don't actually apply changes to
4714DIRECTORY, but instead show all those changes in a `diff-mode' buffer
4715with `default-directory' set to DIRECTORY.
4716You can then selectively apply changes with `diff-mode' commands like
4717`diff-apply-hunk' and `diff-apply-buffer'.
4718
4719If any changes to be moved or copied can't be applied to DIRECTORY, it
4720is an error, and (except with \\[universal-argument] \\[universal-argument]) \
4721no changes are applied.
4722If any changes to be moved can't be reverse-applied to this working
4723tree, it is an error, and no changes are moved."
4724 (interactive
4725 (list
4726 (vc--prompt-other-working-tree
4727 (vc-responsible-backend default-directory)
4728 (format "%s changes to working tree"
4729 (if (equal current-prefix-arg '(4)) "Move" "Apply")))
4730 (equal current-prefix-arg '(4))
4731 (equal current-prefix-arg '(16))))
4732 (cond ((and move preview)
4733 (error "Invalid arguments to vc-apply-root-to-other-working-tree"))
4734 (preview
4735 ;; In this mode, no need to abort if some hunks aren't
4736 ;; applicable.
4737 (vc-root-diff nil t)
4738 (setq default-directory directory)
4739 (message
4740 (substitute-command-keys
4741 "Use \\[diff-hunk-kill] to kill hunks not to be copied \
4742then \\[diff-apply-buffer] to copy changes,
4743or use \\[diff-apply-hunk] to copy individual hunks. \
4744Type \\[describe-mode] for more commands")))
4745 (t
4746 (let ((default-directory (vc-root-dir)))
4747 (vc--apply-to-other-working-tree directory directory
4748 `(,(vc-deduce-backend)
4749 (,default-directory))
4750 nil move)))))
4751
4752(defcustom vc-no-confirm-moving-changes nil
4753 "Whether VC commands prompt before moving changes between working trees.
4754
4755Normally the commands \\[vc-apply-to-other-working-tree] \
4756and \\[vc-apply-root-to-other-working-tree] prompt for confirmation
4757when asked to move changes between working trees (i.e., when invoked
4758with a prefix argument). This is because it can be surprising to have
4759work disappear from your current working tree. You can customize this
4760option to non-nil to skip the prompting."
4761 :type '(choice (const :tag "Prompt before moving changes" nil)
4762 (const :tag "Move changes without prompting" t))
4763 :group 'vc
4764 :version "31.1")
4765
4766(defun vc--apply-to-other-working-tree
4767 (directory mirror-dir fileset patch-string move)
4768 "Workhorse routine for copying/moving changes to other working trees.
4769DIRECTORY is the root of the target working tree
4770(used only for messages).
4771MIRROR-DIR is the target directory for application.
4772FILESET is the VC fileset from which to copy changes.
4773PATCH-STRING non-nil overrides calling `vc-diff-internal' on FILESET to
4774determine the changes to copy or move.
4775MOVE non-nil means to move instead of copy."
4776 (unless (or (not move)
4777 vc-no-confirm-moving-changes
4778 (yes-or-no-p
4779 (format "Really %s uncommitted work out of this working tree?"
4780 (propertize "move" 'face 'bold))))
4781 (user-error "Aborted"))
4782 (vc-buffer-sync-fileset fileset nil)
4783 (with-temp-buffer
4784 (if (not patch-string)
4785 (let ((display-buffer-overriding-action '(display-buffer-no-window
4786 (allow-no-window . t))))
4787 (vc-diff-internal nil fileset nil nil nil (current-buffer)))
4788 (diff-mode)
4789 (insert patch-string))
4790 (let ((default-directory mirror-dir))
4791 (vc-buffer-sync-fileset (diff-vc-deduce-fileset) nil))
4792 (when-let* (move
4793 (failed (diff-apply-buffer nil nil 'reverse 'test)))
4794 ;; If PATCH-STRING is non-nil and this fails, the user called us
4795 ;; from a `diff-mode' buffer that doesn't reverse-apply; that's
4796 ;; a `user-error'.
4797 ;; If PATCH-STRING is nil and this fails, `vc-diff-internal'
4798 ;; generated a nonsense diff -- not the user's fault.
4799 (funcall (if patch-string #'user-error #'error)
4800 (ngettext "%d hunk does not reverse-apply to this working tree"
4801 "%d hunks do not reverse-apply to this working tree"
4802 failed)
4803 failed))
4804 (let ((default-directory mirror-dir))
4805 (when-let* ((failed (diff-apply-buffer)))
4806 (user-error (ngettext "%d hunk does not apply to `%s'"
4807 "%d hunks do not apply to `%s'"
4808 failed)
4809 failed directory)))
4810 (when move
4811 (diff-apply-buffer nil nil 'reverse))
4812 (message "Changes %s to `%s'"
4813 (if move "moved" "applied") directory)))
4814
4664 4815
4665 4816
4666;; These things should probably be generally available 4817;; These things should probably be generally available