Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### New features

* [#1837](https://github.com/bbatsov/projectile/issues/1837): Add `eat` project terminal commands with keybindings `x x` and `x 4 x`.
* [#1694](https://github.com/bbatsov/projectile/issues/1694): Add `projectile-invalidate-cache-all` command to clear the file cache for all known projects at once.

### Bugs fixed

Expand Down
3 changes: 3 additions & 0 deletions doc/modules/ROOT/pages/troubleshooting.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ Note that Projectile caches the operation of checking which project
(if any) a file belongs to. If you have already opened a file, then
later added a marker file like `.projectile`, run `M-x
projectile-invalidate-cache` to reset the cache.
To invalidate the cache for all known projects at once (useful before
using `projectile-find-file-in-known-projects`), run `M-x
projectile-invalidate-cache-all`.

=== I upgraded Projectile using `package.el` and nothing changed

Expand Down
26 changes: 26 additions & 0 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -1124,6 +1124,31 @@ argument)."
(when (fboundp 'recentf-cleanup)
(recentf-cleanup)))

;;;###autoload
(defun projectile-invalidate-cache-all ()
"Remove all known projects' files from `projectile-projects-cache'.

Also clears `projectile-projects-cache-time',
`projectile-project-type-cache', and `projectile-project-root-cache'.

When persistent caching is enabled, on-disk cache files are also
cleared for all known projects (excluding remote TRAMP paths)."
(interactive)
(setq projectile-project-root-cache (make-hash-table :test 'equal))
(let ((count (hash-table-count projectile-projects-cache)))
(setq projectile-projects-cache (make-hash-table :test 'equal))
(setq projectile-projects-cache-time (make-hash-table :test 'equal))
(setq projectile-project-type-cache (make-hash-table :test 'equal))
(when (projectile-persistent-cache-p)
(dolist (project projectile-known-projects)
(when (and (not (file-remote-p project))
(file-exists-p project))
(projectile-serialize nil (projectile-project-cache-file project)))))
(when (fboundp 'recentf-cleanup)
(recentf-cleanup))
(when projectile-verbose
(message "Invalidated Projectile cache for %d projects." count))))

(defun projectile-time-seconds ()
"Return the number of seconds since the unix epoch."
(if (fboundp 'time-convert)
Expand Down Expand Up @@ -6374,6 +6399,7 @@ thing shown in the mode line otherwise."
"--"
["Cache current file" projectile-cache-current-file]
["Invalidate cache" projectile-invalidate-cache]
["Invalidate all caches" projectile-invalidate-cache-all]
["Regenerate [e|g]tags" projectile-regenerate-tags]
"--"
["Toggle project wide read-only" projectile-toggle-project-read-only]
Expand Down
61 changes: 61 additions & 0 deletions test/projectile-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,67 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'.
(spy-on 'file-newer-than-file-p :and-return-value t)
(expect (projectile-maybe-invalidate-cache nil) :to-be-truthy)))

(describe "projectile-invalidate-cache-all"
(it "should clear projectile-projects-cache"
(puthash "/project1/" '("file1.el") projectile-projects-cache)
(puthash "/project2/" '("file2.el") projectile-projects-cache)
(projectile-invalidate-cache-all)
(expect (hash-table-count projectile-projects-cache) :to-equal 0))
(it "should clear projectile-projects-cache-time"
(puthash "/project1/" 1234567890 projectile-projects-cache-time)
(projectile-invalidate-cache-all)
(expect (hash-table-count projectile-projects-cache-time) :to-equal 0))
(it "should clear projectile-project-root-cache"
(puthash "buffer1" "/project1/" projectile-project-root-cache)
(projectile-invalidate-cache-all)
(expect (hash-table-count projectile-project-root-cache) :to-equal 0))
(it "should clear projectile-project-type-cache"
(puthash "/project1/" 'generic projectile-project-type-cache)
(projectile-invalidate-cache-all)
(expect (hash-table-count projectile-project-type-cache) :to-equal 0))
(it "should call projectile-serialize for persistent cache"
(spy-on 'projectile-serialize)
(let* ((dir1 (make-temp-file "projectile-test1" t))
(dir2 (make-temp-file "projectile-test2" t))
(projectile-enable-caching 'persistent)
(projectile-known-projects (list (file-name-as-directory dir1)
(file-name-as-directory dir2))))
(unwind-protect
(progn
(projectile-invalidate-cache-all)
(expect 'projectile-serialize :to-have-been-called-times 2))
(delete-directory dir1 t)
(delete-directory dir2 t))))
(it "should work when no projects are cached"
(projectile-invalidate-cache-all)
(expect (hash-table-count projectile-projects-cache) :to-equal 0))
(it "should call recentf-cleanup when available"
(spy-on 'recentf-cleanup)
(projectile-invalidate-cache-all)
(expect 'recentf-cleanup :to-have-been-called))
(it "should skip non-existent projects for persistent cache"
(spy-on 'projectile-serialize)
(let* ((dir1 (make-temp-file "projectile-test1" t))
(projectile-enable-caching 'persistent)
(projectile-known-projects (list (file-name-as-directory dir1)
"/definitely-nonexistent-dir/")))
(unwind-protect
(progn
(projectile-invalidate-cache-all)
(expect 'projectile-serialize :to-have-been-called-times 1))
(delete-directory dir1 t))))
(it "should skip remote projects for persistent cache"
(spy-on 'projectile-serialize)
(let* ((dir1 (make-temp-file "projectile-test1" t))
(projectile-enable-caching 'persistent)
(projectile-known-projects (list (file-name-as-directory dir1)
"/ssh:host:/remote/project/")))
(unwind-protect
(progn
(projectile-invalidate-cache-all)
(expect 'projectile-serialize :to-have-been-called-times 1))
(delete-directory dir1 t)))))

(describe "projectile-root-top-down"
(it "identifies the root directory of a project by top-down search"
(projectile-test-with-sandbox
Expand Down