branch: externals/vc-jj
commit ac1f798dcdcde50c8d4008b97dce788b45e83fa4
Author: Kristoffer Balintona <krisbalint...@gmail.com>
Commit: Kristoffer Balintona <krisbalint...@gmail.com>
fix: Support "removed" VC file state
Previously, deleted files would be incorrectly reported as "ignored"
by vc-jj. Now, they are properly reported as "removed," both in
`vc-jj-state` and `vc-jj-dir-status-files`.
Additionally, this commit includes a few other ancillary changes:
- More informative docstrings.
- Updated comments documenting the logic of the functions modified by
this commit.
- Use symbol names more consistent with the names of the VC states.
- Update NEWS file with a new entry announcing this bug fix.
---
NEWS.org | 2 ++
vc-jj-tests.el | 13 ++++++++++
vc-jj.el | 75 ++++++++++++++++++++++++++++++++++++++++++----------------
3 files changed, 69 insertions(+), 21 deletions(-)
diff --git a/NEWS.org b/NEWS.org
index c096e773c5..98d3c72460 100644
--- a/NEWS.org
+++ b/NEWS.org
@@ -29,6 +29,8 @@
- Fix error when calling =vc-find-revision= due to erroneous argument to
=erase-buffer=, which accepts no arguments. Commands that use
=vc-find-revision=, like =vc-revision-other-window=, now do not error.
+- Identify the "removed" vc file state. Previously, removed files
+ would be incorrectly labeled as "ignored."
** [[https://codeberg.org/emacs-jj-vc/vc-jj.el/compare/v0.2...v0.3][0.3]] -
2025-06-15
diff --git a/vc-jj-tests.el b/vc-jj-tests.el
index fc963fe66e..1261124b1d 100644
--- a/vc-jj-tests.el
+++ b/vc-jj-tests.el
@@ -97,6 +97,19 @@ is needed."
(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-delete-file ()
+ "Test \"removed\" vc state and `vc-jj-delete-file'."
+ (vc-jj-test-with-repo repo
+ (write-region "First file" nil "first-file")
+ (should (eq (vc-jj-state "first-file") 'added))
+ (vc-jj-checkin '("first-file") "Commit")
+ (vc-jj-delete-file "first-file")
+ (should (eq (vc-jj-state "first-file") 'removed))
+ (write-region "Second file" nil "second-file")
+ (should (eq (vc-jj-state "second-file") 'added))
+ (should (equal (vc-jj-dir-status-files repo nil (lambda (x y) x))
+ '(("second-file" added) ("first-file" removed))))))
+
(ert-deftest vc-jj-test-conflict ()
(vc-jj-test-with-repo repo
(let (branch-1 branch-2 branch-merged)
diff --git a/vc-jj.el b/vc-jj.el
index 126da39eff..45a2806e74 100644
--- a/vc-jj.el
+++ b/vc-jj.el
@@ -195,33 +195,61 @@ stderr and1 `vc-do-command' cannot separate output to
stdout and stderr."
(/= (point-min) (point-max))))))
(defun vc-jj-state (file)
- "JJ implementation of `vc-state' for FILE."
- ;; We need to run two commands for the complete state:
+ "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 file list -T 'conflict' FILE" gets us conflicted (output
- ;; "true"), ignored (no output) or tracked (output "false", but
- ;; could be added or modified)
+ ;; - "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), but we deduce these with the
+ ;; command below instead.
+ ;;
+ ;; - "jj file list -T 'conflict' FILE" gets us the "conflict" state
+ ;; (when output is "true"), the "ignored" state (when there is no
+ ;; output) and states for tracked files (when output is "false",
+ ;; either the "added", "removed", "edited" or "up-to-date" state).
+ ;; We deduce the "added", "removed" and "edited" states with the
+ ;; command above.
;;
- ;; - "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
+ ;; The only remaining possibility is the "up-to-date" state, which
+ ;; we can deduce because when all other states have not been
+ ;; matched against.
(let* ((default-directory (vc-jj-root file))
(file (vc-jj--filename-to-fileset file))
+ (changed
+ (vc-jj--command-parseable "diff" "--summary" "--" file))
(conflicted-ignored
- (vc-jj--command-parseable "file" "list" "-T" "conflict" "--" file))
- (modified-added
- (vc-jj--command-parseable "diff" "--summary" "--" file)))
+ (vc-jj--command-parseable "file" "list" "-T" "conflict" "--" file)))
(cond
- ((string-empty-p conflicted-ignored) 'ignored)
+ ((string-prefix-p "A " changed) 'added)
+ ((string-prefix-p "D " changed) 'removed)
+ ((string-prefix-p "M " changed) 'edited)
((string= conflicted-ignored "true") 'conflict)
- ((string-prefix-p "M " modified-added) 'edited)
- ((string-prefix-p "A " modified-added) 'added)
+ ((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 nil))))
+ (t (error "VC state of %s is not recognized" 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."
+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
@@ -252,7 +280,11 @@ Return the result of applying UPDATE-FUNCTION to that
list."
(and (string-prefix-p "A " entry)
(list (substring entry 2))))
changed))
- (modified-files (mapcan (lambda (entry)
+ (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))
@@ -263,17 +295,18 @@ Return the result of applying UPDATE-FUNCTION to that
list."
(file-relative-name (expand-file-name
entry project-root) dir))
(vc-jj--process-lines "file" "list"
"-T" "if(conflict,
path ++ \"\\n\")" "--" dir)))
- (unchanged-files (cl-remove-if (lambda (entry) (or (member entry
conflicted-files)
- (member entry
modified-files)
+ (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 'edited))
modified-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))
unchanged-files))))
+ (mapcar (lambda (entry) (list entry 'up-to-date))
up-to-date-files))))
(funcall update-function result nil))))
(defun vc-jj-dir-extra-headers (dir)