aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--doc/modules/ROOT/pages/configuration.adoc74
-rw-r--r--projectile.el35
-rw-r--r--test/projectile-test.el73
4 files changed, 170 insertions, 15 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e16240..eca17ad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@
### Changes
+* Document the `hybrid` indexing method in the manual and add a feature matrix showing which Projectile knobs (dirconfig, global ignores/unignores, sort order, default caching) apply under `native`/`hybrid`/`alien`.
+* `projectile-dir-files-alien` now accepts an optional `vcs` argument so the dispatcher can thread through the already-resolved VCS instead of recomputing it. Existing single-argument callers are unaffected.
+* `projectile-index-directory` (native indexing) now relies on `directory-files-no-dot-files-regexp` to filter out `.` and `..` at the C level instead of walking past them in Elisp.
* `projectile-project-root-cache` now keys entries on cons cells (`(FUNC . DIR)` for per-function results, `('none . DIR)` for the overall failure marker) instead of formatted strings. This is internal state, but third-party code that reaches into the cache directly will need to update.
* `projectile-parse-dirconfig-file' now returns a `projectile-dirconfig' struct (with `keep', `ignore', `ensure', and `prefixless-ignore' slots) instead of a positional 3-tuple. External callers should use the accessors (`projectile-dirconfig-keep' etc.) rather than `car'/`cadr'/`caddr'.
* Soft-deprecate prefix-less ignore entries in `.projectile'. Lines without a `+'/`-'/`!' prefix are still treated as ignore patterns for backward compatibility, but a one-time warning is now shown for each project that uses them. Set `projectile-warn-on-prefixless-dirconfig-lines' to nil to silence.
diff --git a/doc/modules/ROOT/pages/configuration.adoc b/doc/modules/ROOT/pages/configuration.adoc
index 4427bd2..1492633 100644
--- a/doc/modules/ROOT/pages/configuration.adoc
+++ b/doc/modules/ROOT/pages/configuration.adoc
@@ -146,6 +146,80 @@ You can also customize the path to the `fd` executable and the arguments passed
(setq projectile-git-fd-args "-H -0 -E .git -tf --strip-cwd-prefix -c never")
----
+=== Hybrid indexing
+
+The `hybrid` method runs the same external command as `alien` and then
+post-processes the result with Projectile's filtering rules. In other
+words, it's `alien` plus a second pass that applies:
+
+* `.projectile` (dirconfig) ignore/keep/ensure entries
+* `projectile-globally-ignored-files`,
+ `projectile-globally-ignored-directories`,
+ `projectile-globally-ignored-file-suffixes`, and
+ `projectile-global-ignore-file-patterns`
+* `projectile-globally-unignored-files` and
+ `projectile-globally-unignored-directories`
+* The configured sort order from `projectile-sort-order`
+
+This is the right choice when your VCS lists more files than you want
+Projectile to surface (e.g. you want to drop `*.elc` even though they're
+checked in), or when you rely on `.projectile` to keep/ignore subtrees
+inside an otherwise large repository. It's slower than `alien` because
+of the extra pass, but it remains substantially faster than `native` for
+big projects since the file list itself is still produced by the
+external tool.
+
+=== Indexing method comparison
+
+Not all Projectile features apply to every indexing method. The table
+below summarises which configuration knobs take effect under which mode:
+
+[cols="3,1,1,1", options="header"]
+|===
+| Feature | `native` | `hybrid` | `alien`
+
+| Project file listing speed
+| Slow (Elisp)
+| Fast
+| Fastest
+
+| Honors `.projectile` (dirconfig) `+`/`-`/`!` rules
+| Yes
+| Yes
+| No
+
+| Honors `projectile-globally-ignored-files` / `-directories` / `-file-suffixes` / `projectile-global-ignore-file-patterns`
+| Yes
+| Yes
+| No (rely on the VCS / `fd` / `find` ignores instead)
+
+| Honors `projectile-globally-unignored-*`
+| Yes
+| Yes
+| No
+
+| Sorts via `projectile-sort-order`
+| Yes
+| Yes
+| No (returned in the external tool's order)
+
+| File caching enabled by default
+| Yes
+| No
+| No
+
+| Works on Windows out of the box
+| Yes
+| Requires Unix utilities
+| Requires Unix utilities
+|===
+
+NOTE: When `alien` is in use and a non-empty `.projectile` file is
+present, Projectile emits a one-time warning so the silent bypass
+doesn't catch you off guard. Switch to `hybrid` (or `native`) if you
+need those rules applied; set
+`projectile-warn-when-dirconfig-is-ignored` to nil to silence.
+
== Sorting
You can choose how Projectile sorts files by customizing `projectile-sort-order`.
diff --git a/projectile.el b/projectile.el
index bcdf1a3..638642e 100644
--- a/projectile.el
+++ b/projectile.el
@@ -1599,13 +1599,14 @@ Files are returned as relative paths to DIRECTORY."
(gethash directory projectile-projects-cache))))
;; cache disabled or cache miss
(or files-list
- (let ((vcs (projectile-project-vcs directory)))
- (pcase projectile-indexing-method
- ('native (projectile-dir-files-native directory))
- ;; use external tools to get the project files
- ('hybrid (projectile-adjust-files directory vcs (projectile-dir-files-alien directory)))
- ('alien (projectile-dir-files-alien directory))
- (_ (user-error "Unsupported indexing method `%S'" projectile-indexing-method)))))))
+ (pcase projectile-indexing-method
+ ('native (projectile-dir-files-native directory))
+ ;; use external tools to get the project files
+ ('hybrid (let ((vcs (projectile-project-vcs directory)))
+ (projectile-adjust-files directory vcs
+ (projectile-dir-files-alien directory vcs))))
+ ('alien (projectile-dir-files-alien directory))
+ (_ (user-error "Unsupported indexing method `%S'" projectile-indexing-method))))))
;;; Native Project Indexing
;;
@@ -1637,8 +1638,7 @@ IGNORED-DIRECTORIES may optionally be provided."
(mapcar
(lambda (f)
(let ((local-f (file-name-nondirectory (directory-file-name f))))
- (unless (or (and patterns (projectile-ignored-rel-p f directory patterns))
- (member local-f '("." "..")))
+ (unless (and patterns (projectile-ignored-rel-p f directory patterns))
(progress-reporter-update progress-reporter)
(if (file-directory-p f)
(unless (projectile-ignored-directory-p
@@ -1651,17 +1651,24 @@ IGNORED-DIRECTORIES may optionally be provided."
(list f))))))
;; Use ignore-errors to skip unreadable directories (e.g.
;; .Spotlight-V100 on macOS) instead of aborting the entire
- ;; indexing operation.
- (ignore-errors (directory-files directory t))))))
+ ;; indexing operation. `directory-files-no-dot-files-regexp'
+ ;; filters out . and .. at the C level so we don't have to
+ ;; do it again in the loop.
+ (ignore-errors
+ (directory-files directory t directory-files-no-dot-files-regexp))))))
;;; Alien Project Indexing
;;
;; This corresponds to `projectile-indexing-method' being set to hybrid or alien.
;; The only difference between the two methods is that alien doesn't do
;; any post-processing of the files obtained via the external command.
-(defun projectile-dir-files-alien (directory)
- "Get the files for DIRECTORY using external tools."
- (let ((vcs (projectile-project-vcs directory)))
+(defun projectile-dir-files-alien (directory &optional vcs)
+ "Get the files for DIRECTORY using external tools.
+VCS, when supplied, must be the project's VCS as returned by
+`projectile-project-vcs'. It is computed from DIRECTORY when
+omitted; callers that already resolved the VCS can pass it in to
+avoid the redundant work."
+ (let ((vcs (or vcs (projectile-project-vcs directory))))
(cond
((eq vcs 'git)
(let* ((files (nconc (projectile-files-via-ext-command directory (projectile-get-ext-command vcs))
diff --git a/test/projectile-test.el b/test/projectile-test.el
index 4d56d4d..a546a8f 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -919,7 +919,78 @@ Just delegates OPERATION and ARGS for all operations except for`shell-command`'.
(delete-file "deleted.txt")
(let ((files (projectile-dir-files-alien default-directory)))
(expect files :to-contain "existing.txt")
- (expect files :not :to-contain "deleted.txt")))))))
+ (expect files :not :to-contain "deleted.txt"))))))
+ (it "uses the VCS argument when supplied without recomputing it"
+ (spy-on 'projectile-project-vcs)
+ (spy-on 'projectile-files-via-ext-command :and-return-value '("a"))
+ (spy-on 'projectile-get-sub-projects-files :and-return-value nil)
+ (spy-on 'projectile-git-deleted-files :and-return-value nil)
+ (let ((projectile-git-use-fd nil)
+ (projectile-fd-executable nil))
+ (projectile-dir-files-alien "/my/root/" 'git))
+ (expect 'projectile-project-vcs :not :to-have-been-called)))
+
+(describe "hybrid indexing"
+ (it "applies projectile-globally-ignored-file-suffixes on top of the alien result"
+ (spy-on 'projectile-files-via-ext-command :and-return-value
+ '("foo.el" "build/foo.elc" "README"))
+ (spy-on 'projectile-get-sub-projects-files :and-return-value nil)
+ (spy-on 'projectile-git-deleted-files :and-return-value nil)
+ (spy-on 'projectile-project-vcs :and-return-value 'git)
+ (spy-on 'projectile-project-root :and-return-value "/my/root/")
+ (spy-on 'file-directory-p :and-call-fake
+ (lambda (filename) (equal filename "/my/root/")))
+ (spy-on 'projectile-parse-dirconfig-file :and-return-value nil)
+ (let ((projectile-indexing-method 'hybrid)
+ (projectile-enable-caching nil)
+ (projectile-globally-ignored-file-suffixes '(".elc"))
+ (projectile-globally-ignored-files nil)
+ (projectile-globally-ignored-directories nil)
+ (projectile-globally-unignored-files nil)
+ (projectile-globally-unignored-directories nil)
+ (projectile-git-use-fd nil)
+ (projectile-fd-executable nil))
+ (let ((files (projectile-dir-files "/my/root/")))
+ (expect files :to-contain "foo.el")
+ (expect files :to-contain "README")
+ (expect files :not :to-contain "build/foo.elc"))))
+ (it "honors dirconfig ignore patterns on top of the alien result"
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("project/"
+ "project/keep.txt"
+ "project/drop.txt"
+ "project/.projectile")
+ (let ((root (file-truename (expand-file-name "project/"))))
+ (with-temp-file (expand-file-name ".projectile" root)
+ (insert "-/drop.txt\n"))
+ (spy-on 'projectile-project-root :and-return-value root)
+ (spy-on 'projectile-project-vcs :and-return-value 'git)
+ (spy-on 'projectile-files-via-ext-command :and-return-value
+ '("keep.txt" "drop.txt"))
+ (spy-on 'projectile-get-sub-projects-files :and-return-value nil)
+ (spy-on 'projectile-git-deleted-files :and-return-value nil)
+ (let ((projectile-indexing-method 'hybrid)
+ (projectile-enable-caching nil)
+ (projectile-git-use-fd nil)
+ (projectile-fd-executable nil))
+ (let ((files (projectile-dir-files root)))
+ (expect files :to-contain "keep.txt")
+ (expect files :not :to-contain "drop.txt")))))))
+ (it "passes the resolved VCS to projectile-dir-files-alien"
+ (spy-on 'projectile-project-vcs :and-return-value 'git)
+ (spy-on 'projectile-dir-files-alien :and-return-value '("a"))
+ (spy-on 'projectile-adjust-files :and-call-fake (lambda (_p _v files) files))
+ (spy-on 'file-directory-p :and-call-fake
+ (lambda (filename) (equal filename "/my/root/")))
+ (let ((projectile-indexing-method 'hybrid)
+ (projectile-enable-caching nil))
+ (projectile-dir-files "/my/root/"))
+ ;; vcs is resolved once by the dispatcher and threaded through; the
+ ;; redundant call inside projectile-dir-files-alien is gone.
+ (expect 'projectile-project-vcs :to-have-been-called-times 1)
+ (expect 'projectile-dir-files-alien
+ :to-have-been-called-with "/my/root/" 'git)))
(describe "projectile-project-dirs"
(it "includes intermediate directories that contain only subdirectories"