`git-commit` is no longer distributed with `magit` and no longer declares its dependencies, causing "file missing: with-editor" and similar errors for folks who don't have magit installed. Also, VC's commit workflows don't utilize the mode, so there's no reason to keep it in this module. Ref: magit/magit@c170fcf399 Ref: #8003
278 lines
12 KiB
EmacsLisp
278 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)
|
|
(add-hook 'magit-process-mode-hook #'goto-address-mode)
|
|
|
|
(defadvice! +magit-revert-repo-buffers-deferred-a (&rest _)
|
|
:after '(magit-checkout magit-branch-and-checkout)
|
|
;; Since the project likely now contains new files, best we undo the
|
|
;; projectile cache so it can be regenerated later.
|
|
(projectile-invalidate-cache nil)
|
|
;; Use a more efficient strategy to auto-revert buffers whose git state has
|
|
;; changed: refresh the visible buffers immediately...
|
|
(+magit-mark-stale-buffers-h))
|
|
;; ...then refresh the rest only when we switch to them, not all at once.
|
|
(add-hook 'doom-switch-buffer-hook #'+magit-revert-buffer-maybe-h)
|
|
|
|
;; The default location for git-credential-cache is in
|
|
;; ~/.cache/git/credential. However, if ~/.git-credential-cache/ exists, then
|
|
;; it is used instead. Magit seems to be hardcoded to use the latter, so here
|
|
;; we override it to have more correct behavior.
|
|
(unless (file-exists-p "~/.git-credential-cache/")
|
|
(setq magit-credential-cache-daemon-socket
|
|
(doom-glob (or (getenv "XDG_CACHE_HOME")
|
|
"~/.cache/")
|
|
"git/credential/socket")))
|
|
|
|
;; 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 transient-display-buffer-action '(display-buffer-below-selected)
|
|
magit-display-buffer-function #'+magit-display-buffer-fn
|
|
magit-bury-buffer-function #'magit-mode-quit-window)
|
|
(set-popup-rule! "^\\(?:\\*magit\\|magit:\\| \\*transient\\*\\)" :ignore t)
|
|
(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 "i"
|
|
'("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)
|
|
(after! magit-section
|
|
;; 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)))))
|