I'm using Emacs (23.3.1) on OS X. I'm issuing git commands from a terminal, not using any of Emacs' VC functionality. I have Emacs set up to refresh when files are modified, which is enabled by these lines of my .emacs file:
(custom-set-variables
...
'(auto-revert-interval 1)
...
)
(global-auto-revert-mode 1)
This has always worked as I expected; if I pulled, my Emacs buffers would update with the merges or conflicts as if I had quit the buffers and freshly loaded each one.
Recently I started rebasing (calling git-fetch and then git-rebase) instead of simply pulling (with git-pull), and I don't know if the issue actually has to do with git-rebase, but I certainly didn't notice the problem before then and do now.
So here's the problem: I update a file, commit the changes with git commit -am "..."
, run git fetch origin
, then run git rebase origin/master
(sometimes squashing commits together in interactive mode). Now that I've pulled in the remote updates, I go to edit the file some more, and find that it looks like I've lost all my recent changes. The first time it happened, I got very mad and cursed the rebase algorithm for somehow losing my changes, until I realized that the file was completely intact and it was just that Emacs had somehow reverted to the version of the file prior to the changes I'd just committed. I can always solve the problem by closing the buffer and loading the file again, but this is quite a pain to do every time.
Does anyone have any idea what the problem is?
I'm the original author of autorevert
, however, I have to dig quite deep into my memory as I haven't worked with it for quite some time.
I believe this boils down to a problem with the Emacs core function verify-visited-file-modtime
, which auto-revert use to check if a buffers needs to be refreshed.
This could be caused by a number of reasons:
This could be a race condition, where Emacs reads a file before it was completely written, but sets the buffer modtime to the end time. (When this occurs, Emacs has auto-reverted the buffer half-way, but a new auto-revert does not trigger to revert the final version of the file.)
If the two variants of the file have the same time stamp, then Emacs will not see the difference.
I don't think that there is little you can do on the lisp side to get this working properly (without resolving to brute-force solutions). Emacs really need to atomically get a fingerprint of the file that has been opened and read into a buffer, and this can only be done from the core (which is something outside my area). Also, some file systems don't have a higher timestap resolution than one second, making it inherently difficult to use the timestamp to detect if a file has been changed.
It is a race condition as mentioned before. "git pull --rebase" may modify the same file multiple times as it replays your commits. If the following occurs it's game over:
- git changes file "foo"
- auto-revert kicks in and reloads "foo"
- git changes file "foo" again in the same second
Since unix file times have second resolution there's no way for emacs to tell that the second change occurred. I recently noticed I lost a bunch of changes that way so I decided to try to address the problem in emacs. I have patched the emacs code as follows. If the time the file is modified is the same as the current system time, I postpone the auto-revert until the next interval:
;;
;; Fix the auto-revert-handler so that if the system time is the
;; same as the new modified time for a file, skip it on this
;; iteration. This should fix race conditions when a file is changed
;; multiple times within the same second.
;;
(defun file-change-too-close-for-comfort ()
(let* ((file-time-raw (nth 5 (file-attributes (buffer-file-name))))
(file-time (+ (lsh (nth 0 file-time-raw) 16) (nth 1 file-time-raw)))
(current-time (+ (lsh (nth 0 (current-time)) 16) (nth 1 (current-time)))))
(and (eq current-time file-time)
(message "%s: postpone revert" (buffer-name))
t)))
(defun auto-revert-handler ()
"Revert current buffer, if appropriate.
This is an internal function used by Auto-Revert Mode."
(when (or auto-revert-tail-mode (not (buffer-modified-p)))
(let* ((buffer (current-buffer)) size
(revert
(or (and buffer-file-name
(file-readable-p buffer-file-name)
(if auto-revert-tail-mode
;; Tramp caches the file attributes. Setting
;; `tramp-cache-inhibit' forces Tramp to
;; reread the values.
(let ((tramp-cache-inhibit-cache t))
(/= auto-revert-tail-pos
(setq size
(nth 7 (file-attributes
buffer-file-name)))))
(and (not (file-remote-p buffer-file-name))
(not (verify-visited-file-modtime buffer))
(not (file-change-too-close-for-comfort)))))
(and (or auto-revert-mode
global-auto-revert-non-file-buffers)
revert-buffer-function
(boundp 'buffer-stale-function)
(functionp buffer-stale-function)
(funcall buffer-stale-function t))))
eob eoblist)
(when revert
(when (and auto-revert-verbose
(not (eq revert 'fast)))
(message "Reverting buffer `%s'." (buffer-name)))
;; If point (or a window point) is at the end of the buffer,
;; we want to keep it at the end after reverting. This allows
;; to tail a file.
(when buffer-file-name
(setq eob (eobp))
(walk-windows
#'(lambda (window)
(and (eq (window-buffer window) buffer)
(= (window-point window) (point-max))
(push window eoblist)))
'no-mini t))
(if auto-revert-tail-mode
(auto-revert-tail-handler size)
;; Bind buffer-read-only in case user has done C-x C-q,
;; so as not to forget that. This gives undesirable results
;; when the file's mode changes, but that is less common.
(let ((buffer-read-only buffer-read-only))
(revert-buffer 'ignore-auto 'dont-ask 'preserve-modes)))
(when buffer-file-name
(when eob (goto-char (point-max)))
(dolist (window eoblist)
(set-window-point window (point-max)))))
;; `preserve-modes' avoids changing the (minor) modes. But we
;; do want to reset the mode for VC, so we do it manually.
(when (or revert auto-revert-check-vc-info)
(vc-find-file-hook)))))
auto-revert-mode
decides whether to revert based solely on the file's modification date. My guess is that git manipulates the modification date of the rebased files during the rebasing process, stopping auto-revert-mode
from seeing every change.
Possible solutions include touch
ing files after rebasing (yuck!), writing a replacement version of the auto-revert-handler
function which checks for changes in file size, or rebasing within magit, which is a rather wonderful emacs git interface.
it may be a timing issue with multiple modifications to the file in quick succession. does it always happen, or is it random?
also, maybe setting auto-revert-check-vc-info
to t
could help (assuming you are using a git mode which is vc compatible in emacs).
as all the answers have mentioned thus far, it's probably a timing issue. it may seem counter intuitive, but maybe setting the autorevert interval to something longer may fix the problem (thus increasing the chances that when the buffer is reverted, it is reverted to its final state).
Another solution would be to close Emacs when doing Git operations and reopen after. I am almost ready to resort to that if I can find a way to preserve the opened buffers from one Emacs session to the other.
来源:https://stackoverflow.com/questions/6512086/emacs-reverts-buffer-to-weird-previous-state-with-git-rebase