diff options
| author | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-26 07:13:42 +0100 |
|---|---|---|
| committer | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-26 07:13:42 +0100 |
| commit | 67981d6d93b957edf3359d94f6216837427834bc (patch) | |
| tree | a425158198f405ee313de72377bf698559406f53 | |
| parent | 183023602f1002f39d6e5c82ca44a317d6b517fe (diff) | |
Skip cache for projectile-root-local (#1211)
projectile-root-local reads the buffer-local projectile-project-root
variable rather than inspecting its DIR argument, so caching its
result by (FUNC . DIR) was wrong: two buffers visiting the same
directory but with different file-local overrides would share the
first buffer's cached answer. Skip the cache for this function
specifically - it's a single variable read, no FS work to amortize.
Adds a regression test that visits two buffers with different
file-local roots from the same default-directory and confirms each
sees its own override.
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | projectile.el | 21 | ||||
| -rw-r--r-- | test/projectile-test.el | 21 |
3 files changed, 34 insertions, 9 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b64613..07e7a67 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### Bugs fixed +* [#1211](https://github.com/bbatsov/projectile/issues/1211): Fix file-local `projectile-project-root` overrides being ignored after the first buffer in a directory was visited. The cache used `(default-directory)` as part of the key but `projectile-root-local` reads a buffer-local variable, so two buffers in the same directory with different overrides got the first buffer's answer. Results from `projectile-root-local` are no longer cached. * [#1508](https://github.com/bbatsov/projectile/issues/1508): Fix dirconfig parser silently treating lines as ignore patterns when the `+`/`-`/`!` prefix or the comment character is preceded by whitespace; leading spaces and tabs are now skipped before prefix dispatch. * Fix `projectile-files-via-ext-command` executing empty string as shell command for non-git VCS sub-projects. * Fix `projectile-select-files` crashing on filenames with regexp metacharacters by using `string-search` instead of `string-match`. diff --git a/projectile.el b/projectile.el index bcd8302..0530925 100644 --- a/projectile.el +++ b/projectile.el @@ -1478,16 +1478,21 @@ If DIR is not supplied it's set to the current directory by default." (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 + ;; 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. (seq-some (lambda (func) - (let* ((cache-key (cons func dir)) - (cache-value (gethash cache-key projectile-project-root-cache))) - (if (and cache-value (file-exists-p cache-value)) - cache-value - (let ((value (funcall func (file-truename dir)))) - (puthash cache-key value projectile-project-root-cache) - value)))) + (if (eq func 'projectile-root-local) + (funcall func dir) + (let* ((cache-key (cons func dir)) + (cache-value (gethash cache-key projectile-project-root-cache))) + (if (and cache-value (file-exists-p cache-value)) + cache-value + (let ((value (funcall func (file-truename dir)))) + (puthash cache-key value 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 diff --git a/test/projectile-test.el b/test/projectile-test.el index f099d5f..52f24b1 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -1264,7 +1264,26 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. ;; since the failure was transitory, there should be nothing cached (expect (gethash cache-key projectile-project-root-cache) :to-be nil) ;; and projectile-project-root should still return nil - (expect (projectile-project-root dir) :to-be nil))))))) + (expect (projectile-project-root dir) :to-be nil)))))) + + (it "honors a buffer-local projectile-project-root after it changes (#1211)" + (projectile-test-with-sandbox + (projectile-test-with-files + ("alpha/.projectile" + "beta/.projectile" + "host/notes.org") + (let* ((dir (expand-file-name "host/")) + (alpha-root (file-truename (expand-file-name "alpha/"))) + (beta-root (file-truename (expand-file-name "beta/"))) + (default-directory dir) + (projectile-project-root-cache (make-hash-table :test 'equal))) + ;; First "buffer": file-local override pointing at alpha. + (let ((projectile-project-root alpha-root)) + (expect (projectile-project-root) :to-equal alpha-root)) + ;; Second "buffer": same default-directory, different override. + ;; The cache must not return the previous buffer's answer. + (let ((projectile-project-root beta-root)) + (expect (projectile-project-root) :to-equal beta-root))))))) (describe "projectile-file-exists-p" (it "returns t if file exists" |
