branch: elpa/projectile
commit 83ee3f34b630b06f3bcd58f519dea203f8925012
Author: Bozhidar Batsov <[email protected]>
Commit: Bozhidar Batsov <[email protected]>

    Fix projectile-root-top-down to search top-down, not bottom-up (#1729)
    
    projectile-root-top-down used projectile-locate-dominating-file which
    stops at the first (bottommost) match going up the directory tree.
    This made it behave identically to projectile-root-bottom-up.
    
    Add projectile-locate-dominating-file-top-down which walks the entire
    directory hierarchy and returns the topmost match.  For example, with
    nested Makefiles at both project/ and project/subdir/, searching from
    project/subdir/ now correctly returns project/ instead of
    project/subdir/.
---
 CHANGELOG.md            |  1 +
 projectile.el           | 23 ++++++++++++++++++++++-
 test/projectile-test.el | 11 +++++++++++
 3 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a3a850e47..930f5a1967 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
 
 * [#1748](https://github.com/bbatsov/projectile/issues/1748): Fix 
`projectile-replace` falling back to the legacy Emacs 25/26 code path on Emacs 
27+ because `fileloop` was not loaded.
 * [#1741](https://github.com/bbatsov/projectile/issues/1741): Fix 
`projectile-replace` treating the search string as a regexp instead of a 
literal string on Emacs 27+.
+* [#1729](https://github.com/bbatsov/projectile/issues/1729): Fix 
`projectile-root-top-down` to actually return the topmost matching project root 
instead of the bottommost.
 * [#1596](https://github.com/bbatsov/projectile/issues/1596): 
`projectile-find-dir` now includes intermediate directories that contain only 
subdirectories (e.g. `src/` when it only has `src/ComponentA/`, 
`src/ComponentB/`).
 * [#1551](https://github.com/bbatsov/projectile/issues/1551): Don't add 
nonexistent files to the project cache (e.g. when visiting a new file with 
`find-file` and then abandoning the buffer).
 * [#1554](https://github.com/bbatsov/projectile/issues/1554): Fix 
`projectile-files-with-string` failing on special characters when using `grep` 
or `git-grep` by adding the `-F` (fixed-string) flag.
diff --git a/projectile.el b/projectile.el
index 407fadee96..0098b2c656 100644
--- a/projectile.el
+++ b/projectile.el
@@ -1312,6 +1312,27 @@ which we're looking."
              (setq file nil))))
     (and root (expand-file-name (file-name-as-directory root)))))
 
+(defun projectile-locate-dominating-file-top-down (file name)
+  "Look up the directory hierarchy from FILE for a directory containing NAME.
+Unlike `projectile-locate-dominating-file' which returns the first (bottommost)
+match, this returns the topmost match.  Return nil if not found.
+Instead of a string, NAME can also be a predicate taking one argument
+\(a directory) and returning a non-nil value if that directory is the one for
+which we're looking."
+  (setq file (abbreviate-file-name file))
+  (let ((root nil)
+        try)
+    (while (not (or (null file)
+                    (string-match locate-dominating-stop-dir-regexp file)))
+      (setq try (if (stringp name)
+                    (projectile-file-exists-p 
(projectile-expand-file-name-wildcard name file))
+                  (funcall name file)))
+      (when try (setq root file))
+      (if (equal file (setq file (file-name-directory
+                                   (directory-file-name file))))
+          (setq file nil)))
+    (and root (expand-file-name (file-name-as-directory root)))))
+
 (defvar-local projectile-project-root nil
   "Defines a custom Projectile project root.
 This is intended to be used as a file local variable.")
@@ -1324,7 +1345,7 @@ This is intended to be used as a file local variable.")
   "Identify a project root in DIR by top-down search for files in LIST.
 If LIST is nil, use `projectile-project-root-files' instead.
 Return the first (topmost) matched directory or nil if not found."
-  (projectile-locate-dominating-file
+  (projectile-locate-dominating-file-top-down
    dir
    (lambda (dir)
      (cl-find-if (lambda (f)
diff --git a/test/projectile-test.el b/test/projectile-test.el
index 2d828bd254..2c97762e7c 100644
--- a/test/projectile-test.el
+++ b/test/projectile-test.el
@@ -682,6 +682,8 @@ Just delegates OPERATION and ARGS for all operations except 
for`shell-command`'.
        "projectA/src/framework/lib/"
        "projectA/src/framework.conf"
        "projectA/src/html/index.html")
+      ;; .git is a directory, so it's not matched by top-down (file markers 
only);
+      ;; framework.conf at projectA/src/ is the only match
       (expect (projectile-root-top-down "projectA/src/framework/lib" 
'("framework.conf" ".git"))
               :to-equal
               (expand-file-name "projectA/src/"))
@@ -691,6 +693,15 @@ Just delegates OPERATION and ARGS for all operations 
except for`shell-command`'.
       (expect (projectile-root-top-down "projectA/src/html/" '("index.html"))
               :to-equal
               (expand-file-name "projectA/src/html/")))))
+  (it "returns the topmost match when file markers exist at multiple levels"
+    (projectile-test-with-sandbox
+     (projectile-test-with-files
+      ("project/Makefile"
+       "project/subdir/Makefile"
+       "project/subdir/file.txt")
+      (expect (projectile-root-top-down "project/subdir" '("Makefile"))
+              :to-equal
+              (expand-file-name "project/")))))
   (it "does not match directories for file-type markers"
     (projectile-test-with-sandbox
      (projectile-test-with-files

Reply via email to