GNU bug report logs - #26028
26.0.50; epatch for multifile patches

Previous Next

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

To reply to this bug, email your comments to 26028 AT debbugs.gnu.org.

Toggle the display of automated, internal messages from the tracker.

View this report as an mbox folder, status mbox, maintainer mbox


Report forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Wed, 08 Mar 2017 17:34:01 GMT) Full text and rfc822 format available.

Acknowledgement sent to Arseny Sher <sher-ars <at> yandex.ru>:
New bug report received and forwarded. Copy sent to bug-gnu-emacs <at> gnu.org. (Wed, 08 Mar 2017 17:34:01 GMT) Full text and rfc822 format available.

Message #5 received at submit <at> debbugs.gnu.org (full text, mbox):

From: Arseny Sher <sher-ars <at> yandex.ru>
To: bug-gnu-emacs <at> gnu.org
Subject: 26.0.50; epatch for multifile patches
Date: Wed, 08 Mar 2017 20:29:52 +0300
Hello,

I am having trouble using epatch while applying multifile patches.
Consider the following simple example:

ars <at> ars-thinkpad ~/tmp $ mkdir -p old/src
ars <at> ars-thinkpad ~/tmp $ echo "void main() { }" > old/src/hello.c
ars <at> ars-thinkpad ~/tmp $ mkdir -p new/src
ars <at> ars-thinkpad ~/tmp $ echo "int main() { return 0; }" > new/src/hello.c
ars <at> ars-thinkpad ~/tmp $ diff -cr old/ new/ > tmp.patch
ars <at> ars-thinkpad ~/tmp $ cat tmp.patch
diff -cr old/src/hello.c new/src/hello.c
*** old/src/hello.c	2017-03-08 14:29:24.743846995 +0300
--- new/src/hello.c	2017-03-08 14:29:32.399846824 +0300
***************
*** 1 ****
! void main() { }
--- 1 ----
! int main() { return 0; }


Now I start emacs and do M-x epatch. First of all, it asks me for
patch buffer or file, I point to '~/tmp/tmp.patch'. Then it asks for
directory to patch, I say '~/tmp/old' and ediff complains

Ediff has inferred that
	/home/ars/tmp/old/hello.c
is assumed to be the target for this patch.  However, this file does not exist.

Please enter an alternative patch target ...


because it ignores the path ('src' directory in this case) to the file.
Ediff manual says: "Ediff can recognize multi-file patches only if they
are in the context format or GNU unified format. All other patches are
treated as 1-file patches. Ediff is [hopefully] using the same algorithm
as patch to determine which files need to be patched." If I understand
this correctly, my scenario should work, so it seems like a bug.

I tried it on GNU Emacs versions 25.1, 25.2 RC2 and daily build
26.0.50.2, sometimes on clean Emacs (emacs -q), and sometimes with
unified diff format (diff -u), the result is the same.

I use GNU/Linux, Linux Mint 17 (based on Ubuntu 14.04).

Below goes autogenerated info from report-emacs-bug.

In GNU Emacs 26.0.50.2 (x86_64-pc-linux-gnu, GTK+ Version 3.10.8)
 of 2016-10-26 built on lgw01-50
Windowing system distributor 'The X.Org Foundation', version 11.0.11501000
System Description:	Linux Mint 17 Qiana

Recent messages:
my-misc loaded
Loading /home/ars/.emacs.d/recentf...done
Cleaning up the recentf list...done (0 removed)
my-text-processing loaded
Loading font-lock...done
my-windows-frames-funcs loaded
coding styles loaded
my-gdb-stuff loaded
Loading quail/cyrillic...done
For information about GNU Emacs and the GNU system, type C-h C-a.

