8

When working on a project under version control with git, I often want to do some things in a shell that affect many of my open files, then revert every buffer that I have open to make sure that I don't accidentally clobber the new version with whatever I had open. I know magit can be helpful here, but I'm used to my workflow in the shell and I'd like to keep it for now. So instead, I'd like to revert all open buffers, and maybe close any that have stopped existing (e.g. because of a git checkout of a branch that no longer has that file).

I have the following snippet of elisp that I grabbed from a Google search:

(defun revert-all-buffers ()
  "Refreshes all open buffers from their respective files"
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (when (and (buffer-file-name buffer) 
                 (not (buffer-modified-p buffer)))
        (set-buffer buffer)
        (revert-buffer t t t))
      (setq list (cdr list))
      (setq buffer (car list))))
  (message "Refreshed open files"))

But this breaks if it hits an error in one of my open files, i.e. when reverting B1, B2, B3,...,Bn an error while trying to revert B2 prevents B3-Bn from being reverted.

How can I tell emacs to ignore any errors that pop up in this case? I don't want to use global-auto-revert-mode because each revert triggers some heavy duty stuff like my auto-complete and syntax checker re-parsing the file, hanging emacs for a second or so.

  • What kind of error prevents reverting the B2 buffer in your example. I use a very similar function (most likely derived from this snippet) and it has worked fine. – Kaushal Modi Jul 8 '16 at 17:02
  • @Kaushal: it seems like the "file no longer exists" one does, and/or errors thrown by packages I have that re-run buffer revert. Mostly I've noticed that after running it I will still get a "File has changed since last visited!" on C-x s – Patrick Collins Jul 8 '16 at 17:06
  • "file no longer exists" .. aha! my version fixes that :) Will post it shortly. – Kaushal Modi Jul 8 '16 at 17:07
9

Original

Here is my slightly improved version of the snippet in the question. Reviewing my VC history, I confirm that the below snippet started out as the snippet posted by the OP. So I do pay attribute to that.

Here's the code that has been stable for me:

(defun modi/revert-all-file-buffers ()
  "Refresh all open buffers from their respective files."
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (let ((filename (buffer-file-name buffer)))
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers like *Messages*.
        (when (and filename
                   (not (buffer-modified-p buffer)))
          (if (file-exists-p filename)
              ;; If the file exists, revert the buffer.
              (with-current-buffer buffer
                (revert-buffer :ignore-auto :noconfirm :preserve-modes))
            ;; If the file doesn't exist, kill the buffer.
            (let (kill-buffer-query-functions) ; No query done when killing buffer
              (kill-buffer buffer)
              (message "Killed non-existing file buffer: %s" filename)))))
      (setq buffer (pop list)))
    (message "Finished reverting buffers containing unmodified files.")))

Update

Here's an improved and a better documented version of above after looking at @Drew's solution.

(defun modi/revert-all-file-buffers ()
  "Refresh all open file buffers without confirmation.
Buffers in modified (not yet saved) state in emacs will not be reverted. They
will be reverted though if they were modified outside emacs.
Buffers visiting files which do not exist any more or are no longer readable
will be killed."
  (interactive)
  (dolist (buf (buffer-list))
    (let ((filename (buffer-file-name buf)))
      ;; Revert only buffers containing files, which are not modified;
      ;; do not try to revert non-file buffers like *Messages*.
      (when (and filename
                 (not (buffer-modified-p buf)))
        (if (file-readable-p filename)
            ;; If the file exists and is readable, revert the buffer.
            (with-current-buffer buf
              (revert-buffer :ignore-auto :noconfirm :preserve-modes))
          ;; Otherwise, kill the buffer.
          (let (kill-buffer-query-functions) ; No query done when killing buffer
            (kill-buffer buf)
            (message "Killed non-existing/unreadable file buffer: %s" filename))))))
  (message "Finished reverting buffers containing unmodified files."))

Reference

3

Another:

