Emacs使用Git-Rebase将缓冲区恢复为奇怪的先前状态

时间:2011-06-28 19:32:00

标签: git emacs git-rebase

我在OS X上使用Emacs(23.3.1)。我从终端发出git命令,而不使用任何Emacs的VC功能。我将Emacs设置为在修改文件时进行刷新,这是通过我的.emacs文件的这些行启用的:

(custom-set-variables
 ...
 '(auto-revert-interval 1)
 ... 
)
(global-auto-revert-mode 1)

这一直按我的预期运作;如果我拉,我的Emacs缓冲区将更新合并或冲突,就好像我已经退出缓冲区并重新加载每个缓冲区。

最近我开始重新定位(调用git-fetch然后git-rebase)而不是简单地拉(使用git-pull),我不知道这个问题是否与git-rebase有关,但我当然在此之前没有注意到这个问题,现在就做了。

所以这就是问题:我更新文件,使用git commit -am "..."提交更改,运行git fetch origin,然后运行git rebase origin/master(有时在交互模式下将提交压缩在一起)。现在我已经进入了远程更新,我将再次编辑该文件,并发现它看起来我已经丢失了所有最近的更改。它第一次发生时,我非常生气并诅咒rebase算法以某种方式丢失我的更改,直到我意识到文件完全完好无损并且只是Emacs在更改之前以某种方式恢复到文件的版本我我承诺。我总是可以通过关闭缓冲区并再次加载文件来解决问题,但每次都这样做很麻烦。

有谁知道问题是什么?

5 个答案:

答案 0 :(得分:3)

我是autorevert的原创作者,然而,我必须深入挖掘我的记忆,因为我已经很长时间没有使用它了。

我认为这归结为Emacs核心函数verify-visited-file-modtime的一个问题,它会自动恢复使用以检查缓冲区是否需要刷新。

这可能是由多种原因造成的:

  • 这可能是竞争条件,其中Emacs在完全写入之前读取文件,但将缓冲区modtime设置为结束时间。 (当发生这种情况时,Emacs已将缓冲区中途自动恢复,但新的自动恢复不会触发恢复文件的最终版本。)

  • 如果文件的两个变体具有相同的时间戳,那么Emacs将看不到差异。

我不认为你在lisp方面可以做的很少,以使其正常工作(不解决暴力解决方案)。 Emacs确实需要原子地获取已打开并读入缓冲区的文件的指纹,这只能从核心(这是我所在区域之外的东西)完成。此外,某些文件系统的时间戳分辨率不高于一秒,因此使用时间戳来检测文件是否已更改本来就很困难。

答案 1 :(得分:2)

如前所述,这是一种竞争条件。 “git pull --rebase”可能会在重播您的提交时多次修改同一文件。如果发生以下情况,则游戏结束:

  1. git更改文件“foo”
  2. 自动恢复启动并重新加载“foo”
  3. git在同一个第二个
  4. 中再次更改文件“foo”

    由于unix文件时间具有第二分辨率,因此emacs无法判断第二次更改是否发生。我最近注意到我丢失了一堆更改,所以我决定尝试解决emacs中的问题。我修改了emacs代码如下。如果修改文件的时间与当前系统时间相同,我将自动恢复推迟到下一个时间间隔:

    ;;
    ;; 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)))))
    

答案 2 :(得分:1)

这可能是一个时间问题,需要快速连续多次修改文件。它总是会发生,还是随机的?

另外,将auto-revert-check-vc-info设置为t可能有所帮助(假设您使用的是与emacs中vc兼容的git模式)。

到目前为止,所有答案都提到过,这可能是一个时间问题。它可能看起来反直觉,但可能将自动反转间隔设置为更长的可能会解决问题(从而增加了当恢复缓冲区时,它恢复到最终状态的机会)。

答案 3 :(得分:1)

auto-revert-mode决定是否仅根据文件的修改日期进行还原。我的猜测是,git在重新定位过程中操纵了已更改文件的修改日期,阻止auto-revert-mode看到每个更改。

可能的解决方案包括重新定位后的touch个文件(哎呀!),编写auto-revert-handler函数的替换版本,用于检查文件大小的变化,或者在magit内进行变基,哪个是一个相当精彩的emacs git界面。

答案 4 :(得分:0)

另一种解决方案是在执行Git操作时关闭Emacs,然后重新打开。如果我能找到一种方法来保存从一个Emacs会话到另一个Emacs会话的打开的缓冲区,我几乎准备诉诸于此。