EDIT: I figured it out. It seems to be an emacs bug. My walk-through of the bug is below.
I just now found out about emacs-show-diff-output
, which is bound to D by default in an ediff session.
When I make changes to a buffer without saving the file and then run M-x ediff-current-file
to start an ediff session for the changes that haven't been saved, if I then press D, a new frame pops up with a buffer named *ediff-custom-diff*
, but it's empty.
The *Messages*
buffer shows:
Computing differences between ediff100678ZHa and built-in.el ...
Buffer A: Processing difference region 0 of 1
Buffer B: Processing difference region 0 of 1
Processing difference regions ... done
I tried searching around but couldn't find anything related to ediff-show-diff-output
. Anyone have any idea as to what may be happening?
EDIT: I've noticed that if I do it from an ediff session initiated from magit with E w
(ediff of the working tree changes), then D works fine. It seems to be when I do it via M-x ediff-current-file
that it results in an empty diff. My guess would be that perhaps the file isn't found or something but the *Messages*
output seems to show that it is working? It's just not ending up in the buffer.
EDIT: I've been stepping through the function ediff-compute-custom-diffs-maybe
:
;; This function operates only from an ediff control buffer
(defun ediff-compute-custom-diffs-maybe ()
(let ((buf-A-file-name (buffer-file-name ediff-buffer-A))
(buf-B-file-name (buffer-file-name ediff-buffer-B))
file-A file-B)
(unless (and buf-A-file-name
(file-exists-p buf-A-file-name)
(not (ediff-file-remote-p buf-A-file-name)))
(setq file-A (ediff-make-temp-file ediff-buffer-A)))
(unless (and buf-B-file-name
(file-exists-p buf-B-file-name)
(not (ediff-file-remote-p buf-B-file-name)))
(setq file-B (ediff-make-temp-file ediff-buffer-B)))
(or (ediff-buffer-live-p ediff-custom-diff-buffer)
(setq ediff-custom-diff-buffer
(get-buffer-create
(ediff-unique-buffer-name "*ediff-custom-diff" "*"))))
(ediff-with-current-buffer ediff-custom-diff-buffer
(setq buffer-read-only nil)
(erase-buffer))
(ediff-exec-process
ediff-custom-diff-program ediff-custom-diff-buffer 'synchronize
ediff-custom-diff-options
(or file-A buf-A-file-name)
(or file-B buf-B-file-name))
;; put the diff file in diff-mode, if it is available
(if (fboundp 'diff-mode)
(with-current-buffer ediff-custom-diff-buffer
(diff-mode)))
(and file-A (file-exists-p file-A) (delete-file file-A))
(and file-B (file-exists-p file-B) (delete-file file-B))
))
Let's go through it a bit at a time. First here's an outline of what I believe is occurring in the first few lines:
- get the file paths backing the buffers being compared
- the one for which there is no file path is the one for which ediff needs to create a temporary file for with the contents of the buffer, the unchanged file can simply be read from disk
So first it tries to get the file name of each buffer:
(let ((buf-A-file-name (buffer-file-name ediff-buffer-A))
(buf-B-file-name (buffer-file-name ediff-buffer-B))
When you run M-x ediff-current-file
, the left buffer (A) represents the unmodified file on disk and it's called FILE=/path/to/file
. The buffer representing the unsaved changes is on the right (B) and it's named after the file on disk.
So what ends up happening here is that, perhaps because buffer A has a FILE=
prefix, buffer-file-name
fails so that buf-A-file-name
is nil
. Meanwhile, buffer B which again represents the dirty unsaved buffer does succeed on buffer-file-name
and sets buf-B-file-name
to the file path to the underlying file.
(unless (and buf-A-file-name
(file-exists-p buf-A-file-name)
(not (ediff-file-remote-p buf-A-file-name)))
(setq file-A (ediff-make-temp-file ediff-buffer-A)))
This part then executes if buf-A-file-name
is nil
, which it is, so it proceeds to set file-A
to a temporary file that it creates from the contents of buffer A. In effect this creates a temporary file with the same contents as the unchanged file on disk.
Now it's B's turn:
(unless (and buf-B-file-name
(file-exists-p buf-B-file-name)
(not (ediff-file-remote-p buf-B-file-name)))
(setq file-B (ediff-make-temp-file ediff-buffer-B)))
In this case buf-B-file-name
is not nil
, the file does exist, and it's not a remote file, so it doesn't create a temporary file for buffer B, instead it'll simply read the unchanged file on disk.
So at this point we now have:
- a temporary file with the contents of the unchanged file
- the unchanged file on the disk
These are clearly equivalent, so running diff
on them produces no output, hence the empty buffer.
I've confirmed that this is the problem by "fixing" the visited files of the ediff buffers. So I focus buffer A and run:
(set-visited-file-name "/path/to/file")
I focus buffer B and run:
(set-visited-file-name "DIRTY=/path/to/file")
Then I press D again (M-x ediff-show-diff-output
) and IT WORKS!!.
So to recap, I believe the problem is that the FILE=
prefix added to buffer A which represents the file on disk makes ediff think that it's actually the dirty buffer for which it needs to create a temporary file, albeit with the contents of buffer A which is the contents of the file. Then it goes on to buffer B and it determines that the file backing that buffer is the unchanged file on disk, when really it should actually determine that it's the dirty buffer and create a temporary file with the contents of the dirty buffer. In the end we end up with a temporary file and the unchanged file both with the same contents, so diff produces no output and thus an empty buffer.
I'm not sure how to go about solving this. Perhaps I can change the names with a setup-hook but that may be fragile.
M-x report-emacs-bug
– phils May 9 '16 at 1:42