bbatsov / projectile

Project Interaction Library for Emacs
https://docs.projectile.mx
GNU General Public License v3.0
3.97k stars 584 forks source link

Projectile fails to find project-level marker file when path not readable until root #1816

Open pascalfleury opened 1 year ago

pascalfleury commented 1 year ago

Context: this happening eg. on TermUX, but I confirmed it on a Linux system when the project is in a directory that is only executable.

Expected behavior

Pressing 's-p f' (projectile-find-file) will fail to resolve files.

Actual behavior

Should be the same behavior as when there is no file until root, or some fallback when no project file is found.

Steps to reproduce the problem

Create a dir, eg. /home/$USER/TEMP/hidden/my_projects/ create a file in there, eg. /home/$USER/TEMP/hidden/my_projects/config.sh then

chmod 111 /home/$USER/TEMP/hidden

so that one can go into the dir, but locked cannot be listed. Then try to use projectil-find-file in that dir. It should fail like this:

Debugger entered--Lisp error: (file-error "Opening directory" "Permission denied" "/home/fleury/TEMP/hidden")
  directory-files("/home/fleury/TEMP/hidden/" nil "\\`[^\0][^\0]*\\.sln\\'")
  file-expand-wildcards("/home/fleury/TEMP/hidden/?*.sln")
  projectile-expand-file-name-wildcard("?*.sln" "~/TEMP/hidden/")
  #f(compiled-function (f) #<bytecode -0x35b48e202190b>)("?*.sln")
  cl--position(nil ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...) 0 nil nil)
  cl-position(nil ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...) :if #f(compiled-function (f) #<bytecode -0x35b48e202190b>))
  apply(cl-position nil ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...) (:if #f(compiled-function (f) #<bytecode -0x35b48e202190b>)))
  cl-find(nil ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...) :if #f(compiled-function (f) #<bytecode -0x35b48e202190b>))
  apply(cl-find nil ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...) :if #f(compiled-function (f) #<bytecode -0x35b48e202190b>) nil)
  cl-find-if(#f(compiled-function (f) #<bytecode -0x35b48e202190b>) ("dune-project" "pubspec.yaml" "info.rkt" "Cargo.toml" "stack.yaml" "DESCRIPTION" "Eldev" "Cask" "shard.yml" "Gemfile" ".bloop" "deps.edn" "build.boot" "project.clj" "build.sc" "build.sbt" "application.yml" "gradlew" "build.gradle" "pom.xml" "poetry.lock" "Pipfile" "tox.ini" "setup.py" "requirements.txt" "manage.py" "angular.json" "package.json" "gulpfile.js" "Gruntfile.js" "mix.exs" "rebar.config" "composer.json" "Taskfile.yml" "CMakeLists.txt" "GNUMakefile" "Makefile" "debian/control" "WORKSPACE" "flake.nix" "default.nix" "meson.build" "SConstruct" "?*.sln" "?*.fsproj" "?*.csproj" "GTAGS" "TAGS" "configure.ac" "configure.in" ...))
  #f(compiled-function (dir) #<bytecode -0x1af6462017d34c78>)("~/TEMP/hidden/")
  projectile-locate-dominating-file("/home/fleury/TEMP/hidden/OrgFiles-priv/" #f(compiled-function (dir) #<bytecode -0x1af6462017d34c78>))
  projectile-root-top-down("/home/fleury/TEMP/hidden/my_project/")
  #f(compiled-function (func) #<bytecode -0x1a666cea8f11cd91>)(projectile-root-top-down)
  cl-some(#f(compiled-function (func) #<bytecode -0x1a666cea8f11cd91>) (projectile-root-local projectile-root-marked projectile-root-bottom-up projectile-root-top-down projectile-root-top-down-recurring))
  projectile-project-root()
  projectile-project-name()
  projectile-default-mode-line()
  projectile-update-mode-line()
  projectile-find-file-hook-function()
  run-hooks(find-file-hook)
  after-find-file(nil t)
  find-file-noselect-1(#<buffer theater.org> "~/TEMP/hidden/my_project/readme.org" nil nil "~/TEMP/hidden/my_project/readme.org" (18482647 65025))
  find-file-noselect("/home/fleury/TEMP/hidden/my_project/readme.org" nil nil nil)
  find-file("/home/fleury/TEMP/hidden/my_project/readme.org")
  dired--find-file(find-file "/home/fleury/TEMP/hidden/my_project/readme.org")
  dired--find-possibly-alternative-file("/home/fleury/TEMP/hidden/my_project/readme.org")
  dired-find-file()
  funcall-interactively(dired-find-file)
  #<subr call-interactively>(dired-find-file nil nil)
  apply(#<subr call-interactively> dired-find-file (nil nil))
  call-interactively@ido-cr+-record-current-command(#<subr call-interactively> dired-find-file nil nil)
  apply(call-interactively@ido-cr+-record-current-command #<subr call-interactively> (dired-find-file nil nil))
  call-interactively(dired-find-file nil nil)
  command-execute(dired-find-file)

I get the wanted behavior in adding ignore-errors in this function:

(defun projectile-expand-file-name-wildcard (name-pattern dir)
  "...."
  (let ((expanded (expand-file-name name-pattern dir)))
    (or (if (string-match-p "[[*?]" name-pattern)
            (car
             (ignore-errors  ;;  <===== I added this
               (file-expand-wildcards expanded))))
        expanded)))

Environment & Version information

Projectile version information

Projectile version: 20221122.2032
     Commit: 14beeaee7a77601aee4d4982811f6a27f696403c

Emacs version

GNU Emacs 28.1 (build 1, x86_64-pc-linux-gnu, GTK+ Version 3.24.34, cairo version 1.16.0) of 2022-12-02, modified by Debian

Operating system

Linux Debian

sleep-walker commented 11 months ago

I have exactly the same problem when opening /tmp/user/1000/test when permissions looks like this

drwx--x--x 3 root  root  60 Oct  5 19:33 /tmp/user
drwx------ 2 tcech users 40 Oct  5 19:36 /tmp/user/1000
amake commented 6 months ago

This is easy to trigger on macOS when you have opened a file in a restricted directory such as iCloud Drive (you can open a specific iCloud Drive file in Emacs, but Emacs can't enumerate the content of the ~/Library/Mobile Documents/com~apple~CloudDocs/Docs directory).

I am working around this with the following advice:

(defun my-safe-projectile-expand-file-name-wildcard (old-func &rest args)
  "Swallow errors and return the same fallback as original function."
  (condition-case nil
      (apply old-func args)
    (file-error (expand-file-name (car args) (cadr args)))))
(advice-add #'projectile-expand-file-name-wildcard :around #'my-safe-projectile-expand-file-name-wildcard)