Skip to content
Matteo Gamboz edited this page Oct 19, 2022 · 3 revisions

This page is dedicated to tips n’ tricks. Feel free to add you own.

Table of Contents

Miscellaneous

Ignoring whitespace changes in status diffs

D -b g toggles the --ignore-space-change switch in the magit-diff-refresh-popup.

Jump to status buffer while using ido to open a file

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.

Inspecting hints

Compare current branch to another

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

Performance hints

Also see the performance hints in the manual.

Performance with virtualbox-mounted directories is bad

According to #2108 Git has the same issue and using ext2 helps

Useful code snippets

Show staged and unstaged changes, but nothing else

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.

Ask for confirmation before pushing to origin/master

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

Cycle margin visibility

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

Automatically displaying the process buffer

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)

Handling third-party prompts

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)

Hook run per file-visiting buffer when Magit status is refreshed

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.

Updating VC’s mode-line information

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.