Skip to content
Merged
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 @@ -31,6 +31,7 @@

### Bugs fixed

- [#2196](https://github.com/bbatsov/projectile/pull/2196): With the default in-memory cache, a project with a `.projectile` no longer throws its cache away and re-indexes on every `projectile-find-file` ([#2193](https://github.com/bbatsov/projectile/issues/2193)).
- [#2195](https://github.com/bbatsov/projectile/pull/2195): `projectile-dispatch` can be extended with `transient-insert-suffix`/`transient-append-suffix` as soon as `transient` is loaded, instead of failing with "Not a transient prefix command" until the menu had been opened once ([#2194](https://github.com/bbatsov/projectile/discussions/2194)).
- [#2191](https://github.com/bbatsov/projectile/pull/2191): The test suite passes under native compilation with an empty eln cache, as in distro package builds, instead of failing two dirconfig specs with `native-lisp-load-failed` ([#2100](https://github.com/bbatsov/projectile/issues/2100)).
- [#2190](https://github.com/bbatsov/projectile/pull/2190): Alien indexing no longer hands git exclude pathspecs to an `fd` whose `projectile-git-fd-args` lacks `--strip-cwd-prefix`, which made it fail with `No valid search paths given` ([#2187](https://github.com/bbatsov/projectile/issues/2187)).
Expand Down
22 changes: 20 additions & 2 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -2588,10 +2588,28 @@ is tracked in `projectile--current-project'."
(run-hook-with-args 'projectile-project-changed-functions
project-root previous))))))

(defun projectile--dirconfig-newer-than-cache-p (project-root)
"Return non-nil if PROJECT-ROOT's dirconfig changed after its files were cached.
When the files are cached in memory, what counts is when that cache was
filled; otherwise it's the persistent cache file, if there is one. With
no cache at all there is nothing to be stale."
(let ((dirconfig (projectile-dirconfig-file project-root))
(cache-time (gethash project-root projectile-projects-cache-time)))
(if cache-time
;; The cache time is in whole seconds, so an edit in the second the
;; cache was filled counts as newer: re-indexing once too often
;; beats serving a file list that ignores the edit.
(when-let* ((attributes (file-attributes dirconfig)))
(time-less-p cache-time (file-attribute-modification-time attributes)))
(let ((cache-file (projectile-project-cache-file project-root)))
(and (file-exists-p cache-file)
(file-newer-than-file-p dirconfig cache-file))))))

(defun projectile-maybe-invalidate-cache (force)
"Invalidate if FORCE or project's dirconfig newer than cache."
(when (or force (file-newer-than-file-p (projectile-dirconfig-file)
(projectile-project-cache-file)))
(when (or force
(when-let* ((project-root (projectile-project-root)))
(projectile--dirconfig-newer-than-cache-p project-root)))
(projectile-invalidate-cache nil)))


Expand Down
50 changes: 48 additions & 2 deletions test/projectile-root-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,54 @@
(expect (projectile-maybe-invalidate-cache t) :to-be-truthy))
(it "should invalidate cache if dirconfig is newer than cache"
(spy-on 'projectile-invalidate-cache :and-return-value t)
(spy-on 'file-newer-than-file-p :and-return-value t)
(expect (projectile-maybe-invalidate-cache nil) :to-be-truthy)))
(spy-on 'projectile--dirconfig-newer-than-cache-p :and-return-value t)
(expect (projectile-maybe-invalidate-cache nil) :to-be-truthy))
(it "leaves an in-memory cache alone when the dirconfig predates it (#2193)"
(projectile-test-with-stub-root "project" (".projectile")
(let ((root (projectile-project-root)))
(set-file-times (projectile-dirconfig-file root) (seconds-to-time 1000))
(puthash root 2000 projectile-projects-cache-time)
(spy-on 'projectile-invalidate-cache)
(projectile-maybe-invalidate-cache nil)
(expect 'projectile-invalidate-cache :not :to-have-been-called)))))

(describe "projectile--dirconfig-newer-than-cache-p"
:var (root)
(it "keeps an in-memory cache filled after the dirconfig was written"
(projectile-test-with-stub-root "project" (".projectile")
(setq root (projectile-project-root))
(set-file-times (projectile-dirconfig-file root) (seconds-to-time 1000))
(puthash root 2000 projectile-projects-cache-time)
;; No cache file: the in-memory cache never writes one.
(expect (file-exists-p (projectile-project-cache-file root)) :to-be nil)
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be nil)))

(it "detects a dirconfig edited after the in-memory cache was filled"
(projectile-test-with-stub-root "project" (".projectile")
(setq root (projectile-project-root))
(set-file-times (projectile-dirconfig-file root) (seconds-to-time 3000))
(puthash root 2000 projectile-projects-cache-time)
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be-truthy)))

(it "falls back to the cache file when nothing is cached in memory"
(projectile-test-with-stub-root "project" (".projectile" ".projectile-cache.eld")
(setq root (projectile-project-root))
(set-file-times (projectile-project-cache-file root) (seconds-to-time 2000))
(set-file-times (projectile-dirconfig-file root) (seconds-to-time 3000))
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be-truthy)
(set-file-times (projectile-dirconfig-file root) (seconds-to-time 1000))
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be nil)))

(it "has nothing to invalidate when there is no cache at all"
(projectile-test-with-stub-root "project" (".projectile")
(setq root (projectile-project-root))
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be nil)))

(it "has nothing to invalidate when there is no dirconfig"
(projectile-test-with-stub-root "project" ("a.el")
(setq root (projectile-project-root))
(puthash root 2000 projectile-projects-cache-time)
(expect (projectile--dirconfig-newer-than-cache-p root) :to-be nil))))

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