10

I need the diff of two buffers. One way would be to create temporary files containing the contents of these buffers and to use the diff function. However, the buffers contain sensitive information and I'd prefer not to have that information in clear text on disk.

I thought about using ediff which can compare buffers directly but ediff starts an interactive session and I want to use this in a script.

tmalsburg
  • 2,540
  • 1
  • 14
  • 29
  • Just to clarify, you want a function that gives the diff of two buffers without any user interaction? – user2699 Sep 27 '16 at 11:28
  • @user2699, precisely. Context: http://emacs.stackexchange.com/questions/27349/automatically-show-diff-before-saving-file – tmalsburg Sep 27 '16 at 11:40
  • Is it possible to use named pipes for this? – tmalsburg Sep 27 '16 at 11:43
  • 1
    I'm not familiar with named pipes, but it does appear the best solution would be something beyond emacs. Looking over the source code of `ediff-buffers` very briefly, it appears to save buffers to temporary files on disk, then call the system diff utility on those files, so there wouldn't be any practical difference from calling `diff` yourself. – user2699 Sep 27 '16 at 11:46
  • This answer may be interesting. Pipe both buffers in and it will diff them for you with no identifying data (except a couple of directory entries for the pipes). At that point, it's just a matter of figuring out where ediff formats the default diff output for Emacs and calling that: http://stackoverflow.com/a/345536/3255378 – JCC Sep 27 '16 at 12:23
  • 3
    Under which operating system? The diff function uses an external program, and communicating with an external program is OS-dependent. The straightforward solution would be to store the files in an in-memory filesystem; those are standard on Linux nowadays but may not exist on other platforms. – Gilles 'SO- stop being evil' Sep 28 '16 at 15:57

4 Answers4

4

