branch: externals/vc-jj
commit 290cd89ad88e89d4dba7812ef121153b5d829870
Author: Rudi Schlatte <r...@constantly.at>
Commit: Rudi Schlatte <r...@constantly.at>

    Try to make vc-dir more performant
    
    Behavior change: do the same as vc-git and only display files in the
    directory we're asked for.  This is less useful than what we used to
    have, but potentially more performant.
---
 vc-jj-tests.el |  41 +++++++++++++++++++++--
 vc-jj.el       | 101 ++++++++++++++++++++++++++++++++++-----------------------
 2 files changed, 98 insertions(+), 44 deletions(-)

diff --git a/vc-jj-tests.el b/vc-jj-tests.el
index ccfed7c0b1..63ee7df26d 100644
--- a/vc-jj-tests.el
+++ b/vc-jj-tests.el
@@ -73,12 +73,19 @@ is needed."
        (let ((process-environment
               (append (vc-jj-test-environment 0) process-environment)))
          (vc-create-repo 'jj))
-       ,@body)))
+       ;; On macOS, the generated filename "/var/folders/..." was in
+       ;; reality "/private/var/folders/...", which got unfolded by
+       ;; `vc-jj-root' within some tests -- do this here already
+       (let ((,name (vc-jj-root ,name))
+             (default-directory ,name))
+         ,@body))))
 
 (ert-deftest vc-jj-test-add-file ()
   (vc-jj-test-with-repo repo
     (write-region "New file" nil "README")
-    (should (eq (vc-state "README" 'jj) 'added))))
+    (should (eq (vc-state "README" 'jj) 'added))
+    (should (equal (vc-jj-dir-status-files repo nil (lambda (x y) x))
+                   '(("README" added))))))
 
 (ert-deftest vc-jj-test-added-tracked ()
   (vc-jj-test-with-repo repo
@@ -86,7 +93,9 @@ is needed."
     (vc-jj-checkin '("first-file") "First commit")
     (write-region "In second commit" nil "second-file")
     (should (eq (vc-jj-state "second-file") 'added))
-    (should (eq (vc-jj-state "first-file") 'up-to-date))))
+    (should (eq (vc-jj-state "first-file") 'up-to-date))
+    (should (equal (vc-jj-dir-status-files repo nil (lambda (x y) x))
+                   '(("second-file" added) ("first-file" up-to-date))))))
 
 (ert-deftest vc-jj-test-conflict ()
   (vc-jj-test-with-repo repo
@@ -149,5 +158,31 @@ is needed."
     (should (eq (vc-jj-state "root-ignored.txt") 'added))
     (should (eq (vc-jj-state "subdir/subdir-ignored.txt") 'added))))
 
+(ert-deftest vc-jj-list-files ()
+  (vc-jj-test-with-repo repo
+    (let (branch-1 branch-2 branch-merged)
+      ;; the root change id is always zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+      (shell-command "jj new zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz")
+      (write-region "Unconflicted" nil "unconflicted.txt")
+      (write-region "Branch 1" nil "conflicted.txt")
+      (make-directory "subdir")
+      (write-region "Branch 1" nil "subdir/conflicted.txt")
+      (setq branch-1 (vc-jj-working-revision "unconflicted.txt"))
+      (shell-command "jj new zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz")
+      (write-region "Unconflicted" nil "unconflicted.txt")
+      (write-region "Branch 2" nil "conflicted.txt")
+      (make-directory "subdir")
+      (write-region "Branch 2" nil "subdir/conflicted.txt")
+      (setq branch-2 (vc-jj-working-revision "unconflicted.txt"))
+      (shell-command (concat "jj new " branch-1 " " branch-2))
+      (write-region "Added" nil "added.txt")
+      (should (equal (vc-jj-dir-status-files repo nil (lambda (x y) x))
+                     '(("conflicted.txt" conflict)
+                       ("subdir/conflicted.txt" conflict)
+                       ("added.txt" added)
+                       ("unconflicted.txt" up-to-date))))
+      (should (equal (vc-jj-dir-status-files (expand-file-name "subdir/" repo) 
nil (lambda (x y) x))
+                     '(("conflicted.txt" conflict)))))))
+
 (provide 'vc-jj-tests)
 ;;; vc-jj-tests.el ends here
diff --git a/vc-jj.el b/vc-jj.el
index dc45932a98..475041193c 100644
--- a/vc-jj.el
+++ b/vc-jj.el
@@ -150,14 +150,13 @@ process object in the asynchronous case."
   ;; - "jj diff --summary FILE" gets us modified (output starts with
   ;;   "M ") or added (output starts with "A "), but no output could
   ;;   be conflicted, ignored or unchanged
-  (when-let* ((default-directory (vc-jj-root file))
-              ;; (relative (file-relative-name file)) ;; done by 
`vc-do-command'
-              (conflicted-ignored
-               (with-output-to-string
-                 (vc-jj-command standard-output 0 file "file" "list" "-T" 
"conflict" "--")))
-              (modified-added
-               (with-output-to-string (vc-jj-command standard-output 0 file
-                                                     "diff" "--summary" 
"--"))))
+  (let* ((default-directory (vc-jj-root file))
+         (conflicted-ignored
+          (with-output-to-string
+            (vc-jj-command standard-output 0 file "file" "list" "-T" 
"conflict" "--")))
+         (modified-added
+          (with-output-to-string (vc-jj-command standard-output 0 file
+                                                "diff" "--summary" "--"))))
     (cond
      ((string-empty-p conflicted-ignored) 'ignored)
      ((string= conflicted-ignored "true") 'conflict)
@@ -168,40 +167,60 @@ process object in the asynchronous case."
 
 (defun vc-jj-dir-status-files (dir _files update-function)
   "Calculate a list of (FILE STATE EXTRA) entries for DIR.
-The list is passed to UPDATE-FUNCTION."
-  ;; TODO: could be async!
+Return the result result of applying UPDATE-FUNCTION to that list."
+  ;; This function is specified below the STATE-QUERYING FUNCTIONS
+  ;; header in the comments at the beginning of vc.el.  The
+  ;; specification says the 'dir-status-files' backend function
+  ;; returns "a list of lists ... for FILES in DIR", which does not
+  ;; say anything about subdirectories.  We follow the example of
+  ;; 'vc-git' and return the state of files in subdirectories of DIR
+  ;; as well (except for ignored files, since we don't want to cons up
+  ;; a list of every file below DIR).
+  ;;
+  ;; Unfortunately this function needs to do a lot of work:
+  ;; - There is no single jj command that gives us all the info we
+  ;;   need, so we cannot run asynchronously.
+  ;; - jj prints filenames relative to the repository root, while we
+  ;;   need them relative to DIR.
+  ;;
+  ;; TODO: we should use hash tables, since we're doing a lot of set
+  ;; operations, which are slow on lists.
   (let* ((dir (expand-file-name dir))
-         ;; TODO: Instead of the two `mapcan' calls, it should be more
-         ;; efficient to write the output to a buffer and then search
-         ;; for lines beginning with A or M, pushing them into a list.
-         (changed-files (process-lines vc-jj-program "diff" "--summary" "--" 
dir))
-         (added (mapcan (lambda (file) (and (string-prefix-p "A " file)
-                                            (list (substring file 2))))
-                        changed-files))
-         (modified (mapcan (lambda (file) (and (string-prefix-p "M " file)
-                                               (list (substring file 2))))
-                           changed-files))
-         (files (mapcan (lambda (file) (list (substring file 2))) 
changed-files))
-         ;; The output of `jj resolve --list' is a list of file names
-         ;; plus a free-text conflict description per line -- rather
-         ;; than trying to be fancy and parsing each line (and getting
-         ;; bugs with file names with spaces), use `string-prefix-p'
-         ;; later.  Note that 'jj resolve' errors when there are no
-         ;; conflicts, which is harmless.
-         (conflicted (process-lines-ignore-status vc-jj-program "resolve" 
"--list")))
-    (let ((result
-           (mapcar
-            (lambda (file)
-              (let ((vc-state
-                     (cond ((seq-find
-                             (apply-partially #'string-prefix-p file) 
conflicted)
-                            'conflict)
-                           ((member file added) 'added)
-                           ((member file modified) 'edited)
-                           (t 'up-to-date))))
-                (list file vc-state)))
-            files)))
-      (funcall update-function result nil))))
+         (default-directory dir)
+         (project-root (vc-jj-root dir))
+         (registered-files (process-lines vc-jj-program "file" "list" "--" 
dir))
+         (ignored-files (seq-difference (cl-delete-if #'file-directory-p
+                                                      (directory-files dir nil 
nil t))
+                                        registered-files))
+         (changed (process-lines vc-jj-program "diff" "--summary" "--" dir))
+         (added-files (mapcan (lambda (entry)
+                                (and (string-prefix-p "A " entry)
+                                     (list (substring entry 2))))
+                              changed))
+         (modified-files (mapcan (lambda (entry)
+                                   (and (string-prefix-p "M " entry)
+                                        (list (substring entry 2))))
+                                 changed))
+         ;; The command below only prints conflicted files in DIR, but
+         ;; relative to project-root, hence the dance with
+         ;; expand-file-name / file-relative-name
+         (conflicted-files (mapcar (lambda (entry)
+                                     (file-relative-name (expand-file-name 
entry project-root) dir))
+                                   (process-lines vc-jj-program
+                                                  "file" "list"
+                                                  "-T" "if(conflict, path ++ 
\"\\n\")" "--" dir)))
+         (unchanged-files (cl-remove-if (lambda (entry) (or (member entry 
conflicted-files)
+                                                            (member entry 
modified-files)
+                                                            (member entry 
added-files)
+                                                            (member entry 
ignored-files)))
+                                        registered-files))
+         (result
+          (nconc (mapcar (lambda (entry) (list entry 'conflict)) 
conflicted-files)
+                 (mapcar (lambda (entry) (list entry 'added)) added-files)
+                 (mapcar (lambda (entry) (list entry 'edited)) modified-files)
+                 (mapcar (lambda (entry) (list entry 'ignored)) ignored-files)
+                 (mapcar (lambda (entry) (list entry 'up-to-date)) 
unchanged-files))))
+    (funcall update-function result nil)))
 
 (defun vc-jj-dir-extra-headers (dir)
   "Return extra headers for `vc-dir' when executed inside DIR.

Reply via email to