2

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:

  1. get the file paths backing the buffers being compared
  2. 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:

  1. a temporary file with the contents of the unchanged file
  2. 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.

  • Please M-x report-emacs-bug – phils May 9 '16 at 1:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.