version-control.el 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. ;;;
  2. ;;; Browse at remote
  3. ;;;
  4. (setq browse-at-remote-remote-type-domains
  5. '(("gitlab.intr" . "gitlab")
  6. ("gitlab.corp1.majordomo.ru" . "gitlab")
  7. ("bitbucket.org" . "bitbucket")
  8. ("github.com" . "github")
  9. ("gitlab.com" . "gitlab")
  10. ("git.savannah.gnu.org" . "gnu")
  11. ("git.net-core.org" . "gitlab")))
  12. ;;;
  13. ;;; Git patch
  14. ;;;
  15. ;; List of Email addresses to send patches for `gitpatch-mail' command
  16. (setq gitpatch-mail-database (list "guix-patches@gnu.org"))
  17. ;;;
  18. ;;; vc tools
  19. ;;;
  20. (setq vc-follow-symlinks t) ; Do not ask about following link in Git projects
  21. (with-eval-after-load 'vc-git
  22. (let ((map vc-git-log-edit-mode-map))
  23. (define-key map (kbd "C-c /") 'hydra-dabbrev-expand/body)
  24. (define-key map (kbd "C-c l") 'vc-chlog))
  25. (let ((map vc-git-log-view-mode-map))
  26. (define-key map (kbd "s") 'magit-show-commit)))
  27. ;;;
  28. ;;; Git Gutter
  29. ;;;
  30. (defun wi-git-gutter:stage-hunk ()
  31. "Stage this hunk like 'git add -p'."
  32. (interactive)
  33. (flet ((yes-or-no-p (action)
  34. (y-or-n-p
  35. (format "%s current hunk ? " action))))
  36. (git-gutter:query-action "Stage"
  37. #'git-gutter:do-stage-hunk
  38. #'git-gutter)))
  39. (advice-add 'git-gutter:stage-hunk
  40. :override #'wi-git-gutter:stage-hunk)
  41. (defun wi-git-gutter-refresh-visible-buffers ()
  42. "Refresh command `git-gutter-mode' on all visible command `git-gutter-mode' buffers."
  43. (dolist (buff (buffer-list))
  44. (with-current-buffer buff
  45. (when (and git-gutter-mode (get-buffer-window buff))
  46. (git-gutter-mode t)))))
  47. ;;;
  48. ;;; vc-chlog
  49. ;;;
  50. (defun vc-chlog ()
  51. "Insert output of `vc-chlog'."
  52. (interactive)
  53. (let ((default-directory (projectile-project-root)))
  54. (insert (shell-command-to-string (mapconcat 'identity
  55. (list "vc-chlog"
  56. "| sed 's/^[ \t]*//'"
  57. "| tail +2")
  58. " ")))))
  59. ;;;
  60. ;;; GitLab
  61. ;;;
  62. (with-eval-after-load 'gitlab-snip-helm
  63. (defun gitlab-snip-helm-auth (host user)
  64. (let ((secret (plist-get (nth 0
  65. (auth-source-search
  66. :host host
  67. :user user))
  68. :secret)))
  69. (and (functionp secret)
  70. (funcall secret))))
  71. (setq gitlab-snip-helm-server "https://gitlab.com")
  72. (setq gitlab-snip-helm-user-token (gitlab-snip-helm-auth "gitlab.com/api/v4" "wigust^token"))
  73. (defun gitlab-intr-snippet ()
  74. (interactive)
  75. (let ((gitlab-snip-helm-server "https://gitlab.intr")
  76. (gitlab-snip-helm-user-token (gitlab-snip-helm-auth "gitlab.intr/api/v4" "pyhalov^token")))
  77. (gitlab-snip-helm-insert))))
  78. ;;;
  79. ;;; Magit
  80. ;;;
  81. (defvar wi-src (expand-file-name "~/src"))
  82. (defun wi-magit-status-dir (dir)
  83. "Open magit in DIR directory."
  84. (let ((default-directory (expand-file-name dir))
  85. (magit-display-buffer-function 'magit-display-buffer-fullframe-status-v1))
  86. (magit-status)))
  87. (defvar magit-read-reuse-message-target "ORIG_HEAD")
  88. (setq magit-read-reuse-message-target "HEAD")
  89. (defun wi-magit-read-reuse-message (prompt &optional default)
  90. "Reuse message for Git commit."
  91. (magit-completing-read prompt (magit-list-refnames)
  92. nil nil nil 'magit-revision-history
  93. (or default
  94. (and (magit-rev-verify
  95. magit-read-reuse-message-target)
  96. magit-read-reuse-message-target))))
  97. (advice-add 'magit-read-reuse-message
  98. :override #'wi-magit-read-reuse-message)
  99. ;; TODO: This is slow down Emacs startup.
  100. ;; (magit-org-todos-autoinsert)
  101. (setq magit-repository-directories
  102. (if (boundp #'f-directories)
  103. (mapcar (lambda (dir)
  104. (cons dir 0))
  105. (f-directories wi-src))
  106. '()))
  107. (setq magit-repository-directories-depth 0)
  108. (setq magit-log-arguments '("--graph" "--color" "--decorate" "-n64"))
  109. (setq magit-log-section-arguments (list "-n256" "--decorate"))
  110. ;; Use `magit-describe-section'
  111. (defun wi-local-magit-initially-hide-unmerged (section)
  112. "Hide unmerged files in magit SECTION."
  113. (and (not magit-insert-section--oldroot)
  114. (or (eq (magit-section-type section) 'unpushed)
  115. (equal (magit-section-value section) "@{upstream}..")
  116. (eq (magit-section-type section) 'stashes)
  117. (equal (magit-section-value section) "refs/stash"))
  118. 'hide))
  119. (defun magit-init-bare (directory)
  120. "Initialize a bare Git repository.
  121. If the directory is below an existing repository, then the user
  122. has to confirm that a new one should be created inside. If the
  123. directory is the root of the existing repository, then the user
  124. has to confirm that it should be reinitialized.
  125. Non-interactively DIRECTORY is (re-)initialized unconditionally."
  126. (interactive
  127. (let ((directory (file-name-as-directory
  128. (expand-file-name
  129. (read-directory-name "Create repository in: ")))))
  130. (-when-let (toplevel (magit-toplevel directory))
  131. (setq toplevel (expand-file-name toplevel))
  132. (unless (y-or-n-p (if (file-equal-p toplevel directory)
  133. (format "Reinitialize existing repository %s? "
  134. directory)
  135. (format "%s is a repository. Create another in %s? "
  136. toplevel directory)))
  137. (user-error "Abort")))
  138. (list directory)))
  139. ;; `git init' does not understand the meaning of "~"!
  140. (magit-call-git "init" "--bare"
  141. (magit-convert-filename-for-git
  142. (expand-file-name directory))))
  143. (defun wi-git-init+add-remote+push (source destination)
  144. "Initialize bare Git repository in DESTINATION directory fetched from SOURCE.
  145. Then add local remote pointing to DESTINATION directory.
  146. And finally push branch master to local/master."
  147. (interactive
  148. (list
  149. (read-directory-name "Source directory: ")
  150. (if wi-git wi-git
  151. (read-directory-name "Destination directory: "))))
  152. (let ((destination
  153. (concat (directory-file-name destination)
  154. "/" (file-name-base
  155. (directory-file-name (vc-git-root source))))))
  156. (magit-init-bare destination)
  157. (magit-remote-add "local" (concat "file://" destination))
  158. (magit-push "master" "local/master" nil)))
  159. ;; TODO: Another way will be in a new release,
  160. ;; see <https://emacs.stackexchange.com/a/38782/15092>.
  161. ;; (add-to-list 'magit-section-initial-visibility-alist '(stashes . hide))
  162. ;; XXX: eq: Symbol’s function definition is void: magit-section-type
  163. ;; (add-hook 'magit-section-set-visibility-hook
  164. ;; 'wi-local-magit-initially-hide-unmerged)
  165. (add-hook 'git-commit-mode-hook 'auto-fill-mode)
  166. ;; Origin <https://github.com/alphapapa/unpackaged.el#improved-magit-status-command>.
  167. (defun unpackaged/magit-status ()
  168. "Open a `magit-status' buffer and close the other window so only Magit is visible.
  169. If a file was visited in the buffer that was active when this
  170. command was called, go to its unstaged changes section."
  171. (interactive)
  172. (let* ((buffer-file-path (when buffer-file-name
  173. (file-relative-name buffer-file-name
  174. (locate-dominating-file buffer-file-name ".git"))))
  175. (section-ident `((file . ,buffer-file-path) (unstaged) (status))))
  176. (magit-status)
  177. (delete-other-windows)
  178. (when buffer-file-path
  179. (goto-char (point-min))
  180. (cl-loop until (when (equal section-ident (magit-section-ident (magit-current-section)))
  181. (magit-section-show (magit-current-section))
  182. (recenter)
  183. t)
  184. do (condition-case nil
  185. (magit-section-forward)
  186. (error (cl-return (magit-status-goto-initial-section-1))))))))
  187. ;; Origin <https://github.com/alphapapa/unpackaged.el>
  188. (when (macrop #'defhydra)
  189. (defhydra unpackaged/smerge-hydra
  190. (:color pink :hint nil :post (smerge-auto-leave))
  191. "
  192. ^Move^ ^Keep^ ^Diff^ ^Other^
  193. ^^-----------^^-------------------^^---------------------^^-------
  194. _n_ext _b_ase _<_: upper/base _C_ombine
  195. _p_rev _u_pper _=_: upper/lower _r_esolve
  196. ^^ _l_ower _>_: base/lower _k_ill current
  197. ^^ _a_ll _R_efine
  198. ^^ _RET_: current _E_diff
  199. "
  200. ("n" smerge-next)
  201. ("p" smerge-prev)
  202. ("b" smerge-keep-base)
  203. ("u" smerge-keep-upper)
  204. ("l" smerge-keep-lower)
  205. ("a" smerge-keep-all)
  206. ("RET" smerge-keep-current)
  207. ("\C-m" smerge-keep-current)
  208. ("<" smerge-diff-base-upper)
  209. ("=" smerge-diff-upper-lower)
  210. (">" smerge-diff-base-lower)
  211. ("R" smerge-refine)
  212. ("E" smerge-ediff)
  213. ("C" smerge-combine-with-next)
  214. ("r" smerge-resolve)
  215. ("k" smerge-kill-current)
  216. ("ZZ" (lambda ()
  217. (interactive)
  218. (save-buffer)
  219. (bury-buffer))
  220. "Save and bury buffer" :color blue)
  221. ("q" nil "cancel" :color blue)))
  222. (defun wi-magit-init (directory group)
  223. "Call `magit-init' and create GitLab repository in project DIRECTORY for GROUP."
  224. (interactive "DCreate repository in: \nsGroup: ")
  225. (magit-init directory)
  226. (let ((name (file-name-nondirectory (directory-file-name directory)))
  227. (buffer (get-buffer-create "*wi-magit-init*")))
  228. (call-process "gitlab" nil buffer nil "create_project" name
  229. (format "{visibility: public, namespace_id: %s}"
  230. group))
  231. (call-process "git" nil buffer nil "remote" "add" "origin"
  232. (format "git@gitlab:~s/~s.git"
  233. group name))))
  234. (add-hook 'magit-diff-visit-file-hook
  235. '(lambda ()
  236. (when smerge-mode
  237. (unpackaged/smerge-hydra/body))))
  238. (defmacro wi-define-magit-status-repo (name directory)
  239. `(defun ,(intern (concat "wi-magit-status-repo-"
  240. (symbol-name name)))
  241. nil
  242. (interactive)
  243. (magit-status ,directory)))
  244. (wi-define-magit-status-repo guix (expand-file-name "/home/oleg/src/git.savannah.gnu.org/git/guix"))
  245. (with-eval-after-load 'forge
  246. (setq forge-alist
  247. (append '(("gitlab.intr" "gitlab.intr/api/v4" "gitlab.intr"
  248. forge-gitlab-repository)
  249. ("gitlab.wugi.info" "gitlab.wugi.info/api/v4" "gitlab.wugi.info"
  250. forge-gitlab-repository))
  251. forge-alist)))
  252. (wi-define-browse-url-git-commit
  253. "emacs"
  254. (expand-file-name "~/src/emacs")
  255. (lambda (url) (car (last (split-string url "=")))))
  256. (wi-define-browse-url-git-commit
  257. "guix"
  258. (expand-file-name "/home/oleg/src/git.savannah.gnu.org/git/guix")
  259. (lambda (url) (car (last (split-string url "=")))))
  260. (with-eval-after-load 'magit
  261. ;; TODO: Delete magit-process-password-prompt-regexps after magit update.
  262. ;; https://github.com/magit/magit/issues/3843
  263. (setq magit-process-password-prompt-regexps
  264. '("^\r?\\(Enter \\)?[Pp]assphrase\\( for \\(RSA \\)?key '.*'\\)?: ?$" "^\\(Enter \\)?[Pp]assword\\( for '\\(https?://\\)?\\(?99:.*\\)'\\)?: ?$" "^.*'s password: ?$" "^Yubikey for .*: ?$" "^Enter PIN for .*: ?$")))
  265. ;;;
  266. ;;; magit-todos
  267. ;;;
  268. (with-eval-after-load 'magit-todos
  269. (setq magit-todos-keywords-list
  270. (add-to-list 'magit-todos-keywords-list "XXX")))
  271. ;; NOTE: Use ivy-magit-todos instead of magit-todos-mode
  272. (when (boundp #'magit-todos-mode)
  273. (magit-todos-mode))