Subject: Re: bug#34949: 27.0.50; Docstring of `vc-deduce-fileset' incomplete

From: Juri Linkov <[email-masked]>
Date: Fri, 21 Feb 2020 01:14:20 +0200

>> I think the only possible problem here is that lambda is invoked
>> asynchronously. But we can sleep while waiting for it.
>
> Would it be possible to call dir-status-files synchronously?

If asynchronousness is inevitable, we could rely on callbacks.
Indeed this path leads to callback hell, but what are other options?

This unfinished patch shows a possible development direction:
vc-next-action executed in Dired will call vc-dired-deduce-fileset
with a callback to continue with a prepared vc-fileset.
vc-dired-deduce-fileset will call a more general
vc-directory-deduce-fileset that will call a currently
unimplemented function vc-dir-status-files whose implementation
will be like in your diff-hl-dired-update.

Also a new command vc-next-action-on-root will commit all modified files,
ignoring any unregistered files.  I miss such a command in VC-Dir:
typing 'v' (vc-next-action) at the beginning of *vc-dir* buffer signals
the error: "When applying VC operations to multiple files, the files
are required to be in similar VC states.  File in state 'edited' clashes
with state 'unregistered'".  Often I have a lot of unregistered files
lying around for a long time, and it takes too much time manually marking
only edited files for every commit, skipping each unregistered file.

Also some parts of the previous patch should be reverted
because I discovered that state changing VC operations in Dired
even on marked files (as opposed to directories) is unreliable:
when a file is modified, using vc-state doesn't return its real state
when updated outside of Emacs.  This part should be replaced
with asynchronous dir-status-files that returns the most accurate state.

diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el
index 345a28d3f1..80c580e5ec 100644
--- a/lisp/vc/vc-hooks.el
+++ b/lisp/vc/vc-hooks.el
@@ -897,6 +897,7 @@ vc-prefix-map
     (define-key map "s" 'vc-create-tag)
     (define-key map "u" 'vc-revert)
     (define-key map "v" 'vc-next-action)
+    (define-key map "V" 'vc-next-action-on-root)
     (define-key map "+" 'vc-update)
     ;; I'd prefer some kind of symmetry with vc-update:
     (define-key map "P" 'vc-push)
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index f7d651fac6..13d60b6fcf 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -1065,19 +1067,27 @@ vc-deduce-fileset
 	      (list buffer-file-name))))
      (t (error "File is not under version control")))))
 
+(defun vc-root-deduce-fileset (callback)
+  (let* ((backend (vc-responsible-backend default-directory))
+         (rootdir (vc-call-backend backend 'root default-directory)))
+    (vc-directory-deduce-fileset rootdir callback)))
+
+(defun vc-directory-deduce-fileset (dir callback)
+  ;; TODO: use vc-dir-status-files
+  )
+
 (declare-function dired-get-marked-files "dired"
                   (&optional localp arg filter distinguish-one-marked error))
 
-(defun vc-dired-deduce-fileset (&optional state-model-only-files observer)
+(defun vc-dired-deduce-fileset (&optional callback)
   (let ((backend (vc-responsible-backend default-directory))
         (files (dired-get-marked-files nil nil nil nil t))
 	only-files-list
 	state
 	model)
-    (when (and (not observer) (cl-some #'file-directory-p files))
-      (error "State changing VC operations on directories not supported in `dired-mode'"))
 
-    (when state-model-only-files
+    (when callback
+      ;; TODO: use vc-directory-deduce-fileset
       (setq only-files-list (mapcar (lambda (file) (cons file (vc-state file))) files))
       (setq state (cdar only-files-list))
       ;; Check that all files are in a consistent state, since we use that
@@ -1132,6 +1142,11 @@ vc-read-backend
    (completing-read prompt (mapcar #'symbol-name vc-handled-backends)
                     nil 'require-match)))
 
+(defun vc-next-action-on-root (verbose)
+  (interactive "P")
+  (vc-root-deduce-fileset
+   (lambda (vc-fileset) (vc-next-action-on-fileset vc-fileset verbose))))
+
 ;; Here's the major entry point.
 
 ;;;###autoload
@@ -1158,8 +1173,16 @@ vc-next-action
   If every file is locked by you and unchanged, unlock them.
   If every file is locked by someone else, offer to steal the lock."
   (interactive "P")
-  (let* ((vc-fileset (vc-deduce-fileset nil t 'state-model-only-files))
-         (backend (car vc-fileset))
+  (cond
+   ((derived-mode-p 'dired-mode)
+    ;; Async operation
+    (vc-dired-deduce-fileset
+     (lambda (vc-fileset) (vc-next-action-on-fileset vc-fileset verbose))))
+   (t (vc-next-action-on-fileset
+       (vc-deduce-fileset nil t 'state-model-only-files) verbose))))
+
+(defun vc-next-action-on-fileset (vc-fileset verbose)
+  (let* ((backend (car vc-fileset))
 	 (files (nth 1 vc-fileset))
          ;; (fileset-only-files (nth 2 vc-fileset))
          ;; FIXME: We used to call `vc-recompute-state' here.
@@ -3138,6 +3171,10 @@ vc-default-dir-status-files
   (funcall update-function
            (mapcar (lambda (file) (list file 'up-to-date)) files)))
 
+(defun vc-dir-status-files (backend dir files update-function)
+  (funcall update-function
+           (mapcar (lambda (file) (list file 'up-to-date)) files)))
+
 (defun vc-check-headers ()
   "Check if the current file has any headers in it."
   (interactive)