Subject: bug#66649: 29.1; `project-remember-projects-under' behavior doesn't match its doc

From: Philip Kaludercic <[email-masked]>
Date: Wed, 01 Nov 2023 13:12:27 +0000

Dmitry Gutov <[email-masked]> writes:

> Hi Damien, thanks for the report.
>
> On 20/10/2023 14:48, Damien Cassou wrote:
>> the documentation of `project-remember-projects-under' is:
>>        "Index all projects below a directory DIR.  If RECURSIVE is
>>      non-nil, recurse into all subdirectories to find more projects.
>>      After finishing, a message is printed summarizing the progress.  The
>>      function returns the number of detected projects."
>> Regardless of the value of RECURSIVE, I understand from the above
>> that
>> all child directories of the DIR argument will be investigated. The doc
>> doesn't say anything about investigating if DIR is itself a project or
>> not so I think it would make sense if the function wasn't.
>> But the code says otherwise (as far as I understand it):
>> (defun project-remember-projects-under (dir &optional recursive)
>>    (let ((queue (list dir)))
>>      ;; …
>>      (while queue
>>        (when-let ((subdir (pop queue))
>>                   ((file-directory-p subdir)))
>>          ;; maybe register `subdir' as a project
>>          ;; …
>>          (when (and recursive (file-directory-p subdir))
>>            (setq queue (nconc (directory-files subdir …) queue)))))))
>> The code above seems to investigate DIR first and, if RECURSIVE is
>> non-nil, look at the directories below it.
>> Also, the second check (file-directory-p subdir) seems unnecessary
>> because of the first one.
>> There is a part of the code I don't understand:
>>    (unless (eq recursive 'in-progress)
>> It seems nowhere in the code nor in the documentation do we say
>> anything
>> about this 'in-progress special value. Is it a left over from a previous
>> (recursive) version of the algorithm?
>
> Philip, could you look into this?

One idea would be to just simplify the entire implementation by relying
on directory-files and directory-files-recursively.  Say something like
this:

diff --git a/lisp/progmodes/project.el b/lisp/progmodes/project.el
index fda1081eb62..e0d5e706e82 100644
--- a/lisp/progmodes/project.el
+++ b/lisp/progmodes/project.el
@@ -1821,35 +1821,28 @@ project-remember-projects-under
 projects."
   (interactive "DDirectory: \nP")
   (project--ensure-read-project-list)
-  (let ((queue (list dir))
-        (count 0)
-        (known (make-hash-table
-                :size (* 2 (length project--list))
-                :test #'equal )))
+  (let ((dirs (if recursive
+                  (directory-files-recursively dir "" t #'file-directory-p)
+                (cl-delete-if-not #'file-directory-p (directory-files dir t))))
+        (known (make-hash-table :size (* 2 (length project--list))
+                                :test #'equal))
+        (count 0))
     (dolist (project (mapcar #'car project--list))
       (puthash project t known))
-    (while queue
-      (when-let ((subdir (pop queue))
-                 ((file-directory-p subdir)))
-        (when-let ((project (project--find-in-directory subdir))
-                   (project-root (project-root project))
-                   ((not (gethash project-root known))))
-          (project-remember-project project t)
-          (puthash project-root t known)
-          (message "Found %s..." project-root)
-          (setq count (1+ count)))
-        (when (and recursive (file-directory-p subdir))
-          (setq queue
-                (nconc
-                 (directory-files
-                  subdir t directory-files-no-dot-files-regexp t)
-                 queue)))))
-    (unless (eq recursive 'in-progress)
-      (if (zerop count)
-          (message "No projects were found")
-        (project--write-project-list)
-        (message "%d project%s were found"
-                 count (if (= count 1) "" "s"))))
+    (dolist (subdir dirs)
+      (when-let (((file-directory-p subdir))
+                 (project (project--find-in-directory subdir))
+                 (project-root (project-root project))
+                 ((not (gethash project-root known))))
+        (project-remember-project project t)
+        (puthash project-root t known)
+        (message "Found %s..." project-root)
+        (setq count (1+ count))))
+    (if (zerop count)
+        (message "No projects were found")
+      (project--write-project-list)
+      (message "%d project%s were found"
+               count (if (= count 1) "" "s")))
     count))
 
 (defun project-forget-zombie-projects ()