Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #171 make Magit hooks great again #172

Closed
wants to merge 1 commit into from
Closed

Fix #171 make Magit hooks great again #172

wants to merge 1 commit into from

Conversation

wyuenho
Copy link
Contributor

@wyuenho wyuenho commented Oct 8, 2021

Re: #171 calling magit-unstaged-files when magit is refreshing will always result in nil because magit only refreshes when everything is done.

diff-hl.el Outdated
(let ((topdir (magit-toplevel)))
(dolist (buf (buffer-list))
(when-let* ((file-name (buffer-file-name buf))
(state (vc-state file-name)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tarsius Could you review this patch?

This seems like a problematic change triggering a process call per buffer. Which means O(N) for a fairly expensive O.

IIUC the previously used (magit-unstaged-files t) didn't have this problem -- it either accessed a pre-existing list or fetched it with one process call. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the properties for vc-state are cached in an obarray?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cached, yes.

But if they have been reset and not updated for each of those buffers yet, the effect will be exactly as described.

Whether that is the case, again, depends on what Magit does behind the scenes.

@wyuenho
Copy link
Contributor Author

wyuenho commented Aug 30, 2022

I'm going to close this PR. While I think this PR is perfectly reasonable, it's still not the most thorough way to keep all git backed buffers up to date. The One True Way to keep all git backed buffers up to date to listen to changes from one single source of truth and then trigger the necessary updates on all relevant buffers. I'm dropping the following hack for posterity. The code size could perhaps be cut in half, or there may be bugs, but it's a good start.

;; Refresh VC related states reliably across packages.
(when file-notify--library
  (defvar watched-git-heads nil
    "A list of .git/logs/HEAD file to watcher mappings.")

  (defun refresh-git-backed-buffer ()
    "Refresh the current buffer base on the current git state."
    (if (and (boundp 'magit-mode) (derived-mode-p 'magit-mode))
        (magit-refresh-buffer)
      (unless revert-buffer-in-progress-p
        (cond
         ((or (bound-and-true-p diff-hl-flydiff-mode)
              (bound-and-true-p diff-hl-mode))
          (diff-hl-update))
         ((bound-and-true-p diff-hl-dired-mode)
          (diff-hl-dired-update))
         ((bound-and-true-p vc-dir-mode)
          (vc-dir-refresh)
          (when (bound-and-true-p diff-hl-dir-mode)
            (diff-hl-dir-update)))
         ((vc-backend (buffer-file-name))
          (vc-refresh-state))))))

  (defun handle-git-state-change (event)
    "Callback to `file-notify-add-watch' to handle git state change.

EVENT is a file system notification event.  If EVENT is a change
event, find all the visible buffers belonging to the same git
repo and refresh all of the version control minor mode states.
If EVENT is a deleted or renamed event, remove the file system
notification watcher.

See `file-notify-add-watch' for more details."
    (pcase-let ((`(,desc ,action ,file ,@_) event))
      (pcase action
        ((or 'deleted 'renamed)
         (file-notify-rm-watch desc))
        ('changed
         (let ((vc-root (apply 'file-name-concat "/" (seq-subseq (split-string file "/") 0 -3))))
           (dolist (frame (frame-list))
             (when (not (frame-parent frame))
               (dolist (buf (buffer-list frame))
                 (when (not (string-prefix-p " " (buffer-name buf)))
                   (with-current-buffer buf
                     (when (string-prefix-p vc-root (expand-file-name default-directory))
                       (refresh-git-backed-buffer))))))))))))

  (defun watch-git-head ()
    (let ((file (buffer-file-name)))
      (when (eq 'Git (vc-backend file))
        (let* ((root (expand-file-name (vc-git-root file)))
               (head (file-name-concat root ".git" "logs" "HEAD")))
          (unless (assoc-default head watched-git-heads)
            (ignore-errors
              (setf (alist-get head watched-git-heads nil nil 'equal)
                    (file-notify-add-watch head '(change) 'handle-git-state-change))))))))

  (defun reap-git-head-watchers ()
    (when-let* ((buffer-default-dirs-sans-current
                 (seq-uniq
                  (mapcar
                   (lambda (buf) (with-current-buffer buf (expand-file-name default-directory)))
                   (seq-filter
                    (lambda (buf)
                      (not (or (string-prefix-p " " (buffer-name buf))
                               (equal buf (current-buffer)))))
                    (apply 'append
                           (mapcar
                            'buffer-list
                            (seq-filter
                             (lambda (frame) (not (frame-parent frame)))
                             (frame-list))))))))
                (git-heads-to-delete
                 (seq-filter
                  (lambda (git-head)
                    (let ((vc-root (apply 'file-name-concat "/" (seq-subseq (split-string git-head "/") 0 -3))))
                      (not (seq-some (apply-partially 'string-prefix-p vc-root) buffer-default-dirs-sans-current))))
                  (mapcar 'car watched-git-heads))))

      (dolist (git-head git-heads-to-delete)
        (let ((watcher (assoc-default git-head watched-git-heads)))
          (file-notify-rm-watch watcher)
          (setf (alist-get git-head watched-git-heads nil t 'equal) nil)))))

  (add-hook 'find-file-hook 'watch-git-head)
  (add-hook 'kill-buffer-hook 'reap-git-head-watchers))

@wyuenho wyuenho closed this Aug 30, 2022
@dgutov
Copy link
Owner

dgutov commented Mar 18, 2023 via email

@tarsius
Copy link
Contributor

tarsius commented Mar 18, 2023

Yeah, this has kept falling through the cracks. But it is a "just one more thing" issue, and I am currently trying to take a break from those (see https://www.reddit.com/r/emacs/comments/11f7urf/thanks_for_your_support_and_patience/). I still plan to look into this again at some point, but won't get around to that for at least another few weeks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants