Function: projectile--git-submodules

projectile--git-submodules is a byte-compiled function defined in projectile.el.

Signature

(projectile--git-submodules PATH)

Documentation

Return the raw submodule listing for the Git repo containing PATH.

The result is a list of submodule paths relative to PATH.

With projectile-git-submodule-command at its default value the listing is produced without a shell by projectile--git-submodule-paths
(see issue #1600); when the variable is customized it is honored as a
shell command, and when nil submodules are disabled.

For Git projects without a .gitmodules file there are no submodules to find, so the listing is skipped altogether. PATH may be inside a Git repo without being its toplevel (e.g. a subproject of an outer repo) so .gitmodules is looked up at the toplevel of the repo containing PATH - the nearest parent with a .git entry - which is where git itself resolves it. Stopping at the repo boundary also means a populated submodule doesn't pick up its superproject's
.gitmodules.

Alien/hybrid indexing calls this on every file listing, so the result is cached in projectile--git-submodules-cache and recomputed only when .gitmodules changes on disk - a stat is far cheaper than the listing (see issue #1953). projectile-invalidate-cache also drops the cached listing.

Source Code

;; Defined in ~/.emacs.d/elpa/projectile-20260820.1509/projectile.el
(defun projectile--git-submodules (path)
  "Return the raw submodule listing for the Git repo containing PATH.
The result is a list of submodule paths relative to PATH.

With `projectile-git-submodule-command' at its default value the
listing is produced without a shell by `projectile--git-submodule-paths'
\(see issue #1600); when the variable is customized it is honored as a
shell command, and when nil submodules are disabled.

For Git projects without a `.gitmodules' file there are no submodules
to find, so the listing is skipped altogether.  PATH may be inside a
Git repo without being its toplevel \(e.g. a subproject of an outer
repo) so `.gitmodules' is looked up at the toplevel of the repo
containing PATH - the nearest parent with a `.git' entry - which is
where git itself resolves it.  Stopping at the repo boundary also
means a populated submodule doesn't pick up its superproject's
`.gitmodules'.

Alien/hybrid indexing calls this on every file listing, so the result
is cached in `projectile--git-submodules-cache' and recomputed only
when `.gitmodules' changes on disk - a stat is far cheaper than the
listing (see issue #1953).  `projectile-invalidate-cache' also
drops the cached listing."
  (when-let* ((gitmodules-dir (locate-dominating-file path ".git"))
              (gitmodules (expand-file-name ".gitmodules" gitmodules-dir))
              ;; A plain `_' binding trips "variable `_' not left unused"
              ;; in the Emacs 28/29 byte-compilers.
              (gitmodules-exists (file-exists-p gitmodules)))
    (let* ((mtime (file-attribute-modification-time
                   (file-attributes gitmodules)))
           (command (projectile-get-sub-projects-command 'git))
           (cached (gethash path projectile--git-submodules-cache)))
      (pcase-let ((`(,cached-gitmodules ,cached-mtime ,cached-command ,cached-result)
                   cached))
        (if (and cached
                 (equal cached-gitmodules gitmodules)
                 (equal cached-mtime mtime)
                 (equal cached-command command))
            cached-result
          (let ((submodules
                 (cond
                  ;; nil disables submodule listing altogether.
                  ((null command) nil)
                  ;; The stock command is never actually run: list the
                  ;; submodules shell-free instead (issue #1600).
                  ((equal command projectile--default-git-submodule-command)
                   (let ((dir (file-name-as-directory
                               (expand-file-name gitmodules-dir)))
                         (paths (projectile--git-submodule-paths gitmodules-dir)))
                     (if (equal dir (file-name-as-directory (expand-file-name path)))
                         paths
                       ;; PATH is below the `.gitmodules' dir: rebase the
                       ;; listing so it stays relative to PATH.
                       (mapcar (lambda (submodule)
                                 (file-relative-name
                                  (expand-file-name submodule dir) path))
                               paths))))
                  ;; A customized command is still run through the shell.
                  (t (projectile-files-via-ext-command path command)))))
            (puthash path (list gitmodules mtime command submodules)
                     projectile--git-submodules-cache)
            submodules))))))