【问题标题】:Emacs Reverts Buffer to Weird Previous State with Git-RebaseEmacs 使用 Git-Rebase 将缓冲区恢复到奇怪的先前状态
【发布时间】:2011-06-28 19:31:59
【问题描述】:

我在 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(有时在交互模式下将提交压缩在一起)。现在我已经提取了远程更新,我再去编辑文件,发现它看起来好像我已经丢失了我最近的所有更改。第一次发生时,我非常生气并诅咒变基算法以某种方式丢失了我的更改,直到我意识到文件完全完好无损,只是 Emacs 以某种方式恢复到我更改之前的文件版本'刚刚承诺。我总是可以通过关闭缓冲区并再次加载文件来解决问题,但每次这样做都很痛苦。

有人知道问题出在哪里吗?

【问题讨论】:

    标签: git emacs git-rebase


    【解决方案1】:

    我是autorevert 的原作者,但是,由于我已经很长时间没有使用它了,所以我必须深入挖掘我的记忆。

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

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

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

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

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

    【讨论】:

    • 这么多好答案!无论如何,这回答了我最初的问题“导致问题的原因是什么?”最好的,所以我选择了它。在实际解决问题方面,我将尝试@jtahlborn 的增加自动恢复间隔的建议,如果这不起作用则切换到 magit。
    • +1 用于 magit 模式。我遇到了另一个问题,即 emacs 无法打开您可以选择/压缩/编辑提交的视图。但只需打开 magit,然后使用 E 进行交互式 rebase 效果很好。如果您需要编辑以前的提交,您甚至可以在按下 E 后指定提交 sha1。
    【解决方案2】:

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

    1. git 更改文件“foo”
    2. 自动恢复启动并重新加载“foo”
    3. git 在同一秒内再次更改文件“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)))))
    

    【讨论】:

      【解决方案3】:

      auto-revert-mode 仅根据文件的修改日期决定是否恢复。我的猜测是 git 在变基过程中操纵变基文件的修改日期,阻止auto-revert-mode 看到每一个更改。

      可能的解决方案包括在变基后touching 文件(糟糕!),编写检查文件大小变化的auto-revert-handler 函数的替换版本,或在magit 内变基,这是一个相当出色的 emacs git界面。

      【讨论】:

        【解决方案4】:

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

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

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

        【讨论】:

        • 查看autorevert.elauto-revert-check-vc-info的唯一作用是强制刷新模式行中的VC信息;它不会影响何时/是否恢复缓冲区的决定。
        • 是的,但我希望恢复 vc 信息可能会强制对缓冲区内容进行一些更改。
        • 嗯,对,我错过了你的意图,我认为你正在做某事。
        【解决方案5】:

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

        【讨论】:

          猜你喜欢
          • 2016-04-16
          • 1970-01-01
          • 2012-08-05
          • 2012-06-13
          • 2018-12-01
          • 1970-01-01
          • 1970-01-01
          • 2011-06-27
          • 1970-01-01
          相关资源
          最近更新 更多