| 1 | ;;; magit-autorevert.el --- Revert buffers when files in repository change -*- lexical-binding:t -*- |
| 2 | |
| 3 | ;; Copyright (C) 2008-2026 The Magit Project Contributors |
| 4 | |
| 5 | ;; Author: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 6 | ;; Maintainer: Jonas Bernoulli <emacs.magit@jonas.bernoulli.dev> |
| 7 | |
| 8 | ;; SPDX-License-Identifier: GPL-3.0-or-later |
| 9 | |
| 10 | ;; Magit is free software: you can redistribute it and/or modify it |
| 11 | ;; under the terms of the GNU General Public License as published by |
| 12 | ;; the Free Software Foundation, either version 3 of the License, or |
| 13 | ;; (at your option) any later version. |
| 14 | ;; |
| 15 | ;; Magit is distributed in the hope that it will be useful, but WITHOUT |
| 16 | ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY |
| 17 | ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public |
| 18 | ;; License for more details. |
| 19 | ;; |
| 20 | ;; You should have received a copy of the GNU General Public License |
| 21 | ;; along with Magit. If not, see <https://www.gnu.org/licenses/>. |
| 22 | |
| 23 | ;;; Commentary: |
| 24 | |
| 25 | ;; This library implements support for automatically reverting buffers |
| 26 | ;; when visited files in the repository change. |
| 27 | |
| 28 | ;; See (info "(magit)Automatic Reverting of File-Visiting Buffers"). |
| 29 | |
| 30 | ;;; Code: |
| 31 | |
| 32 | (require 'autorevert) |
| 33 | (require 'compat) |
| 34 | |
| 35 | (declare-function magit-file-tracked-p "magit-git" (file)) |
| 36 | (declare-function magit-toplevel "magit-git" (&optional directory)) |
| 37 | (declare-function magit-git-executable "magit-git" ()) |
| 38 | |
| 39 | ;;; Options |
| 40 | |
| 41 | (defgroup magit-auto-revert nil |
| 42 | "Revert buffers when files in repository change." |
| 43 | :link '(custom-group-link auto-revert) |
| 44 | :link '(info-link "(magit)Automatic Reverting of File-Visiting Buffers") |
| 45 | :group 'auto-revert |
| 46 | :group 'magit-essentials |
| 47 | :group 'magit-modes) |
| 48 | |
| 49 | (defcustom auto-revert-buffer-list-filter nil |
| 50 | "Filter that determines which buffers `auto-revert-buffers' reverts. |
| 51 | |
| 52 | This option is provided by Magit, which also advises |
| 53 | `auto-revert-buffers' to respect it. Magit users who do not turn |
| 54 | on the local mode `auto-revert-mode' themselves, are best served |
| 55 | by setting the value to `magit-auto-revert-repository-buffer-p'. |
| 56 | |
| 57 | However the default is nil, so as not to disturb users who do use |
| 58 | the local mode directly. If you experience delays when running |
| 59 | Magit commands, then you should consider using one of the |
| 60 | predicates provided by Magit - especially if you also use Tramp. |
| 61 | |
| 62 | Users who do turn on `auto-revert-mode' in buffers in which Magit |
| 63 | doesn't do that for them, should likely not use any filter. |
| 64 | Users who turn on `global-auto-revert-mode', do not have to worry |
| 65 | about this option, because it is disregarded if the global mode |
| 66 | is enabled." |
| 67 | :package-version '(magit . "2.4.2") |
| 68 | :group 'auto-revert |
| 69 | :group 'magit-auto-revert |
| 70 | :group 'magit-related |
| 71 | :type `(radio (const :tag "No filter" nil) |
| 72 | (function-item ,#'magit-auto-revert-buffer-p) |
| 73 | (function-item ,#'magit-auto-revert-repository-buffer-p) |
| 74 | function)) |
| 75 | |
| 76 | (defcustom magit-auto-revert-tracked-only t |
| 77 | "Whether `magit-auto-revert-mode' only reverts tracked files." |
| 78 | :package-version '(magit . "2.4.0") |
| 79 | :group 'magit-auto-revert |
| 80 | :type 'boolean |
| 81 | :set (lambda (var val) |
| 82 | (set var val) |
| 83 | (when (and (bound-and-true-p magit-auto-revert-mode) |
| 84 | (featurep 'magit-autorevert)) |
| 85 | (magit-auto-revert-mode -1) |
| 86 | (magit-auto-revert-mode)))) |
| 87 | |
| 88 | (defcustom magit-auto-revert-immediately t |
| 89 | "Whether Magit reverts buffers immediately. |
| 90 | |
| 91 | If this is non-nil and either `global-auto-revert-mode' or |
| 92 | `magit-auto-revert-mode' is enabled, then Magit immediately |
| 93 | reverts buffers by explicitly calling `auto-revert-buffers' |
| 94 | after running Git for side-effects. |
| 95 | |
| 96 | If `auto-revert-use-notify' is non-nil (and file notifications |
| 97 | are actually supported), then `magit-auto-revert-immediately' |
| 98 | does not have to be non-nil, because the reverts happen |
| 99 | immediately anyway. |
| 100 | |
| 101 | If `magit-auto-revert-immediately' and `auto-revert-use-notify' |
| 102 | are both nil, then reverts happen after `auto-revert-interval' |
| 103 | seconds of user inactivity. That is not desirable." |
| 104 | :package-version '(magit . "2.4.0") |
| 105 | :group 'magit-auto-revert |
| 106 | :type 'boolean) |
| 107 | |
| 108 | ;;; Mode |
| 109 | |
| 110 | ;;;###autoload |
| 111 | (progn ; magit-auto-revert-mode--initialize |
| 112 | (defun magit-auto-revert-mode--initialize (symbol value) |
| 113 | (internal--define-uninitialized-variable symbol) |
| 114 | (if (not load-file-name) |
| 115 | (custom-initialize-set symbol value) |
| 116 | ;; Bugs in package managers prevent the use of lexical |
| 117 | ;; bindings in autoloaded code. See #5476 and #5485. |
| 118 | (defalias 'magit-auto-revert-mode--after-load |
| 119 | (apply-partially |
| 120 | (lambda (symbol value mode-file file) |
| 121 | (when (equal file mode-file) |
| 122 | (remove-hook 'after-load-functions |
| 123 | 'magit-auto-revert-mode--after-load) |
| 124 | (fmakunbound 'magit-auto-revert-mode--after-load) |
| 125 | (if after-init-time |
| 126 | (custom-initialize-set symbol value) |
| 127 | ;; Delay activation in case the user disables the mode |
| 128 | ;; after loading this library but still during startup. |
| 129 | (defalias 'magit-auto-revert-mode--after-init |
| 130 | (apply-partially |
| 131 | (lambda (symbol value) |
| 132 | (remove-hook 'after-init-hook |
| 133 | 'magit-auto-revert-mode--after-init) |
| 134 | (fmakunbound 'magit-auto-revert-mode--after-init) |
| 135 | (custom-initialize-set symbol value)) |
| 136 | symbol value)) |
| 137 | (add-hook 'after-init-hook 'magit-auto-revert-mode--after-init)))) |
| 138 | symbol value load-file-name)) |
| 139 | (add-hook 'after-load-functions 'magit-auto-revert-mode--after-load)))) |
| 140 | |
| 141 | (defun magit-turn-on-auto-revert-mode-if-desired (&optional file) |
| 142 | (cond (file |
| 143 | (let ((buffer (find-buffer-visiting file))) |
| 144 | (when buffer |
| 145 | (with-current-buffer buffer |
| 146 | (magit-turn-on-auto-revert-mode-if-desired))))) |
| 147 | ((and (not auto-revert-mode) ; see #3014 |
| 148 | (not global-auto-revert-mode) ; see #3460 |
| 149 | buffer-file-name |
| 150 | (or auto-revert-remote-files ; see #5422 |
| 151 | (not (file-remote-p buffer-file-name))) |
| 152 | (file-readable-p buffer-file-name) |
| 153 | (require 'magit-process) |
| 154 | (executable-find (magit-git-executable) t) |
| 155 | (magit-toplevel) |
| 156 | (or (not magit-auto-revert-tracked-only) |
| 157 | (magit-file-tracked-p buffer-file-name))) |
| 158 | (auto-revert-mode 1)))) |
| 159 | |
| 160 | ;;;###autoload |
| 161 | (define-globalized-minor-mode magit-auto-revert-mode auto-revert-mode |
| 162 | magit-turn-on-auto-revert-mode-if-desired |
| 163 | :package-version '(magit . "2.4.0") |
| 164 | :link '(info-link "(magit)Automatic Reverting of File-Visiting Buffers") |
| 165 | :group 'magit-auto-revert |
| 166 | :group 'magit-essentials |
| 167 | :init-value (not (or global-auto-revert-mode noninteractive)) |
| 168 | :initialize #'magit-auto-revert-mode--initialize) |
| 169 | |
| 170 | (defun magit-auto-revert-mode--disable () |
| 171 | "When enabling `global-auto-revert-mode', disable `magit-auto-revert-mode'." |
| 172 | (when (and global-auto-revert-mode |
| 173 | (bound-and-true-p magit-auto-revert-mode)) |
| 174 | (magit-auto-revert-mode -1))) |
| 175 | |
| 176 | (add-hook 'global-auto-revert-mode-hook #'magit-auto-revert-mode--disable) |
| 177 | |
| 178 | (put 'magit-auto-revert-mode 'function-documentation |
| 179 | "Toggle Magit Auto Revert mode. |
| 180 | |
| 181 | If called interactively, enable Magit Auto Revert mode if ARG is |
| 182 | positive, and disable it if ARG is zero or negative. If called |
| 183 | from Lisp, also enable the mode if ARG is omitted or nil, and |
| 184 | toggle it if ARG is `toggle'; disable the mode otherwise. |
| 185 | |
| 186 | Magit Auto Revert mode is a global minor mode that reverts |
| 187 | buffers associated with a file that is located inside a Git |
| 188 | repository when the file changes on disk. Use `auto-revert-mode' |
| 189 | to revert a particular buffer. Or use `global-auto-revert-mode' |
| 190 | to revert all file-visiting buffers, not just those that visit |
| 191 | a file located inside a Git repository. |
| 192 | |
| 193 | This global mode works by turning on the buffer-local mode |
| 194 | `auto-revert-mode' at the time a buffer is first created. The |
| 195 | local mode is turned on if the visited file is being tracked in |
| 196 | a Git repository at the time when the buffer is created. |
| 197 | |
| 198 | If `magit-auto-revert-tracked-only' is non-nil (the default), |
| 199 | then only tracked files are reverted. But if you stage a |
| 200 | previously untracked file using `magit-stage', then this mode |
| 201 | notices that. |
| 202 | |
| 203 | Unlike `global-auto-revert-mode', this mode never reverts any |
| 204 | buffers that are not visiting files. |
| 205 | |
| 206 | The behavior of this mode can be customized using the options |
| 207 | in the `autorevert' and `magit-autorevert' groups. |
| 208 | |
| 209 | This function calls the hook `magit-auto-revert-mode-hook'. |
| 210 | |
| 211 | Like nearly every mode, this mode should be enabled or disabled |
| 212 | by calling the respective mode function, the reason being that |
| 213 | changing the state of a mode involves more than merely toggling |
| 214 | a single switch, so setting the mode variable is not enough. |
| 215 | Also, you should not use `after-init-hook' to disable this mode. |
| 216 | |
| 217 | \(fn &optional ARG)") |
| 218 | |
| 219 | ;;;###autoload |
| 220 | (defun magit-auto-revert-buffers () |
| 221 | (when (and magit-auto-revert-immediately |
| 222 | (or global-auto-revert-mode |
| 223 | (and magit-auto-revert-mode auto-revert-buffer-list))) |
| 224 | (let ((auto-revert-buffer-list-filter |
| 225 | (or auto-revert-buffer-list-filter |
| 226 | #'magit-auto-revert-repository-buffer-p))) |
| 227 | (auto-revert-buffers)))) |
| 228 | |
| 229 | (defvar magit-auto-revert-toplevel nil) |
| 230 | |
| 231 | (defvar magit-auto-revert-counter 1 |
| 232 | "Incremented each time `auto-revert-buffers' is called.") |
| 233 | |
| 234 | (defun magit-auto-revert-buffer-p (buffer) |
| 235 | "Return non-nil if BUFFER visits a file inside the current repository. |
| 236 | The current repository is the one containing `default-directory'. |
| 237 | If there is no current repository, then return t for any BUFFER." |
| 238 | (magit-auto-revert-repository-buffer-p buffer t)) |
| 239 | |
| 240 | (defun magit-auto-revert-repository-buffer-p (buffer &optional fallback) |
| 241 | "Return non-nil if BUFFER visits a file inside the current repository. |
| 242 | The current repository is the one containing `default-directory'. |
| 243 | If there is no current repository, then return FALLBACK (which |
| 244 | defaults to nil) for any BUFFER." |
| 245 | ;; Call `magit-toplevel' just once per cycle. |
| 246 | (unless (and magit-auto-revert-toplevel |
| 247 | (= (cdr magit-auto-revert-toplevel) |
| 248 | magit-auto-revert-counter)) |
| 249 | (require 'magit-process) |
| 250 | (setq magit-auto-revert-toplevel |
| 251 | (cons (or (magit-toplevel) 'no-repo) |
| 252 | magit-auto-revert-counter))) |
| 253 | (let ((top (car magit-auto-revert-toplevel))) |
| 254 | (if (eq top 'no-repo) |
| 255 | fallback |
| 256 | (let ((dir (buffer-local-value 'default-directory buffer))) |
| 257 | (and (equal (file-remote-p dir) |
| 258 | (file-remote-p top)) |
| 259 | ;; ^ `tramp-handle-file-in-directory-p' lacks this optimization. |
| 260 | (file-in-directory-p dir top)))))) |
| 261 | |
| 262 | (define-advice auto-revert-buffers (:around (fn) buffer-list-filter) |
| 263 | (incf magit-auto-revert-counter) |
| 264 | (if (or global-auto-revert-mode |
| 265 | (not auto-revert-buffer-list) |
| 266 | (not auto-revert-buffer-list-filter)) |
| 267 | (funcall fn) |
| 268 | (let ((auto-revert-buffer-list |
| 269 | (seq-filter auto-revert-buffer-list-filter |
| 270 | auto-revert-buffer-list))) |
| 271 | (funcall fn)) |
| 272 | (unless auto-revert-timer |
| 273 | (auto-revert-set-timer)))) |
| 274 | |
| 275 | ;;; _ |
| 276 | (provide 'magit-autorevert) |
| 277 | ;; `cond-let' intentionally not used. |
| 278 | ;;; magit-autorevert.el ends here |