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"

Reply via email to