mirror of https://github.com/Chizi123/.emacs.d.git

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