Make diff-apply-hunk consider an active region

* lisp/vc/diff-mode.el (diff-apply-buffer): New 'no-save'
meaning for fourth optional argument.  Reserve other non-nil
values for this argument.  Use ngettext for one message.
(diff-apply-hunk): If the region is active, apply all hunks that
the region overlaps, like diff-apply-buffer.
* doc/emacs/files.texi (Diff Mode):
* etc/NEWS: Document the change to diff-apply-hunk.
This commit is contained in:
Sean Whitton 2025-11-25 14:53:19 +00:00
parent 1844ce4a0f
commit 59e8b7267f
3 changed files with 116 additions and 75 deletions

View file

@ -1824,6 +1824,13 @@ reverse of the hunk, which changes the ``new'' version into the ``old''
version. If @code{diff-jump-to-old-file} is non-@code{nil}, apply the
hunk to the ``old'' version of the file instead.
@code{diff-apply-hunk} prompts you to confirm deleting files and
applying hunks to backup files, and it offers to reverse-apply hunks
that are already applied. If the region is active, however, it applies
all hunks that the region overlaps without doing any prompting. In this
mode, it simply fails if any hunks do not apply cleanly, and does not
confirm deletions or applying hunks to backup files.
@findex diff-revert-and-kill-hunk
@item C-c M-u
Revert this hunk, and then remove the hunk from the diffs

View file

@ -2045,19 +2045,22 @@ useful to prepare a "*vc-diff*" buffer for committing a single hunk.
When the region is active, it deletes all hunks that the region does not
overlap.
*** 'diff-apply-hunk' now supports creating and deleting files.
---
*** 'vc-version-diff' and 'vc-root-version-diff' changed default for REV1.
They suggest the previous revision as the default for REV1, not the last
one as before. This makes them different from 'vc-diff' and
'vc-root-diff' when those are called without a prefix argument.
*** 'diff-apply-hunk' now supports creating and deleting files.
+++
*** 'diff-apply-buffer' now considers the region and can reverse-apply.
If the region is active, this command now applies all hunks that the
region overlaps; otherwise, it applies all hunks.
With a prefix argument, it now reverse-applies the hunks.
*** 'diff-apply-hunk' and 'diff-apply-buffer' now consider the region.
If the region is active, these commands now apply all hunks that the
region overlaps. Otherwise, they have their existing behavior.
+++
*** 'diff-apply-buffer' can reverse-apply.
With a prefix argument, it now reverse-applies hunks.
This matches the existing prefix argument to 'diff-apply-hunk'.
** Ediff

View file

