emacs-31 b6e7962700d: Fix "diff-apply-buffer applies to the wrong file"

Dmitry Gutov <[email protected]>
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: emacs-31
commit b6e7962700dd1a7ae774b9f2ae2a2c0d9e33151d
Author: Dmitry Gutov <[email protected]>
Commit: Dmitry Gutov <[email protected]>

    Fix "diff-apply-buffer applies to the wrong file"
    
    * lisp/vc/diff-mode.el (diff-find-file-name): Inhibit the "drop
    dir" behavior in diffs produced by Git and Hg (bug#81210).
    Except for the virtual subdirectories like a/b/etc, which is
    moved and happens for all such diffs.  Tighten the check for
    such virtual directory names, though (characters are the chars
    used by the configuration option 'diff.mnemonicPrefix').
    (diff-setup-buffer-type): Recognize Hg diffs where only one
    revision is specified, too.
    
    * test/lisp/vc/diff-mode-tests.el
    (diff-mode-test-setup-buffer-type)
    (diff-mode-test-find-file-name-create): New tests.
---
 lisp/vc/diff-mode.el            | 15 +++++++------
 test/lisp/vc/diff-mode-tests.el | 48 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 6 deletions(-)

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 32f34ebaa10..c1227573351 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1176,6 +1176,10 @@ PREFIX is only used internally: don't use it."
         (or (ignore-errors (diff-beginning-of-file))
 	    (re-search-forward diff-file-header-re nil t)))
       (let ((fs (diff-hunk-file-names old)))
+        (when (memq diff-buffer-type '(git hg))
+          (setq fs
+                (mapcar (lambda (f) (replace-regexp-in-string "\\`[icoawib]/" "" f))
+                        fs)))
         (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs)))
         (or
          ;; use any previously used preference
@@ -1186,7 +1190,10 @@ PREFIX is only used internally: don't use it."
 	     (if (and newfile (file-exists-p newfile)) (cl-return newfile))))
          ;; look for each file in turn.  If none found, try again but
          ;; ignoring the first level of directory, ...
-         (cl-do* ((files fs (delq nil (mapcar #'diff-filename-drop-dir files)))
+         (cl-do* ((files fs (and (not (and (memq diff-buffer-type '(git hg))
+                                           (not old)
+                                           (equal null-device (cadr files))))
+                              (delq nil (mapcar #'diff-filename-drop-dir files))))
                   (file nil nil))
 	     ((or (null files)
 		  (setq file (cl-do* ((files files (cdr files))
@@ -1212,10 +1219,6 @@ PREFIX is only used internally: don't use it."
            (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)
@@ -1822,7 +1825,7 @@ modified lines of the diff."
     (setq-local diff-buffer-type
                 (if (re-search-forward "^diff --git" nil t)
                     'git
-                  (if (re-search-forward "^diff -r.*-r" nil t)
+                  (if (re-search-forward "^diff -r " nil t)
                       'hg
                     nil))))
   (when (eq diff-buffer-type 'git)
diff --git a/test/lisp/vc/diff-mode-tests.el b/test/lisp/vc/diff-mode-tests.el
index c75e7ef066b..36291fee0a5 100644
--- a/test/lisp/vc/diff-mode-tests.el
+++ b/test/lisp/vc/diff-mode-tests.el
@@ -744,5 +744,53 @@ plum
           (set-buffer-modified-p nil)
           (kill-buffer buf-after))))))
 
+(ert-deftest diff-mode-test-setup-buffer-type ()
+  "Check that `diff-setup-buffer-type' recognizes the diff's origin."
+  (with-temp-buffer
+    (insert "diff --git a/foo b/foo\n--- a/foo\n+++ b/foo\n")
+    (diff-mode)
+    (should (eq diff-buffer-type 'git)))
+  (with-temp-buffer
+    (insert "diff -r 0123456789ab foo\n--- a/foo\n+++ b/foo\n")
+    (diff-mode)
+    (should (eq diff-buffer-type 'hg)))
+  (with-temp-buffer
+    (insert "--- foo\n+++ foo\n@@ -1 +1 @@\n-a\n+b\n")
+    (diff-mode)
+    (should (eq diff-buffer-type nil))))
+
+(ert-deftest diff-mode-test-find-file-name-create ()
+  "Check `diff-find-file-name' for a Git/Hg file creation.
+It should not use existing file without subdirectory."
+  (ert-with-temp-directory temp-dir
+    (let ((default-directory temp-dir)
+          call-dir call-initial call-mustmatch)
+      ;; A decoy with the same basename as the created file, but in a
+      ;; different directory.
+      (with-temp-file (expand-file-name "created.txt" temp-dir)
+        (insert "decoy\n"))
+      (with-temp-buffer
+        (insert "diff --git a/sub/created.txt b/sub/created.txt
+new file mode 100644
+index 0000000..3456789
+--- /dev/null
++++ b/sub/created.txt
+@@ -0,0 +1 @@
++new
+")
+        (diff-mode)
+        (goto-char (point-min))
+        (let ((read-file-name-function
+               (lambda (_p &optional dir _def mustmatch initial _pred)
+                 (setq call-dir dir
+                       call-initial initial
+                       call-mustmatch mustmatch)
+                 "magic")))
+          (should (equal (diff-find-file-name)
+                         "magic"))
+          (should (equal call-dir (expand-file-name "sub/" temp-dir)))
+          (should (equal call-initial "created.txt"))
+          (should (equal call-mustmatch nil)))))))
+
 (provide 'diff-mode-tests)
 ;;; diff-mode-tests.el ends here
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.