From 2aa53d9299f0f3ca72b182d21b3d42bc2edc7980 Mon Sep 17 00:00:00 2001 From: Dmitry Gutov Date: Tue, 8 Oct 2024 02:25:14 +0300 Subject: [PATCH] Support file creation and deletion in diff-apply-hunk * lisp/vc/diff-mode.el (diff-find-file-name): Allow entering non-existing file name when the corresponding hunk is of type "create file" (bug#62731). Default to file name with deleted prefix if diff-buffer-type is Git or Hg. Make sure not to add such input to diff-remembered-files-alist, it would be hard to change otherwise in case of typo. (diff-setup-buffer-type): Match against the diff header common to 'hg diff' output. (diff-find-source-location): Look at the other source when the buffer is applied in reverse. (diff-apply-hunk): Delect file deletion and pass a different argument to 'diff-find-source-location' in such case. Bind diff-vc-backend to nil to avoid older revision buffer being returned. In the end, offer to delete the file if the hunk was of corresponding type and matched the existing contents. * etc/NEWS: Mention the new capability. (cherry picked from commit 2d139141a6c249691a50ade9c8d08dc35fcdf456) --- etc/NEWS | 2 ++ lisp/vc/diff-mode.el | 37 +++++++++++++++++++++++++++++-------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index b55db508078..b6ec3a09e23 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -378,6 +378,8 @@ 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. + ** php-ts-mode --- diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index cca8ce2be4e..71754aa1ca9 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -1090,13 +1090,24 @@ PREFIX is only used internally: don't use it." (diff-find-file-name old noprompt (match-string 1))) ;; if all else fails, ask the user (unless noprompt - (let ((file (expand-file-name (or (car fs) "")))) + (let ((file (or (car fs) "")) + (creation (equal null-device + (car (diff-hunk-file-names (not old)))))) + (when (and (memq diff-buffer-type '(git hg)) + (string-match "/" file)) + ;; Strip the dst prefix (like b/) if diff is from Git/Hg. + (setq file (substring file (match-end 0)))) + (setq file (expand-file-name file)) (setq file (read-file-name (format "Use file %s: " file) - (file-name-directory file) file t + (file-name-directory file) file + ;; Allow non-matching for creation. + (not creation) (file-name-nondirectory file))) - (setq-local diff-remembered-files-alist - (cons (cons fs file) diff-remembered-files-alist)) + (when (or (not creation) (file-exists-p file)) + ;; Only remember files that exist. User might have mistyped. + (setq-local diff-remembered-files-alist + (cons (cons fs file) diff-remembered-files-alist))) file))))))) @@ -1646,7 +1657,9 @@ modified lines of the diff." (setq-local diff-buffer-type (if (re-search-forward "^diff --git" nil t) 'git - nil))) + (if (re-search-forward "^diff -r.*-r" nil t) + 'hg + nil)))) (when (eq diff-buffer-type 'git) (setq diff-outline-regexp (concat "\\(^diff --git.*\\|" diff-hunk-header-re "\\)"))) @@ -1956,7 +1969,7 @@ SWITCHED is non-nil if the patch is already applied." diff-context-mid-hunk-header-re nil t) (error "Can't find the hunk separator")) (match-string 1))))) - (file (or (diff-find-file-name other noprompt) + (file (or (diff-find-file-name (xor other reverse) noprompt) (error "Can't find the file"))) (revision (and other diff-vc-backend (if reverse (nth 1 diff-vc-revisions) @@ -2026,7 +2039,11 @@ 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 ((`(,buf ,line-offset ,pos ,old ,new ,switched) + (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 @@ -2036,7 +2053,7 @@ With a prefix argument, REVERSE the hunk." ;; TODO: make it possible to ask explicitly for this behavior. ;; ;; This is duplicated in diff-test-hunk. - (diff-find-source-location nil reverse))) + (diff-find-source-location deletion reverse))) (cond ((null line-offset) (user-error "Can't find the text to patch")) @@ -2062,6 +2079,10 @@ With a prefix argument, REVERSE the hunk." "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 (run-hook-with-args 'diff-apply-hunk-functions -- 2.39.5