branch: elpa/projectile
commit d7d5e932d22f43dafb588073799d8b9e2dbff9a1
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>
Restore git-root-wins as the default project root
Two recent commits made every registered language manifest a bottom-up
root marker, so the innermost manifest beat an enclosing VC root in
polyglot or monorepo layouts. That inverts the long-standing default
most users and IDEs expect (the git repository is the project) and
misfires for the common homogeneous multi-module repo: pom.xml,
build.gradle, package.json, Cargo.toml, CMakeLists.txt and friends
routinely recur in every module, so a buffer deep in a reactor would
report the module dir rather than the repo root.
Revert to seeding projectile-project-root-files-bottom-up with VCS
markers only. Manifests remain top-down markers (the fallback after
the bottom-up search), as before. Polyglot users who do want a deeper
subproject to win already have a clean, explicit escape hatch: a
.projectile file, which projectile-root-marked honors ahead of the
bottom-up search.
Drops the projectile-non-root-manifest-files denylist that only existed
to prune the over-broad bottom-up seeding.
---
CHANGELOG.md | 1 -
doc/modules/ROOT/pages/projects.adoc | 32 +++++++++++++----------------
projectile.el | 39 +++++++++---------------------------
test/projectile-test.el | 20 ++++++++++++++----
4 files changed, 39 insertions(+), 53 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1149c30cdb..ac8a16ed1b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,7 +4,6 @@
### Changes
-* `projectile-register-project-type` now appends each type's `:project-file`
to `projectile-project-root-files-bottom-up` (in addition to the existing
top-down list), so a deeper language manifest beats an enclosing VC root in
polyglot or monorepo layouts. Filenames listed in the new
`projectile-non-root-manifest-files` defconst (Makefile, GNUMakefile,
application.yml, manage.py, requirements.txt, gradlew) and wildcard patterns
(`?*.csproj`, etc.) are skipped, since they either legitimat [...]
* `projectile-get-immediate-sub-projects` skips the `git submodule foreach`
shell-out for git projects with no `.gitmodules` file anywhere up the parent
chain. Hot path for monorepos that index the project root often.
* `projectile-discover-projects-in-directory` now uses
`directory-files-no-dot-files-regexp` to skip `.` and `..` at the C level
instead of doing the post-filter in Elisp - matches the indexing walker.
* Document the anchored vs `*`-prefixed semantics of
`projectile-globally-ignored-directories`, the `find` fallback's lack of common
directory exclusions when `fd` isn't available, and how `fd`/`git ls-files`
handle deleted-but-unstaged files differently.
diff --git a/doc/modules/ROOT/pages/projects.adoc
b/doc/modules/ROOT/pages/projects.adoc
index 55a3e75c1e..404cbd1ab2 100644
--- a/doc/modules/ROOT/pages/projects.adoc
+++ b/doc/modules/ROOT/pages/projects.adoc
@@ -754,29 +754,25 @@ precedence with respect to project detection. Let's
examine the defaults:
discovery logic.
* `projectile-root-bottom-up` will start looking for a project marker
- file/folder (e.g. `.projectile`, `.hg`, `.git`, `deps.edn`,
- `Cargo.toml`, `pyproject.toml`) from the current folder
+ file/folder (e.g. `.projectile`, `.hg`, `.git`) from the current folder
(a.k.a. `default-directory` in Emacs lingo) up the directory tree. It will
return the first match it discovers. The bottom-most (closest to the
- current dir) match wins, so in a polyglot or monorepo layout where a
- `.git` sits at the top and a language manifest lives in a subdirectory,
- the language subdirectory wins. The seed list
- (`projectile-project-root-files-bottom-up`) contains only VCS markers;
- per-language manifests are appended automatically by
- `projectile-register-project-type` from each type's `:project-file`
- value, with two exclusions: filenames listed in
- `projectile-non-root-manifest-files` (e.g. `Makefile`,
- `application.yml`, `requirements.txt`, `manage.py`, `gradlew`), which
- legitimately appear at multiple levels of a single project, and
- wildcard patterns (e.g. `?*.csproj`), which the bottom-up search does
- not expand.
+ current dir) match wins. The list it searches
+ (`projectile-project-root-files-bottom-up`) holds only VCS markers, so in
+ a monorepo layout where a `.git` sits at the top and a language manifest
+ lives in a subdirectory, the enclosing repository wins - the git repo is
+ the project, matching what most users and IDEs expect. If you instead want
+ a deeper subproject to win, drop a `.projectile` file in it;
+ `projectile-root-marked` runs before this step, so the marked subproject
+ takes precedence over the outer VC root.
* `projectile-root-top-down` is similar, but it will return the top-most
(farthest from the current directory) match. It is configurable via
- `projectile-project-root-files`, also populated automatically from
- `projectile-register-project-type`. It is consulted after bottom-up
- fails, so today it primarily covers project types whose marker is a
- wildcard pattern (e.g. `?*.csproj`, `?*.sln`).
+ `projectile-project-root-files`, which is populated automatically from the
+ project types registered via `projectile-register-project-type`. It is
+ consulted after the bottom-up search fails, so it's where the per-language
+ manifests (`deps.edn`, `Cargo.toml`, `pom.xml`, `?*.csproj`, ...) actually
+ get matched.
* `projectile-root-top-down-recurring` will look for project markers that can
appear at every level of a project (e.g. `Makefile` or `.svn`) and will
return
diff --git a/projectile.el b/projectile.el
index e726ec63e2..338d371dc5 100644
--- a/projectile.el
+++ b/projectile.el
@@ -357,29 +357,17 @@ See `projectile-register-project-type'."
"A list of files considered to mark the root of a project.
The bottommost (parentmost) match has precedence.
-This seed list contains only VCS markers; per-language project
-manifests (e.g. `deps.edn', `Cargo.toml', `pom.xml') are appended at
-load time by `projectile-register-project-type' from each type's
-`:project-file' value, so the closer language subproject wins over an
-enclosing VC root in a polyglot or monorepo layout.
-
-Filenames listed in `projectile-non-root-manifest-files' and wildcard
-patterns are *not* auto-added here; they remain top-down only."
+This list holds only VCS markers (plus whatever you add yourself).
+Per-language project manifests are deliberately *not* included, so an
+enclosing VC root wins over a manifest sitting in a subdirectory - the
+behavior most users and IDEs expect (the git repository is the
+project). In a polyglot or monorepo layout where you want a deeper
+subproject to win instead, drop a `.projectile' file in it;
+`projectile-root-marked' runs before `projectile-root-bottom-up', so
+the marked subproject takes precedence over the outer VC root."
:group 'projectile
:type '(repeat string))
-(defconst projectile-non-root-manifest-files
- '("Makefile"
- "GNUMakefile"
- "application.yml" ; Spring Boot config, found in many resource dirs
- "manage.py" ; Django, can appear in subapps
- "requirements.txt" ; Python, often present in multiple subdirs
- "gradlew") ; Gradle wrapper; only the top-level one matters
- "Manifest filenames that legitimately appear at multiple levels of a
-single project, so they must not be treated as bottom-up root markers.
-`projectile-register-project-type' skips these when auto-populating
-`projectile-project-root-files-bottom-up'.")
-
(defcustom projectile-project-root-files-top-down-recurring
'(".svn" ; Svn VCS root dir
"CVS" ; CVS VCS root dir
@@ -3623,16 +3611,7 @@ files such as test/impl/other files as below:
(list project-file))))
(dolist (project-file project-files)
(when (and project-file (not (member project-file
projectile-project-root-files)))
- (add-to-list 'projectile-project-root-files project-file))
- ;; Also seed the bottom-up list so a deeper project manifest beats
- ;; an outer VC root. Skip wildcards (the bottom-up search uses
- ;; `file-exists-p' rather than glob expansion) and filenames known
- ;; to legitimately recur in non-root subdirectories.
- (when (and project-file
- (not (string-match-p "[*?]" project-file))
- (not (member project-file projectile-non-root-manifest-files))
- (not (member project-file
projectile-project-root-files-bottom-up)))
- (add-to-list 'projectile-project-root-files-bottom-up project-file t)))
+ (add-to-list 'projectile-project-root-files project-file)))
(when test-suffix
(plist-put project-plist 'test-suffix test-suffix))
(when test-prefix
diff --git a/test/projectile-test.el b/test/projectile-test.el
index 139f856407..5bbc0ed81e 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -1633,10 +1633,10 @@ by `projectile-files-via-ext-command')."
(expect (projectile-root-bottom-up "worktree/src/" '(".git"))
:to-equal
(expand-file-name "worktree/")))))
- (it "prefers a nearer project manifest over an outer VC root via the default
list"
- ;; Regression: in a polyglot/monorepo layout (`.git' at the top, a
- ;; language manifest deeper down),
`projectile-project-root-files-bottom-up'
- ;; ships the common manifests, so the closer subproject root wins.
+ (it "lets an outer VC root win over a nearer manifest by default"
+ ;; The default bottom-up list is VCS markers only, so in a monorepo
+ ;; layout (`.git' at the top, a language manifest deeper down) the
+ ;; enclosing repository wins - the git repo is the project.
(projectile-test-with-sandbox
(projectile-test-with-files
("monorepo/.git/"
@@ -1646,6 +1646,18 @@ by `projectile-files-via-ext-command')."
"monorepo/clj/src/"
projectile-project-root-files-bottom-up)
:to-equal
+ (expand-file-name "monorepo/")))))
+ (it "lets a .projectile-marked subproject win over an outer VC root"
+ ;; The escape hatch for polyglot/monorepo layouts: drop a
+ ;; `.projectile' in the subproject and `projectile-root-marked'
+ ;; (which runs before the bottom-up search) anchors the root there.
+ (projectile-test-with-sandbox
+ (projectile-test-with-files
+ ("monorepo/.git/"
+ "monorepo/clj/.projectile"
+ "monorepo/clj/src/foo.clj")
+ (expect (projectile-root-marked "monorepo/clj/src/")
+ :to-equal
(expand-file-name "monorepo/clj/"))))))
(describe "projectile-root-marked"