From 66b54e9038c8aadf5a515758ca3c0acb03977b0e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 26 Apr 2026 07:49:03 +0100 Subject: Add projectile-discard-root-cache command (#1936) The existing projectile-invalidate-cache always tries to also clear the per-project files cache, which means it either prompts for a project (with prefix arg) or only operates when you're already in a project. Users who just created a marker file in a directory that Projectile previously considered rootless ended up picking an unrelated project from the prompt just to get the root cache flushed. projectile-discard-root-cache is a focused alternative that empties projectile-project-root-cache only. Wired into the menu next to the existing 'Invalidate cache' entry; deliberately not bound on the prefix map since the keymap is already dense. --- CHANGELOG.md | 1 + doc/modules/ROOT/pages/projects.adoc | 4 ++++ projectile.el | 15 +++++++++++++++ test/projectile-test.el | 17 +++++++++++++++++ 4 files changed, 37 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a906264..3e16240 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ### New features +* [#1936](https://github.com/bbatsov/projectile/issues/1936): Add `projectile-discard-root-cache` command to clear `projectile-project-root-cache` without touching other Projectile caches. Useful after creating or removing a project marker, since the existing `projectile-invalidate-cache` either also drops the file list cache or prompts for a project depending on context. * Warn once per session when `projectile-indexing-method' is `alien' but the project has a non-empty `.projectile' file, so users notice their dirconfig rules are being bypassed. Controlled by the new `projectile-warn-when-dirconfig-is-ignored' option. * Warn when a `+' keep entry in `.projectile' contains glob metacharacters. The `+' prefix is for subdirectory paths only and globs are silently coerced into a non-matching directory name; the warning surfaces the misuse rather than letting it fail silently. * [#1964](https://github.com/bbatsov/projectile/issues/1964): Implement `project-name` and `project-buffers` methods for the `project.el` integration, so that code using `project.el` APIs returns correct results for Projectile-managed projects. diff --git a/doc/modules/ROOT/pages/projects.adoc b/doc/modules/ROOT/pages/projects.adoc index c58572c..d08bae8 100644 --- a/doc/modules/ROOT/pages/projects.adoc +++ b/doc/modules/ROOT/pages/projects.adoc @@ -822,6 +822,10 @@ The cache is populated lazily and only invalidated when: project, so this is also the right command to run after creating a new `.projectile`/`.git`/etc. in a directory that Projectile previously considered rootless. +* You call `projectile-discard-root-cache` if you want to clear *only* the + project root cache without dropping the per-project file lists - useful + when you've just added a marker file and don't want to re-index large + projects. * You restart Emacs. The cache is keyed on the search start directory and a positive entry is diff --git a/projectile.el b/projectile.el index a61fcf4..c29d9cf 100644 --- a/projectile.el +++ b/projectile.el @@ -1189,6 +1189,20 @@ argument)." (when (fboundp 'recentf-cleanup) (recentf-cleanup))) +;;;###autoload +(defun projectile-discard-root-cache () + "Clear `projectile-project-root-cache' without touching other caches. +Useful after creating, removing, or moving a project marker (e.g. +`.projectile' or `.git') - Projectile would otherwise keep returning +its previously cached answer for that directory. + +See also `projectile-invalidate-cache', which does this and also drops +the per-project file list and project-type caches." + (interactive) + (setq projectile-project-root-cache (make-hash-table :test 'equal)) + (when projectile-verbose + (message "Cleared Projectile project root cache."))) + (defun projectile-time-seconds () "Return the number of seconds since the unix epoch." (time-convert nil 'integer)) @@ -6698,6 +6712,7 @@ Magit that don't trigger `find-file-hook'." "--" ["Cache current file" projectile-cache-current-file] ["Invalidate cache" projectile-invalidate-cache] + ["Discard project root cache" projectile-discard-root-cache] ["Regenerate [e|g]tags" projectile-regenerate-tags] "--" ["Toggle project wide read-only" projectile-toggle-project-read-only] diff --git a/test/projectile-test.el b/test/projectile-test.el index 600e137..4d56d4d 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -1406,6 +1406,23 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (spy-on 'projectile-project-root :and-return-value nil) (expect (projectile-acquire-root) :to-equal "/here/")))) +(describe "projectile-discard-root-cache" + (it "empties the root cache without touching other caches" + (let ((projectile-project-root-cache (make-hash-table :test 'equal)) + (projectile-projects-cache (make-hash-table :test 'equal)) + (projectile-project-type-cache (make-hash-table :test 'equal)) + (projectile-verbose nil)) + (puthash (cons 'projectile-root-bottom-up "/x/") "/x/" + projectile-project-root-cache) + (puthash (cons 'none "/y/") 'none projectile-project-root-cache) + (puthash "/x/" '("a" "b") projectile-projects-cache) + (puthash "/x/" 'emacs-eldev projectile-project-type-cache) + (projectile-discard-root-cache) + (expect (hash-table-count projectile-project-root-cache) :to-equal 0) + ;; Other caches must be untouched. + (expect (gethash "/x/" projectile-projects-cache) :to-equal '("a" "b")) + (expect (gethash "/x/" projectile-project-type-cache) :to-equal 'emacs-eldev)))) + (describe "projectile-file-exists-p" (it "returns t if file exists" (projectile-test-with-sandbox -- cgit v1.0