From 7a6f5a5167037cdc3a0e9e312393781daedec085 Mon Sep 17 00:00:00 2001 From: Juri Linkov Date: Mon, 30 Mar 2020 01:34:47 +0300 Subject: [PATCH] Support state changing VC operations on directories in Dired (bug#34949) * lisp/dired-aux.el (dired-vc-next-action): New command. (dired-vc-deduce-fileset): Rename from vc-dired-deduce-fileset in vc.el. * lisp/dired.el (dired-mode-map): Remap vc-next-action to dired-vc-next-action. * lisp/vc/vc-dir.el (vc-dir-mark-files): New function. (vc-dir-refresh): Run hook vc-dir-refresh-hook. * lisp/vc/vc.el (vc-deduce-fileset): Rename arg 'observer' to 'not-state-changing' and document it in docstring. (vc-dired-deduce-fileset): Rename to dired-vc-deduce-fileset in dired-aux.el. * lisp/cedet/ede.el (ede-turn-on-hook, ede-minor-mode): * lisp/desktop.el (desktop-minor-mode-table): Rename the long ago obsolete vc-dired-mode to vc-dir-mode. --- etc/NEWS | 4 +-- lisp/cedet/ede.el | 4 +-- lisp/desktop.el | 2 +- lisp/dired-aux.el | 62 +++++++++++++++++++++++++++++++++++++++++++++++ lisp/dired.el | 1 + lisp/vc/vc-dir.el | 14 ++++++++++- lisp/vc/vc.el | 40 +++++++----------------------- 7 files changed, 90 insertions(+), 37 deletions(-) diff --git a/etc/NEWS b/etc/NEWS index 4b477e5def6..bb5f549a2e2 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -108,8 +108,8 @@ Mark mode, then Dired commands operate only on files in the active region. The values 'file' and 'line' of this user option define the details of marking the file at the end of the region. -*** State changing VC operations are supported in 'dired-mode' on files -(but still not on directories). +*** State changing VC operations are supported in Dired on files and +directories with the help of new command 'dired-vc-next-action'. ** Change Logs and VC diff --git a/lisp/cedet/ede.el b/lisp/cedet/ede.el index c2036878288..8c336117c92 100644 --- a/lisp/cedet/ede.el +++ b/lisp/cedet/ede.el @@ -470,7 +470,7 @@ To be used in hook functions." ;; Emacs 21 has no buffer file name for directory edits. ;; so we need to add these hacks in. (eq major-mode 'dired-mode) - (eq major-mode 'vc-dired-mode)) + (eq major-mode 'vc-dir-mode)) (ede-minor-mode 1))) (define-minor-mode ede-minor-mode @@ -481,7 +481,7 @@ controlled project, then this mode is activated automatically provided `global-ede-mode' is enabled." :group 'ede (cond ((or (eq major-mode 'dired-mode) - (eq major-mode 'vc-dired-mode)) + (eq major-mode 'vc-dir-mode)) (ede-dired-minor-mode (if ede-minor-mode 1 -1))) (ede-minor-mode (if (not ede-constructing) diff --git a/lisp/desktop.el b/lisp/desktop.el index de601a4de8c..9d117c6f0d6 100644 --- a/lisp/desktop.el +++ b/lisp/desktop.el @@ -534,7 +534,7 @@ can guess how to load the mode's definition.") '((defining-kbd-macro nil) (isearch-mode nil) (vc-mode nil) - (vc-dired-mode nil) + (vc-dir-mode nil) (erc-track-minor-mode nil) (savehist-mode nil)) "Table mapping minor mode variables to minor mode functions. diff --git a/lisp/dired-aux.el b/lisp/dired-aux.el index 6f50a3da6ca..60a352d78e0 100644 --- a/lisp/dired-aux.el +++ b/lisp/dired-aux.el @@ -3050,6 +3050,68 @@ instead." (backward-delete-char 1)) (message "%s" (buffer-string))))) + +;;; Version control from dired + +(declare-function vc-dir-unmark-all-files "vc-dir") +(declare-function vc-dir-mark-files "vc-dir") + +;;;###autoload +(defun dired-vc-next-action (verbose) + "Do the next version control operation on marked files/directories. +When only files are marked then call `vc-next-action' with the +same value of the VERBOSE argument. +When also directories are marked then call `vc-dir' and mark +the same files/directories in the VC-Dir buffer that were marked +in the Dired buffer." + (interactive "P") + (let* ((marked-files + (dired-get-marked-files nil nil nil nil t)) + (mark-files + (when (cl-some #'file-directory-p marked-files) + ;; Fix deficiency of Dired by adding slash to dirs + (mapcar (lambda (file) + (if (file-directory-p file) + (file-name-as-directory file) + file)) + marked-files)))) + (if mark-files + (let ((transient-hook (make-symbol "vc-dir-mark-files"))) + (fset transient-hook + (lambda () + (remove-hook 'vc-dir-refresh-hook transient-hook t) + (vc-dir-unmark-all-files t) + (vc-dir-mark-files mark-files))) + (vc-dir-root) + (add-hook 'vc-dir-refresh-hook transient-hook nil t)) + (vc-next-action verbose)))) + +(declare-function vc-compatible-state "vc") + +(defun dired-vc-deduce-fileset (&optional state-model-only-files not-state-changing) + (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 not-state-changing) (cl-some #'file-directory-p files)) + (user-error "State changing VC operations on directories supported only in `vc-dir'")) + + (when state-model-only-files + (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 + ;; state to decide which operation to perform. + (dolist (crt (cdr only-files-list)) + (unless (vc-compatible-state (cdr crt) state) + (error "When applying VC operations to multiple files, the files are required\nto be in similar VC states.\n%s in state %s clashes with %s in state %s" + (car crt) (cdr crt) (caar only-files-list) state))) + (setq only-files-list (mapcar 'car only-files-list)) + (when (and state (not (eq state 'unregistered))) + (setq model (vc-checkout-model backend only-files-list)))) + (list backend files only-files-list state model))) + + (provide 'dired-aux) ;; Local Variables: diff --git a/lisp/dired.el b/lisp/dired.el index 41bbf9f56a2..72d1cc250a3 100644 --- a/lisp/dired.el +++ b/lisp/dired.el @@ -1870,6 +1870,7 @@ Do so according to the former subdir alist OLD-SUBDIR-ALIST." (define-key map "\177" 'dired-unmark-backward) (define-key map [remap undo] 'dired-undo) (define-key map [remap advertised-undo] 'dired-undo) + (define-key map [remap vc-next-action] 'dired-vc-next-action) ;; thumbnail manipulation (image-dired) (define-key map "\C-td" 'image-dired-display-thumbs) (define-key map "\C-tt" 'image-dired-tag-files) diff --git a/lisp/vc/vc-dir.el b/lisp/vc/vc-dir.el index b760e170676..ab5943917b8 100644 --- a/lisp/vc/vc-dir.el +++ b/lisp/vc/vc-dir.el @@ -696,6 +696,17 @@ share the same state." (vc-dir-mark-file crt))) (setq crt (ewoc-next vc-ewoc crt)))))))) +(defun vc-dir-mark-files (mark-files) + "Mark files specified by file names in the argument MARK-FILES. +MARK-FILES should be a list of absolute filenames." + (ewoc-map + (lambda (filearg) + (when (member (expand-file-name (vc-dir-fileinfo->name filearg)) + mark-files) + (setf (vc-dir-fileinfo->marked filearg) t) + t)) + vc-ewoc)) + (defun vc-dir-unmark-file () ;; Unmark the current file and move to the next line. (let* ((crt (ewoc-locate vc-ewoc)) @@ -1193,7 +1204,8 @@ Throw an error if another update process is in progress." (if remaining (vc-dir-refresh-files (mapcar 'vc-dir-fileinfo->name remaining)) - (setq mode-line-process nil)))))))))))) + (setq mode-line-process nil) + (run-hooks 'vc-dir-refresh-hook)))))))))))) (defun vc-dir-show-fileentry (file) "Insert an entry for a specific file into the current *VC-dir* listing. diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 607fb37807c..d4323d59eb3 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -1006,12 +1006,18 @@ Within directories, only files already under version control are noticed." (declare-function vc-dir-current-file "vc-dir" ()) (declare-function vc-dir-deduce-fileset "vc-dir" (&optional state-model-only-files)) +(declare-function dired-vc-deduce-fileset "dired-aux" (&optional state-model-only-files not-state-changing)) -(defun vc-deduce-fileset (&optional observer allow-unregistered +(defun vc-deduce-fileset (&optional not-state-changing + allow-unregistered state-model-only-files) "Deduce a set of files and a backend to which to apply an operation. Return (BACKEND FILESET FILESET-ONLY-FILES STATE CHECKOUT-MODEL). +NOT-STATE-CHANGING if non-nil, means that the operation +requesting the fileset doesn't intend to change VC state, +such as printing the log or showing the diff. + If we're in VC-dir mode, FILESET is the list of marked files, or the directory if no files are marked. Otherwise, if in a buffer visiting a version-controlled file, @@ -1025,14 +1031,12 @@ the FILESET-ONLY-FILES STATE and MODEL info. Otherwise, that part may be skipped. BEWARE: this function may change the current buffer." - ;; FIXME: OBSERVER is unused. The name is not intuitive and is not - ;; documented. It's set to t when called from diff and print-log. (let (backend) (cond ((derived-mode-p 'vc-dir-mode) (vc-dir-deduce-fileset state-model-only-files)) ((derived-mode-p 'dired-mode) - (vc-dired-deduce-fileset state-model-only-files observer)) + (dired-vc-deduce-fileset state-model-only-files not-state-changing)) ((setq backend (vc-backend buffer-file-name)) (if state-model-only-files (list backend (list buffer-file-name) @@ -1048,7 +1052,7 @@ BEWARE: this function may change the current buffer." (derived-mode-p 'dired-mode))))) (progn ;FIXME: Why not `with-current-buffer'? --Stef. (set-buffer vc-parent-buffer) - (vc-deduce-fileset observer allow-unregistered state-model-only-files))) + (vc-deduce-fileset not-state-changing allow-unregistered state-model-only-files))) ((and (derived-mode-p 'log-view-mode) (setq backend (vc-responsible-backend default-directory))) (list backend nil)) @@ -1065,32 +1069,6 @@ BEWARE: this function may change the current buffer." (list buffer-file-name)))) (t (error "File is not under version control"))))) -(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) - (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 - (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 - ;; state to decide which operation to perform. - (dolist (crt (cdr only-files-list)) - (unless (vc-compatible-state (cdr crt) state) - (error "When applying VC operations to multiple files, the files are required\nto be in similar VC states.\n%s in state %s clashes with %s in state %s" - (car crt) (cdr crt) (caar only-files-list) state))) - (setq only-files-list (mapcar 'car only-files-list)) - (when (and state (not (eq state 'unregistered))) - (setq model (vc-checkout-model backend only-files-list)))) - (list backend files only-files-list state model))) - (defun vc-ensure-vc-buffer () "Make sure that the current buffer visits a version-controlled file." (cond