Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
79 changes: 51 additions & 28 deletions projectile.el
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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)))
Expand Down
19 changes: 18 additions & 1 deletion test/projectile-indexing-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,10 @@
:to-equal "fd . -0 --type f --color=never --search-path src/"))
(it "handles fd's --strip-cwd-prefix=<when> 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"
Expand Down Expand Up @@ -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
Expand Down
Loading