@tmalsburg, Following command calls diff on 2 buffers without the creation of temporary files. It uses named pipes as you suggested above:

    (require 'diff)
    (defun diff-buffers-without-temp-files (buffer1 buffer2 &optional switches)
      "Run diff program on BUFFER1 and BUFFER2.
    Make the comparison without the creation of temporary files.

    When called interactively with a prefix argument, prompt
    interactively for diff switches.  Otherwise, the switches
    specified in the variable `diff-switches' are passed to the diff command."
      (interactive
       (list (read-buffer "buffer1: " (current-buffer))
             (read-buffer "buffer2: " (current-buffer))
             (diff-switches)))
      (or switches (setq switches diff-switches))
      (unless (listp switches) (setq switches (list switches)))
      (let ((buffers (list buffer1 buffer2))
            (buf (get-buffer-create "*diff-buffers*"))
            fifos res)
        (dotimes (_ 2) (push (make-temp-name "/tmp/pipe") fifos))
        (setq fifos (nreverse fifos))
        (with-current-buffer buf (erase-buffer))
        (unwind-protect
            (progn
              (dotimes (i 2)
                (let ((cmd (format "cat > %s << EOF\n%s\nEOF"
                                   (nth i fifos)
                                   (with-current-buffer (nth i buffers)
                                     (buffer-string)))))
                  (call-process "mkfifo" nil nil nil (nth i fifos))
                  (start-process-shell-command (format "p%d" i) nil cmd)))
              (setq res (apply #'call-process diff-command nil buf nil (car fifos) (cadr fifos) switches))
              (if (zerop res)
                  (message "Buffers have same content")
                (display-buffer buf)
                (with-current-buffer buf (diff-mode))
                (message "Buffer contents are different"))
              res)
          ;; Clean up.
          (dolist (x fifos)
            (and (file-exists-p x) (delete-file x))))))
  1. When called interactively, it shows the diff when the buffers have different content.
  2. When called from Lisp, it returns the exit code of the diff program; that is, 0 if the buffers have same content, 1 otherwise.
        (diff-buffers-without-temp-files (get-buffer "*scratch*") (get-buffer "*scratch*"))
        => 0
    
    
        (diff-buffers-without-temp-files (get-buffer "*scratch*") (get-buffer "*Messages*"))
        => 1
    

Tested for Emacs version 24.3 in a machine running Debian GNU/Linux 9.0 (stretch).

  • The code above seems to work whed called from Lisp. Unfortunately, most of the time shows a truncated diff in interactive calls.

  • The following version uses the 3rd party async library; it doesn't truncate the diffs.

    (require 'diff)
    (require 'async)
    (defun diff-buffers-without-temp-files (buffer1 buffer2 &optional switches)
      "Run diff program on BUFFER1 and BUFFER2.
    Make the comparison without the creation of temporary files.

    When called interactively with a prefix argument, prompt
    interactively for diff switches.  Otherwise, the switches
    specified in the variable `diff-switches' are passed to the diff command."
      (interactive
       (list (read-buffer "buffer1: " (current-buffer))
             (read-buffer "buffer2: " (current-buffer))
             (diff-switches)))
      (or switches (setq switches diff-switches))
      (unless (listp switches) (setq switches (list switches)))
      (let ((buffers (list buffer1 buffer2))
            (buf (get-buffer-create "*diff-buffers*"))
            fifos res)
        (dotimes (_ 2) (push (make-temp-name "/tmp/pipe") fifos))
        (setq fifos (nreverse fifos))
        (with-current-buffer buf (erase-buffer))
        (unwind-protect
            (progn
              (dotimes (i 2)
                (let ((cmd (format "cat > %s" (nth i fifos))))
                  (call-process "mkfifo" nil nil nil (nth i fifos))
                  (async-start
                   `(lambda ()
                      (with-temp-buffer
                        (insert ,(with-current-buffer (nth i buffers) (buffer-string)))
                        (call-process-region
                         1 (point-max) shell-file-name nil nil nil
                         shell-command-switch ,cmd))))))
              (setq res (apply #'call-process diff-command nil buf nil (car fifos) (cadr fifos) switches))
              (if (zerop res)
                  (message "Buffers have same content")
                (display-buffer buf)
                (with-current-buffer buf (diff-mode))
                (message "Buffer contents are different"))
              res)
          ;; Clean up.
          (dolist (x fifos)
            (and (file-exists-p x) (delete-file x))))))
Tino
  • 420
  • 5
  • 9
0

AFAIU Emacs takes external programms to do the diff. For example ediff-make-diff2-buffer, which would compare two buffers, internally calls

(ediff-exec-process ediff-diff-program
                    diff-buffer
                    'synchronize
                    ediff-actual-diff-options file1 file2)

Where ediff-diff-program might represent the GNU/Linux diff. With new FFI the system diff might be accessible. Also an Emacs Lisp diff-implementation might do the job.

Andreas Röhler
  • 1,894
  • 10
  • 10
0

How about you use shell-command to call diff, passing it an output buffer? Or, shell-command-to-string to get the diff in a string

russell
  • 101
  • 1
  • I'm not sure I understand what you mean by passing it an output buffer. Could you please elaborate this a bit? Thanks. – tmalsburg Oct 01 '16 at 09:04
  • shell-command's syntax is (shell-command COMMAND &optional OUTPUT-BUFFER ERROR-BUFFER). The optional second argument is: """The optional second argument OUTPUT-BUFFER, if non-nil, says to put the output in some other buffer. If OUTPUT-BUFFER is a buffer or buffer name, put the output there. If OUTPUT-BUFFER is not a buffer and not nil, insert output in current buffer. (This cannot be done asynchronously.) In either case, the buffer is first erased, and the output is inserted after point (leaving mark after it).""" – russell Oct 02 '16 at 16:44
0

If your are OK with Ediff, this will do:

(defun my/diff-buffers (buffer-A buffer-B)
  "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B."
  (ediff-buffers-internal buffer-A buffer-B nil nil 'ediff-buffers))

and call it like this:

(my/diff-buffers "*temp*" "*temp*<2>")
Yasushi Shoji
  • 2,151
  • 15
  • 35
  • Thanks for the response. There are two problems with this solution. 1. To my knowledge ediff uses diff and thus needs temporary files. 2. Your solution starts an interactive session but I just want the diff in a buffer or string. – tmalsburg Dec 04 '16 at 12:04
  • 1
    Ah, I mis-read your question. I thought you don't want to create temp files by yourself. http://emacs.stackexchange.com/questions/19812 is asking elisp version of diff. No mention about temp files, though. Reading your question again, you you have to have diff output? or you want to just compare your string and know the diff? A caller example might help. – Yasushi Shoji Dec 05 '16 at 01:38
  • ```(defun my/diff-buffers (buffer-A buffer-B) "Run Ediff on a pair of buffers, BUFFER-A and BUFFER-B." (interactive (list (read-buffer "buffer1: " (current-buffer)) (read-buffer "buffer2: " (current-buffer)))) (ediff-buffers-internal buffer-A buffer-B nil nil 'ediff-buffers))``` – HappyFace Jul 18 '19 at 05:28