mirror of
https://github.com/doomemacs/doomemacs
synced 2025-08-21 13:53:38 -05:00
This prevents naming collisions between projects that resolve to the same name (e.g. foo/bar/src and baz/foo/src both resolve to the same project name: "src"), causing Magit to destructively reuse magit bufffers across projects (which is especially disruptive if you use workspaces and are using magit in both, in unrelated projects that happen to have the same name).
293 lines
12 KiB
EmacsLisp
293 lines
12 KiB
EmacsLisp
;;; tools/magit/config.el -*- lexical-binding: t; -*-
|
|
|
|
(defvar +magit-open-windows-in-direction 'right
|
|
"What direction to open new windows from the status buffer.
|
|
For example, diffs and log buffers. Accepts `left', `right', `up', and `down'.")
|
|
|
|
(defvar +magit-fringe-size '(13 . 1)
|
|
"Size of the fringe in magit-mode buffers.
|
|
|
|
Can be an integer or a cons cell whose CAR and CDR are integer widths for the
|
|
left and right fringe.
|
|
|
|
Only has an effect in GUI Emacs.")
|
|
|
|
|
|
;;
|
|
;;; Packages
|
|
|
|
(use-package! magit
|
|
:commands magit-file-delete
|
|
:defer-incrementally (dash f s with-editor git-commit package eieio transient)
|
|
:init
|
|
(setq magit-auto-revert-mode nil) ; we do this ourselves further down
|
|
;; Must be set early to prevent ~/.config/emacs/transient from being created
|
|
(setq transient-levels-file (concat doom-data-dir "transient/levels")
|
|
transient-values-file (concat doom-data-dir "transient/values")
|
|
transient-history-file (concat doom-data-dir "transient/history"))
|
|
:config
|
|
(add-to-list 'doom-debug-variables 'magit-refresh-verbose)
|
|
|
|
(setq transient-default-level 5
|
|
magit-diff-refine-hunk t ; show granular diffs in selected hunk
|
|
;; Don't autosave repo buffers. This is too magical, and saving can
|
|
;; trigger a bunch of unwanted side-effects, like save hooks and
|
|
;; formatters. Trust the user to know what they're doing.
|
|
magit-save-repository-buffers nil
|
|
;; Don't display parent/related refs in commit buffers; they are rarely
|
|
;; helpful and only add to runtime costs.
|
|
magit-revision-insert-related-refs nil
|
|
;; If two projects have the same project name (e.g. A/src and B/src will
|
|
;; both resolve to the name "src"), Magit will treat them as the same
|
|
;; project and destructively hijack each other's magit buffers. This is
|
|
;; especially problematic if you use workspaces and have magit open in
|
|
;; each, and the two projects happen to have the same name! By unsetting
|
|
;; `magit-uniquify-buffer-names', magit uses the project's full path as
|
|
;; its name, preventing such naming collisions.
|
|
magit-uniquify-buffer-names nil)
|
|
|
|
;; Turn ref links into clickable buttons.
|
|
(add-hook 'magit-process-mode-hook #'goto-address-mode)
|
|
|
|
;; Since the project likely now contains new files, best we undo the
|
|
;; projectile cache so it can be regenerated later.
|
|
(add-hook! 'magit-post-refresh-hook
|
|
(defun +magit-invalidate-projectile-cache-h ()
|
|
(projectile-invalidate-cache nil)))
|
|
;; Use a more efficient strategy to auto-revert buffers whose git state has
|
|
;; changed: refresh the visible buffers immediately...
|
|
(add-hook 'magit-post-refresh-hook #'+magit-mark-stale-buffers-h)
|
|
;; ...then refresh the rest only when we switch to them or refocus the active
|
|
;; frame, not all at once.
|
|
(add-hook 'doom-switch-buffer-hook #'+magit-revert-buffer-maybe-h)
|
|
(add-hook 'doom-switch-frame-hook #'+magit-mark-stale-buffers-h)
|
|
|
|
;; Prevent sudden window position resets when staging/unstaging/discarding/etc
|
|
;; hunks in `magit-status-mode' buffers. It's disorienting, especially on
|
|
;; larger projects.
|
|
(defvar +magit--pos nil)
|
|
(add-hook! 'magit-pre-refresh-hook
|
|
(defun +magit--set-window-state-h ()
|
|
(setq-local +magit--pos (list (current-buffer) (point) (window-start)))))
|
|
(add-hook! 'magit-post-refresh-hook
|
|
(defun +magit--restore-window-state-h ()
|
|
(when (and +magit--pos (eq (current-buffer) (car +magit--pos)))
|
|
(goto-char (cadr +magit--pos))
|
|
(set-window-start nil (caddr +magit--pos) t)
|
|
(kill-local-variable '+magit--pos))))
|
|
|
|
;; Magit uses `magit-display-buffer-traditional' to display windows, by
|
|
;; default, which is a little primitive. `+magit-display-buffer' marries
|
|
;; `magit-display-buffer-fullcolumn-most-v1' with
|
|
;; `magit-display-buffer-same-window-except-diff-v1', except:
|
|
;;
|
|
;; 1. Magit sub-buffers (like `magit-log') that aren't spawned from a status
|
|
;; screen are opened as popups.
|
|
;; 2. The status screen isn't buried when viewing diffs or logs from the
|
|
;; status screen.
|
|
(setq magit-display-buffer-function #'+magit-display-buffer-fn
|
|
magit-bury-buffer-function #'magit-mode-quit-window)
|
|
;; Pop up transient windows at the bottom of the window where it was invoked.
|
|
;; This is more ergonomic for users with large displays or many splits.
|
|
(setq transient-display-buffer-action
|
|
'(display-buffer-below-selected
|
|
(dedicated . t)
|
|
(inhibit-same-window . t))
|
|
transient-show-during-minibuffer-read t)
|
|
|
|
(set-popup-rule! "^\\(?:\\*magit\\|magit:\\| \\*transient\\*\\)" :ignore t)
|
|
|
|
;; The mode-line isn't useful in these popups and take up valuable screen
|
|
;; estate, so free it up.
|
|
(add-hook 'magit-popup-mode-hook #'hide-mode-line-mode)
|
|
|
|
;; Add additional switches that seem common enough
|
|
(transient-append-suffix 'magit-fetch "-p"
|
|
'("-t" "Fetch all tags" ("-t" "--tags")))
|
|
(transient-append-suffix 'magit-pull "-r"
|
|
'("-a" "Autostash" "--autostash"))
|
|
|
|
;; so magit buffers can be switched to (except for process buffers)
|
|
(add-hook! 'doom-real-buffer-functions
|
|
(defun +magit-buffer-p (buf)
|
|
(with-current-buffer buf
|
|
(and (derived-mode-p 'magit-mode)
|
|
(not (eq major-mode 'magit-process-mode))))))
|
|
|
|
;; Clean up after magit by killing leftover magit buffers and reverting
|
|
;; affected buffers (or at least marking them as need-to-be-reverted).
|
|
(define-key magit-mode-map "q" #'+magit/quit)
|
|
(define-key magit-mode-map "Q" #'+magit/quit-all)
|
|
|
|
;; Close transient with ESC
|
|
(define-key transient-map [escape] #'transient-quit-one)
|
|
|
|
(add-hook! 'magit-section-mode-hook
|
|
(add-hook! 'window-configuration-change-hook :local
|
|
(defun +magit-enlargen-fringe-h ()
|
|
"Make fringe larger in magit."
|
|
(and (display-graphic-p)
|
|
(derived-mode-p 'magit-section-mode)
|
|
+magit-fringe-size
|
|
(let ((left (or (car-safe +magit-fringe-size) +magit-fringe-size))
|
|
(right (or (cdr-safe +magit-fringe-size) +magit-fringe-size)))
|
|
(set-window-fringes nil left right))))))
|
|
|
|
;; An optimization that particularly affects macOS and Windows users: by
|
|
;; resolving `magit-git-executable' Emacs does less work to find the
|
|
;; executable in your PATH, which is great because it is called so frequently.
|
|
;; However, absolute paths will break magit in TRAMP/remote projects if the
|
|
;; git executable isn't in the exact same location.
|
|
(add-hook! 'magit-status-mode-hook
|
|
(defun +magit-optimize-process-calls-h ()
|
|
(when-let (path (executable-find magit-git-executable t))
|
|
(setq-local magit-git-executable path))))
|
|
|
|
(add-hook! 'magit-diff-visit-file-hook
|
|
(defun +magit-reveal-point-if-invisible-h ()
|
|
"Reveal the point if in an invisible region."
|
|
(if (derived-mode-p 'org-mode)
|
|
(org-reveal '(4))
|
|
(require 'reveal)
|
|
(reveal-post-command)))))
|
|
|
|
|
|
(use-package! forge
|
|
:when (modulep! +forge)
|
|
;; We defer loading even further because forge's dependencies will try to
|
|
;; compile emacsql, which is a slow and blocking operation.
|
|
:after-call magit-status
|
|
:commands forge-create-pullreq forge-create-issue
|
|
:preface
|
|
(setq forge-database-file (concat doom-data-dir "forge/forge-database.sqlite"))
|
|
(setq forge-add-default-bindings (not (modulep! :editor evil +everywhere)))
|
|
:config
|
|
;; All forge list modes are derived from `forge-topic-list-mode'
|
|
(map! :map forge-topic-list-mode-map :n "q" #'kill-current-buffer)
|
|
(when (not forge-add-default-bindings)
|
|
(map! :map magit-mode-map [remap magit-browse-thing] #'forge-browse
|
|
:map magit-remote-section-map [remap magit-browse-thing] #'forge-browse-remote
|
|
:map magit-branch-section-map [remap magit-browse-thing] #'forge-browse-branch))
|
|
(set-popup-rule! "^\\*?[0-9]+:\\(?:new-\\|[0-9]+$\\)" :size 0.45 :modeline t :ttl 0 :quit nil)
|
|
(set-popup-rule! "^\\*\\(?:[^/]+/[^ ]+ #[0-9]+\\*$\\|Issues\\|Pull-Requests\\|forge\\)" :ignore t))
|
|
|
|
|
|
(use-package! code-review
|
|
:when (modulep! +forge)
|
|
:after magit
|
|
:init
|
|
;; TODO This needs to either a) be cleaned up or better b) better map things
|
|
;; to fit
|
|
(after! evil-collection-magit
|
|
(dolist (binding evil-collection-magit-mode-map-bindings)
|
|
(pcase-let* ((`(,states _ ,evil-binding ,fn) binding))
|
|
(dolist (state states)
|
|
(evil-collection-define-key state 'code-review-mode-map evil-binding fn))))
|
|
(evil-set-initial-state 'code-review-mode evil-default-state))
|
|
(setq code-review-db-database-file (concat doom-data-dir "code-review/code-review-db-file.sqlite")
|
|
code-review-log-file (concat doom-data-dir "code-review/code-review-error.log")
|
|
code-review-download-dir (concat doom-data-dir "code-review/"))
|
|
:config
|
|
(transient-append-suffix 'magit-merge "d"
|
|
'("y" "Review pull request" +magit/start-code-review))
|
|
(after! forge
|
|
(transient-append-suffix 'forge-dispatch "c u"
|
|
'("c r" "Review pull request" +magit/start-code-review))))
|
|
|
|
|
|
(use-package! evil-collection-magit
|
|
:when (modulep! :editor evil +everywhere)
|
|
:defer t
|
|
:init (defvar evil-collection-magit-use-z-for-folds t)
|
|
:config
|
|
;; q is enough; ESC is way too easy for a vimmer to accidentally press,
|
|
;; especially when traversing modes in magit buffers.
|
|
(evil-define-key* 'normal magit-status-mode-map [escape] nil)
|
|
|
|
(after! code-review
|
|
(map! :map code-review-mode-map
|
|
:n "r" #'code-review-transient-api
|
|
:n "RET" #'code-review-comment-add-or-edit))
|
|
|
|
;; Some extra vim-isms I thought were missing from upstream
|
|
(evil-define-key* '(normal visual) magit-mode-map
|
|
"*" #'magit-worktree
|
|
"zt" #'evil-scroll-line-to-top
|
|
"zz" #'evil-scroll-line-to-center
|
|
"zb" #'evil-scroll-line-to-bottom
|
|
"g=" #'magit-diff-default-context
|
|
"gi" #'forge-jump-to-issues
|
|
"gm" #'forge-jump-to-pullreqs)
|
|
|
|
;; Fix these keybinds because they are blacklisted
|
|
;; REVIEW There must be a better way to exclude particular evil-collection
|
|
;; modules from the blacklist.
|
|
(map! (:map magit-mode-map
|
|
:nv "q" #'+magit/quit
|
|
:nv "Q" #'+magit/quit-all
|
|
:nv "]" #'magit-section-forward-sibling
|
|
:nv "[" #'magit-section-backward-sibling
|
|
:nv "gr" #'magit-refresh
|
|
:nv "gR" #'magit-refresh-all)
|
|
(:map magit-status-mode-map
|
|
:nv "gz" #'magit-refresh)
|
|
(:map magit-diff-mode-map
|
|
:nv "gd" #'magit-jump-to-diffstat-or-diff)
|
|
;; Don't open recursive process buffers
|
|
(:map magit-process-mode-map
|
|
:nv "`" #'ignore))
|
|
|
|
;; A more intuitive behavior for TAB in magit buffers:
|
|
(define-key! 'normal
|
|
(magit-status-mode-map
|
|
magit-stash-mode-map
|
|
magit-revision-mode-map
|
|
magit-process-mode-map
|
|
magit-diff-mode-map)
|
|
[tab] #'magit-section-toggle)
|
|
|
|
(after! git-rebase
|
|
(dolist (key '(("M-k" . "gk") ("M-j" . "gj")))
|
|
(when-let (desc (assoc (car key) evil-collection-magit-rebase-commands-w-descriptions))
|
|
(setcar desc (cdr key))))
|
|
(evil-define-key* evil-collection-magit-state git-rebase-mode-map
|
|
"gj" #'git-rebase-move-line-down
|
|
"gk" #'git-rebase-move-line-up)))
|
|
|
|
|
|
(use-package! evil-collection-magit-section
|
|
:when (modulep! :editor evil +everywhere)
|
|
:defer t
|
|
:init
|
|
(defvar evil-collection-magit-section-use-z-for-folds evil-collection-magit-use-z-for-folds)
|
|
:config
|
|
(defadvice! +magit--override-evil-collection-defaults-a (&rest _)
|
|
:after #'evil-collection-magit-section-setup
|
|
;; These numbered keys mask the numerical prefix keys. Since they've already
|
|
;; been replaced with z1, z2, z3, etc (and 0 with g=), there's no need to
|
|
;; keep them around:
|
|
(undefine-key! magit-section-mode-map "M-1" "M-2" "M-3" "M-4" "1" "2" "3" "4" "0")
|
|
;; `evil-collection-magit-section' binds these redundant keys.
|
|
(map! :map magit-section-mode-map :n "1" nil :n "2" nil :n "3" nil :n "4" nil)))
|
|
|
|
|
|
(use-package! git-commit
|
|
:hook (doom-first-file . global-git-commit-mode)
|
|
:config
|
|
(set-yas-minor-mode! 'git-commit-mode)
|
|
|
|
;; Enforce git commit conventions.
|
|
;; See https://chris.beams.io/posts/git-commit/
|
|
(setq git-commit-summary-max-length 50
|
|
git-commit-style-convention-checks '(overlong-summary-line non-empty-second-line))
|
|
(setq-hook! 'git-commit-mode-hook fill-column 72)
|
|
|
|
(add-hook! 'git-commit-setup-hook
|
|
(defun +vc-start-in-insert-state-maybe-h ()
|
|
"Start git-commit-mode in insert state if in a blank commit message,
|
|
otherwise in default state."
|
|
(when (and (bound-and-true-p evil-local-mode)
|
|
(not (evil-emacs-state-p))
|
|
(bobp) (eolp))
|
|
(evil-insert-state)))))
|