protesilaos.dotfiles/emacs/.emacs.d/prot-emacs-modules/prot-emacs-git.el

310 lines
11 KiB
EmacsLisp

;;;; `project'
(prot-emacs-configure
(prot-emacs-keybind global-map
"C-x p ." #'project-dired
"C-x p C-g" #'keyboard-quit
"C-x p <return>" #'project-dired
"C-x p <delete>" #'project-forget-project)
(setopt project-switch-commands
'((project-find-file "Find file")
(project-find-regexp "Find regexp")
(project-find-dir "Find directory")
(project-dired "Root dired")
(project-vc-dir "VC-Dir")
(project-shell "Shell")
(keyboard-quit "Quit")))
(setq project-vc-extra-root-markers '(".project")) ; Emacs 29
(setq project-key-prompt-style t) ; Emacs 30
(advice-add #'project-switch-project :after #'prot-common-clear-minibuffer-message)
(autoload #'prot-project-maybe-in-tab "prot-project")
(autoload #'prot-project-switch "prot-project")
(define-key project-prefix-map (kbd "p") #'prot-project-maybe-in-tab))
;;;; `diff-mode'
(prot-emacs-configure
(setq diff-default-read-only t)
(setq diff-advance-after-apply-hunk t)
(setq diff-update-on-the-fly t)
;; The following are from Emacs 27.1
(setq diff-refine nil) ; I do it on demand, with my `agitate' package (more below)
(setq diff-font-lock-prettify t) ; I think nil is better for patches, but let me try this for a while
(setq diff-font-lock-syntax nil))
;;;; `diff-hl'
(prot-emacs-configure
(prot-emacs-install diff-hl)
(setq diff-hl-draw-borders nil)
(setq diff-hl-update-async t)
(prot-emacs-keybind global-map
"C-x v <up>" #'diff-hl-previous-hunk
"C-x v <down>" #'diff-hl-next-hunk)
(prot-emacs-hook (prog-mode-hook vc-dir-mode-hook) turn-on-diff-hl-mode)
(with-eval-after-load 'magit
(add-hook 'magit-post-refresh-hook #'diff-hl-magit-post-refresh)))
;;;; `ediff'
(prot-emacs-configure
(setq ediff-split-window-function 'split-window-horizontally)
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
(setq ediff-keep-variants nil)
(setq ediff-make-buffers-readonly-at-startup nil)
(setq ediff-merge-revisions-with-ancestor t)
(setq ediff-show-clashes-only t)
(with-eval-after-load 'outline
(add-hook 'ediff-prepare-buffer-hook #'outline-show-all))
(prot-emacs-autoload
(prot-ediff-visible-buffers-2
prot-ediff-visible-buffers-3
prot-ediff-store-layout
prot-ediff-restore-layout)
"prot-ediff")
;; The C-x v prefix is for all "version control" commands that are
;; already built into Emacs. It makes sense to extend it for this
;; use-case.
(prot-emacs-keybind global-map
"C-x v 2" #'prot-ediff-visible-buffers-2
"C-x v 3" #'prot-ediff-visible-buffers-3)
(add-hook 'ediff-before-setup-hook #'prot-ediff-store-layout)
(add-hook 'ediff-quit-hook #'prot-ediff-restore-layout))
;;;; `smerge-mode'
(prot-emacs-configure
(setq smerge-diff-buffer-name "*smerge-diff*")
(setq smerge-refine-shadow-cursor nil)) ; Emacs 31
;;; Version control framework (vc.el, vc-git.el, and more)
(prot-emacs-configure
(setq vc-follow-symlinks t)
(with-eval-after-load 'vc
;; Those offer various types of functionality, such as blaming,
;; viewing logs, showing a dedicated buffer with changes to affected
;; files.
(require 'vc-annotate)
(require 'vc-dir)
(require 'vc-git)
(require 'add-log)
(require 'log-view)
;; I only use Git. If I ever need another, I will include it here.
;; This may have an effect on performance, as Emacs will not try to
;; check for a bunch of backends.
(setq vc-handled-backends '(Git))
(setq vc-dir-save-some-buffers-on-revert t) ; Emacs 31
(setq vc-dir-show-key-binding-hints nil) ; Emacs 32
;; This one is for editing commit messages.
(require 'log-edit)
(setq log-edit-confirm 'changed)
(setq log-edit-keep-buffer nil)
(setq log-edit-require-final-newline t)
(setq log-edit-setup-add-author nil)
(prot-emacs-hook
log-edit-hook
(log-edit-insert-message-template
log-edit-maybe-show-diff)
nil
log-edit)
(setq vc-display-failed-async-commands t) ; Emacs 31
(setq vc-find-revision-no-save t)
(setq vc-annotate-display-mode 'scale) ; scale to oldest
;; I use a different account for git commits
(setq add-log-mailing-address "info@protesilaos.com")
(setq add-log-keep-changes-together t)
(setq vc-git-diff-switches '("--patch-with-stat" "--histogram"))
(setq vc-git-log-switches '("--stat"))
(setq vc-git-print-log-follow t)
(setq vc-git-revision-complete-only-branches nil) ; Emacs 28
(setq vc-git-root-log-format
`("%d %h %ai %an: %s"
;; The first shy group matches the characters drawn by --graph.
;; We use numbered groups because `log-view-message-re' wants the
;; revision number to be group 1.
,(concat "^\\(?:[*/\\|]+\\)\\(?:[*/\\| ]+\\)?"
"\\(?2: ([^)]+) \\)?\\(?1:[0-9a-z]+\\) "
"\\(?4:[0-9]\\{4\\}-[0-9-]\\{4\\}[0-9\s+:-]\\{16\\}\\) "
"\\(?3:.*?\\):")
((1 'log-view-message)
(2 'change-log-list nil lax)
(3 'change-log-name)
(4 'change-log-date))))
;; These two are from Emacs 29
(setq vc-git-log-edit-summary-target-len 50)
(setq vc-git-log-edit-summary-max-len 70)
(define-advice vc-push (:around (&rest args) prot)
(let ((current-window (selected-window)))
(apply args)
(select-window current-window)))
(define-advice vc-pull (:around (&rest args) prot)
(let ((current-window (selected-window)))
(apply args)
(select-window current-window)))
(defun prot/vc-diff-dwim ()
"Show diff of buffer against file or against VC history."
(interactive)
(if-let* ((buffer (current-buffer))
(_ (buffer-modified-p buffer)))
(diff-buffer-with-file buffer)
(call-interactively #'vc-diff)))
(defvar prot/vc-git-grep-history nil
"Minibuffer history for `prot/vc-git-grep'.")
(defun prot/vc-git-grep (directory regexp)
"Use `vc-git-grep' with REGEXP in the current root Git DIRECTORY."
(interactive
(let ((directory (or (vc-root-dir)
(locate-dominating-file "." ".git")
(user-error "No VC root available"))))
(list
directory
(read-regexp
(format "vc-git-grep for REGEXP in `%s': "
(propertize directory 'face 'warning))
nil 'prot/vc-git-grep-history))))
(vc-git-grep regexp "*" directory))
;; NOTE: I override lots of the defaults
(prot-emacs-keybind global-map
"C-x v B" #'vc-annotate ; Blame mnemonic
"C-x v g" #'prot/vc-git-grep ; override original `vc-annotate' key
"C-x v e" #'vc-ediff
"C-x v k" #'vc-delete-file ; 'k' for kill==>delete is more common
"C-x v G" #'vc-log-search ; git log --grep
"C-x v t" #'vc-create-tag
"C-x v c" #'vc-clone ; Emacs 31
"C-x v d" #'prot/vc-diff-dwim
"C-x v ." #'vc-dir-root ; `vc-dir-root' is from Emacs 28
"C-x v <return>" #'vc-dir-root)
(prot-emacs-keybind vc-dir-mode-map
"t" #'vc-create-tag
"I" #'vc-log-incoming
"O" #'vc-log-outgoing
"o" #'vc-dir-find-file-other-window
"d" #'vc-diff ; parallel to D: `vc-root-diff'
"k" #'vc-dir-delete-file
"G" #'vc-revert)
(prot-emacs-keybind vc-git-stash-shared-map
"a" #'vc-git-stash-apply-at-point
"c" #'vc-git-stash ; "create" named stash
"k" #'vc-git-stash-delete-at-point ; symmetry with `vc-dir-delete-file'
"p" #'vc-git-stash-pop-at-point
"s" #'vc-git-stash-snapshot)
(prot-emacs-keybind vc-annotate-mode-map
"M-q" #'vc-annotate-toggle-annotation-visibility
"C-c C-c" #'vc-annotate-goto-line
"<return>" #'vc-annotate-find-revision-at-line)
(prot-emacs-keybind log-edit-mode-map
"M-s" nil ; I use M-s for my search commands
"M-r" nil)
(prot-emacs-keybind log-view-mode-map
"<tab>" #'log-view-toggle-entry-display
"<return>" #'log-view-find-revision
"s" #'vc-log-search))
(defun prot/modus-vc-annotate ()
(modus-themes-with-colors
(setq vc-annotate-background-mode nil)
(setq vc-annotate-very-old-color fg-dim)
(setq vc-annotate-color-map
`(( 20. . ,red)
( 40. . ,red-cooler)
( 60. . ,red-warmer)
( 80. . ,yellow-warmer)
(100. . ,yellow)
(120. . ,yellow-cooler)
(140. . ,green-warmer)
(160. . ,green)
(180. . ,green-cooler)
(200. . ,cyan-cooler)
(220. . ,cyan-warmer)
(240. . ,cyan)
(260. . ,blue-warmer)
(280. . ,blue)
(300. . ,blue-cooler)
(320. . ,blue-intense)
(340. . ,magenta-cooler)
(360. . ,fg-dim)))))
(when (memq prot-emacs-load-theme-family '(modus ef standard))
(with-eval-after-load 'vc-annotate
(prot/modus-vc-annotate)
(add-hook 'modus-themes-after-load-theme-hook #'prot/modus-vc-annotate))))
;;; Interactive and powerful git front-end (Magit)
(prot-emacs-configure
(setq transient-show-popup 0.5)
(prot-emacs-install magit)
;; Let `display-buffer-alist' do its job
(setq magit-display-buffer-function #'display-buffer)
(define-key global-map (kbd "C-c g") #'magit-status)
(with-eval-after-load 'magit
(prot-emacs-keybind magit-mode-map
"C-w" #'nil
"M-w" #'nil))
(setq magit-define-global-key-bindings nil)
(setq magit-section-visibility-indicators
`(;; (magit-fringe-bitmap> . magit-fringe-bitmapv)
("" . t))) ; same as `org-ellipsis'
;; Show icons for files in the Magit status and other buffers. This
;; is from my prot-icons.el.
(with-eval-after-load 'prot-icons
(setq magit-format-file-function
(lambda (_kind file face &rest _)
(let ((icon (prot-icons-get-file-icon file)))
(format "%s %s" icon (propertize file 'font-lock-face face))))))
(setq magit-diff-refine-hunk t)
(setq magit-diff-fontify-hunk nil)
(setq magit-diff-refine-ignore-whitespace t)
(setq magit-log-auto-more t)
(setq magit-repository-directories
'(("~/Git/Projects" . 1)))
(setq magit-repolist-columns
`(("Name" 25 ,#'magit-repolist-column-ident)
("Version" 15 ,#'magit-repolist-column-version
((:sort magit-repolist-version<)))
("Unpulled" 10 ,#'magit-repolist-column-unpulled-from-upstream
((:help-echo "Upstream changes not in branch")
(:right-align t)
(:sort <)))
("Unpushed" 10 ,#'magit-repolist-column-unpushed-to-upstream
((:help-echo "Local changes not in upstream")
(:right-align t)
(:sort <)))
("Path" 99 ,#'magit-repolist-column-path)))
(setq git-commit-summary-max-length 50)
;; NOTE 2023-01-24: I used to also include `overlong-summary-line'
;; in this list, but I realised I do not need it. My summaries are
;; always in check. When I exceed the limit, it is for a good
;; reason.
(setq git-commit-style-convention-checks '(non-empty-second-line))
(setq git-commit-major-mode #'text-mode))
(provide 'prot-emacs-git)