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

    refactor: Delegate VC state-deducing logic to helper functions
    
    `vc-jj--deduce-state` now does the heavy-lifting for deducing the
    state for a given file.  It takes two other arguments that provide all
    the information sufficient to deduce every state.  Those arguments can
    be constructed appropriately with our new helpers:
    `vc-jj--parse-diff-types-file-alist` and
    `vc-jj--parse-conflict-aware-file-alist`.
    
    `vc-jj-state` and similar functions are expected to rely on the new
    `vc-jj--deduce-state` from now on.
---
 vc-jj.el | 193 ++++++++++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 148 insertions(+), 45 deletions(-)

diff --git a/vc-jj.el b/vc-jj.el
index 0fb4c0845a..bf5ed78502 100644
--- a/vc-jj.el
+++ b/vc-jj.el
@@ -392,52 +392,155 @@ Return non-nil when FILE is file tracked by JJ and nil 
when not."
 
 ;;;; state
 
+(defconst vc-jj--conflict-aware-file-list-template
+  "if(self.conflict(), 'C', 'T') ++ ' ' ++ self.path() ++ '\n'"
+  "Template for outputting a file list with conflict information.
+This template is intended as the value of the \"-T\" argument passed to
+\"jj file list\".  It is meant to be used in conjunction with
+`vc-jj--parse-conflict-aware-file-table'.")
+
+(defun vc-jj--parse-conflict-aware-file-table (lines)
+  "Return a hash table of tracked files with information on conflicted state.
+The returned table maps FILE to CONFLICTEDP, where FILE is a tracked
+file and CONFLICTEDP is t if that file is in a conflicted state.  This
+table helps deduce whether a given file is tracked and whether it is
+conflicted.
+
+LINES is a list of strings, where each string is a line of the output of
+\"jj file list\" with a template of
+`vc-jj--conflict-aware-file-list-template', which shows a list of all
+tracked files, with each path prepended with \"C\" or \"T\" depending on
+its conflict state."
+  (let ((table (make-hash-table :test #'equal)))
+    (mapc (lambda (line)
+            (puthash (substring line 2) (eq ?C (aref line 0)) table))
+          lines)
+    table))
+
+(defun vc-jj--parse-diff-types-file-table (lines)
+  "Return a hash table of changed files with their diff types.
+The returned table maps FILE to TYPES, where FILE is a changed file and
+TYPES is a two-character string indicating the before and after types of
+that file.  See `vc-jj--deduce-state-from-diff-types' for which VC
+states can be deduced from this two-character string.
+
+LINES is a list of strings, where each string is a line of the output of
+\"jj diff --types\", which shows a list of all changed files, with each
+path prepended with a two-character type string indicating the before
+and after types of the file."
+  (let ((table (make-hash-table :test #'equal)))
+    (mapc (lambda (line)
+            (puthash (substring line 3) (substring line 0 2) table))
+          lines)
+    table))
+
+(defun vc-jj--deduce-state-from-diff-types (diff-types)
+  "Return the file state deduced from DIFF-TYPES.
+DIFF-TYPES is either nil or a two-character string at the beginning of
+each line in of \"jj diff --types\".  See \"jj diff --help\" for a list
+of all possible characters and their meanings.
+
+When DIFF-TYPES is nil, return nil.  When it is a two-character string,
+return a symbol representing the VC state deduced from DIFF-TYPES.  The
+possible state symbols returned are:
+- \\='conflict
+- \\='added
+- \\='removed
+- \\='edited
+See `vc-jj-state' for a list of all VC states relevant to Jujutsu.
+
+Please note that the conflict state is only partially deduced by this
+function: \"jj diff\" reports a conflict only when the conflict
+originates in the working copy, not if the conflict originates in an
+earlier revision.  To definitively determine the conflict state of a
+file, something like \"jj file list -T \\='self.conflict()\\='\" should
+be used (see `vc-jj--parse-conflict-aware-file-table'.)"
+  (when diff-types
+    (let ((before (aref diff-types 0))
+          (after (aref diff-types 1)))
+      (cond ((eq ?C after) 'conflict)
+            ((eq ?- before) 'added)
+            ((eq ?- after) 'removed)
+            ((and (memq before '(?F ?L ?G)) (memq after  '(?F ?L ?G)))
+             'edited)
+            (t (error "vc-jj: Unexpected diff types string: %s" 
diff-types))))))
+
+(defun vc-jj--deduce-state (file file-diff-types-table file-conflict-table)
+  "Deduce the VC state of FILE from already parsed jj output.
+Return a symbol corresponding to the VC state of FILE.  See
+`vc-jj-state' for the possible VC state symbols.
+
+FILE is a path relative to the repository root.  FILE-DIFF-TYPES-TABLE
+is a hash table produced by `vc-jj--parse-diff-types-file-table'.
+FILE-CONFLICT-TABLE is a hash table produced by
+`vc-jj--parse-conflict-aware-file-table'.  Together,
+FILE-DIFF-TYPES-TABLE and FILE-CONFLICT-TABLE suffice to deduce the VC
+state of FILE.
+
+Note that since FILE-DIFF-TYPES-TABLE should be produced by
+`vc-jj--parse-diff-types-file-table', FILE-DIFF-TYPES-TABLE will only
+contain file entries for files with changes in the working copy.  On the
+other hand, since FILE-CONFLICT-TABLE should be produced by
+`vc-jj--parse-conflict-aware-file-table', FILE-CONFLICT-TABLE will only
+contain file entries for tracked files.
+
+To avoid unnecessary computation, calls to
+`vc-jj--parse-diff-types-file-table' and
+`vc-jj--parse-conflict-aware-file-table' can be passed jj output
+specific to FILE.  As an example for
+`vc-jj--parse-diff-types-file-table',
+
+  (vc-jj--process-lines FILE \"diff\" \"--types\")"
+  ;; We use hash tables rather than e.g. alists for the sake of
+  ;; performance: table lookups are O(1) whereas list lookups are O(n)
+  (let* ((diff-types (gethash file file-diff-types-table))
+         (diff-types-state (vc-jj--deduce-state-from-diff-types diff-types)))
+    (or
+     ;; DIFF-TYPES-STATE definitely deduces the edited, removed, and
+     ;; added states, as well as the conflict state when the conflict
+     ;; originates in the working copy
+     diff-types-state
+     ;; When DIFF-TYPES-STATE is nil (i.e., FILE has no changes in the
+     ;; working copy), use `file-conflict-table' to distinguish
+     ;; between the ignored, up-to-date, and conflict states.  (The
+     ;; conflict state can only be caught by DIFF-TYPES-STATE when the
+     ;; conflict originates in the working copy; `file-conflict-table'
+     ;; catches when a conflict originates in an earlier revision.)
+     (let ((conflictp (gethash file file-conflict-table 'not-tracked)))
+       (cond ((eq conflictp 'not-tracked) 'ignored)
+             (conflictp 'conflict)
+             (t 'up-to-date))))))
+
 (defun vc-jj-state (file)
-  "JJ implementation of `vc-state' for FILE.
-There are several file states recognized by vc (see the docstring of
-`vc-state' for the full list).  Several of these are relevant to
-jujutsu.  They are:
-- Added (new file)
-- Removed (deleted file)
-- Edited (modified file)
-- Up-to-date (unmodified file)
-- Conflict (merge conflict)
-- Ignored (ignored by repository)
-
-Other VCS backends would also recognize the \"unregistered\" state, but
-there is no such state in jj since jj automatically registers new files."
-  ;; We can deduce all of the vc states listed above with two
-  ;; commands:
-  ;;
-  ;; - "jj diff --summary FILE" gets us the "added" state (when output
-  ;;   starts with "A "), the "removed" state (when output starts with
-  ;;   "D ") and "edited" state (when output starts with "M ").  No
-  ;;   output is also possible (this could mean the "conflict",
-  ;;   "ignored" or "unchanged" state), so we deduce these with the
-  ;;   next command:
-  ;;
-  ;; - "jj file list -T 'conflict' FILE" prints "true" when the file
-  ;;   is in "conflict" state, prints an empty string when the file is
-  ;;   in "ignored" state, and "false" for any other state -- but most
-  ;;   of these are already covered by the previous command, so we
-  ;;   deduce "up-to-date".
-  (let* ((default-directory (vc-jj-root file))
-         (changed (vc-jj--command-parseable file "diff" "--summary")))
-    (cond
-     ((string-prefix-p "A " changed) 'added)
-     ((string-prefix-p "D " changed) 'removed)
-     ((string-prefix-p "M " changed) 'edited)
-     (t (let ((conflicted-ignored
-               (vc-jj--command-parseable file "file" "list" "-T" "conflict")))
-          (cond
-           ((string= conflicted-ignored "true") 'conflict)
-           ((string-empty-p conflicted-ignored) 'ignored)
-           ;; If the file hasn't matched anything yet, this leaves
-           ;; only one possible state: up-to-date
-           ((string= conflicted-ignored "false") 'up-to-date)
-           (t
-            (warn "VC state of %s is not recognized, assuming up-to-date" file)
-            'up-to-date)))))))
+  "Return VC state symbol of FILE.
+There are several file states recognized by VC (see the docstring of
+`vc-state' for the full list).  Only several of these are relevant to
+Jujutsu.  They are:
+- \\='added (new file)
+- \\='removed (deleted file)
+- \\='edited (modified file)
+- \\='conflict (merge conflict)
+- \\='up-to-date (unmodified file)
+- \\='ignored (ignored by repository)
+Other VC backends would also recognize the \"unregistered\" state, but
+there is no such state in Jujutsu since Jujutsu automatically registers
+new files."
+  ;; TODO 2026-03-22: Ideally, we would only call one jj command, or
+  ;; only call a second one when necessary (instead of calling two
+  ;; commands always).  If, in the future, jj provides templates that
+  ;; make this possible, then this function, its helpers, and
+  ;; `vc-jj-dir-status-files' should be refactored
+  (let* ((root (vc-jj-root file))
+         (file (file-relative-name file root))
+         (default-directory root)
+         (file-diff-types-table
+          (vc-jj--parse-diff-types-file-table
+           (vc-jj--process-lines file "diff" "--types")))
+         (file-conflict-table
+          (vc-jj--parse-conflict-aware-file-table
+           (vc-jj--process-lines file "file" "list"
+                                 "-T" 
vc-jj--conflict-aware-file-list-template))))
+    (vc-jj--deduce-state file file-diff-types-table file-conflict-table)))
 
 ;;;; dir-status-file
 

Reply via email to