diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1c7b3da..6816912d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ ### Bugs fixed +- [#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)). - [#2183](https://github.com/bbatsov/projectile/pull/2183): `projectile-run-test-at-point` now builds a usable file path when the project is reached through a symlink (anything under `/tmp` on macOS, or a symlinked `~/src`), instead of one that climbs out of the project and gets rejected by the test runner. - [#2178](https://github.com/bbatsov/projectile/pull/2178): The file-extension filter of `projectile-replace` and friends is now shell-quoted. ag received it unquoted, so a `*.el` filter reached it as the regexp `.el$` and also matched files ending in `model` or `panel`; on the other tools an extension containing a quote broke the command outright. - [#2177](https://github.com/bbatsov/projectile/pull/2177): `projectile-ripgrep` no longer dies under zsh with `no matches found`. The `ripgrep` package runs its command line through the shell, and the ignore exclusions went in unquoted - harmless until the generated-directory defaults introduced a pattern with a `*` in it. diff --git a/projectile.el b/projectile.el index 0326a6785..41255b6a8 100644 --- a/projectile.el +++ b/projectile.el @@ -3439,12 +3439,37 @@ for the regexps matched below." ;; alien keeps doing no Lisp-side filtering. Only the tools that can't express ;; exclusions fall back to filtering in Emacs. +(defun projectile--ext-command-program (command) + "Return the name of the program COMMAND runs. +The name loses its directory and a Windows `.exe' suffix, so `git.exe' +and `/usr/bin/git' both come back as \"git\". COMMAND may be nil, in +which case so is the result." + (when-let* ((command) + (program (car (split-string command)))) + (string-remove-suffix ".exe" (file-name-nondirectory program)))) + (defun projectile--fd-command-p (command) - "Return non-nil when COMMAND is one of Projectile's `fd' recipes. -Recognised by the `--strip-cwd-prefix' flag Projectile puts in them, the -same way `projectile--ext-command-line' does. COMMAND may be nil, which -is how Projectile spells \"external-command indexing is disabled\"." - (and command (string-match-p "--strip-cwd-prefix\\b" command))) + "Return non-nil when COMMAND runs `fd'. +Recognised by the program it runs (`fd', or `fdfind' as Debian names it), +or by the `--strip-cwd-prefix' flag Projectile puts in its `fd' recipes, +which also catches an `fd' installed under some other name. COMMAND may +be nil, which is how Projectile spells \"external-command indexing is +disabled\"." + (and command + (or (member (projectile--ext-command-program command) '("fd" "fdfind")) + (string-match-p "--strip-cwd-prefix\\b" command)))) + +(defun projectile--alien-exclude-style (vcs command) + "Return the kind of exclusion arguments COMMAND for VCS accepts, or nil. +The result is `fd' for an `fd' command, `git' for a `git' command in a +git project, and nil for a command with no way to express exclusions. +It goes by the program COMMAND runs rather than by VCS alone, since a +git project may well be listed by `fd' or something else (see #2187)." + (cond + ((projectile--fd-command-p command) 'fd) + ((and (eq vcs 'git) + (equal (projectile--ext-command-program command) "git")) + 'git))) (defun projectile--alien-exclude-glob (glob style) "Translate GLOB into an exclusion pattern of the given STYLE. @@ -3480,25 +3505,23 @@ Lisp instead (see `projectile--maybe-remove-ignored'). VCS is the project's version-control system as returned by `projectile-project-vcs'." (when globs - (cond - ;; `fd' takes repeated `--exclude' globs. Checked before VCS because - ;; git projects use fd too when `projectile-git-use-fd' is on. - ((projectile--fd-command-p command) - (mapconcat (lambda (glob) - (concat "-E " (shell-quote-argument - (projectile--alien-exclude-glob glob 'fd)))) - globs " ")) - ;; `git ls-files' takes exclude pathspecs. A pathspec list made up - ;; entirely of exclusions still lists everything else, so there's no - ;; need to add a positive pathspec alongside them. - ((eq vcs 'git) - (concat "-- " - (mapconcat (lambda (glob) - (shell-quote-argument - (concat ":(exclude,glob)" - (projectile--alien-exclude-glob glob 'git)))) - globs " "))) - (t nil)))) + (pcase (projectile--alien-exclude-style vcs command) + ;; `fd' takes repeated `--exclude' globs. + ('fd + (mapconcat (lambda (glob) + (concat "-E " (shell-quote-argument + (projectile--alien-exclude-glob glob 'fd)))) + globs " ")) + ;; `git ls-files' takes exclude pathspecs. A pathspec list made up + ;; entirely of exclusions still lists everything else, so there's no + ;; need to add a positive pathspec alongside them. + ('git + (concat "-- " + (mapconcat (lambda (glob) + (shell-quote-argument + (concat ":(exclude,glob)" + (projectile--alien-exclude-glob glob 'git)))) + globs " ")))))) (defun projectile--alien-ext-command (vcs directory) "Return the external listing command for DIRECTORY, honoring ignore rules. @@ -3530,7 +3553,7 @@ nor `fd --exclude' has a way to un-exclude a path), so such a project is filtered in Lisp, where the ensure patterns can rescue the files the ignore patterns matched. DIRECTORY is the project root the ensure entries are read from; it defaults to `default-directory'." - (and (or (projectile--fd-command-p command) (eq vcs 'git)) + (and (projectile--alien-exclude-style vcs command) (null (projectile--ensure-patterns directory)) t)) @@ -3873,11 +3896,11 @@ would be taken as the search pattern, and `fd' 9+ additionally rejects for `fd' commands we drop `--strip-cwd-prefix' (Projectile strips the `./' prefix from the output anyway) and pass the directories via `--search-path', which is unambiguous regardless of whether the command -already carries a search pattern. `fd' commands are recognised by the -`--strip-cwd-prefix' flag Projectile puts in its default `fd' recipes." +already carries a search pattern. `fd' commands are recognised by +`projectile--fd-command-p'." (if (not pathspecs) command - (if (string-match-p "--strip-cwd-prefix\\b" command) + (if (projectile--fd-command-p command) (concat (projectile--strip-fd-cwd-prefix-flag command) " " (mapconcat (lambda (path) (concat "--search-path " (shell-quote-argument path))) diff --git a/test/projectile-indexing-test.el b/test/projectile-indexing-test.el index b73217cae..3c9159102 100644 --- a/test/projectile-indexing-test.el +++ b/test/projectile-indexing-test.el @@ -551,7 +551,10 @@ :to-equal "fd . -0 --type f --color=never --search-path src/")) (it "handles fd's --strip-cwd-prefix= value form" (expect (projectile--ext-command-line "fd . --strip-cwd-prefix=always" '("src/")) - :to-equal "fd . --search-path src/"))) + :to-equal "fd . --search-path src/")) + (it "passes fd dirs via --search-path when the command lacks --strip-cwd-prefix (#2187)" + (expect (projectile--ext-command-line "fd -H -0 -E .git -tf -c never" '("src/")) + :to-equal "fd -H -0 -E .git -tf -c never --search-path src/"))) (describe "projectile-files-via-ext-command" (it "returns nil when command is nil or empty" @@ -1070,6 +1073,20 @@ (expect (projectile--alien-command-excludes-p 'git projectile-git-command) :to-be-truthy)) + (it "tells the tool apart by the program the command runs (#2187)" + ;; `projectile-git-fd-args' customised without `--strip-cwd-prefix' + ;; used to be taken for `git ls-files' and handed pathspecs + (expect (projectile--alien-exclude-style 'git "fd -H -0 -E .git -tf -c never") + :to-be 'fd) + (expect (projectile--alien-exclude-style 'none "/usr/bin/fdfind . -0 --type f") + :to-be 'fd) + (expect (projectile--alien-exclude-style 'git "git.exe ls-files -zco") + :to-be 'git) + ;; anything else set as `projectile-git-command' is filtered in Lisp + (expect (projectile--alien-exclude-style 'git "rg --files --null") :to-be nil) + (expect (projectile--alien-command-excludes-p 'git "rg --files --null") + :to-be nil)) + (it "declines for a project with dirconfig ensure entries" ;; an exclusion argument can't be taken back, so a project with `!' ;; entries has to be filtered in Lisp, where they can rescue files