magit-autorevert.el 10.7 KB
Newer Older
1
;;; magit-autorevert.el --- revert buffers when files in repository change  -*- lexical-binding: t -*-
2

Jonas Bernoulli's avatar
Jonas Bernoulli committed
3
;; Copyright (C) 2010-2018  The Magit Project Contributors
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
;;
;; You should have received a copy of the AUTHORS.md file which
;; lists all contributors.  If not, see http://magit.vc/authors.

;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>

;; Magit is free software; you can redistribute it and/or modify it
;; under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;;
;; Magit is distributed in the hope that it will be useful, but WITHOUT
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
;; or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
;; License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with Magit.  If not, see http://www.gnu.org/licenses.

;;; Code:

(require 'cl-lib)
(require 'dash)

(require 'magit-git)

(require 'autorevert)

33 34
;;; Options

35 36
(defgroup magit-auto-revert nil
  "Revert buffers when files in repository change."
37 38
  :link '(custom-group-link auto-revert)
  :link '(info-link "(magit)Automatic Reverting of File-Visiting Buffers")
39
  :group 'auto-revert
40 41
  :group 'magit-essentials
  :group 'magit-modes)
42

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
(defcustom auto-revert-buffer-list-filter nil
  "Filter that determines which buffers `auto-revert-buffers' reverts.

This option is provided by `magit', which also redefines
`auto-revert-buffers' to respect it.  Magit users who do not turn
on the local mode `auto-revert-mode' themselves, are best served
by setting the value to `magit-auto-revert-repository-buffers-p'.

However the default is nil, to not disturb users who do use the
local mode directly.  If you experience delays when running Magit
commands, then you should consider using one of the predicates
provided by Magit - especially if you also use Tramp.

Users who do turn on `auto-revert-mode' in buffers in which Magit
doesn't do that for them, should likely not use any filter.
Users who turn on `global-auto-revert-mode', do not have to worry
about this option, because it is disregarded if the global mode
is enabled."
  :package-version '(magit . "2.4.2")
  :group 'auto-revert
  :group 'magit-auto-revert
64
  :group 'magit-related
65 66 67 68 69
  :type '(radio (const :tag "no filter" nil)
                (function-item magit-auto-revert-buffer-p)
                (function-item magit-auto-revert-repository-buffer-p)
                function))

70 71 72
(defcustom magit-auto-revert-tracked-only t
  "Whether `magit-auto-revert-mode' only reverts tracked files."
  :package-version '(magit . "2.4.0")
73
  :group 'magit-auto-revert
74 75 76
  :type 'boolean
  :set (lambda (var val)
         (set var val)
77 78
         (when (and (bound-and-true-p magit-auto-revert-mode)
                    (featurep 'magit-autorevert))
79 80 81
           (magit-auto-revert-mode -1)
           (magit-auto-revert-mode))))

82
(defcustom magit-auto-revert-immediately t
83 84 85 86 87 88 89 90 91
  "Whether Magit reverts buffers immediately.

If this is non-nil and either `global-auto-revert-mode' or
`magit-auto-revert-mode' is enabled, then Magit immediately
reverts buffers by explicitly calling `auto-revert-buffers'
after running git for side-effects.

If `auto-revert-use-notify' is non-nil (and file notifications
are actually supported), then `magit-auto-revert-immediately'
92 93
does not have to be non-nil, because the reverts happen
immediately anyway.
94 95 96 97 98

If `magit-auto-revert-immediately' and `auto-revert-use-notify'
are both nil, then reverts happen after `auto-revert-interval'
seconds of user inactivity.  That is not desirable."
  :package-version '(magit . "2.4.0")
99
  :group 'magit-auto-revert
100 101
  :type 'boolean)

102 103
;;; Mode

104 105 106 107 108 109
(defun magit-turn-on-auto-revert-mode-if-desired (&optional file)
  (if file
      (--when-let (find-buffer-visiting file)
        (with-current-buffer it
          (magit-turn-on-auto-revert-mode-if-desired)))
    (when (and buffer-file-name
110
               (file-readable-p buffer-file-name)
111 112
               (magit-toplevel)
               (or (not magit-auto-revert-tracked-only)
113
                   (magit-file-tracked-p buffer-file-name))
114 115
               (not auto-revert-mode)         ; see #3014
               (not global-auto-revert-mode)) ; see #3460
116
      (auto-revert-mode 1))))
117 118 119 120 121

;;;###autoload
(define-globalized-minor-mode magit-auto-revert-mode auto-revert-mode
  magit-turn-on-auto-revert-mode-if-desired
  :package-version '(magit . "2.4.0")
122
  :link '(info-link "(magit)Automatic Reverting of File-Visiting Buffers")
123
  :group 'magit-auto-revert
124
  :group 'magit-essentials
125 126 127 128 129 130 131 132 133 134
  ;; - When `global-auto-revert-mode' is enabled, then this mode is
  ;;   redundant.
  ;; - In all other cases enable the mode because if buffers are not
  ;;   automatically reverted that would make many very common tasks
  ;;   much more cumbersome.
  ;; - When `magit-revert-buffers' is nil, then the user has opted out
  ;;   of the automatic reverts while a very old implementation was
  ;;   still in use.  We continued to respect that setting for another
  ;;   two and a half years, but no longer do so now.
  :init-value (and (not global-auto-revert-mode)
135
                   (not noninteractive)))
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
;; - Unfortunately `:init-value t' only sets the value of the mode
;;   variable but does not cause the mode function to be called.
;; - I don't think it works like this on purpose, but since one usually
;;   should not enable global modes by default, it is understandable.
;; - If the user has set the variable `magit-auto-revert-mode' to nil
;;   after loading magit (instead of doing so before loading magit or
;;   by using the function), then we should still respect that setting.
;; - If the user has set the obsolete variable `magit-revert-buffers'
;;   to nil before or after loading magit, then we should still respect
;;   that setting.
;; - If the user sets one of these variables after loading magit and
;;   after `after-init-hook' has run, then that won't have an effect
;;   and there is nothing we can do about it.
(defun magit-auto-revert-mode--init-kludge ()
  "This is an internal kludge to be used on `after-init-hook'.
Do not use this function elsewhere, and don't remove it from
the `after-init-hook'.  For more information see the comments
and code surrounding the definition of this function."
154
  (if magit-auto-revert-mode
155
      (let ((start (current-time)))
156
        (magit-message "Turning on magit-auto-revert-mode...")
157
        (magit-auto-revert-mode 1)
158
        (magit-message
159 160 161 162 163 164
         "Turning on magit-auto-revert-mode...done%s"
         (let ((elapsed (float-time (time-subtract (current-time) start))))
           (if (> elapsed 0.2)
               (format " (%.3fs, %s buffers checked)" elapsed
                       (length (buffer-list)))
             ""))))
165
    (magit-auto-revert-mode -1)))
166 167 168 169 170 171 172
(if after-init-time
    ;; Since `after-init-hook' has already been
    ;; run, turn the mode on or off right now.
    (magit-auto-revert-mode--init-kludge)
  ;; By the time the init file has been fully loaded the
  ;; values of the relevant variables might have changed.
  (add-hook 'after-init-hook #'magit-auto-revert-mode--init-kludge t))
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199

(put 'magit-auto-revert-mode 'function-documentation
     "Toggle Magit Auto Revert mode.
With a prefix argument ARG, enable Magit Auto Revert mode if ARG
is positive, and disable it otherwise.  If called from Lisp,
enable the mode if ARG is omitted or nil.

Magit Auto Revert mode is a global minor mode that reverts
buffers associated with a file that is located inside a Git
repository when the file changes on disk.  Use `auto-revert-mode'
to revert a particular buffer.  Or use `global-auto-revert-mode'
to revert all file-visiting buffers, not just those that visit
a file located inside a Git repository.

This global mode works by turning on the buffer-local mode
`auto-revert-mode' at the time a buffer is first created.  The
local mode is turned on if the visited file is being tracked in
a Git repository at the time when the buffer is created.

If `magit-auto-revert-tracked-only' is non-nil (the default),
then only tracked files are reverted.  But if you stage a
previously untracked file using `magit-stage', then this mode
notices that.

Unlike `global-auto-revert-mode', this mode never reverts any
buffers that are not visiting files.

200 201 202
The behavior of this mode can be customized using the options
in the `autorevert' and `magit-autorevert' groups.

203 204 205 206
This function calls the hook `magit-auto-revert-mode-hook'.")

(defun magit-auto-revert-buffers ()
  (when (and magit-auto-revert-immediately
207 208
             (or global-auto-revert-mode
                 (and magit-auto-revert-mode auto-revert-buffer-list)))
209 210 211 212 213 214 215
    (let ((auto-revert-buffer-list-filter
           (or auto-revert-buffer-list-filter
               'magit-auto-revert-repository-buffer-p)))
      (auto-revert-buffers))))

(defvar magit-auto-revert-toplevel nil)

216 217 218 219
(when (< emacs-major-version 25)
  (defvar auto-revert-buffers-counter 1
    "Incremented each time `auto-revert-buffers' is called"))

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
(defun magit-auto-revert-buffer-p (buffer)
  "Return t if BUFFER visits a file inside the current repository.
The current repository is the one in which `default-directory' is
located.  If there is no current repository, then return t for
any BUFFER."
  (magit-auto-revert-repository-buffer-p buffer t))

(defun magit-auto-revert-repository-buffer-p (buffer &optional fallback)
  "Return t if BUFFER visits a file inside the current repository.
The current repository is the one in which `default-directory' is
located.  If there is no current repository, then return FALLBACK
\(which defaults to nil) for any BUFFER."
  ;; Call `magit-toplevel' just once per cycle.
  (unless (and magit-auto-revert-toplevel
               (= (cdr magit-auto-revert-toplevel)
                  auto-revert-buffers-counter))
    (setq magit-auto-revert-toplevel
          (cons (or (magit-toplevel) 'no-repo)
                auto-revert-buffers-counter)))
  (let ((top (car magit-auto-revert-toplevel)))
    (if (eq top 'no-repo)
        fallback
      (let ((dir (with-current-buffer buffer default-directory)))
        (and (equal (file-remote-p dir)
                    (file-remote-p top))
             ;; ^ `tramp-handle-file-in-directory-p' lacks this optimization.
             (file-in-directory-p dir top))))))
247

248 249 250 251 252 253 254 255 256 257
(defun auto-revert-buffers--buffer-list-filter ()
  (when (< emacs-major-version 25)
    (cl-incf auto-revert-buffers-counter))
  (when auto-revert-buffer-list-filter
    (setq auto-revert-buffer-list
          (--filter auto-revert-buffer-list-filter
                    auto-revert-buffer-list))))

(advice-add 'auto-revert-buffers :before
            'auto-revert-buffers--buffer-list-filter)
258

259
;;; _
260 261
(provide 'magit-autorevert)
;;; magit-autorevert.el ends here