Configured using:
 'configure --build=x86_64-linux-gnu --prefix=/usr
 '--includedir=${prefix}/include' '--mandir=${prefix}/share/man'
 '--infodir=${prefix}/share/info' --sysconfdir=/etc --localstatedir=/var
 '--libdir=${prefix}/lib/x86_64-linux-gnu'
 '--libexecdir=${prefix}/lib/x86_64-linux-gnu' --disable-maintainer-mode
 --disable-dependency-tracking --prefix=/usr --sharedstatedir=/var/lib
 --program-suffix=-snapshot --with-modules=yes --with-x=yes
 --with-x-toolkit=gtk3 --with-xwidgets=yes 'CFLAGS=-g -O2
 -fstack-protector --param=ssp-buffer-size=4 -Wformat
 -Werror=format-security' CPPFLAGS=-D_FORTIFY_SOURCE=2
 'LDFLAGS=-Wl,-Bsymbolic-functions -Wl,-z,relro''

Configured features:
XPM JPEG TIFF GIF PNG RSVG IMAGEMAGICK SOUND GPM DBUS GCONF GSETTINGS
NOTIFY LIBSELINUX GNUTLS LIBXML2 FREETYPE M17N_FLT LIBOTF XFT ZLIB
TOOLKIT_SCROLL_BARS GTK3 X11 MODULES XWIDGETS

Important settings:
  value of $LC_ALL: en_US.UTF-8
  value of $LC_MONETARY: ru_RU.UTF-8
  value of $LC_NUMERIC: ru_RU.UTF-8
  value of $LC_TIME: ru_RU.UTF-8
  value of $LANG: en_US.UTF-8
  locale-coding-system: utf-8-unix

Major mode: Fundamental

Minor modes in effect:
  gud-tooltip-mode: t
  diff-hl-flydiff-mode: t
  global-diff-hl-mode: t
  diff-auto-refine-mode: t
  global-flycheck-mode: t
  projectile-mode: t
  ivy-mode: t
  show-paren-mode: t
  my-global-fci-mode: t
  global-linum-mode: t
  dired-omit-mode: t
  delete-selection-mode: t
  cua-mode: t
  recentf-mode: t
  savehist-mode: t
  global-undo-tree-mode: t
  global-auto-revert-mode: t
  tooltip-mode: t
  global-eldoc-mode: t
  electric-indent-mode: t
  mouse-wheel-mode: t
  file-name-shadow-mode: t
  global-font-lock-mode: t
  blink-cursor-mode: t
  auto-composition-mode: t
  auto-encryption-mode: t
  auto-compression-mode: t
  buffer-read-only: t
  column-number-mode: t
  line-number-mode: t
  transient-mark-mode: t

Load-path shadows:
~/.emacs.d/static_packages/all-the-icons.el/all-the-icons hides ~/.emacs.d/static_packages/all-the-icons
/usr/share/emacs/site-lisp/dictionaries-common/ispell hides /usr/share/emacs/26.0.50/lisp/textmodes/ispell
/usr/share/emacs/site-lisp/dictionaries-common/flyspell hides /usr/share/emacs/26.0.50/lisp/textmodes/flyspell
/usr/share/emacs/site-lisp/latex-cjk-thai/thai-word hides /usr/share/emacs/26.0.50/lisp/language/thai-word

