Klimi's new dotfiles with stow.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

665 lines
29 KiB

  1. ;;; magit-submodule.el --- submodule support for Magit -*- lexical-binding: t -*-
  2. ;; Copyright (C) 2011-2019 The Magit Project Contributors
  3. ;;
  4. ;; You should have received a copy of the AUTHORS.md file which
  5. ;; lists all contributors. If not, see http://magit.vc/authors.
  6. ;; Author: Jonas Bernoulli <jonas@bernoul.li>
  7. ;; Maintainer: Jonas Bernoulli <jonas@bernoul.li>
  8. ;; Magit is free software; you can redistribute it and/or modify it
  9. ;; under the terms of the GNU General Public License as published by
  10. ;; the Free Software Foundation; either version 3, or (at your option)
  11. ;; any later version.
  12. ;;
  13. ;; Magit is distributed in the hope that it will be useful, but WITHOUT
  14. ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  15. ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
  16. ;; License for more details.
  17. ;;
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with Magit. If not, see http://www.gnu.org/licenses.
  20. ;;; Code:
  21. (eval-when-compile
  22. (require 'subr-x))
  23. (require 'magit)
  24. (defvar x-stretch-cursor)
  25. ;;; Options
  26. (defcustom magit-module-sections-hook
  27. '(magit-insert-modules-overview
  28. magit-insert-modules-unpulled-from-upstream
  29. magit-insert-modules-unpulled-from-pushremote
  30. magit-insert-modules-unpushed-to-upstream
  31. magit-insert-modules-unpushed-to-pushremote)
  32. "Hook run by `magit-insert-modules'.
  33. That function isn't part of `magit-status-sections-hook's default
  34. value, so you have to add it yourself for this hook to have any
  35. effect."
  36. :package-version '(magit . "2.11.0")
  37. :group 'magit-status
  38. :type 'hook)
  39. (defcustom magit-module-sections-nested t
  40. "Whether `magit-insert-modules' wraps inserted sections.
  41. If this is non-nil, then only a single top-level section
  42. is inserted. If it is nil, then all sections listed in
  43. `magit-module-sections-hook' become top-level sections."
  44. :package-version '(magit . "2.11.0")
  45. :group 'magit-status
  46. :type 'boolean)
  47. (defcustom magit-submodule-list-mode-hook '(hl-line-mode)
  48. "Hook run after entering Magit-Submodule-List mode."
  49. :package-version '(magit . "2.9.0")
  50. :group 'magit-repolist
  51. :type 'hook
  52. :get 'magit-hook-custom-get
  53. :options '(hl-line-mode))
  54. (defcustom magit-submodule-list-columns
  55. '(("Path" 25 magit-modulelist-column-path nil)
  56. ("Version" 25 magit-repolist-column-version nil)
  57. ("Branch" 20 magit-repolist-column-branch nil)
  58. ("B<U" 3 magit-repolist-column-unpulled-from-upstream ((:right-align t)))
  59. ("B>U" 3 magit-repolist-column-unpushed-to-upstream ((:right-align t)))
  60. ("B<P" 3 magit-repolist-column-unpulled-from-pushremote ((:right-align t)))
  61. ("B>P" 3 magit-repolist-column-unpushed-to-pushremote ((:right-align t)))
  62. ("B" 3 magit-repolist-column-branches ((:right-align t)))
  63. ("S" 3 magit-repolist-column-stashes ((:right-align t))))
  64. "List of columns displayed by `magit-list-submodules'.
  65. Each element has the form (HEADER WIDTH FORMAT PROPS).
  66. HEADER is the string displayed in the header. WIDTH is the width
  67. of the column. FORMAT is a function that is called with one
  68. argument, the repository identification (usually its basename),
  69. and with `default-directory' bound to the toplevel of its working
  70. tree. It has to return a string to be inserted or nil. PROPS is
  71. an alist that supports the keys `:right-align' and `:pad-right'."
  72. :package-version '(magit . "2.8.0")
  73. :group 'magit-repolist-mode
  74. :type `(repeat (list :tag "Column"
  75. (string :tag "Header Label")
  76. (integer :tag "Column Width")
  77. (function :tag "Inserter Function")
  78. (repeat :tag "Properties"
  79. (list (choice :tag "Property"
  80. (const :right-align)
  81. (const :pad-right)
  82. (symbol))
  83. (sexp :tag "Value"))))))
  84. (defcustom magit-submodule-remove-trash-gitdirs nil
  85. "Whether `magit-submodule-remove' offers to trash module gitdirs.
  86. If this is nil, then that command does not offer to do so unless
  87. a prefix argument is used. When this is t, then it does offer to
  88. do so even without a prefix argument.
  89. In both cases the action still has to be confirmed unless that is
  90. disabled using the option `magit-no-confirm'. Doing the latter
  91. and also setting this variable to t will lead to tears."
  92. :package-version '(magit . "2.90.0")
  93. :group 'magit-commands
  94. :type 'boolean)
  95. ;;; Popup
  96. ;;;###autoload (autoload 'magit-submodule "magit-submodule" nil t)
  97. (define-transient-command magit-submodule ()
  98. "Act on a submodule."
  99. :man-page "git-submodule"
  100. ["Arguments"
  101. ("-f" "Force" ("-f" "--force"))
  102. ("-r" "Recursive" "--recursive")
  103. ("-N" "Do not fetch" ("-N" "--no-fetch"))
  104. ("-C" "Checkout tip" "--checkout")
  105. ("-R" "Rebase onto tip" "--rebase")
  106. ("-M" "Merge tip" "--merge")
  107. ("-U" "Use upstream tip" "--remote")]
  108. ["One module actions"
  109. ("a" magit-submodule-add)
  110. ("r" magit-submodule-register)
  111. ("p" magit-submodule-populate)
  112. ("u" magit-submodule-update)
  113. ("s" magit-submodule-synchronize)
  114. ("d" magit-submodule-unpopulate)
  115. ("k" "Remove" magit-submodule-remove)]
  116. ["All modules actions"
  117. ("l" "List all modules" magit-list-submodules)
  118. ("f" "Fetch all modules" magit-fetch-modules)])
  119. (defun magit-submodule-arguments (&rest filters)
  120. (--filter (and (member it filters) it)
  121. (transient-args 'magit-submodule)))
  122. (defclass magit--git-submodule-suffix (transient-suffix)
  123. ())
  124. (cl-defmethod transient-format-description ((obj magit--git-submodule-suffix))
  125. (let ((value (transient-args transient--prefix)))
  126. (replace-regexp-in-string
  127. "\\[--[^]]+\\]"
  128. (lambda (match)
  129. (format (propertize "[%s]" 'face 'transient-inactive-argument)
  130. (mapconcat (lambda (arg)
  131. (propertize arg 'face
  132. (if (member arg value)
  133. 'transient-argument
  134. 'transient-inactive-argument)))
  135. (save-match-data
  136. (split-string (substring match 1 -1) "|"))
  137. (propertize "|" 'face 'transient-inactive-argument))))
  138. (cl-call-next-method obj))))
  139. ;;;###autoload (autoload 'magit-submodule-add "magit-submodule" nil t)
  140. (define-suffix-command magit-submodule-add (url &optional path name args)
  141. "Add the repository at URL as a module.
  142. Optional PATH is the path to the module relative to the root of
  143. the superproject. If it is nil, then the path is determined
  144. based on the URL. Optional NAME is the name of the module. If
  145. it is nil, then PATH also becomes the name."
  146. :class 'magit--git-submodule-suffix
  147. :description "Add git submodule add [--force]"
  148. (interactive
  149. (magit-with-toplevel
  150. (let* ((url (magit-read-string-ns "Add submodule (remote url)"))
  151. (path (let ((read-file-name-function
  152. (if (or (eq read-file-name-function 'ido-read-file-name)
  153. (advice-function-member-p
  154. 'ido-read-file-name
  155. read-file-name-function))
  156. ;; The Ido variant doesn't work properly here.
  157. #'read-file-name-default
  158. read-file-name-function)))
  159. (directory-file-name
  160. (file-relative-name
  161. (read-directory-name
  162. "Add submodules at path: " nil nil nil
  163. (and (string-match "\\([^./]+\\)\\(\\.git\\)?$" url)
  164. (match-string 1 url))))))))
  165. (list url
  166. (directory-file-name path)
  167. (magit-submodule-read-name-for-path path)
  168. (magit-submodule-arguments "--force")))))
  169. (magit-submodule-add-1 url path name args))
  170. (defun magit-submodule-add-1 (url &optional path name args)
  171. (magit-with-toplevel
  172. (magit-submodule--maybe-reuse-gitdir name path)
  173. (magit-run-git-async "submodule" "add"
  174. (and name (list "--name" name))
  175. args "--" url path)
  176. (set-process-sentinel
  177. magit-this-process
  178. (lambda (process event)
  179. (when (memq (process-status process) '(exit signal))
  180. (if (> (process-exit-status process) 0)
  181. (magit-process-sentinel process event)
  182. (process-put process 'inhibit-refresh t)
  183. (magit-process-sentinel process event)
  184. (unless (version< (magit-git-version) "2.12.0")
  185. (magit-call-git "submodule" "absorbgitdirs" path))
  186. (magit-refresh)))))))
  187. ;;;###autoload
  188. (defun magit-submodule-read-name-for-path (path &optional prefer-short)
  189. (let* ((path (directory-file-name (file-relative-name path)))
  190. (name (file-name-nondirectory path)))
  191. (push (if prefer-short path name) minibuffer-history)
  192. (magit-read-string-ns
  193. "Submodule name" nil (cons 'minibuffer-history 2)
  194. (or (--keep (pcase-let ((`(,var ,val) (split-string it "=")))
  195. (and (equal val path)
  196. (cadr (split-string var "\\."))))
  197. (magit-git-lines "config" "--list" "-f" ".gitmodules"))
  198. (if prefer-short name path)))))
  199. ;;;###autoload (autoload 'magit-submodule-register "magit-submodule" nil t)
  200. (define-suffix-command magit-submodule-register (modules)
  201. "Register MODULES.
  202. With a prefix argument act on all suitable modules. Otherwise,
  203. if the region selects modules, then act on those. Otherwise, if
  204. there is a module at point, then act on that. Otherwise read a
  205. single module from the user."
  206. ;; This command and the underlying "git submodule init" do NOT
  207. ;; "initialize" modules. They merely "register" modules in the
  208. ;; super-projects $GIT_DIR/config file, the purpose of which is to
  209. ;; allow users to change such values before actually initializing
  210. ;; the modules.
  211. :description "Register git submodule init"
  212. (interactive
  213. (list (magit-module-confirm "Register" 'magit-module-no-worktree-p)))
  214. (magit-with-toplevel
  215. (magit-run-git-async "submodule" "init" "--" modules)))
  216. ;;;###autoload (autoload 'magit-submodule-populate "magit-submodule" nil t)
  217. (define-suffix-command magit-submodule-populate (modules)
  218. "Create MODULES working directories, checking out the recorded commits.
  219. With a prefix argument act on all suitable modules. Otherwise,
  220. if the region selects modules, then act on those. Otherwise, if
  221. there is a module at point, then act on that. Otherwise read a
  222. single module from the user."
  223. ;; This is the command that actually "initializes" modules.
  224. ;; A module is initialized when it has a working directory,
  225. ;; a gitlink, and a .gitmodules entry.
  226. :description "Populate git submodule update --init"
  227. (interactive
  228. (list (magit-module-confirm "Populate" 'magit-module-no-worktree-p)))
  229. (magit-with-toplevel
  230. (magit-run-git-async "submodule" "update" "--init" "--" modules)))
  231. ;;;###autoload (autoload 'magit-submodule-update "magit-submodule" nil t)
  232. (define-suffix-command magit-submodule-update (modules args)
  233. "Update MODULES by checking out the recorded commits.
  234. With a prefix argument act on all suitable modules. Otherwise,
  235. if the region selects modules, then act on those. Otherwise, if
  236. there is a module at point, then act on that. Otherwise read a
  237. single module from the user."
  238. ;; Unlike `git-submodule's `update' command ours can only update
  239. ;; "initialized" modules by checking out other commits but not
  240. ;; "initialize" modules by creating the working directories.
  241. ;; To do the latter we provide the "setup" command.
  242. :class 'magit--git-submodule-suffix
  243. :description "Update git submodule update [--force] [--no-fetch]
  244. [--remote] [--recursive] [--checkout|--rebase|--merge]"
  245. (interactive
  246. (list (magit-module-confirm "Update" 'magit-module-worktree-p)
  247. (magit-submodule-arguments
  248. "--force" "--remote" "--recursive" "--checkout" "--rebase" "--merge"
  249. "--no-fetch")))
  250. (magit-with-toplevel
  251. (magit-run-git-async "submodule" "update" args "--" modules)))
  252. ;;;###autoload (autoload 'magit-submodule-synchronize "magit-submodule" nil t)
  253. (define-suffix-command magit-submodule-synchronize (modules args)
  254. "Synchronize url configuration of MODULES.
  255. With a prefix argument act on all suitable modules. Otherwise,
  256. if the region selects modules, then act on those. Otherwise, if
  257. there is a module at point, then act on that. Otherwise read a
  258. single module from the user."
  259. :class 'magit--git-submodule-suffix
  260. :description "Synchronize git submodule sync [--recursive]"
  261. (interactive
  262. (list (magit-module-confirm "Synchronize" 'magit-module-worktree-p)
  263. (magit-submodule-arguments "--recursive")))
  264. (magit-with-toplevel
  265. (magit-run-git-async "submodule" "sync" args "--" modules)))
  266. ;;;###autoload (autoload 'magit-submodule-unpopulate "magit-submodule" nil t)
  267. (define-suffix-command magit-submodule-unpopulate (modules args)
  268. "Remove working directories of MODULES.
  269. With a prefix argument act on all suitable modules. Otherwise,
  270. if the region selects modules, then act on those. Otherwise, if
  271. there is a module at point, then act on that. Otherwise read a
  272. single module from the user."
  273. ;; Even though a package is "uninitialized" (it has no worktree)
  274. ;; the super-projects $GIT_DIR/config may never-the-less set the
  275. ;; module's url. This may happen if you `deinit' and then `init'
  276. ;; to register (NOT initialize). Because the purpose of `deinit'
  277. ;; is to remove the working directory AND to remove the url, this
  278. ;; command does not limit itself to modules that have no working
  279. ;; directory.
  280. :class 'magit--git-submodule-suffix
  281. :description "Unpopulate git submodule deinit [--force]"
  282. (interactive
  283. (list (magit-module-confirm "Unpopulate")
  284. (magit-submodule-arguments "--force")))
  285. (magit-with-toplevel
  286. (magit-run-git-async "submodule" "deinit" args "--" modules)))
  287. ;;;###autoload
  288. (defun magit-submodule-remove (modules args trash-gitdirs)
  289. "Unregister MODULES and remove their working directories.
  290. For safety reasons, do not remove the gitdirs and if a module has
  291. uncomitted changes, then do not remove it at all. If a module's
  292. gitdir is located inside the working directory, then move it into
  293. the gitdir of the superproject first.
  294. With the \"--force\" argument offer to remove dirty working
  295. directories and with a prefix argument offer to delete gitdirs.
  296. Both actions are very dangerous and have to be confirmed. There
  297. are additional safety precautions in place, so you might be able
  298. to recover from making a mistake here, but don't count on it."
  299. (interactive
  300. (list (if-let ((modules (magit-region-values 'magit-module-section t)))
  301. (magit-confirm 'remove-modules nil "Remove %i modules" nil modules)
  302. (list (magit-read-module-path "Remove module")))
  303. (magit-submodule-arguments "--force")
  304. current-prefix-arg))
  305. (when (version< (magit-git-version) "2.12.0")
  306. (error "This command requires Git v2.12.0"))
  307. (when magit-submodule-remove-trash-gitdirs
  308. (setq trash-gitdirs t))
  309. (magit-with-toplevel
  310. (when-let
  311. ((modified
  312. (-filter (lambda (module)
  313. (let ((default-directory (file-name-as-directory
  314. (expand-file-name module))))
  315. (and (cddr (directory-files default-directory))
  316. (magit-anything-modified-p))))
  317. modules)))
  318. (if (member "--force" args)
  319. (if (magit-confirm 'remove-dirty-modules
  320. "Remove dirty module %s"
  321. "Remove %i dirty modules"
  322. t modified)
  323. (dolist (module modified)
  324. (let ((default-directory (file-name-as-directory
  325. (expand-file-name module))))
  326. (magit-git "stash" "push"
  327. "-m" "backup before removal of this module")))
  328. (setq modules (cl-set-difference modules modified)))
  329. (if (cdr modified)
  330. (message "Omitting %s modules with uncommitted changes: %s"
  331. (length modified)
  332. (mapconcat #'identity modified ", "))
  333. (message "Omitting module %s, it has uncommitted changes"
  334. (car modified)))
  335. (setq modules (cl-set-difference modules modified))))
  336. (when modules
  337. (let ((alist
  338. (and trash-gitdirs
  339. (--map (split-string it "\0")
  340. (magit-git-lines "submodule" "foreach" "-q"
  341. "printf \"$sm_path\\0$name\n\"")))))
  342. (magit-git "submodule" "absorbgitdirs" "--" modules)
  343. (magit-git "submodule" "deinit" args "--" modules)
  344. (magit-git "rm" args "--" modules)
  345. (when (and trash-gitdirs
  346. (magit-confirm 'trash-module-gitdirs
  347. "Trash gitdir of module %s"
  348. "Trash gitdirs of %i modules"
  349. t modules))
  350. (dolist (module modules)
  351. (if-let ((name (cadr (assoc module alist))))
  352. ;; Disregard if `magit-delete-by-moving-to-trash'
  353. ;; is nil. Not doing so would be too dangerous.
  354. (delete-directory (magit-git-dir
  355. (convert-standard-filename
  356. (concat "modules/" name)))
  357. t t)
  358. (error "BUG: Weird module name and/or path for %s" module)))))
  359. (magit-refresh))))
  360. ;;; Sections
  361. ;;;###autoload
  362. (defun magit-insert-modules ()
  363. "Insert submodule sections.
  364. Hook `magit-module-sections-hook' controls which module sections
  365. are inserted, and option `magit-module-sections-nested' controls
  366. whether they are wrapped in an additional section."
  367. (when-let ((modules (magit-list-module-paths)))
  368. (if magit-module-sections-nested
  369. (magit-insert-section (modules nil t)
  370. (magit-insert-heading
  371. (format "%s (%s)"
  372. (propertize "Modules"
  373. 'font-lock-face 'magit-section-heading)
  374. (length modules)))
  375. (magit-insert-section-body
  376. (magit--insert-modules)))
  377. (magit--insert-modules))))
  378. (defun magit--insert-modules (&optional _section)
  379. (magit-run-section-hook 'magit-module-sections-hook))
  380. ;;;###autoload
  381. (defun magit-insert-modules-overview ()
  382. "Insert sections for all modules.
  383. For each section insert the path and the output of `git describe --tags',
  384. or, failing that, the abbreviated HEAD commit hash."
  385. (when-let ((modules (magit-list-module-paths)))
  386. (magit-insert-section (modules nil t)
  387. (magit-insert-heading
  388. (format "%s (%s)"
  389. (propertize "Modules overview"
  390. 'font-lock-face 'magit-section-heading)
  391. (length modules)))
  392. (magit-insert-section-body
  393. (magit--insert-modules-overview)))))
  394. (defvar magit-modules-overview-align-numbers t)
  395. (defun magit--insert-modules-overview (&optional _section)
  396. (magit-with-toplevel
  397. (let* ((modules (magit-list-module-paths))
  398. (path-format (format "%%-%is "
  399. (min (apply 'max (mapcar 'length modules))
  400. (/ (window-width) 2))))
  401. (branch-format (format "%%-%is " (min 25 (/ (window-width) 3)))))
  402. (dolist (module modules)
  403. (let ((default-directory
  404. (expand-file-name (file-name-as-directory module))))
  405. (magit-insert-section (magit-module-section module t)
  406. (insert (propertize (format path-format module)
  407. 'font-lock-face 'magit-diff-file-heading))
  408. (if (not (file-exists-p ".git"))
  409. (insert "(unpopulated)")
  410. (insert (format
  411. branch-format
  412. (--if-let (magit-get-current-branch)
  413. (propertize it 'font-lock-face 'magit-branch-local)
  414. (propertize "(detached)" 'font-lock-face 'warning))))
  415. (--if-let (magit-git-string "describe" "--tags")
  416. (progn (when (and magit-modules-overview-align-numbers
  417. (string-match-p "\\`[0-9]" it))
  418. (insert ?\s))
  419. (insert (propertize it 'font-lock-face 'magit-tag)))
  420. (--when-let (magit-rev-format "%h")
  421. (insert (propertize it 'font-lock-face 'magit-hash)))))
  422. (insert ?\n))))))
  423. (insert ?\n))
  424. (defvar magit-modules-section-map
  425. (let ((map (make-sparse-keymap)))
  426. (define-key map [remap magit-visit-thing] 'magit-list-submodules)
  427. map)
  428. "Keymap for `modules' sections.")
  429. (defvar magit-module-section-map
  430. (let ((map (make-sparse-keymap)))
  431. (set-keymap-parent map magit-file-section-map)
  432. (unless (featurep 'jkl)
  433. (define-key map "\C-j" 'magit-submodule-visit))
  434. (define-key map [C-return] 'magit-submodule-visit)
  435. (define-key map [remap magit-visit-thing] 'magit-submodule-visit)
  436. (define-key map [remap magit-delete-thing] 'magit-submodule-unpopulate)
  437. (define-key map "K" 'magit-file-untrack)
  438. (define-key map "R" 'magit-file-rename)
  439. map)
  440. "Keymap for `module' sections.")
  441. (defun magit-submodule-visit (module &optional other-window)
  442. "Visit MODULE by calling `magit-status' on it.
  443. Offer to initialize MODULE if it's not checked out yet.
  444. With a prefix argument, visit in another window."
  445. (interactive (list (or (magit-section-value-if 'module)
  446. (magit-read-module-path "Visit module"))
  447. current-prefix-arg))
  448. (magit-with-toplevel
  449. (let ((path (expand-file-name module)))
  450. (cond
  451. ((file-exists-p (expand-file-name ".git" module))
  452. (magit-diff-visit-directory path other-window))
  453. ((y-or-n-p (format "Initialize submodule '%s' first?" module))
  454. (magit-run-git-async "submodule" "update" "--init" "--" module)
  455. (set-process-sentinel
  456. magit-this-process
  457. (lambda (process event)
  458. (let ((magit-process-raise-error t))
  459. (magit-process-sentinel process event))
  460. (when (and (eq (process-status process) 'exit)
  461. (= (process-exit-status process) 0))
  462. (magit-diff-visit-directory path other-window)))))
  463. ((file-exists-p path)
  464. (dired-jump other-window (concat path "/.")))))))
  465. ;;;###autoload
  466. (defun magit-insert-modules-unpulled-from-upstream ()
  467. "Insert sections for modules that haven't been pulled from the upstream.
  468. These sections can be expanded to show the respective commits."
  469. (magit--insert-modules-logs "Modules unpulled from @{upstream}"
  470. 'modules-unpulled-from-upstream
  471. "HEAD..@{upstream}"))
  472. ;;;###autoload
  473. (defun magit-insert-modules-unpulled-from-pushremote ()
  474. "Insert sections for modules that haven't been pulled from the push-remote.
  475. These sections can be expanded to show the respective commits."
  476. (magit--insert-modules-logs "Modules unpulled from @{push}"
  477. 'modules-unpulled-from-pushremote
  478. "HEAD..@{push}"))
  479. ;;;###autoload
  480. (defun magit-insert-modules-unpushed-to-upstream ()
  481. "Insert sections for modules that haven't been pushed to the upstream.
  482. These sections can be expanded to show the respective commits."
  483. (magit--insert-modules-logs "Modules unmerged into @{upstream}"
  484. 'modules-unpushed-to-upstream
  485. "@{upstream}..HEAD"))
  486. ;;;###autoload
  487. (defun magit-insert-modules-unpushed-to-pushremote ()
  488. "Insert sections for modules that haven't been pushed to the push-remote.
  489. These sections can be expanded to show the respective commits."
  490. (magit--insert-modules-logs "Modules unpushed to @{push}"
  491. 'modules-unpushed-to-pushremote
  492. "@{push}..HEAD"))
  493. (defun magit--insert-modules-logs (heading type range)
  494. "For internal use, don't add to a hook."
  495. (unless (magit-ignore-submodules-p)
  496. (when-let ((modules (magit-list-module-paths)))
  497. (magit-insert-section section ((eval type) nil t)
  498. (string-match "\\`\\(.+\\) \\([^ ]+\\)\\'" heading)
  499. (magit-insert-heading
  500. (propertize (match-string 1 heading)
  501. 'font-lock-face 'magit-section-heading)
  502. " "
  503. (propertize (match-string 2 heading)
  504. 'font-lock-face 'magit-branch-remote)
  505. ":")
  506. (magit-with-toplevel
  507. (dolist (module modules)
  508. (when (magit-module-worktree-p module)
  509. (let ((default-directory
  510. (expand-file-name (file-name-as-directory module))))
  511. (when (magit-file-accessible-directory-p default-directory)
  512. (magit-insert-section sec (magit-module-section module t)
  513. (magit-insert-heading
  514. (propertize module
  515. 'font-lock-face 'magit-diff-file-heading)
  516. ":")
  517. (magit-git-wash
  518. (apply-partially 'magit-log-wash-log 'module)
  519. "-c" "push.default=current" "log" "--oneline" range)
  520. (when (> (point)
  521. (oref sec content))
  522. (delete-char -1))))))))
  523. (if (> (point)
  524. (oref section content))
  525. (insert ?\n)
  526. (magit-cancel-section))))))
  527. ;;; List
  528. ;;;###autoload
  529. (defun magit-list-submodules ()
  530. "Display a list of the current repository's submodules."
  531. (interactive)
  532. (magit-display-buffer
  533. (or (magit-get-mode-buffer 'magit-submodule-list-mode)
  534. (magit-with-toplevel
  535. (magit-generate-new-buffer 'magit-submodule-list-mode))))
  536. (magit-submodule-list-mode)
  537. (magit-submodule-list-refresh)
  538. (tabulated-list-print))
  539. (defvar magit-submodule-list-mode-map
  540. (let ((map (make-sparse-keymap)))
  541. (set-keymap-parent map magit-repolist-mode-map)
  542. map)
  543. "Local keymap for Magit-Submodule-List mode buffers.")
  544. (define-derived-mode magit-submodule-list-mode tabulated-list-mode "Modules"
  545. "Major mode for browsing a list of Git submodules."
  546. :group 'magit-repolist-mode
  547. (setq-local x-stretch-cursor nil)
  548. (setq tabulated-list-padding 0)
  549. (setq tabulated-list-sort-key (cons "Path" nil))
  550. (setq tabulated-list-format
  551. (vconcat (mapcar (pcase-lambda (`(,title ,width ,_fn ,props))
  552. (nconc (list title width t)
  553. (-flatten props)))
  554. magit-submodule-list-columns)))
  555. (tabulated-list-init-header)
  556. (add-hook 'tabulated-list-revert-hook 'magit-submodule-list-refresh nil t)
  557. (setq imenu-prev-index-position-function
  558. #'magit-imenu--submodule-prev-index-position-function)
  559. (setq imenu-extract-index-name-function
  560. #'magit-imenu--submodule-extract-index-name-function))
  561. (defun magit-submodule-list-refresh ()
  562. (setq tabulated-list-entries
  563. (-keep (lambda (module)
  564. (let ((default-directory
  565. (expand-file-name (file-name-as-directory module))))
  566. (and (file-exists-p ".git")
  567. (list module
  568. (vconcat
  569. (--map (or (funcall (nth 2 it) module) "")
  570. magit-submodule-list-columns))))))
  571. (magit-list-module-paths))))
  572. (defun magit-modulelist-column-path (path)
  573. "Insert the relative path of the submodule."
  574. path)
  575. ;;; Utilities
  576. (defun magit-submodule--maybe-reuse-gitdir (name path)
  577. (let ((gitdir
  578. (magit-git-dir (convert-standard-filename (concat "modules/" name)))))
  579. (when (and (file-exists-p gitdir)
  580. (not (file-exists-p path)))
  581. (pcase (read-char-choice
  582. (concat
  583. gitdir " already exists.\n"
  584. "Type [u] to use the existing gitdir and create the working tree\n"
  585. " [r] to rename the existing gitdir and clone again\n"
  586. " [t] to trash the existing gitdir and clone again\n"
  587. " [C-g] to abort ")
  588. '(?u ?r ?t))
  589. (?u (magit-submodule--restore-worktree (expand-file-name path) gitdir))
  590. (?r (rename-file gitdir (concat gitdir "-"
  591. (format-time-string "%F-%T"))))
  592. (?t (delete-directory gitdir t t))))))
  593. (defun magit-submodule--restore-worktree (worktree gitdir)
  594. (make-directory worktree t)
  595. (with-temp-file (expand-file-name ".git" worktree)
  596. (insert "gitdir: " (file-relative-name gitdir worktree) "\n"))
  597. (let ((default-directory worktree))
  598. (magit-call-git "reset" "--hard" "HEAD" "--")))
  599. ;;; _
  600. (provide 'magit-submodule)
  601. ;;; magit-submodule.el ends here