(defun revert-all-no-confirm ()
  "Revert all file buffers, without confirmation.
Buffers visiting files that no longer exist are ignored.
Files that are not readable (including do not exist) are ignored.
Other errors while reverting a buffer are reported only as messages."
  (interactive)
  (let (file)
    (dolist (buf  (buffer-list))
      (setq file  (buffer-file-name buf))
      (when (and file  (file-readable-p file))
        (with-current-buffer buf
          (with-demoted-errors "Error: %S" (revert-buffer t t)))))))
  • Thanks. I am stealing the dolist style to replace car and pop. Funny how you can keep on improving your config as you learn more elisp :) – Kaushal Modi Jul 8 '16 at 18:56
  • @KaushalModi That's why I posted it, in part. ;-) – Drew Jul 8 '16 at 22:47
1

I accepted Kausal's answer since it was closest to what I wanted, but I grabbed part of Drew's solution, too. I wrapped revert-buffer in with-demoted-errors and dropped the :preserve-modes parameter so that my syntax checker would re-parse all of my open files. I also let it kill modified files as well as unmodified, since I often get in trouble by accidentally C-x s-ing after a git checkout with a modified file open.

The final version is:

(defun revert-all-buffers ()
  "Refresh all open buffers from their respective files."
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (let ((filename (buffer-file-name buffer)))
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers like *Messages*.
        (when filename
          (if (file-exists-p filename)
              ;; If the file exists, revert the buffer.
              (with-demoted-errors "Error: %S"
                (with-current-buffer buffer
                  (revert-buffer :ignore-auto :noconfirm)))
            ;; If the file doesn't exist, kill the buffer.
            (let (kill-buffer-query-functions) ; No query done when killing buffer
              (kill-buffer buffer)
              (message "Killed non-existing file buffer: %s" buffer))))
        (setq buffer (pop list)))))
  (message "Finished reverting non-file buffers."))
1

I'd fix this with a condition-case or ignore-errors (docs here). I don't know exactly what you'll want it to do; if you want to do something with errors, if you can use condition-case to specify the outcome, or you can use ignore-errors to just continue. Something like:

(defun revert-all-buffers ()
  "Refreshes all open buffers from their respective files"
  (interactive)
  (let* ((list (buffer-list))
         (buffer (car list)))
    (while buffer
      (when (and (buffer-file-name buffer) 
                 (not (buffer-modified-p buffer)))
        (set-buffer buffer)
        (ignore-errors (revert-buffer t t t)))
      (setq list (cdr list))
      (setq buffer (car list))))
  (message "Refreshed open files"))
0

Based on @Drew's answer, with progress reporting since it can be slow with many files open.

(defun revert-all-buffers ()
  "Refresh all open buffers from their respective files."
  (interactive)

  (message "Begin reverting all buffers...")
  (let*
    (
      ;; pairs of (filename, buf)
      (ls
        (mapcar
          (lambda (buf) (list (buffer-file-name buf) buf))
          (seq-filter 'buffer-file-name (buffer-list))))
      (count (length ls))
      (index 1)
      )
    (while ls
      (cl-destructuring-bind (filename buf) (pop ls)
        ;; Revert only buffers containing files, which are not modified;
        ;; do not try to revert non-file buffers like *Messages*.
        (message "Reverting [%3d of %3d] %4d%%: %S"
          index count (round (* 100 (/ (float index) count))) filename)
        (if (file-exists-p filename)
          ;; If the file exists, revert the buffer.
          (with-demoted-errors "Error: %S"
            (with-current-buffer buf
              (revert-buffer :ignore-auto :noconfirm))
            )
          ;; If the file doesn't exist, kill the buffer.
          (let (kill-buffer-query-functions) ; No query done when killing buffer.
            (kill-buffer buf)
            (message "Killed non-existing file buffer: %s" buf)
            )
          )
        (setq index (1+ index))
        )
      )
    (message "Finished reverting %d file buffer(s)." count)
    )
  )

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.