Features:
(shadow sort mail-extr emacsbug message puny format-spec rfc822 mml
mml-sec epa derived epg gnus-util rmail rmail-loaddefs mm-decode
mm-bodies mm-encode mail-parse rfc2231 mailabbrev gmm-utils mailheader
sendmail rfc2047 rfc2045 ietf-drums mm-util mail-prsvr mail-utils colir
color ggtags etags xref project windmove quail gud my-gdb-stuff
diff-hl-flydiff diff-hl edmacro kmacro face-remap vc-hg vc-git vc-dir
ewoc vc vc-dispatcher diff-mode flycheck json map find-func rx subr-x
coding-styles cc-styles cc-align cc-engine cc-vars cc-defs projectile
advice grep compile comint ansi-color ibuf-ext ibuffer ibuffer-loaddefs
ivy ivy-overlay ffap thingatpt xcscope ring paren disp-table
fill-column-indicator easy-mmode linum dired-x dired dired-loaddefs
my-windows-frames-funcs all-the-icons all-the-icons-faces
data-weathericons data-octicons data-fileicons data-faicons
data-alltheicons font-lock+ cl dash my-text-processing drag-stuff delsel
cua-base recentf tree-widget wid-edit savehist undo-tree diff autorevert
filenotify wombat-theme my-misc finder-inf tex-site info package
epg-config url-handlers url-parse auth-source cl-seq eieio eieio-core
cl-macs eieio-loaddefs password-cache url-vars seq byte-opt gv bytecomp
byte-compile cl-extra help-mode easymenu cconv cl-loaddefs pcase cl-lib
time-date mule-util tooltip eldoc electric uniquify ediff-hook vc-hooks
lisp-float-type mwheel term/x-win x-win term/common-win x-dnd tool-bar
dnd fontset image regexp-opt fringe tabulated-list newcomment elisp-mode
lisp-mode prog-mode register page menu-bar rfn-eshadow timer select
scroll-bar mouse jit-lock font-lock syntax facemenu font-core
term/tty-colors frame cl-generic cham georgian utf-8-lang misc-lang
vietnamese tibetan thai tai-viet lao korean japanese eucjp-ms cp51932
hebrew greek romanian slovak czech european ethiopic indian cyrillic
chinese charscript case-table epa-hook jka-cmpr-hook help simple abbrev
obarray minibuffer cl-preloaded nadvice loaddefs button faces cus-face
macroexp files text-properties overlay sha1 md5 base64 format env
code-pages mule custom widget hashtable-print-readable backquote
dbusbind inotify dynamic-setting system-font-setting font-render-setting
xwidget-internal move-toolbar gtk x-toolkit x multi-tty
make-network-process emacs)

Memory information:
((conses 16 288745 21478)
 (symbols 48 35267 1)
 (miscs 40 517 1228)
 (strings 32 74748 9913)
 (string-bytes 1 2122820)
 (vectors 16 35442)
 (vector-slots 8 683178 9673)
 (floats 8 665 276)
 (intervals 56 444 300)
 (buffers 976 13)
 (heap 1024 41802 1607))




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Wed, 08 Mar 2017 19:18:02 GMT) Full text and rfc822 format available.

Message #8 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Eli Zaretskii <eliz <at> gnu.org>
To: Arseny Sher <sher-ars <at> yandex.ru>
Cc: 26028 <at> debbugs.gnu.org
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Wed, 08 Mar 2017 21:17:12 +0200
> From: Arseny Sher <sher-ars <at> yandex.ru>
> Date: Wed, 08 Mar 2017 20:29:52 +0300
> 
> ars <at> ars-thinkpad ~/tmp $ mkdir -p old/src
> ars <at> ars-thinkpad ~/tmp $ echo "void main() { }" > old/src/hello.c
> ars <at> ars-thinkpad ~/tmp $ mkdir -p new/src
> ars <at> ars-thinkpad ~/tmp $ echo "int main() { return 0; }" > new/src/hello.c
> ars <at> ars-thinkpad ~/tmp $ diff -cr old/ new/ > tmp.patch
> ars <at> ars-thinkpad ~/tmp $ cat tmp.patch
> diff -cr old/src/hello.c new/src/hello.c
> *** old/src/hello.c	2017-03-08 14:29:24.743846995 +0300
> --- new/src/hello.c	2017-03-08 14:29:32.399846824 +0300
> ***************
> *** 1 ****
> ! void main() { }
> --- 1 ----
> ! int main() { return 0; }
> 
> 
> Now I start emacs and do M-x epatch. First of all, it asks me for
> patch buffer or file, I point to '~/tmp/tmp.patch'. Then it asks for
> directory to patch, I say '~/tmp/old' and ediff complains
> 
> Ediff has inferred that
> 	/home/ars/tmp/old/hello.c
> is assumed to be the target for this patch.  However, this file does not exist.

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.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Wed, 08 Mar 2017 21:54:02 GMT) Full text and rfc822 format available.

Message #11 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Arseny Sher <sher-ars <at> yandex.ru>
To: Eli Zaretskii <eliz <at> gnu.org>
Cc: 26028 <at> debbugs.gnu.org
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Thu, 09 Mar 2017 00:53:41 +0300
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?

