Function: projectile-project-root
projectile-project-root is a byte-compiled function defined in
projectile.el.
Signature
(projectile-project-root &optional DIR)
Documentation
Return the root directory of the project containing DIR, or nil.
If DIR is not supplied it defaults to default-directory.
While projectile--root-override is non-nil that directory is returned
instead, whatever DIR is.
Each function in projectile-project-root-functions is tried in order;
the first non-nil result wins. Results - including failures - are
memoized in projectile-project-root-cache (see the Project root cache
section in the manual). Use projectile-invalidate-cache to reset.
Special cases:
- Tramp archive paths (e.g. inside a .zip) are unwrapped to the
directory that contains the archive before searching.
- Remote files reached via TRAMP whose host is not currently connected
return nil without caching, so reconnecting works without manual cache
invalidation.
Source Code
;; Defined in ~/.emacs.d/elpa/projectile-20260820.1509/projectile.el
(defun projectile-project-root (&optional dir)
"Return the root directory of the project containing DIR, or nil.
If DIR is not supplied it defaults to `default-directory'.
While `projectile--root-override' is non-nil that directory is returned
instead, whatever DIR is.
Each function in `projectile-project-root-functions' is tried in order;
the first non-nil result wins. Results - including failures - are
memoized in `projectile-project-root-cache' (see the Project root cache
section in the manual). Use `projectile-invalidate-cache' to reset.
Special cases:
- Tramp archive paths (e.g. inside a `.zip') are unwrapped to the
directory that contains the archive before searching.
- Remote files reached via TRAMP whose host is not currently connected
return nil without caching, so reconnecting works without manual cache
invalidation."
;; `default-directory' can be nil in some buffers; short-circuit to nil so
;; callers get "no project" instead of a `(wrong-type-argument stringp nil)'
;; from `file-remote-p' and friends below (#1829).
(or projectile--root-override
(when-let* ((dir (or dir default-directory)))
;; Back out of any archives, the project will live on the outside and
;; searching them is slow.
(when (and (fboundp 'tramp-archive-file-name-p)
(tramp-archive-file-name-p dir))
(setq dir (file-name-directory (tramp-archive-file-name-archive dir))))
;; The cached value is 'none when no project root was found (so we don't
;; reevaluate every time when not inside a project); we map that back to
;; nil for callers. Cache keys are conses: (FUNC . DIR) for per-function
;; results, ('none . DIR) for the overall failure marker.
(let ((result (or
;; if we've already failed to find a project dir for this
;; dir, and cached that failure, don't recompute
(gethash (cons 'none dir) projectile-project-root-cache)
;; if the file isn't local, and we're not connected, don't try to
;; find a root now, but don't cache failure, as we might
;; re-connect. The `is-local' and `is-connected' variables are
;; used to fix the behavior where Emacs hangs because of
;; Projectile when you open a file over TRAMP. It basically
;; prevents Projectile from trying to find information about
;; files for which it's not possible to get that information
;; right now.
(let ((is-local (not (file-remote-p dir))) ;; `true' if the file is local
(is-connected (file-remote-p dir nil t))) ;; `true' if the file is remote AND we are connected to the remote
(unless (or is-local is-connected)
'none))
;; if the file is local or we're connected to it via TRAMP, run
;; through the project root functions until we find a project dir.
;; `projectile-root-local' reads a buffer-local variable rather
;; than inspecting DIR, so its result must not be cached - two
;; buffers in the same directory can legitimately disagree.
;; For other functions, both successes and per-function failures
;; (stored as the 'none sentinel) are memoized, so functions
;; earlier in the list that returned nil aren't re-walked on
;; every call.
;;
;; `true-dir-cell' lazily memoizes `(file-truename dir)' across
;; the loop so we pay the (potentially remote) symlink resolution
;; at most once per `projectile-project-root' call instead of
;; once per project-root-function on cache miss.
(let ((true-dir-cell (list nil)))
(seq-some
(lambda (func)
(if (eq func 'projectile-root-local)
(funcall func dir)
(let* ((cache-key (cons func dir))
(cache-value (gethash cache-key projectile-project-root-cache)))
(cond
((eq cache-value 'none) nil)
;; Use `projectile-file-exists-p' so the remote
;; stat is cached (per `projectile-file-exists-remote-cache-expire')
;; instead of round-tripping on every call.
((and cache-value (projectile-file-exists-p cache-value)) cache-value)
(t (let ((value (funcall
func
(or (car true-dir-cell)
(setcar true-dir-cell (file-truename dir))))))
(puthash cache-key (or value 'none) projectile-project-root-cache)
value))))))
projectile-project-root-functions))
;; if we get here, we have failed to find a root by all
;; conventional means, and we assume the failure isn't transient
;; / network related, so cache the failure
(puthash (cons 'none dir) 'none projectile-project-root-cache))))
(unless (eq result 'none) result)))))