Package: emacs;
Reported by: Arseny Sher <sher-ars <at> yandex.ru>
Date: Wed, 8 Mar 2017 17:34:01 UTC
Severity: minor
Tags: patch
Found in version 26.0.50
View this message in rfc822 format
From: Tino Calancha <tino.calancha <at> gmail.com> To: Arseny Sher <sher-ars <at> yandex.ru> Cc: Michael Heerdegen <michael_heerdegen <at> web.de>, 26028 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>, tino.calancha <at> gmail.com, Kaushal Modi <kaushal.modi <at> gmail.com> Subject: bug#26028: 26.0.50; epatch for multifile patches Date: Tue, 23 May 2017 20:26:55 +0900
Arseny Sher <sher-ars <at> yandex.ru> writes: > Eli Zaretskii <eliz <at> gnu.org> writes: > >> I think you should point it to ~/tmp, not ~/tmp/old. IOW, the >> directory should be the one relative to which the file names in the >> patch file will correctly point to the files. > > Okay, it might be not very convenient (as you might have several > versions of project inside ~/tmp, and ediff will ask you which one is to > be patched), but it works. However, how then I am expected to apply > patches generated by VCS, where paths are prefixed with a/ and b/? > Again, let's consider some simple example: > > mkdir -p proj/src > cd proj > echo "void main() {}" > src/hello.c > git init > git add src/ && git commit -m "commit" > echo "int main() { return 0; }" > src/hello.c > git diff > ../tmp.patch > git reset --hard HEAD > > cat ../tmp.patch > diff --git a/src/hello.c b/src/hello.c > index ab73b3a..76e8197 100644 > --- a/src/hello.c > +++ b/src/hello.c > @@ -1 +1 @@ > -void main() {} > +int main() { return 0; } > > How should I apply tmp.patch to proj? Recently i am not using `epatch', but maybe following patch is of interest for someone: --8<-----------------------------cut here---------------start------------->8--- commit 3f4561b160f2cc9e2b47d16e7f27fbb9b86c3f40 Author: Tino Calancha <tino.calancha <at> gmail.com> Date: Tue May 23 19:21:42 2017 +0900 epatch: multi-patch enhancement Handle multi-patches with files belonging to different subdirectories. (ediff-fixup-patch-map): For a multi-patch, set base-dir1 to a/ and base-dir2 to b/ if it is a Git patch; otherwise, set both to . (ediff-map-patch-buffer): Use buffer-substring-no-properties. * lisp/vc/ediff.el (ediff-patch-file): Show differente prompt for multi-patches. For single patches, use the patch header to guess the file to patch, and ensure the input matches an existing file. * test/lisp/vc/ediff-ptch-tests.el (ediff-ptch-test-bug26028): Add test. diff --git a/lisp/vc/ediff-ptch.el b/lisp/vc/ediff-ptch.el index 0340672da2..0d53b2aff4 100644 --- a/lisp/vc/ediff-ptch.el +++ b/lisp/vc/ediff-ptch.el @@ -222,10 +222,10 @@ ediff-map-patch-buffer ;; (filename-from-1st-header-line . filename-from-2nd-line) (setq possible-file-names (cons (if (and beg1 end1) - (buffer-substring beg1 end1) + (buffer-substring-no-properties beg1 end1) "/dev/null") (if (and beg2 end2) - (buffer-substring beg2 end2) + (buffer-substring-no-properties beg2 end2) "/dev/null"))) ;; Remove file junk (Bug#26084). (while (re-search-backward @@ -290,18 +290,25 @@ ediff-fixup-patch-map (or (file-name-directory (cdr proposed-file-names)) "")) ) - ;; If both base-dir1 and base-dir2 are relative and exist, - ;; assume that - ;; these dirs lead to the actual files starting at the present - ;; directory. So, we don't strip these relative dirs from the - ;; file names. This is a heuristic intended to improve guessing (let ((default-directory (file-name-directory filename))) - (unless (or (file-name-absolute-p base-dir1) - (file-name-absolute-p base-dir2) - (not (file-exists-p base-dir1)) - (not (file-exists-p base-dir2))) - (setq base-dir1 "" - base-dir2 ""))) + (cond (multi-patch-p + ;; Git diffs appends 'a/' '/b' to the files. + (if (and (string-match-p "\\`a/" base-dir1) + (string-match-p "\\`b/" base-dir2)) + (setq base-dir1 "a/" base-dir2 "b/") + (setq base-dir1 "" base-dir2 ""))) + (t + ;; If both base-dir1 and base-dir2 are relative and exist, + ;; assume that + ;; these dirs lead to the actual files starting at the present + ;; directory. So, we don't strip these relative dirs from the + ;; file names. This is a heuristic intended to improve guessing + (unless (or (file-name-absolute-p base-dir1) + (file-name-absolute-p base-dir2) + (not (file-exists-p base-dir1)) + (not (file-exists-p base-dir2))) + (setq base-dir1 "" + base-dir2 ""))))) (or (string= (car proposed-file-names) "/dev/null") (setcar proposed-file-names (ediff-file-name-sans-prefix diff --git a/lisp/vc/ediff.el b/lisp/vc/ediff.el index 4751bb6ddc..e41839e968 100644 --- a/lisp/vc/ediff.el +++ b/lisp/vc/ediff.el @@ -121,6 +121,7 @@ ediff-date (require 'ediff-init) (require 'ediff-mult) ; required because of the registry stuff +(require 'diff-mode) ; diff-hunk-file-names (defgroup ediff nil "Comprehensive visual interface to `diff' and `patch'." @@ -1355,6 +1356,7 @@ ediff-patch-default-directory (declare-function ediff-dispatch-file-patching-job "ediff-ptch" (patch-buf filename &optional startup-hooks)) +(defvar ediff-patch-map) ;;;###autoload (defun ediff-patch-file (&optional arg patch-buf) "Query for a file name, and then run Ediff by patching that file. @@ -1376,11 +1378,26 @@ ediff-patch-file (expand-file-name (buffer-file-name patch-buf)))) (t default-directory))) - (setq source-file - (read-file-name - "File to patch (directory, if multifile patch): " - ;; use an explicit initial file - source-dir nil nil (ediff-get-default-file-name))) + (let ((multi-patch-p (with-current-buffer patch-buf (cdr ediff-patch-map)))) + (cond ((not multi-patch-p) + (let* ((files (with-current-buffer patch-buf + (diff-hunk-file-names 'old-first))) + (def (if (and (string-match "\\`a/" (car files)) + (string-match "\\`b/" (cadr files))) + (expand-file-name + (substring-no-properties (car files) 2) + default-directory) + (car files)))) + (setq source-file + (read-file-name + "Single file to patch: " + ;; use an explicit initial file + source-dir nil 'mustmatch def)))) + (t ; multi-patch + (setq source-file + (read-file-name + "Directory to patch, use root project dir: " + source-dir))))) (ediff-dispatch-file-patching-job patch-buf source-file))) (declare-function ediff-patch-buffer-internal "ediff-ptch" diff --git a/test/lisp/vc/ediff-ptch-tests.el b/test/lisp/vc/ediff-ptch-tests.el index 387786ced0..74db053c97 100644 --- a/test/lisp/vc/ediff-ptch-tests.el +++ b/test/lisp/vc/ediff-ptch-tests.el @@ -21,6 +21,8 @@ (require 'ert) (require 'ediff-ptch) +(require 'ediff-diff) ; For `ediff-diff-program'. +(eval-when-compile (require 'cl-lib)) (ert-deftest ediff-ptch-test-bug25010 () "Test for http://debbugs.gnu.org/25010 ." @@ -104,6 +106,151 @@ (delete-directory tmpdir 'recursive) (delete-file patch))))) +(ert-deftest ediff-ptch-test-bug26028 () + "Test for http://debbugs.gnu.org/26028 ." + (skip-unless (executable-find "git")) + (skip-unless (executable-find ediff-patch-program)) + (skip-unless (executable-find ediff-diff-program)) + (let ((git-program (executable-find "git")) + (default-dir default-directory) + tmpdir buffers) + ;;; Simple patch: old/src/hello.c /new/src/hello.c + (unwind-protect + (let* ((dir (make-temp-file "multipatch-test" t)) + (file1 (expand-file-name "old/src/hello.c" dir)) + (file2 (expand-file-name "new/src/hello.c" dir)) + (patch (expand-file-name "tmp.patch" dir)) + (default-directory (file-name-as-directory dir))) + (setq tmpdir dir) + (make-directory (expand-file-name "old/src/" dir) 'parents) + (make-directory (expand-file-name "new/src/" dir) 'parents) + (with-temp-buffer + (insert "void main() { }\n") + (write-region nil nil file1 nil 'silent) + (erase-buffer) + (insert "int main() { return 0; }\n") + (write-region nil nil file2 nil 'silent) + (erase-buffer) + (call-process ediff-diff-program nil t nil "-cr" "old" "new") + (write-region nil nil patch nil 'silent) + (cl-letf (((symbol-function 'y-or-n-p) (lambda (x) nil)) + ((symbol-function 'ediff-prompt-for-patch-file) + (lambda (&rest x) (find-file-noselect patch))) + ((symbol-function 'read-file-name) (lambda (x1 x2 x3 x4 x5) x5)) + ((symbol-function 'ediff-dispatch-file-patching-job) + (lambda (x y) y))) + (should (equal (file-relative-name file1) (epatch nil patch))) + (push (get-file-buffer patch) buffers)))) + (when (file-exists-p tmpdir) + (setq default-directory default-dir) + (delete-directory tmpdir 'recursive)) + (mapc (lambda (b) + (when (buffer-live-p b) (kill-buffer b))) + buffers) + (setq buffers nil)) + ;;; Simple Git patch: proj/src/hello.c + (unwind-protect + (let* ((dir (make-temp-file "multipatch-test" t)) + (rootdir (expand-file-name "proj/src/" dir)) + (file (expand-file-name "hello.c" rootdir)) + (patch (expand-file-name "tmp.patch" dir)) + (default-directory (file-name-as-directory rootdir))) + (make-directory rootdir 'parents) + (setq tmpdir dir) + (with-temp-buffer + (insert "void main() { }\n") + (write-region nil nil file nil 'silent) + (call-process git-program nil nil nil "init") + (call-process git-program nil nil nil "add" ".") + (call-process git-program nil nil nil "commit" "-m" "test repository.") + (erase-buffer) + (insert "int main() { return 0; }\n") + (write-region nil nil file nil 'silent) + (call-process git-program nil `(:file ,patch) nil "diff") + (call-process git-program nil nil nil "reset" "--hard" "head") + (cl-letf (((symbol-function 'y-or-n-p) (lambda (x) nil)) + ((symbol-function 'ediff-prompt-for-patch-file) + (lambda (&rest x) (find-file-noselect patch))) + ((symbol-function 'read-file-name) (lambda (&rest x) file)) + ((symbol-function 'read-file-name) (lambda (x1 x2 x3 x4 x5) x5)) + ((symbol-function 'ediff-dispatch-file-patching-job) + (lambda (x y) y))) + (should (equal file (epatch nil patch))))) + (push (get-file-buffer patch) buffers)) + ;; clean up + (when (file-exists-p tmpdir) + (setq default-directory default-dir) + (delete-directory tmpdir 'recursive)) + (mapc (lambda (b) + (when (buffer-live-p b) (kill-buffer b))) + buffers) + (setq buffers nil)) + ;;; Git multipatch. + (unwind-protect + (let* ((dir (make-temp-file "multipatch-test" t)) + (file1 (expand-file-name "proj/src/hello.c" dir)) + (file2 (expand-file-name "proj/src/bye.c" dir)) + (file3 (expand-file-name "proj/lisp/foo.el" dir)) + (file4 (expand-file-name "proj/lisp/bar.el" dir)) + (file5 (expand-file-name "proj/etc/news" dir)) + (patch (expand-file-name "tmp.patch" dir)) + (default-directory (expand-file-name "proj" dir))) + (setq tmpdir dir) + (dolist (d '("src" "lisp" "etc")) + (setq rootdir (expand-file-name (concat "proj/" d) dir)) + (make-directory rootdir 'parents)) + (with-temp-buffer + (insert "void main() { }\n") + (write-region nil nil file1 nil 'silent) + (write-region nil nil file2 nil 'silent) + (erase-buffer) + (insert "(defun foo () nil)\n") + (write-region nil nil file3 nil 'silent) + (erase-buffer) + (insert "(defun bar () nil)\n") + (write-region nil nil file4 nil 'silent) + (erase-buffer) + (insert "new functions 'foo' and 'bar'\n") + (write-region nil nil file5 nil 'silent) + (call-process git-program nil nil nil "init") + (call-process git-program nil nil nil "add" "src" "lisp" "etc") + (call-process git-program nil nil nil "commit" "-m" "test repository.");) + (erase-buffer) + (insert "int main() { return 0;}\n") + (write-region nil nil file1 nil 'silent) + (write-region nil nil file2 nil 'silent) + (erase-buffer) + (insert "(defun qux () nil)\n") + (write-region nil nil file3 nil 'silent) + (erase-buffer) + (insert "(defun quux () nil)\n") + (write-region nil nil file4 nil 'silent) + (erase-buffer) + (insert "new functions 'qux' and 'quux'\n") + (write-region nil nil file5 nil 'silent) + (call-process git-program nil `(:file ,patch) nil "diff") + (call-process git-program nil nil nil "reset" "--hard" "head")) + (cl-letf (((symbol-function 'y-or-n-p) (lambda (x) nil)) + ((symbol-function 'ediff-get-patch-file) (lambda (&rest x) patch)) + ((symbol-function 'read-file-name) (lambda (&rest x) patch))) + (epatch nil patch) + (with-current-buffer "*Ediff Session Group Panel*" + (push (get-file-buffer patch) buffers) + (should (= 5 (length (cdr ediff-meta-list)))) + ;; don't ask confirmation to exit. + (cl-letf (((symbol-function 'y-or-n-p) (lambda (x) t))) + (ediff-quit-meta-buffer))))) + ;; clean up + (when (file-exists-p tmpdir) + (setq default-directory default-dir) + (delete-directory tmpdir 'recursive)) + (when ediff-registry-buffer + (push ediff-registry-buffer buffers)) + (mapc (lambda (b) + (when (buffer-live-p b) (kill-buffer b))) + buffers) + (setq buffers nil)))) + (provide 'ediff-ptch-tests) ;;; ediff-ptch-tests.el ends here --8<-----------------------------cut here---------------end--------------->8--- In GNU Emacs 26.0.50 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.22.11) of 2017-05-23 Repository revision: 4a485410ce74cafd4e9c344e31f7575464a16113
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.