Yet am not still sure that this is not a bug; some other guy said that
this behaviour doesn't make sense, and similar bug was fixed recently:
http://lists.gnu.org/archive/html/help-gnu-emacs/2017-03/msg00064.html
http://emacs.1067599.n8.nabble.com/bug-25010-26-0-50-epatch-might-parse-wrongly-a-file-name-td413747.html

And while I am here, I would like to express my gratitude to ediff
developers; it is in overall a great tool, and it is a pleasure to read
its manual. Thank you guys!




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Thu, 09 Mar 2017 01:42:02 GMT) Full text and rfc822 format available.

Message #14 received at 26028 <at> debbugs.gnu.org (full text, mbox):

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
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Thu, 09 Mar 2017 10:41:18 +0900
Arseny Sher <sher-ars <at> yandex.ru> writes:

> How then I am expected to apply
> patches generated by VCS, where paths are prefixed with a/ and b/?
Ediff will skip those 'a/', 'b/' automatically.
> 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?
Ediff uses the `default-directory' for the patch buffer as a hint.
When i apply patches generated with VCS i _always_ set the
`default-directory' of the patch buffer equal as the root directory of
the project.  That makes multi patches work OK.
This is how i would do in your exmple:
M-: (dired "/tmp/proj") RET
C-x b *p* RET ; Now copy the patch here in your favourite way.
C-x 4 r /tmp/tmp.patch RET
C-x h M-w C-x o C-y
M-x epatch RET y *p* RET /tmp/proj/src RET

> Yet am not still sure that this is not a bug; some other guy said that
> this behaviour doesn't make sense, and similar bug was fixed recently:
> http://lists.gnu.org/archive/html/help-gnu-emacs/2017-03/msg00064.html
> http://emacs.1067599.n8.nabble.com/bug-25010-26-0-50-epatch-might-parse-wrongly-a-file-name-td413747.html
I tend to agree that it might be possible to improve how Ediff
handle these things.  That said, as a user i haven't being disturbed
so much by this inconvenience.  I am also worry if i could break other
important feature while trying to improve this; that's essentially why
i didn't try hard to work in this issue.

If you have some an idea about how to prepare a patch improving
Ediff on this area i am welcome to support you in that task.

Regards,
Tino




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Thu, 09 Mar 2017 11:45:02 GMT) Full text and rfc822 format available.

Message #17 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Arseny Sher <sher-ars <at> yandex.ru>
To: Tino Calancha <tino.calancha <at> gmail.com>
Cc: Michael Heerdegen <michael_heerdegen <at> web.de>, 26028 <at> debbugs.gnu.org,
 Eli Zaretskii <eliz <at> gnu.org>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Thu, 09 Mar 2017 14:44:11 +0300
Tino Calancha <tino.calancha <at> gmail.com> writes:

>> How should I apply tmp.patch to proj?
> Ediff uses the `default-directory' for the patch buffer as a hint.
> When i apply patches generated with VCS i _always_ set the
> `default-directory' of the patch buffer equal as the root directory of
> the project.  That makes multi patches work OK.
> This is how i would do in your exmple:
> M-: (dired "/tmp/proj") RET
> C-x b *p* RET ; Now copy the patch here in your favourite way.
> C-x 4 r /tmp/tmp.patch RET
> C-x h M-w C-x o C-y
> M-x epatch RET y *p* RET /tmp/proj/src RET

Honestly, I didn't understand your answer.
1) I don't see any effects of changing the default directory. I set
   default directory of buffer with patch to ~/tmp/proj as you say, then
   run epatch, firstly point it to this buffer, then specify ~/tmp/proj
   (because this is project root, and I want to patch the whole project!)
   as target directory and it fails again, exactly as before.
2) Interpreting your instructions literally, I should specify
   ~/tmp/proj/src as target directory to ediff (answer to its last
   question). Well, then it works, but this is precisely the behaviour
   which doesn't make any sense to me: ediff just ignores the path to
   file ('src' directory here). And it happens totally independent of
   default directory value, I tried with different ones.
   I suppose that the whole point of applying multifile patch is to allow
   ediff deduce which files (with full paths) it needs to patch
   automatically, without manually specifying dirs like 'src'. Imagine
   we have also hello.h file under proj/include; then, if I tell ediff
   to use ~/tmp/proj/src dir as target directory as you say, it will
   successfully patch hello.c, but it will fail again attempting to
   patch hello.h, because there is no such file proj/src/hello.h.

