From 372386fd17a9698899bc259fc3a67d002c8cd9ac Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 24 Jul 2026 01:16:07 +0200 Subject: [PATCH] Build the search ignores from Projectile's patterns [Fix #221] Projectile 3.3 collapsed its ignore machinery into a single list of gitignore-style patterns and dropped the path lists we were reading - projectile-ignored-files-rel, projectile-ignored-directories-rel and projectile-patterns-to-ignore - so every search command errored out. Read projectile-filtering-patterns instead and split it on the trailing slash that marks a directory-only rule, since helm-grep wants ignored files and directories as two lists. The anchoring markers come off: the search tools take plain globs and have no notion of a rule anchored to the project root. The ack path no longer appends projectile-patterns-to-ignore separately - the pattern list already covers it. One of the new specs goes through Projectile unstubbed. The suite was spying on the very functions that had been deleted, so it stayed green through this breakage. --- CHANGELOG.md | 8 +++++++ helm-projectile.el | 32 +++++++++++++++++++++------ test/helm-projectile-test.el | 42 ++++++++++++++++++++++++++++++------ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e8668..8c952b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## master (unreleased) +### Bugs fixed + +* [#221](https://github.com/bbatsov/helm-projectile/issues/221): Build the search ignores from Projectile's gitignore-style patterns (`projectile-filtering-patterns`), instead of the `projectile-ignored-files-rel`/`projectile-ignored-directories-rel`/`projectile-patterns-to-ignore` path lists that Projectile 3.3 removed. + +### Changes + +* Require Projectile 3.3.0, which is where the ignore rules became a single list of patterns. + ## 1.7.0 (2026-07-08) ### New features diff --git a/helm-projectile.el b/helm-projectile.el index fcb303a..82dd1a7 100644 --- a/helm-projectile.el +++ b/helm-projectile.el @@ -8,7 +8,7 @@ ;; Created: 2011-31-07 ;; Keywords: project, convenience ;; Version: 1.7.0 -;; Package-Requires: ((emacs "28.1") (helm "3.0") (projectile "3.1.0")) +;; Package-Requires: ((emacs "28.1") (helm "3.0") (projectile "3.3.0")) ;; This file is NOT part of GNU Emacs. @@ -1448,18 +1448,40 @@ When set to `search-tool', the above does not happen." "True if the ignore strategy is `projectile'." (eq 'projectile helm-projectile-ignore-strategy)) +(defun helm-projectile--ignore-glob (pattern) + "Turn the gitignore-style PATTERN into a plain glob. +Projectile anchors a rule to the project root with a leading `/' and +floats one explicitly with a leading `**/'. The search tools take plain +globs with no notion of anchoring, so those markers - and the trailing +`/' of a directory-only rule - come off." + (string-remove-prefix + "/" (string-remove-prefix "**/" (string-remove-suffix "/" pattern)))) + +(defun helm-projectile--ignore-globs (directories) + "Return the project's ignore rules as plain globs. +With DIRECTORIES non-nil return the directory-only rules, otherwise all +the others. The rules come from `projectile-filtering-patterns', the +same list Projectile's own indexing and search commands work from." + (cl-remove-if + #'string-empty-p + (mapcar #'helm-projectile--ignore-glob + ;; A trailing slash is how Projectile spells a directory-only rule. + (cl-remove-if (lambda (pattern) + (xor directories (string-suffix-p "/" pattern))) + (car (projectile-filtering-patterns)))))) + (defun helm-projectile--ignored-files () "Compute ignored files." ;; `grep' provides `grep-find-ignored-files'; load it on demand rather than ;; at startup, since it is only needed when a search computes ignores. (require 'grep) - (cl-union (projectile-ignored-files-rel) grep-find-ignored-files + (cl-union (helm-projectile--ignore-globs nil) grep-find-ignored-files :test #'equal)) (defun helm-projectile--ignored-directories () "Compute ignored directories." (require 'grep) - (cl-union (mapcar #'file-name-as-directory (projectile-ignored-directories-rel)) + (cl-union (mapcar #'file-name-as-directory (helm-projectile--ignore-globs t)) (mapcar #'file-name-as-directory grep-find-ignored-directories) :test #'equal)) @@ -1637,9 +1659,7 @@ Use `helm-projectile-rg' or `helm-projectile-ag' instead." path (helm-projectile--wildcard-to-ack-match) (shell-quote-argument)))) - (append - (helm-projectile--ignored-files) - (projectile-patterns-to-ignore))) + (helm-projectile--ignored-files)) :test #'equal) " "))) (helm-ack-grep-executable (cond diff --git a/test/helm-projectile-test.el b/test/helm-projectile-test.el index 12db0ac..80d4f1f 100644 --- a/test/helm-projectile-test.el +++ b/test/helm-projectile-test.el @@ -344,10 +344,11 @@ (describe "helm-projectile ignore lists" ;; The union of Projectile's ignores with the `grep-find-ignored-*' ;; defaults feeds every search command; several past bugs lived here. + ;; Projectile hands over gitignore-style patterns: a trailing slash means + ;; directory-only, a leading `/' or `**/' is an anchoring marker. (before-each - (spy-on 'projectile-ignored-files-rel :and-return-value '("TAGS" "a.log")) - (spy-on 'projectile-ignored-directories-rel - :and-return-value '("build" "node_modules"))) + (spy-on 'projectile-filtering-patterns + :and-return-value '(("TAGS" "a.log" "build/" "node_modules/")))) (it "unions the Projectile and grep ignored files, de-duplicated" (let ((grep-find-ignored-files '("*.o" "TAGS"))) @@ -357,7 +358,37 @@ (it "unions ignored directories as directory names, de-duplicated" (let ((grep-find-ignored-directories '(".git" "build"))) (expect (sort (copy-sequence (helm-projectile--ignored-directories)) #'string<) - :to-equal '(".git/" "build/" "node_modules/"))))) + :to-equal '(".git/" "build/" "node_modules/")))) + + (it "strips the anchoring markers the search tools don't understand" + ;; A rule that is nothing but markers ("/" here) is left with no glob at + ;; all and drops out. + (spy-on 'projectile-filtering-patterns + :and-return-value '(("/TAGS" "**/*.log" "/" "/build/" "**/vendor/"))) + (let ((grep-find-ignored-files nil) + (grep-find-ignored-directories nil)) + (expect (helm-projectile--ignored-files) :to-equal '("TAGS" "*.log")) + (expect (helm-projectile--ignored-directories) + :to-equal '("build/" "vendor/"))))) + +(describe "helm-projectile ignore lists, unstubbed" + ;; Stubs can't catch API drift - the suite kept passing against a Projectile + ;; that had dropped the functions these helpers called (#221). + (it "reads what Projectile actually computes for a project" + (helm-projectile-test-with-sandbox + ;; The matching files exist on disk to pin down that the rules come back + ;; as patterns: `*.log' stays a glob instead of expanding to `a.log', + ;; which is what the removed path-list API used to hand over. + (helm-projectile-test-with-files '("build/" "a.log") + (write-region "-build/\n-*.log\n" nil ".projectile" nil 'silent) + (let ((projectile-globally-ignored-directories '(".git")) + (projectile-globally-ignored-files '("TAGS")) + (projectile-globally-ignored-file-suffixes nil) + (grep-find-ignored-files nil) + (grep-find-ignored-directories nil)) + (expect (helm-projectile--ignored-files) :to-equal '("TAGS" "*.log")) + (expect (helm-projectile--ignored-directories) + :to-equal '(".git/" "build/"))))))) (describe "helm-projectile-grep-or-ack command construction" (before-each @@ -436,8 +467,7 @@ (before-each (spy-on 'projectile-project-root :and-return-value "/proj/") (spy-on 'helm-projectile--ignored-directories :and-return-value '("build/")) - (spy-on 'helm-projectile--ignored-files :and-return-value '("TAGS")) - (spy-on 'projectile-patterns-to-ignore :and-return-value '("*.log"))) + (spy-on 'helm-projectile--ignored-files :and-return-value '("TAGS"))) (it "detects ack and builds --ignore-dir/--ignore-file arguments" (spy-on 'executable-find