diff options
| author | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-25 19:00:09 +0100 |
|---|---|---|
| committer | Bozhidar Batsov <bozhidar@toptal.com> | 2026-04-25 19:00:09 +0100 |
| commit | 5e4471b10dc59274e7608489b28897a69c585cc8 (patch) | |
| tree | a25ccb579536dcba4c35066fdfa74254e5cc288d | |
| parent | 54387baa2e2bd7af9866abd27f5454da71cdbd22 (diff) | |
Warn when alien indexing bypasses a populated .projectile
Under alien indexing the dirconfig file is silently ignored, which
is the most common confusion in the issue tracker (#1322, #1075,
#1534, #1941). Show a one-shot display-warning the first time we
index a project where alien mode meets a non-empty .projectile.
The new projectile-warn-when-dirconfig-is-ignored option lets users
who already understand the trade-off silence the warning.
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | projectile.el | 42 | ||||
| -rw-r--r-- | test/projectile-test.el | 63 |
3 files changed, 105 insertions, 1 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index f9e3843..9b1c821 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### New features +* 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. * [#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. * [#1837](https://github.com/bbatsov/projectile/issues/1837): Add `eat` project terminal commands with keybindings `x x` and `x 4 x`. * Add keybinding `A` (in the projectile command map) and a menu entry for `projectile-add-known-project`. diff --git a/projectile.el b/projectile.el index f0ec260..7d7d745 100644 --- a/projectile.el +++ b/projectile.el @@ -404,6 +404,17 @@ Similar to '#' in .gitignore files." :type 'character :package-version '(projectile . "2.2.0")) +(defcustom projectile-warn-when-dirconfig-is-ignored t + "Whether to warn when a non-empty .projectile is bypassed by alien indexing. +Under the `alien' indexing method, Projectile does not consult the +project's dirconfig file at indexing time. When this option is +non-nil, a one-time warning is shown for each project where a +non-empty dirconfig is present alongside alien indexing, since the +silent bypass is a frequent source of confusion." + :group 'projectile + :type 'boolean + :package-version '(projectile . "2.10.0")) + (defcustom projectile-globally-ignored-files (list projectile-tags-file-name projectile-cache-file) "A list of files globally ignored by projectile. @@ -653,6 +664,9 @@ project." "Cache for parsed dirconfig files, keyed by project root. Each value is a cons of (MTIME . PARSED-RESULT).") +(defvar projectile--alien-dirconfig-warned-projects (make-hash-table :test 'equal) + "Set of project roots already warned about alien indexing skipping the dirconfig.") + (defvar projectile-known-projects nil "List of locations where we have previously seen projects. The list of projects is ordered by the time they have been accessed. @@ -2288,6 +2302,30 @@ project-root for every file." (funcall action res) res))) +(defun projectile--dirconfig-non-empty-p () + "Return non-nil if the current project's dirconfig file has any content." + (let* ((dirconfig (projectile-dirconfig-file)) + (attrs (and (projectile-file-exists-p dirconfig) + (file-attributes dirconfig)))) + (and attrs (> (file-attribute-size attrs) 0)))) + +(defun projectile--maybe-warn-dirconfig-ignored (project-root) + "Warn once per session that PROJECT-ROOT's dirconfig is bypassed by alien mode." + (when (and projectile-warn-when-dirconfig-is-ignored + (eq projectile-indexing-method 'alien) + (not (gethash project-root + projectile--alien-dirconfig-warned-projects)) + (projectile--dirconfig-non-empty-p)) + (puthash project-root t projectile--alien-dirconfig-warned-projects) + (display-warning + 'projectile + (format "Project %s has a non-empty %s but `projectile-indexing-method' \ +is `alien', which bypasses dirconfig filtering. Switch to `hybrid' or \ +`native' if you need those rules to apply, or set \ +`projectile-warn-when-dirconfig-is-ignored' to nil to silence this warning." + project-root projectile-dirconfig-file) + :warning))) + (defun projectile-project-files (project-root) "Return a list of files for the PROJECT-ROOT." (let (files) @@ -2317,7 +2355,9 @@ project-root for every file." (if (eq projectile-indexing-method 'alien) ;; In alien mode we can just skip reading ;; .projectile and find all files in the root dir. - (projectile-dir-files-alien project-root) + (progn + (projectile--maybe-warn-dirconfig-ignored project-root) + (projectile-dir-files-alien project-root)) ;; If a project is defined as a list of subfolders ;; then we'll have the files returned for each subfolder, ;; so they are relative to the project root. diff --git a/test/projectile-test.el b/test/projectile-test.el index f874a83..f460194 100644 --- a/test/projectile-test.el +++ b/test/projectile-test.el @@ -633,6 +633,69 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'. (projectile-invalidate-cache nil) (expect (gethash root projectile--dirconfig-cache) :to-be nil)))))) +(describe "alien-mode dirconfig warning" + (before-each + (clrhash projectile--alien-dirconfig-warned-projects)) + (it "warns once when alien indexing skips a populated .projectile" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "-foo\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'projectile-dir-files-alien :and-return-value '("a")) + (spy-on 'display-warning) + (let ((projectile-indexing-method 'alien) + (projectile-enable-caching nil) + (projectile-warn-when-dirconfig-is-ignored t)) + (projectile-project-files root) + (projectile-project-files root)) + (expect 'display-warning :to-have-been-called-times 1))))) + (it "does not warn for an empty .projectile" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'projectile-dir-files-alien :and-return-value '("a")) + (spy-on 'display-warning) + (let ((projectile-indexing-method 'alien) + (projectile-enable-caching nil) + (projectile-warn-when-dirconfig-is-ignored t)) + (projectile-project-files root)) + (expect 'display-warning :not :to-have-been-called))))) + (it "does not warn when the warning is disabled" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "-foo\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'projectile-dir-files-alien :and-return-value '("a")) + (spy-on 'display-warning) + (let ((projectile-indexing-method 'alien) + (projectile-enable-caching nil) + (projectile-warn-when-dirconfig-is-ignored nil)) + (projectile-project-files root)) + (expect 'display-warning :not :to-have-been-called))))) + (it "does not warn under non-alien indexing" + (projectile-test-with-sandbox + (projectile-test-with-files + ("project/.projectile") + (let ((root (file-truename (expand-file-name "project/")))) + (with-temp-file (expand-file-name ".projectile" root) + (insert "-foo\n")) + (spy-on 'projectile-project-root :and-return-value root) + (spy-on 'projectile-get-project-directories :and-return-value '()) + (spy-on 'display-warning) + (let ((projectile-indexing-method 'native) + (projectile-enable-caching nil) + (projectile-warn-when-dirconfig-is-ignored t)) + (projectile-project-files root)) + (expect 'display-warning :not :to-have-been-called)))))) + (describe "projectile-get-project-directories" (it "gets the list of project directories" (spy-on 'projectile-project-root :and-return-value "/my/root/") |