So, it is hard for me to suggest better epatch behaviour because I
actually don't comprehend its current one. I don't know why other people
don't encounter these problems. Probably they just apply patches via git
and only then look at the diff via ediff, and I should go the same
way...





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Tue, 23 May 2017 11:28:01 GMT) Full text and rfc822 format available.

Message #20 received at 26028 <at> debbugs.gnu.org (full text, mbox):

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: Re: 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





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Tue, 23 May 2017 22:46:02 GMT) Full text and rfc822 format available.

Message #23 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Michael Heerdegen <michael_heerdegen <at> web.de>
To: Tino Calancha <tino.calancha <at> gmail.com>
Cc: 26028 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>,
 Arseny Sher <sher-ars <at> yandex.ru>, Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Wed, 24 May 2017 00:45:42 +0200
Tino Calancha <tino.calancha <at> gmail.com> writes:

> Recently i am not using `epatch', but maybe following patch
> is of interest for someone:

FWIW, do you think you understood all of the code you changed (I ask
because I gave up after a while)?  If you did, do you think we should
install your fix, or are there open questions?


Thanks,

Michael.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Wed, 24 May 2017 00:48:02 GMT) Full text and rfc822 format available.

Message #26 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Tino Calancha <tino.calancha <at> gmail.com>
To: Michael Heerdegen <michael_heerdegen <at> web.de>
Cc: 26028 <at> debbugs.gnu.org, Eli Zaretskii <eliz <at> gnu.org>,
 Kaushal Modi <kaushal.modi <at> gmail.com>, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Wed, 24 May 2017 09:46:59 +0900 (JST)

On Wed, 24 May 2017, Michael Heerdegen wrote:

> Tino Calancha <tino.calancha <at> gmail.com> writes:
>
>> Recently i am not using `epatch', but maybe following patch
>> is of interest for someone:
>
> FWIW, do you think you understood all of the code you changed (I ask
> because I gave up after a while)?
I think so.  I you exclude from the patch the added tests in
test/lisp/vc/ediff-ptch-tests.el
then this patch just touch:
2 files changed, 42 insertions(+), 18 deletions(-)
The only it does:
* tweak the original heuristic part for guessing the file names
* change the suggested defaults in the prompts asking for the patch and
  dirs to patch.

>  If you did, do you think we should
> install your fix, or are there open questions?
I have tested with plain diffs and with Git diffs.  I didn't test it
with other VC than Git.
I don't think solves all the problems, but at least it makes 
`epatch' usable for the most common multipatch situations.

I)
You know, `epatch' has never parsed multipatches with files in different 
dirs, as:
foo/file1
bar/file2

II)
The only multipatches `epatch' worked before is when all the files belong
to the same dir, as:
foo/file1
foo/file2

This patch makes possible to handle I) as well.




Added tag(s) patch. Request was from Stefan Kangas <stefan <at> marxist.se> to control <at> debbugs.gnu.org. (Tue, 11 Aug 2020 07:32:01 GMT) Full text and rfc822 format available.

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Tue, 11 Aug 2020 07:36:01 GMT) Full text and rfc822 format available.

Message #31 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Stefan Kangas <stefan <at> marxist.se>
To: Tino Calancha <tino.calancha <at> gmail.com>
Cc: Michael Heerdegen <michael_heerdegen <at> web.de>, 26028 <at> debbugs.gnu.org,
 Eli Zaretskii <eliz <at> gnu.org>, Arseny Sher <sher-ars <at> yandex.ru>,
 Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Tue, 11 Aug 2020 00:34:55 -0700
Tino Calancha <tino.calancha <at> gmail.com> writes:

> On Wed, 24 May 2017, Michael Heerdegen wrote:
>
>> Tino Calancha <tino.calancha <at> gmail.com> writes:
>>
>>> Recently i am not using `epatch', but maybe following patch
>>> is of interest for someone:
>>
>> FWIW, do you think you understood all of the code you changed (I ask
>> because I gave up after a while)?
> I think so.  I you exclude from the patch the added tests in
> test/lisp/vc/ediff-ptch-tests.el
> then this patch just touch:
> 2 files changed, 42 insertions(+), 18 deletions(-)
> The only it does:
> * tweak the original heuristic part for guessing the file names
> * change the suggested defaults in the prompts asking for the patch and
>   dirs to patch.
>
>>  If you did, do you think we should
>> install your fix, or are there open questions?
> I have tested with plain diffs and with Git diffs.  I didn't test it
> with other VC than Git.
> I don't think solves all the problems, but at least it makes `epatch' usable for
> the most common multipatch situations.

