aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBozhidar Batsov <bozhidar@toptal.com>2026-04-27 07:48:07 +0000
committerBozhidar Batsov <bozhidar@toptal.com>2026-04-27 07:48:07 +0000
commitfc8559850228f54efbc7ee4ea56b2e376cec5b04 (patch)
tree840af3bf3617a5dad6e3234778c7a3729bcab034
parenta55eeb2b1821c5252a5ea3d8c7c79728a42763fe (diff)
Persist disk cache in projectile-purge-dir-from-cache
`projectile-purge-file-from-cache' writes the updated file list to disk under persistent caching; the directory variant only mutated the in-memory hash, so the purged subtree would reappear on the next Emacs session. Mirror the persistence step here.
-rw-r--r--CHANGELOG.md1
-rw-r--r--projectile.el10
-rw-r--r--test/projectile-test.el34
3 files changed, 41 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9f0cd36..298727c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -44,6 +44,7 @@
* Fix `projectile-cache-current-file` calling `projectile-project-root` twice instead of reusing the already-resolved value.
* Fix `projectile-load-project-cache` not recording a cache time, which combined with `projectile-files-cache-expire` made the TTL check immediately re-evict freshly loaded data — every call ended up re-reading the cache file from disk and the data was never reindexed. The cache file's mtime is now used to seed `projectile-projects-cache-time`.
* Fix `projectile-load-project-cache` storing nil in cache on corrupt/empty cache files, preventing future reload attempts.
+* Fix `projectile-purge-dir-from-cache` only updating the in-memory cache; with persistent caching the purged directory's files would reappear on the next session. The on-disk cache is now updated as well, matching the behavior of `projectile-purge-file-from-cache`.
* Fix `projectile--cmake-command-presets` using `mapcar` instead of `mapcan`, producing nested lists for included presets.
* Fix `projectile--eat` ignoring the `new-process` argument when generating buffer names.
* Fix `projectile-check-vcs-status` hanging indefinitely by adding a 30-second timeout to its busy-wait loop.
diff --git a/projectile.el b/projectile.el
index 977860e..f81133d 100644
--- a/projectile.el
+++ b/projectile.el
@@ -1261,10 +1261,12 @@ The cache is created both in memory and on the hard drive."
(projectile-current-project-dirs)
:caller 'projectile-read-directory)))
(let* ((project-root (projectile-project-root))
- (project-cache (gethash project-root projectile-projects-cache)))
- (puthash project-root
- (seq-remove (lambda (str) (string-prefix-p dir str)) project-cache)
- projectile-projects-cache)))
+ (project-cache (gethash project-root projectile-projects-cache))
+ (new-cache (seq-remove (lambda (str) (string-prefix-p dir str))
+ project-cache)))
+ (puthash project-root new-cache projectile-projects-cache)
+ (when (projectile-persistent-cache-p)
+ (projectile-serialize new-cache (projectile-project-cache-file project-root)))))
(defun projectile-file-cached-p (file project)
"Check if FILE is already in PROJECT cache."
diff --git a/test/projectile-test.el b/test/projectile-test.el
index e21effb..9e4c358 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -3232,6 +3232,40 @@ projectile-process-current-project-buffers-current to have similar behaviour"
;; projectile-serialize should be called with the updated list, not the stale one
(expect 'projectile-serialize :to-have-been-called-with '("foo.el" "baz.el") "/tmp/cache.eld"))))
+(describe "projectile-purge-dir-from-cache"
+ (it "removes files under the directory from the in-memory cache"
+ (let ((projectile-projects-cache (make-hash-table :test 'equal))
+ (projectile-enable-caching t))
+ (puthash "/project/"
+ '("src/foo.el" "src/sub/bar.el" "test/baz.el")
+ projectile-projects-cache)
+ (spy-on 'projectile-project-root :and-return-value "/project/")
+ (projectile-purge-dir-from-cache "src/")
+ (expect (gethash "/project/" projectile-projects-cache)
+ :to-equal '("test/baz.el"))))
+ (it "serializes the updated cache when persistent caching is enabled"
+ (let ((projectile-projects-cache (make-hash-table :test 'equal))
+ (projectile-enable-caching 'persistent))
+ (puthash "/project/"
+ '("src/foo.el" "src/sub/bar.el" "test/baz.el")
+ projectile-projects-cache)
+ (spy-on 'projectile-project-root :and-return-value "/project/")
+ (spy-on 'projectile-serialize)
+ (spy-on 'projectile-project-cache-file :and-return-value "/tmp/cache.eld")
+ (projectile-purge-dir-from-cache "src/")
+ (expect 'projectile-serialize
+ :to-have-been-called-with '("test/baz.el") "/tmp/cache.eld")))
+ (it "does not touch disk when persistent caching is disabled"
+ (let ((projectile-projects-cache (make-hash-table :test 'equal))
+ (projectile-enable-caching t))
+ (puthash "/project/"
+ '("src/foo.el" "test/baz.el")
+ projectile-projects-cache)
+ (spy-on 'projectile-project-root :and-return-value "/project/")
+ (spy-on 'projectile-serialize)
+ (projectile-purge-dir-from-cache "src/")
+ (expect 'projectile-serialize :not :to-have-been-called))))
+
(describe "projectile-default-generic-command"
(it "returns a string command as-is"
(let ((projectile-project-types '((test-type compile-command "make"))))