branch: externals/vc-jj
commit bb8918c837b56a8d794cb7eb707569b4aad8c394
Author: Kristoffer Balintona <[email protected]>
Commit: Kristoffer Balintona <[email protected]>

    refactor: Use new state-deducing helpers in `vc-jj-dir-status-files`
    
    Delegate state deduction logic to new state-deducing helper functions.
    This simplifies `vc-jj-dir-status-files` and improves its performance.
    
    Additionally, fixes #164.
---
 NEWS.org |   1 +
 vc-jj.el | 156 +++++++++++++++++++++++++++++++++++++--------------------------
 2 files changed, 94 insertions(+), 63 deletions(-)

diff --git a/NEWS.org b/NEWS.org
index 403d5adfcc..8230b6c05b 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -15,6 +15,7 @@
 *** Fixed
 
 - Fixed a bug where the contents of ​*vc-diff​* were not erased prior to 
invocations of ~vc-jj-diff~.
+- The vc-dir buffer no longer lists entries for up-to-date and ignored files, 
as per the VC specification.
 
 ** [[https://codeberg.org/emacs-jj-vc/vc-jj.el/compare/v0.4...v0.5][0.5]] - 
2025-12-21
 
diff --git a/vc-jj.el b/vc-jj.el
index bf5ed78502..9e699489e7 100644
--- a/vc-jj.el
+++ b/vc-jj.el
@@ -544,69 +544,99 @@ new files."
 
 ;;;; dir-status-file
 
-(defun vc-jj-dir-status-files (dir _files update-function)
-  "Calculate a list of (FILE STATE EXTRA) entries for DIR.
-Return the result of applying UPDATE-FUNCTION to that list.
-
-For a description of the states relevant to jj, see `vc-jj-state'."
-  ;; 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.
-  (with-demoted-errors "JJ error during `vc-dir-status-files': %S"
-    (let* ((dir (expand-file-name dir))
-           (default-directory dir)
-           (project-root (vc-jj-root dir))
-           (registered-files (vc-jj--process-lines dir "file" "list"))
-           (ignored-files (seq-difference (cl-delete-if #'file-directory-p
-                                                        (directory-files dir 
nil nil t))
-                                          registered-files))
-           (changed (vc-jj--process-lines dir "diff" "--summary"))
-           (added-files (mapcan (lambda (entry)
-                                  (and (string-prefix-p "A " entry)
-                                       (list (substring entry 2))))
-                                changed))
-           (removed-files (mapcan (lambda (entry)
-                                    (and (string-prefix-p "D " entry)
-                                         (list (substring entry 2))))
-                                  changed))
-           (edited-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))
-                                     (vc-jj--process-lines dir "file" "list"
-                                                           "-T" "if(conflict, 
path ++ '\n')")))
-           (up-to-date-files (cl-remove-if (lambda (entry) (or (member entry 
conflicted-files)
-                                                              (member entry 
edited-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 'removed)) 
removed-files)
-                   (mapcar (lambda (entry) (list entry 'edited)) edited-files)
-                   (mapcar (lambda (entry) (list entry 'ignored)) 
ignored-files)
-                   (mapcar (lambda (entry) (list entry 'up-to-date)) 
up-to-date-files))))
-      (funcall update-function result nil))))
+;; 2026-03-22(Kris B): As far as I've seen, DIR is always the
+;; repository root.  The dir-status-files specification in the
+;; preamble of vc.el does not make it clear, but this seems to be the
+;; case.
+(defun vc-jj-dir-status-files (root-or-subdir files update-function)
+  "Call UPDATE-FUNCTION on a computed list of entries for ROOT-OR-SUBDIR.
+Compute a list of entries for ROOT-OR-SUBDIR whose elements are of the
+form (FILE STATE EXTRA), where FILE is relative to ROOT-OR-SUBDIR, STATE
+is a VC state symbol.  Return the result of calling UPDATE-FUNCTION with
+that list as an argument.
+
+FILES is either nil or a list of files relative to ROOT-OR-SUBDIR.  If
+FILES is nil, return the state of all files that don't have the
+\\='up-to-date or \\='ignored states (i.e., only files in the \\='added,
+\\='removed, \\='edited, or \\='conflict states).  If FILES is non-nil,
+return the state of all FILES, regardless of their state.
+
+ROOT-OR-SUBDIR is the repository root or a subdirectory of the
+repository.
+
+For a description of the states relevant to Jujutsu, see the docstring
+of `vc-jj-state'."
+  (condition-case err
+      ;; A big consideration of this function is performance in large
+      ;; repositories: minimize the number of operations and loops
+      ;; over lists
+      (let* ((root (vc-jj-root root-or-subdir))
+             (default-directory root)
+             (file-diff-types-table
+              (vc-jj--parse-diff-types-file-table
+               (vc-jj--process-lines root-or-subdir "diff" "--types")))
+             (file-conflict-table
+              (vc-jj--parse-conflict-aware-file-table
+               (vc-jj--process-lines root-or-subdir "file" "list"
+                                     "-T" 
vc-jj--conflict-aware-file-list-template)))
+             (files-to-report
+              (if files
+                  ;; When FILES is non-nil, report on all FILES
+                  ;; regardless of state.
+                  (mapcar (lambda (f)
+                            ;; Make all file paths relative to ROOT
+                            ;; since the paths stored in
+                            ;; `vc-jj--deduce-state' (see below) are
+                            ;; relative to the project root
+                            (file-relative-name (file-name-concat 
root-or-subdir f) root))
+                          files)
+                ;; When FILES is nil, report only on files that are in
+                ;; the edited, added, removed, or conflict state
+                ;; (i.e., not the up-to-date or ignored states).
+                ;;
+                ;; Our strategy: we get all files in
+                ;; FILE-DIFF-TYPES-TABLE (files not in that table are
+                ;; ignored or up-to-date) plus files with conflicts
+                ;; that originate in an earlier revision (keys in
+                ;; FILE-CONFLICT-TABLE with a non-nil value).
+                (let (result)
+                  (maphash (lambda (k _) (push k result)) 
file-diff-types-table)
+                  (maphash (lambda (k v)
+                             (when (and v
+                                        ;; Don't push duplicates of
+                                        ;; conflicted files to RESULT.
+                                        ;; Doing it this way avoids
+                                        ;; having to de-duplicate
+                                        ;; RESULTS later by looping
+                                        ;; the list
+                                        (not (gethash k 
file-diff-types-table)))
+                               (push k result)))
+                           file-conflict-table)
+                  result)))
+             (result
+              (mapcar (lambda (root-rel-file)
+                        (let (;; The files reported should be relative
+                              ;; to ROOT-OR-SUBDIR
+                              (display-path (file-relative-name root-rel-file 
root-or-subdir))
+                              (state (vc-jj--deduce-state root-rel-file
+                                                          file-diff-types-table
+                                                          
file-conflict-table)))
+                          (list display-path state)))
+                      files-to-report)))
+        (funcall update-function result nil))
+    ;; FIXME 2026-03-24(Kris B): Is there a cleaner way to deal with
+    ;; repository corruption errors?  This solution seems a bit
+    ;; fragile and ad hoc...
+    ;;
+    ;; For errors related to repository corruption (jj emits an exit
+    ;; code of 255), report on no files and warn the user about a
+    ;; potential problem.  (See bug#63.) Signal other errors normally.
+    (error (if (string-match-p "exited with status 255" (error-message-string 
err))
+               (progn
+                 (warn "Vc-jj: jj failed, possibly due to a corrupted 
repository (%s)"
+                       (vc-jj-root root-or-subdir))
+                 (funcall update-function nil nil))
+             (signal (car err) (cdr err))))))
 
 ;;;; dir-extra-headers
 

Reply via email to