That was 3 years ago.

It looks like this patch was never installed. Should it be?

Best regards,
Stefan Kangas




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Sat, 03 Oct 2020 22:26:02 GMT) Full text and rfc822 format available.

Message #34 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Michael Heerdegen <michael_heerdegen <at> web.de>
To: Stefan Kangas <stefan <at> marxist.se>
Cc: 26028 <at> debbugs.gnu.org, Kaushal Modi <kaushal.modi <at> gmail.com>,
 Arseny Sher <sher-ars <at> yandex.ru>, Tino Calancha <tino.calancha <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Sun, 04 Oct 2020 00:25:40 +0200
Stefan Kangas <stefan <at> marxist.se> writes:

> It looks like this patch was never installed. Should it be?

AFAIR it solved my problem, but I can't judge whether it's ok to
install since I don't know the code.

Michael.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Mon, 10 May 2021 12:03:01 GMT) Full text and rfc822 format available.

Message #37 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Lars Ingebrigtsen <larsi <at> gnus.org>
To: Michael Heerdegen <michael_heerdegen <at> web.de>
Cc: 26028 <at> debbugs.gnu.org, Tino Calancha <tino.calancha <at> gmail.com>,
 Stefan Kangas <stefan <at> marxist.se>, Arseny Sher <sher-ars <at> yandex.ru>,
 Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Mon, 10 May 2021 14:02:17 +0200
Michael Heerdegen <michael_heerdegen <at> web.de> writes:

> AFAIR it solved my problem, but I can't judge whether it's ok to
> install since I don't know the code.

The patch no longer applies cleanly -- do you have an up-to-date version
of Tino's patch in your tree, by any chance?  Or did you just try it
then, and then back it out again?

Looking at the commit history for ediff, it doesn't really look like we
have any ediff domain experts to evaluate the patch, which makes it hard
to decide whether to try to apply the patch or not.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no




Added tag(s) moreinfo. Request was from Lars Ingebrigtsen <larsi <at> gnus.org> to control <at> debbugs.gnu.org. (Mon, 10 May 2021 12:03:02 GMT) Full text and rfc822 format available.

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Wed, 12 May 2021 09:29:02 GMT) Full text and rfc822 format available.

Message #42 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Michael Heerdegen <michael_heerdegen <at> web.de>
To: Lars Ingebrigtsen <larsi <at> gnus.org>
Cc: 26028 <at> debbugs.gnu.org, Kaushal Modi <kaushal.modi <at> gmail.com>,
 Stefan Kangas <stefan <at> marxist.se>, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Wed, 12 May 2021 11:27:56 +0200
Lars Ingebrigtsen <larsi <at> gnus.org> writes:

> > AFAIR it solved my problem, but I can't judge whether it's ok to
> > install since I don't know the code.
>
> The patch no longer applies cleanly -- do you have an up-to-date version
> of Tino's patch in your tree, by any chance?  Or did you just try it
> then, and then back it out again?

I'm very sorry Lars, but I don't have anything better than Tino's
original patch.  I removed the patch from my tree when the collisions
started to occur.

Regards,

Michael.




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Thu, 13 May 2021 16:30:02 GMT) Full text and rfc822 format available.

