Generalize & simplify implementation of user edits to VC commands

* lisp/vc/vc-dispatcher.el (vc-pre-command-functions)
(vc-want-edit-command-p): Delete.
(vc-filter-command-function): New variable.
(vc-user-edit-command): Factor out of vc-do-command.
(vc-do-command, vc-do-async-command)
* lisp/vc/vc-git.el (vc-git--pushpull)
* lisp/vc/vc.el (vc-print-branch-log): Use vc-filter-command-function
in place of vc-pre-command-functions and vc-want-edit-command-p.
This commit is contained in:
Sean Whitton 2022-09-24 10:39:52 -07:00
parent 8574ae625e
commit 9a5176aec0
3 changed files with 149 additions and 136 deletions

View file

@ -109,6 +109,8 @@
;; TODO:
;; - log buffers need font-locking.
(eval-when-compile (require 'cl-lib))
;; General customization
(defcustom vc-logentry-check-hook nil
@ -156,9 +158,6 @@ BEWARE: Despite its name, this variable is not itself a hook!")
(defvar vc-parent-buffer-name nil)
(put 'vc-parent-buffer-name 'permanent-local t)
(defvar vc-want-edit-command-p nil
"If non-nil, let user edit the VC shell command before running it.")
;; Common command execution logic
(defun vc-process-filter (p s)
@ -270,11 +269,12 @@ SUCCESS process has a zero exit code."
(declare (indent 0) (debug (def-body)))
`(vc-exec-after (lambda () ,@body)))
(defvar vc-pre-command-functions nil
"Hook run at the beginning of `vc-do-command'.
Each function is called inside the buffer in which the command
will be run and is passed 3 arguments: the COMMAND, the FILES and
the FLAGS.")
(defvar vc-filter-command-function (lambda (&rest args) args)
"Function called to transform VC commands before execution.
The function is called inside the buffer in which the command
will be run and is passed the COMMAND, FILE-OR-LIST and FLAGS
arguments to `vc-do-command'. It should return a list of three
elements, the new values for these arguments.")
(defvar vc-post-command-functions nil
"Hook run at the end of `vc-do-command'.
@ -296,6 +296,23 @@ the man pages for \"torsocks\" for more details about Tor."
:version "27.1"
:group 'vc)
(defun vc-user-edit-command (command file-or-list flags)
"Prompt the user to edit VC command COMMAND and FLAGS.
Intended to be used as the value of `vc-filter-command-function'."
(let* ((files-separator-p (string= "--" (car (last flags))))
(edited (split-string-and-unquote
(read-shell-command
(format "Edit VC command & arguments%s: "
(if file-or-list
" (files list to be appended)"
""))
(combine-and-quote-strings
(cons command (remq nil (if files-separator-p
(butlast flags)
flags))))))))
(list (car edited) file-or-list
(nconc (cdr edited) (and files-separator-p '("--"))))))
;;;###autoload
(defun vc-do-command (buffer okstatus command file-or-list &rest flags)
"Execute a slave command, notifying user and checking for errors.
@ -311,109 +328,102 @@ files or be nil (to execute commands that don't expect a file
name or set of files). If an optional list of FLAGS is present,
that is inserted into the command line before the filename.
If `vc-want-edit-command-p' is non-nil, prompt the user to edit
COMMAND and FLAGS before execution.
Return the return value of the slave command in the synchronous
case, and the process object in the asynchronous case."
(when vc-want-edit-command-p
(let* ((files-separator-p (string= "--" (car (last flags))))
(edited (split-string-and-unquote
(read-shell-command
(format "Edit VC command & arguments%s: "
(if file-or-list
" (files list to be appended)"
""))
(combine-and-quote-strings
(cons command (remq nil (if files-separator-p
(butlast flags)
flags))))))))
(setq command (car edited)
flags (nconc (cdr edited)
(and files-separator-p '("--"))))))
(when vc-tor
(push command flags)
(setq command "torsocks"))
;; FIXME: file-relative-name can return a bogus result because
;; it doesn't look at the actual file-system to see if symlinks
;; come into play.
(let* ((files
(mapcar (lambda (f) (file-relative-name (expand-file-name f)))
(if (listp file-or-list) file-or-list (list file-or-list))))
;; Keep entire commands in *Messages* but avoid resizing the
;; echo area. Messages in this function are formatted in
;; a such way that the important parts are at the beginning,
;; due to potential truncation of long messages.
(message-truncate-lines t)
(full-command
(concat (if (string= (substring command -1) "\n")
(substring command 0 -1)
command)
" " (vc-delistify flags)
" " (vc-delistify files)))
(vc-inhibit-message
(or (eq vc-command-messages 'log)
(eq (selected-window) (active-minibuffer-window)))))
(let (;; Keep entire commands in *Messages* but avoid resizing the
;; echo area. Messages in this function are formatted in
;; a such way that the important parts are at the beginning,
;; due to potential truncation of long messages.
(message-truncate-lines t)
(vc-inhibit-message
(or (eq vc-command-messages 'log)
(eq (selected-window) (active-minibuffer-window)))))
(save-current-buffer
(unless (or (eq buffer t)
(and (stringp buffer)
(string= (buffer-name) buffer))
(eq buffer (current-buffer)))
(vc-setup-buffer buffer))
(run-hook-with-args 'vc-pre-command-functions
command file-or-list flags)
;; If there's some previous async process still running, just kill it.
(let ((squeezed (remq nil flags))
(inhibit-read-only t)
(status 0))
(when files
(setq squeezed (nconc squeezed files)))
(let (;; Since some functions need to parse the output
;; from external commands, set LC_MESSAGES to C.
(process-environment (cons "LC_MESSAGES=C" process-environment))
(w32-quote-process-args t))
(if (eq okstatus 'async)
;; Run asynchronously.
(let ((proc
(let ((process-connection-type nil))
(apply #'start-file-process command (current-buffer)
command squeezed))))
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Running in background: %s" full-command)))
;; Get rid of the default message insertion, in case we don't
;; set a sentinel explicitly.
(set-process-sentinel proc #'ignore)
(set-process-filter proc #'vc-process-filter)
(setq status proc)
(when vc-command-messages
(vc-run-delayed
(let ((message-truncate-lines t)
(inhibit-message vc-inhibit-message))
(message "Done in background: %s" full-command)))))
;; Run synchronously
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Running in foreground: %s" full-command)))
(let ((buffer-undo-list t))
(setq status (apply #'process-file command nil t nil squeezed)))
(when (and (not (eq t okstatus))
(or (not (integerp status))
(and okstatus (< okstatus status))))
(unless (eq ?\s (aref (buffer-name (current-buffer)) 0))
(pop-to-buffer (current-buffer))
(goto-char (point-min))
(shrink-window-if-larger-than-buffer))
(error "Failed (%s): %s"
(if (integerp status) (format "status %d" status) status)
full-command))
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Done (status=%d): %s" status full-command)))))
(vc-run-delayed
(run-hook-with-args 'vc-post-command-functions
command file-or-list flags))
status))))
(vc-setup-buffer buffer))
(cl-destructuring-bind (command file-or-list flags)
(funcall vc-filter-command-function command file-or-list flags)
(when vc-tor
(push command flags)
(setq command "torsocks"))
(let* (;; FIXME: file-relative-name can return a bogus result
;; because it doesn't look at the actual file-system to
;; see if symlinks come into play.
(files
(mapcar (lambda (f)
(file-relative-name (expand-file-name f)))
(if (listp file-or-list)
file-or-list
(list file-or-list))))
(full-command
(concat (if (string= (substring command -1) "\n")
(substring command 0 -1)
command)
" " (vc-delistify flags)
" " (vc-delistify files)))
(squeezed (remq nil flags))
(inhibit-read-only t)
(status 0))
;; If there's some previous async process still running,
;; just kill it.
(when files
(setq squeezed (nconc squeezed files)))
(let (;; Since some functions need to parse the output
;; from external commands, set LC_MESSAGES to C.
(process-environment
(cons "LC_MESSAGES=C" process-environment))
(w32-quote-process-args t))
(if (eq okstatus 'async)
;; Run asynchronously.
(let ((proc
(let ((process-connection-type nil))
(apply #'start-file-process command
(current-buffer) command squeezed))))
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Running in background: %s"
full-command)))
;; Get rid of the default message insertion, in case
;; we don't set a sentinel explicitly.
(set-process-sentinel proc #'ignore)
(set-process-filter proc #'vc-process-filter)
(setq status proc)
(when vc-command-messages
(vc-run-delayed
(let ((message-truncate-lines t)
(inhibit-message vc-inhibit-message))
(message "Done in background: %s"
full-command)))))
;; Run synchronously
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Running in foreground: %s" full-command)))
(let ((buffer-undo-list t))
(setq status (apply #'process-file
command nil t nil squeezed)))
(when (and (not (eq t okstatus))
(or (not (integerp status))
(and okstatus (< okstatus status))))
(unless (eq ?\s (aref (buffer-name (current-buffer)) 0))
(pop-to-buffer (current-buffer))
(goto-char (point-min))
(shrink-window-if-larger-than-buffer))
(error "Failed (%s): %s"
(if (integerp status)
(format "status %d" status)
status)
full-command))
(when vc-command-messages
(let ((inhibit-message vc-inhibit-message))
(message "Done (status=%d): %s"
status full-command)))))
(vc-run-delayed
(run-hook-with-args 'vc-post-command-functions
command file-or-list flags))
status)))))
(defvar vc--inhibit-change-window-start nil)
@ -424,29 +434,30 @@ of a buffer, which is created.
ROOT should be the directory in which the command should be run.
The process object is returned.
Display the buffer in some window, but don't select it."
(letrec ((dir default-directory)
(inhibit-read-only t)
(fun (lambda (command _ args)
(remove-hook 'vc-pre-command-functions fun)
(goto-char (point-max))
(unless (eq (point) (point-min))
(insert " \n"))
(setq new-window-start (point))
(insert "Running \"" command)
(dolist (arg args)
(insert " " arg))
(insert "\"...\n")))
(window nil)
(new-window-start nil)
(proc nil))
(let ((dir default-directory)
(inhibit-read-only t)
window new-window-start proc)
(setq buffer (get-buffer-create buffer))
(if (get-buffer-process buffer)
(error "Another VC action on %s is running" root))
(with-current-buffer buffer
(setq default-directory root)
(add-hook 'vc-pre-command-functions fun)
;; Run in the original working directory.
(let ((default-directory dir))
(let* (;; Run in the original working directory.
(default-directory dir)
(orig-fun vc-filter-command-function)
(vc-filter-command-function
(lambda (&rest args)
(cl-destructuring-bind (&whole args cmd _ flags)
(apply orig-fun args)
(goto-char (point-max))
(unless (eq (point) (point-min))
(insert " \n"))
(setq new-window-start (point))
(insert "Running \"" cmd)
(dolist (flag flags)
(insert " " flag))
(insert "\"...\n")
args))))
(setq proc (apply #'vc-do-command t 'async command nil args))))
(setq window (display-buffer buffer))
(when (and window

View file

@ -1094,23 +1094,23 @@ It is based on `log-edit-mode', and has Git-specific extensions."
(defun vc-git--pushpull (command prompt extra-args)
"Run COMMAND (a string; either push or pull) on the current Git branch.
If PROMPT is non-nil, prompt for the Git command to run."
(require 'vc-dispatcher)
(let* ((root (vc-git-root default-directory))
(buffer (format "*vc-git : %s*" (expand-file-name root)))
(git-program vc-git-program)
;; TODO if pushing, prompt if no default push location - cf bzr.
(vc-want-edit-command-p prompt)
proc)
(require 'vc-dispatcher)
(when vc-want-edit-command-p
(with-current-buffer (get-buffer-create buffer)
(add-hook 'vc-pre-command-functions
(lambda (&rest args)
(setq git-program (car args)
command (caaddr args)
extra-args (cdaddr args)))
nil t)))
(setq proc (apply #'vc-do-async-command
buffer root git-program command extra-args))
(vc-filter-command-function
(if prompt
(lambda (&rest args)
(cl-destructuring-bind (&whole args git _ flags)
(apply #'vc-user-edit-command args)
(setq git-program git
command (car flags)
extra-args (cdr flags))
args))
vc-filter-command-function))
(proc (apply #'vc-do-async-command
buffer root git-program command extra-args)))
(with-current-buffer buffer
(vc-run-delayed
(vc-compilation-mode 'git)

View file

@ -2764,7 +2764,9 @@ log."
(error "No branch specified"))
(let* ((backend (vc-responsible-backend default-directory))
(rootdir (vc-call-backend backend 'root default-directory))
(vc-want-edit-command-p arg))
(vc-filter-command-function (if arg
#'vc-user-edit-command
vc-filter-command-function)))
(vc-print-log-internal backend
(list rootdir) branch t
(when (> vc-log-show-limit 0) vc-log-show-limit))))