-
-
Notifications
You must be signed in to change notification settings - Fork 820
Tips and Tricks
This page is dedicated to tips n’ tricks. Feel free to add you own.
Table of Contents
- Miscellaneous
- Inspecting hints
- Performance hints
-
Useful code snippets
- Show staged and unstaged changes, but nothing else
- Ask for confirmation before pushing to origin/master
- Cycle margin visibility
- Automatically displaying the process buffer
- Handling third-party prompts
- Hook run per file-visiting buffer when Magit status is refreshed
- Updating VC’s mode-line information
D -b g
toggles the --ignore-space-change
switch in the
magit-diff-refresh-popup
.
Similar to how it’s possible to instead open a directory in dired while
completing a file name (using C-d
) you can also instead bring up the
appropriate Magit status buffer. This does require some setup, see
ido-enter-magit-status
’s doc-string.
diff range
is what you want. The docs do say this but I wish this
was clearer. d r
asks for a range but you give it the branch you
want to compare to. For example, if you’re on branch feature
and
want to compare to master
:
d r
Diff for range: master
Also see the performance hints in the manual.
According to #2108 Git has the same issue and using ext2 helps
To get a buffer with just the staged and unstaged changes, but not all the other things displayed in the status buffer, use this:
(define-derived-mode magit-staging-mode magit-status-mode "Magit staging"
"Mode for showing staged and unstaged changes."
:group 'magit-status)
(defun magit-staging-refresh-buffer ()
(magit-insert-section (status)
(magit-insert-unstaged-changes)
(magit-insert-staged-changes)))
(defun magit-staging ()
(interactive)
(magit-mode-setup #'magit-staging-mode))
Also see #2219.
The convenient keybindings for magit actions can make it awfully easy
to unintentionally do something bad that you would never do if you had
to type out the git commands, and pushing to upstream – typically
origin/master
– isn’t always easy to recover from.
The following advice adds a yes-or-no-p
query to the
magit-push-current-to-upstream
command (i.e. the P u
binding). If you
generally want to push to your push remote, and only occasionally want
to push to upstream, you may find this a convenient safety net.
;; Protect against accidental pushes to upstream
(define-advice magit-push-current-to-upstream (:before (args) query-yes-or-no)
"Prompt for confirmation before permitting a push to upstream."
(when-let ((branch (magit-get-current-branch)))
(unless (yes-or-no-p (format "Push %s branch upstream to %s? "
branch
(or (magit-get-upstream-branch branch)
(magit-get "branch" branch "remote"))))
(user-error "Push to upstream aborted by user"))))
(defun magit-cycle-margin ()
"Cycle visibility of the Magit margin.
,-> show with details --> show no details -- hide -.
`--------------------------------------------------'"
(interactive)
(if (not (magit-margin-option))
(user-error "Magit margin isn't supported in this buffer")
(pcase (list (nth 0 magit-buffer-margin)
(and (nth 3 magit-buffer-margin) t))
(`(t t)
(setf (nth 3 magit-buffer-margin) nil)
(magit-set-buffer-margin nil t))
(`(t nil)
(setf (nth 0 magit-buffer-margin) nil)
(magit-set-buffer-margin))
(`(nil ,_)
(setf (nth 0 magit-buffer-margin) t)
(setf (nth 3 magit-buffer-margin) t)
(magit-set-buffer-margin nil t)))))
Sometimes it can be desirable to monitor the precise Git commands,
that are executed as a result of Magit operations, such as staging and
unstaging, fetching, merging, etc. This can be easily done by
displaying the process buffer, using the associated key ($
by default)
in any Magit buffer, but if monitoring of all commands is desired, the
following advice will auto-display the process buffer, whenever it’s
updated. (As it is, it will avoid focusing the auto-displayed buffer,
thus stealing the focus from whatever buffer originated the command,
but this can easily be changed, if necessary).
(defun auto-display-magit-process-buffer (&rest args)
"Automatically display the process buffer when it is updated."
(let ((magit-display-buffer-noselect t))
(magit-process-buffer)))
(advice-add 'magit-process-insert-section :before
#'auto-display-magit-process-buffer)
Sometimes hooks for git have self defined prompt, not only yes-or-no,
username, or password. Magit provides magit-process-prompt-functions
to let user add the handler for the special prompt. Below is a
user-provided example to handle the prompt like:
Below files need ...
...
... (a/f/c):
(defun magit-process-apply-force-or-cancel-prompt-hook (proc str)
"Hook method to handle STR in magit process filter with PROC."
(when-let ((regex " [[(]\\([Aa]\\(?:pply\\)?\\)[/|]\\([Ff]\\(?:orce\\)?\\)\
[/|]\\([Cc]\\(?:ancel\\)?\\)[])] ?[?:] ?$")
(beg (string-match regex str))
(choices '(?a ?f ?c))
(resize-mini-windows t))
(process-send-string
proc
(downcase
(concat
(string (magit-process-kill-on-abort proc
(let* ((prompt-str nil)
(prompt-start-regex "Below files need")
(prompt-beg (string-match prompt-start-regex str)))
(if prompt-beg
(setq prompt-str (substring str prompt-beg))
(with-current-buffer (process-buffer proc)
(save-excursion
(goto-char (point-max))
(search-backward prompt-start-regex)
(setq prompt-str
(concat (string-trim-right
(buffer-substring (point) (point-max)))
"\n" str)))))
(read-char-choice prompt-str choices t))))
"\n")))))
(add-hook 'magit-process-prompt-functions
#'magit-process-apply-force-or-cancel-prompt-hook)
Magit no longer provides a hook that is run in each buffer that visits a file that is being tracked in the current repository whenever it refreshes the current Magit buffer. It used to provide such a hook and made use of it itself, but that was highly inefficient and so it was removed.
Such a hook could be implemented as follows. But I am not adding this to Magit because I do not want to commit to this particular implementation. This implementation is optimized to make it more efficient. As a result it doesn’t run the hook for every buffer that matches the above description, only for those “for which it makes sense”. But your use case might require running it for all buffers.
(defvar magit--modified-files nil)
(defun magit-maybe-cache-modified-files ()
"Maybe save a list of modified files.
That list is later used by `magit-update-uncommitted-buffers',
provided it is a member of `magit-post-refresh-hook'. If it is
not, then don't save anything here."
(when (memq 'magit-update-uncommitted-buffers magit-post-refresh-hook)
(setq magit--modified-files (magit-unstaged-files t))))
(add-hook 'magit-pre-refresh-hook #'magit-maybe-cache-modified-files)
(add-hook 'magit-pre-call-git-hook #'magit-maybe-cache-modified-files)
(add-hook 'magit-pre-start-git-hook #'magit-maybe-cache-modified-files)
(defun magit-update-uncommitted-buffers ()
"Update some file-visiting buffers belonging to the current repository.
Run `magit-update-uncommitted-buffer-hook' for each buffer
which visits a file inside the current repository that had
uncommitted changes before running the current Magit command
and/or that does so now."
(let ((topdir (magit-toplevel)))
(dolist (file (delete-consecutive-dups
(sort (nconc (magit-unstaged-files t)
magit--modified-files)
#'string<)))
(--when-let (find-buffer-visiting (expand-file-name file topdir))
(with-current-buffer it
(run-hooks 'magit-update-uncommitted-buffer-hook))))))
(add-hook 'magit-post-refresh-hook #'magit-update-uncommitted-buffers)
This is the implementation I arrived at when asked to provide such a
hook for the benefit of diff-hl
. In this comment on #2530 I
communicate my decision to not include this in Magit. An earlier
implementation was discussed in #2523. The discussion started in
#2491.
If you need such a hook, then you can copy the above implementation to your init file. You are doing so at your own risk. This will impact performance if there are many buffers (including buffers belonging to other repositories) and/or files tracked in the current repository.
Emacs’ own Version Control package, also known as VC, displays
something like Git-master
in the mode-line. When using Magit (but also
when using VC I believe) this information is not always up to date.
The Magit FAQ has an entry on the subject. If after reading that and
the Info node it links to, you still want to ensure that the VC
mode-line information is up-to-date and have also concluded that (setq
auto-revert-check-vc-info t)
is too expensive, then add the above code
instead and also the below.
This approach has the advantage that it doesn’t create a constant load on the cpu. Instead you will likely get a noticeable spike every time you run a Magit command.
(add-hook 'magit-update-uncommitted-buffer-hook 'vc-refresh-state)
Or you could use an alternative trimmed down implementation I wrote some time ago:
(defun magit-refresh-vc-mode-line ()
"Update the information displayed by `vc-mode' in the mode-line.
Like `vc-mode-line' but simpler, more efficient, and less buggy."
(setq vc-mode
(if vc-display-status
(magit-with-toplevel
(let* ((rev (or (magit-get-current-branch)
(magit-rev-parse "--short" "HEAD")))
(msg (cl-letf (((symbol-function #'vc-working-revision)
(lambda (&rest _) rev)))
(vc-default-mode-line-string
'Git buffer-file-name))))
(propertize
(concat " " msg)
'mouse-face 'mode-line-highlight
'help-echo (concat (get-text-property 0 'help-echo msg)
"\nCurrent revision: " rev
"\nmouse-1: Version Control menu")
'local-map vc-mode-line-map)))
" Git"))
(force-mode-line-update))
(add-hook 'magit-update-uncommitted-buffer-hook 'magit-refresh-vc-mode-line )
However I don’t know whether that still works and whether the claims
made in the doc-string are (still) correct. Also I think that
vc-refresh-state
now uses colors. The above snippet has not been
adjusted accordingly.