Message #45 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Filipp Gunbin <fgunbin <at> fastmail.fm>
To: Lars Ingebrigtsen <larsi <at> gnus.org>
Cc: 26028 <at> debbugs.gnu.org, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>,
 Michael Heerdegen <michael_heerdegen <at> web.de>,
 Stefan Kangas <stefan <at> marxist.se>, Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Thu, 13 May 2021 19:29:21 +0300
On 10/05/2021 14:02 +0200, Lars Ingebrigtsen wrote:

> Michael Heerdegen <michael_heerdegen <at> web.de> writes:
>
>> AFAIR it solved my problem, but I can't judge whether it's ok to
>> install since I don't know the code.
>
> The patch no longer applies cleanly -- do you have an up-to-date version
> of Tino's patch in your tree, by any chance?  Or did you just try it
> then, and then back it out again?
>
> Looking at the commit history for ediff, it doesn't really look like we
> have any ediff domain experts to evaluate the patch, which makes it hard
> to decide whether to try to apply the patch or not.

I've looked into ediff some months ago (when writing a function which
opens new multifile ediff session for comparing vc commits; it's
unfinished yet).  So I could look at the patch if needed.

Filipp




Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Sun, 16 May 2021 13:58:02 GMT) Full text and rfc822 format available.

Message #48 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Lars Ingebrigtsen <larsi <at> gnus.org>
To: Filipp Gunbin <fgunbin <at> fastmail.fm>
Cc: 26028 <at> debbugs.gnu.org, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>,
 Michael Heerdegen <michael_heerdegen <at> web.de>,
 Stefan Kangas <stefan <at> marxist.se>, Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Sun, 16 May 2021 15:57:14 +0200
Filipp Gunbin <fgunbin <at> fastmail.fm> writes:

> I've looked into ediff some months ago (when writing a function which
> opens new multifile ediff session for comparing vc commits; it's
> unfinished yet).  So I could look at the patch if needed.

Yes, that would be very helpful.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no




Removed tag(s) moreinfo. Request was from Lars Ingebrigtsen <larsi <at> gnus.org> to control <at> debbugs.gnu.org. (Sun, 13 Jun 2021 17:52:02 GMT) Full text and rfc822 format available.

Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Fri, 23 Jul 2021 12:55:02 GMT) Full text and rfc822 format available.

Message #53 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Lars Ingebrigtsen <larsi <at> gnus.org>
To: Filipp Gunbin <fgunbin <at> fastmail.fm>
Cc: 26028 <at> debbugs.gnu.org, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>,
 Michael Heerdegen <michael_heerdegen <at> web.de>,
 Stefan Kangas <stefan <at> marxist.se>, Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Fri, 23 Jul 2021 14:54:17 +0200
Lars Ingebrigtsen <larsi <at> gnus.org> writes:

> Filipp Gunbin <fgunbin <at> fastmail.fm> writes:
>
>> I've looked into ediff some months ago (when writing a function which
>> opens new multifile ediff session for comparing vc commits; it's
>> unfinished yet).  So I could look at the patch if needed.
>
> Yes, that would be very helpful.

Did you get any further here?





Information forwarded to bug-gnu-emacs <at> gnu.org:
bug#26028; Package emacs. (Thu, 24 Mar 2022 08:35:02 GMT) Full text and rfc822 format available.

Message #56 received at 26028 <at> debbugs.gnu.org (full text, mbox):

From: Lars Ingebrigtsen <larsi <at> gnus.org>
To: Filipp Gunbin <fgunbin <at> fastmail.fm>
Cc: 26028 <at> debbugs.gnu.org, Arseny Sher <sher-ars <at> yandex.ru>,
 Tino Calancha <tino.calancha <at> gmail.com>,
 Michael Heerdegen <michael_heerdegen <at> web.de>,
 Stefan Kangas <stefan <at> marxist.se>, Kaushal Modi <kaushal.modi <at> gmail.com>
Subject: Re: bug#26028: 26.0.50; epatch for multifile patches
Date: Thu, 24 Mar 2022 09:34:01 +0100
[Message part 1 (text/plain, inline)]
Lars Ingebrigtsen <larsi <at> gnus.org> writes:

> Did you get any further here?

I forgot that the patch no longer applied.  I've respun it now, but
somewhat unsure of how the changes to ediff-fixup-patch-map should be
adjusted to the patch.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no
[ediff.patch (text/x-diff, inline)]
diff --git a/lisp/vc/ediff-ptch.el b/lisp/vc/ediff-ptch.el
index 17654f80ec..db39368397 100644
--- a/lisp/vc/ediff-ptch.el
+++ b/lisp/vc/ediff-ptch.el
@@ -217,10 +217,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)
 			  null-device)
 			(if (and beg2 end2)
-			    (buffer-substring beg2 end2)
+			    (buffer-substring-no-properties beg2 end2)
 			  null-device)))
             ;; Remove file junk (Bug#26084).
             (while (re-search-backward
@@ -285,31 +285,42 @@ 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))
-		  (if (and (file-exists-p base-dir1)
-			   (file-exists-p base-dir2))
-		      (setq base-dir1 ""
-			    base-dir2 "")
-		    ;; Strip possible source/destination prefixes
-		    ;; such as a/ and b/ from dir names.
-		    (save-match-data
-		      (let ((m1 (when (string-match "^[^/]+/" base-dir1)
-                                  (cons (substring base-dir1 0 (match-end 0))
-                                        (substring base-dir1 (match-end 0)))))
-			    (m2 (when (string-match "^[^/]+/" base-dir2)
-				  (cons (substring base-dir2 0 (match-end 0))
-                                        (substring base-dir2 (match-end 0))))))
-			(when (and (file-exists-p (cdr m1))
-				   (file-exists-p (cdr m2)))
-			  (setq base-dir1 (car m1)
-				base-dir2 (car m2))))))))
+                (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))
+		    (if (and (file-exists-p base-dir1)
+			     (file-exists-p base-dir2))
+		        (setq base-dir1 ""
+			      base-dir2 "")
+		      ;; Strip possible source/destination prefixes
+		      ;; such as a/ and b/ from dir names.
+		      (save-match-data
+		        (let ((m1
+                               (when (string-match "^[^/]+/" base-dir1)
+                                 (cons (substring base-dir1 0 (match-end 0))
+                                       (substring base-dir1 (match-end 0)))))
+			      (m2
+                               (when (string-match "^[^/]+/" base-dir2)
+				 (cons (substring base-dir2 0 (match-end 0))
+                                       (substring base-dir2 (match-end 0))))))
+			  (when (and (file-exists-p (cdr m1))
+				     (file-exists-p (cdr m2)))
+			    (setq base-dir1 (car m1)
+				  base-dir2 (car m2))))))))))
 	      (or (string= (car proposed-file-names) null-device)
 		  (setcar proposed-file-names
 			  (ediff-file-name-sans-prefix
diff --git a/lisp/vc/ediff.el b/lisp/vc/ediff.el
index 840ab8cf51..e7ee36eb10 100644
--- a/lisp/vc/ediff.el
+++ b/lisp/vc/ediff.el
@@ -111,6 +111,7 @@ ediff-version
 
 (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'."
@@ -1412,6 +1413,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.
@@ -1433,11 +1435,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 935046198f..7f143fe139 100644
--- a/test/lisp/vc/ediff-ptch-tests.el
+++ b/test/lisp/vc/ediff-ptch-tests.el
@@ -24,6 +24,8 @@
 (require 'ert)
 (require 'ert-x)
 (require 'ediff-ptch)
+(require 'ediff-diff) ; For `ediff-diff-program'.
+(eval-when-compile (require 'cl-lib))
 
 (ert-deftest ediff-ptch-test-bug25010 ()
   "Test for https://debbugs.gnu.org/25010 ."
@@ -118,6 +120,151 @@ ediff-ptch-test-bug26084
                             (insert-file-contents backup)
                             (buffer-string))))))))))))
 
+(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

This bug report was last modified 2 years and 42 days ago.

Previous Next


GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997,2003 nCipher Corporation Ltd, 1994-97 Ian Jackson.