@ -2138,73 +2138,97 @@ SWITCHED is non-nil if the patch is already applied."
(defvar diff-apply-hunk-to-backup-file nil)
(defun diff-apply-hunk (&optional reverse)
"Apply the current hunk to the source file and go to the next.
(defun diff-apply-hunk (&optional reverse beg end)
"Apply the current hunk to its source file and go to the next hunk.
By default, the new source file is patched, but if the variable
`diff-jump-to-old-file' is non-nil, then the old source file is
patched instead (some commands, such as `diff-goto-source' can change
the value of this variable when given an appropriate prefix argument).
With a prefix argument, REVERSE the hunk."
(interactive "P")
(diff-beginning-of-hunk t)
(pcase-let* (;; Do not accept BUFFER.REV buffers as source location.
(diff-vc-backend nil)
;; When we detect deletion, we will use the old file name.
(deletion (equal null-device (car (diff-hunk-file-names reverse))))
(`(,buf ,line-offset ,pos ,old ,new ,switched)
;; Sometimes we'd like to have the following behavior: if
;; REVERSE go to the new file, otherwise go to the old.
;; But that means that by default we use the old file, which is
;; the opposite of the default for diff-goto-source, and is thus
;; confusing. Also when you don't know about it it's
;; pretty surprising.
;; TODO: make it possible to ask explicitly for this behavior.
;;
;; This is duplicated in diff-test-hunk.
(diff-find-source-location (xor deletion reverse) reverse)))
(cond
((null line-offset)
(user-error "Can't find the text to patch"))
((with-current-buffer buf
(and buffer-file-name
(backup-file-name-p buffer-file-name)
(not diff-apply-hunk-to-backup-file)
(not (setq-local diff-apply-hunk-to-backup-file
(yes-or-no-p (format "Really apply this hunk to %s? "
(file-name-nondirectory
buffer-file-name)))))))
(user-error "%s"
(substitute-command-keys
(format "Use %s\\[diff-apply-hunk] to apply it to the other file"
(if (not reverse) "\\[universal-argument] ")))))
((and switched
;; A reversed patch was detected, perhaps apply it in reverse.
(not (save-window-excursion
(pop-to-buffer buf)
(goto-char (+ (car pos) (cdr old)))
(y-or-n-p
(if reverse
"Hunk hasn't been applied yet; apply it now? "
"Hunk has already been applied; undo it? ")))))
(message "(Nothing done)"))
((and deletion (not switched))
(when (y-or-n-p (format-message "Delete file `%s'?"
(buffer-file-name buf)))
(delete-file (buffer-file-name buf) delete-by-moving-to-trash)
(kill-buffer buf)))
(t
;; Apply the hunk
(with-current-buffer buf
(goto-char (car pos))
(delete-region (car pos) (cdr pos))
(insert (car new)))
;; Display BUF in a window
(set-window-point (display-buffer buf '(nil (inhibit-same-window . t)))
(+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (xor switched reverse) nil)
(when diff-advance-after-apply-hunk
(diff-hunk-next))))))
With a prefix argument (when called from Lisp, with optional argument
REVERSE non-nil), reverse-apply the hunk(s).
Prompt to confirm deleting files and applying hunks to backup files.
Offer to reverse-apply hunks that are already applied.
Interactively, if the region is active, apply all hunks that the
region overlaps. In this mode, fail instead of prompting if any
hunks do not cleanly apply, and do not confirm deletions or
applying hunks to backup files (the same as the command
`diff-apply-buffer' with an active region, which see).
When called from Lisp with optional arguments BEG and END non-nil,
apply all hunks overlapped by the region from BEG to END as though
called interactively with an active region delimited by BEG and
END."
(interactive (list current-prefix-arg
(use-region-beginning)
(use-region-end)))
(cond*
((xor beg end)
(error "Invalid call to `diff-apply-hunk'"))
(beg
(diff-apply-buffer beg end reverse 'no-save))
(t (diff-beginning-of-hunk t))
((bind*
;; Do not accept BUFFER.REV buffers as source location.
(diff-vc-backend nil)
;; When we detect deletion, we will use the old file name.
(deletion (equal null-device (car (diff-hunk-file-names reverse))))))
((pcase* `(,buf ,line-offset ,pos ,old ,new ,switched)
;; Sometimes we'd like to have the following behavior: if
;; REVERSE go to the new file, otherwise go to the old.
;; But that means that by default we use the old file, which is
;; the opposite of the default for diff-goto-source, and is thus
;; confusing. Also when you don't know about it it's
;; pretty surprising.
;; TODO: make it possible to ask explicitly for this behavior.
;;
;; This is duplicated in diff-test-hunk.
(diff-find-source-location (xor deletion reverse) reverse)))
((null line-offset)
(user-error "Can't find the text to patch"))
((with-current-buffer buf
(and buffer-file-name
(backup-file-name-p buffer-file-name)
(not diff-apply-hunk-to-backup-file)
(not
(setq-local diff-apply-hunk-to-backup-file
(yes-or-no-p
(format "Really apply this hunk to %s? "
(file-name-nondirectory buffer-file-name)))))))
(user-error "%s"
(substitute-command-keys
(format "Use %s\\[diff-apply-hunk] to apply it to the other file"
(and (not reverse) "\\[universal-argument] ")))))
((and switched
;; A reversed patch was detected, perhaps apply it in reverse.
(not (save-window-excursion
(pop-to-buffer buf)
(goto-char (+ (car pos) (cdr old)))
(y-or-n-p
(if reverse
"Hunk hasn't been applied yet; apply it now? "
"Hunk has already been applied; undo it? ")))))
(message "(Nothing done)"))
((and deletion (not switched))
(when (y-or-n-p (format-message "Delete file `%s'?"
(buffer-file-name buf)))
(delete-file (buffer-file-name buf) delete-by-moving-to-trash)
(kill-buffer buf)))
(t
;; Apply the hunk
(with-current-buffer buf
(goto-char (car pos))
(delete-region (car pos) (cdr pos))
(insert (car new)))
;; Display BUF in a window
(set-window-point (display-buffer buf '(nil (inhibit-same-window . t)))
(+ (car pos) (cdr new)))
(diff-hunk-status-msg line-offset (xor switched reverse) nil)
(when diff-advance-after-apply-hunk
(diff-hunk-next)))))
(defun diff-test-hunk (&optional reverse)
@ -2252,7 +2276,7 @@ customize `diff-ask-before-revert-and-kill-hunk' to control that."
(when (null (diff-apply-buffer beg end t))
(diff-hunk-kill)))))
(defun diff-apply-buffer (&optional beg end reverse test)
(defun diff-apply-buffer (&optional beg end reverse test-or-no-save)
"Apply the diff in the entire diff buffer.
Interactively, if the region is active, apply all hunks that the region
overlaps; otherwise, apply all hunks.
@ -2266,15 +2290,18 @@ and saved, or the number of failed hunk applications otherwise.
Optional arguments BEG and END restrict the hunks to be applied to those
lying between BEG and END.
Optional argument REVERSE means to reverse-apply hunks.
Optional argument TEST means to not actually apply or reverse-apply any
hunks, but return the same information: nil if all hunks can be applied,
or the number of hunks that can't be applied."
Optional argument TEST-OR-NO-SAVE `no-save' means not to save any
changed buffers, `test' or t means to not actually apply or
reverse-apply any hunks, but return the same information: nil if
all hunks can be applied, or the number of hunks that can't be
applied. Other non-nil values are reserved."
(interactive (list (use-region-beginning)
(use-region-end)
current-prefix-arg))
(let ((buffer-edits nil)
(failures 0)
(diff-refine nil))
(diff-refine nil)
(test (memq test-or-no-save '(t test))))
(save-excursion
(goto-char (or beg (point-min)))
(diff-beginning-of-hunk t)
@ -2302,8 +2329,12 @@ or the number of hunks that can't be applied."
(goto-char (car pos))
(delete-region (car pos) (cdr pos))
(insert (car dst))))
(save-buffer)))
(message "Saved %d buffers" (length buffer-edits)))
(unless (eq test-or-no-save 'no-save)
(save-buffer))))
(message (ngettext "%s %d buffer" "%s %d buffers"
(length buffer-edits))
(if (eq test-or-no-save 'no-save) "Edited" "Saved")
(length buffer-edits)))
nil)
(t
(unless test