aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSean Whitton2025-04-05 10:58:35 +0800
committerSean Whitton2025-05-09 10:52:06 +0100
commit8e02537d0be3cfdeaaf7764e2ef2db8b66de542a (patch)
tree8c01f49798e56296a2780eb56ef79bbd3703a7a8
parent3739b86f5af654ec0ae3e47a3662e19ea79d2b3c (diff)
downloademacs-8e02537d0be3cfdeaaf7764e2ef2db8b66de542a.tar.gz
emacs-8e02537d0be3cfdeaaf7764e2ef2db8b66de542a.zip
New vc-async-checkin user option
* lisp/vc/vc.el (vc-async-checkin): New option. (vc-checkin): Don't use with-vc-properties on or display messages around asynchronous checkins. * lisp/vc/vc-git.el (vc-git-checkin): * lisp/vc/vc-hg.el (vc-hg-checkin, vc-hg-checkin-patch): Perform an async checkin operation when vc-async-checkin is non-nil. * doc/emacs/vc1-xtra.texi (General VC Options): * etc/NEWS: Document the new option. * lisp/vc/vc-dispatcher.el (vc-wait-for-process-before-save): New function. (vc-set-async-update): If the current buffer visits a file, call vc-refresh-state. * lisp/vc/vc-hg.el (vc-wait-for-process-before-save): Autoload. (vc-hg--async-command, vc-hg--async-buffer, vc-hg--command-1): New utilities, partially factored out of vc-hg-command. (vc-hg-merge-branch): Use vc-hg--async-command, thereby newly respecting vc-hg-global-switches.
-rw-r--r--doc/emacs/vc1-xtra.texi19
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/vc/vc-dispatcher.el66
-rw-r--r--lisp/vc/vc-git.el69
-rw-r--r--lisp/vc/vc-hg.el82
-rw-r--r--lisp/vc/vc.el63
6 files changed, 219 insertions, 84 deletions
diff --git a/doc/emacs/vc1-xtra.texi b/doc/emacs/vc1-xtra.texi
index 5c448e741f2..b4e1e82dcb9 100644
--- a/doc/emacs/vc1-xtra.texi
+++ b/doc/emacs/vc1-xtra.texi
@@ -380,6 +380,25 @@ appropriate version control system. If @code{vc-command-messages} is
380non-@code{nil}, VC displays messages to indicate which shell commands 380non-@code{nil}, VC displays messages to indicate which shell commands
381it runs, and additional messages when the commands finish. 381it runs, and additional messages when the commands finish.
382 382
383@vindex vc-async-checkin
384 Normally checkin operations are done synchronously; that is, Emacs
385waits until the checkin has completed before doing anything else. This
386can be inconvenient for repositories in which the checkin operation is
387slow, such as Git repositories where you check in changes to very large
388files, or Mercurial repositories with a very large number of files.
389
390 For those backends which support it, setting @code{vc-async-checkin}
391to non-nil switches to doing checkin operations asynchronously. This is
392particularly useful as a directory local variable in repositories where
393checkin operations are slow
394(@pxref{Directory Local Variables,,,elisp,GNU Emacs Lisp Reference Manual}).
395
396 While an asynchronous checkin operation is in progress, if you use
397@kbd{C-x C-s} to save a buffer visiting any file within the current VC
398tree, then the operation reverts to a synchronous checkin and Emacs
399waits for it to complete before saving the buffer. This is to avoid
400nondeterminism regarding exactly what changes get checked in.
401
383@node RCS and SCCS 402@node RCS and SCCS
384@subsubsection Options for RCS and SCCS 403@subsubsection Options for RCS and SCCS
385 404
diff --git a/etc/NEWS b/etc/NEWS
index d1b0189da0c..5c2a004164c 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1752,6 +1752,10 @@ were added, removed or edited, Emacs would refuse to proceed.
1752Now Emacs prompts to first register the unregistered files, so that all 1752Now Emacs prompts to first register the unregistered files, so that all
1753files in the fileset are in a compatible state for a checkin. 1753files in the fileset are in a compatible state for a checkin.
1754 1754
1755+++
1756*** New user option 'vc-async-checkin' to enable async checkin operations.
1757Currently only supported by the Git and Mercurial backends.
1758
1755--- 1759---
1756*** New 'log-edit-hook' option to display diff of changes to commit. 1760*** New 'log-edit-hook' option to display diff of changes to commit.
1757You can customize 'log-edit-hook' to include its new 1761You can customize 'log-edit-hook' to include its new
diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el
index a454a0bfc78..27837ddace1 100644
--- a/lisp/vc/vc-dispatcher.el
+++ b/lisp/vc/vc-dispatcher.el
@@ -294,6 +294,41 @@ Only run CODE if the SUCCESS process has a zero exit code."
294 (declare (indent 0) (debug (def-body))) 294 (declare (indent 0) (debug (def-body)))
295 `(vc-exec-after (lambda () ,@body))) 295 `(vc-exec-after (lambda () ,@body)))
296 296
297(defun vc-wait-for-process-before-save (proc message)
298 "Make Emacs wait for PROC before saving buffers under current VC tree.
299If waiting for PROC takes more than a second, display MESSAGE.
300
301This is used to implement `vc-async-checkin'. It effectively switches
302to a synchronous checkin in the case that the user asks to save a buffer
303under the tree in which the checkin operation is running.
304
305The hook installed by this function will make Emacs unconditionally wait
306for PROC if the root of the current VC tree couldn't be determined, and
307whenever writing out a buffer which doesn't have any `buffer-file-name'
308yet."
309 (letrec ((root (vc-root-dir))
310 (hook
311 (lambda ()
312 (cond ((not (process-live-p proc))
313 (remove-hook 'before-save-hook hook))
314 ((or (and buffer-file-name
315 (or (not root)
316 (file-in-directory-p buffer-file-name
317 root)))
318 ;; No known buffer file name but we are saving:
319 ;; perhaps writing out a `special-mode' buffer.
320 ;; A `before-save-hook' cannot know whether or
321 ;; not it'll be written out under ROOT.
322 ;; Err on the side of switching to synchronous.
323 (not buffer-file-name))
324 (with-delayed-message (1 message)
325 (while (process-live-p proc)
326 (when (input-pending-p)
327 (discard-input))
328 (sit-for 0.05)))
329 (remove-hook 'before-save-hook hook))))))
330 (add-hook 'before-save-hook hook)))
331
297(defvar vc-filter-command-function #'list 332(defvar vc-filter-command-function #'list
298 "Function called to transform VC commands before execution. 333 "Function called to transform VC commands before execution.
299The function is called inside the buffer in which the command 334The function is called inside the buffer in which the command
@@ -525,23 +560,24 @@ asynchronous VC command has completed. PROCESS-BUFFER is the
525buffer for the asynchronous VC process. 560buffer for the asynchronous VC process.
526 561
527If the current buffer is a VC Dir buffer, call `vc-dir-refresh'. 562If the current buffer is a VC Dir buffer, call `vc-dir-refresh'.
528If the current buffer is a Dired buffer, revert it." 563If the current buffer is a Dired buffer, revert it.
564If the current buffer visits a file, call `vc-refresh-state'."
529 (let* ((buf (current-buffer)) 565 (let* ((buf (current-buffer))
530 (tick (buffer-modified-tick buf))) 566 (tick (buffer-modified-tick buf)))
531 (cond 567 (cl-macrolet ((run-delayed (&rest body)
532 ((derived-mode-p 'vc-dir-mode) 568 `(with-current-buffer process-buffer
533 (with-current-buffer process-buffer 569 (vc-run-delayed
534 (vc-run-delayed 570 (when (buffer-live-p buf)
535 (if (buffer-live-p buf) 571 (with-current-buffer buf
536 (with-current-buffer buf 572 ,@body))))))
537 (vc-dir-refresh)))))) 573 (cond ((derived-mode-p 'vc-dir-mode)
538 ((derived-mode-p 'dired-mode) 574 (run-delayed (vc-dir-refresh)))
539 (with-current-buffer process-buffer 575 ((derived-mode-p 'dired-mode)
540 (vc-run-delayed 576 (run-delayed
541 (and (buffer-live-p buf) 577 (when (= (buffer-modified-tick buf) tick)
542 (= (buffer-modified-tick buf) tick) 578 (revert-buffer))))
543 (with-current-buffer buf 579 (buffer-file-name
544 (revert-buffer))))))))) 580 (run-delayed (vc-refresh-state)))))))
545 581
546;; These functions are used to ensure that the view the user sees is up to date 582;; These functions are used to ensure that the view the user sees is up to date
547;; even if the dispatcher client mode has messed with file contents (as in, 583;; even if the dispatcher client mode has messed with file contents (as in,
diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el
index 4acacaff203..53dedb05320 100644
--- a/lisp/vc/vc-git.el
+++ b/lisp/vc/vc-git.el
@@ -1209,32 +1209,49 @@ It is based on `log-edit-mode', and has Git-specific extensions."
1209 (vc-git-command nil 0 nil "apply" "--cached" patch-file) 1209 (vc-git-command nil 0 nil "apply" "--cached" patch-file)
1210 (delete-file patch-file)))) 1210 (delete-file patch-file))))
1211 (when to-stash (vc-git--stash-staged-changes to-stash))) 1211 (when to-stash (vc-git--stash-staged-changes to-stash)))
1212 ;; When operating on the whole tree, better pass "-a" than ".", 1212 (let ((files (and only (not vc-git-patch-string) files))
1213 ;; since "." fails when we're committing a merge. 1213 (args (vc-git--log-edit-extract-headers comment))
1214 (apply #'vc-git-command nil 0 1214 (buffer (format "*vc-git : %s*" (expand-file-name root)))
1215 (if (and only (not vc-git-patch-string)) files) 1215 (post
1216 (nconc (if msg-file (list "commit" "-F" 1216 (lambda ()
1217 (file-local-name msg-file)) 1217 (when (and msg-file (file-exists-p msg-file))
1218 (list "commit" "-m")) 1218 (delete-file msg-file))
1219 (let ((args 1219 (when to-stash
1220 (vc-git--log-edit-extract-headers comment))) 1220 (let ((cached (make-nearby-temp-file "git-cached")))
1221 (when msg-file 1221 (unwind-protect
1222 (let ((coding-system-for-write 1222 (progn
1223 (or pcsw vc-git-commits-coding-system))) 1223 (with-temp-file cached
1224 (write-region (car args) nil msg-file)) 1224 (vc-git-command t 0 nil "stash" "show" "-p"))
1225 (setq args (cdr args))) 1225 (vc-git-command nil 0 "apply" "--cached" cached))
1226 args) 1226 (delete-file cached))
1227 (unless vc-git-patch-string 1227 (vc-git-command nil 0 nil "stash" "drop"))))))
1228 (if only (list "--only" "--") '("-a"))))) 1228 (when msg-file
1229 (if (and msg-file (file-exists-p msg-file)) (delete-file msg-file)) 1229 (let ((coding-system-for-write
1230 (when to-stash 1230 (or pcsw vc-git-commits-coding-system)))
1231 (let ((cached (make-nearby-temp-file "git-cached"))) 1231 (write-region (car args) nil msg-file))
1232 (unwind-protect 1232 (setq args (cdr args)))
1233 (progn (with-temp-file cached 1233 (setq args (nconc (if msg-file
1234 (vc-git-command t 0 nil "stash" "show" "-p")) 1234 (list "commit" "-F"
1235 (vc-git-command nil 0 nil "apply" "--cached" cached)) 1235 (file-local-name msg-file))
1236 (delete-file cached)) 1236 (list "commit" "-m"))
1237 (vc-git-command nil 0 nil "stash" "drop"))))) 1237 args
1238 ;; When operating on the whole tree, better pass
1239 ;; "-a" than ".", since "." fails when we're
1240 ;; committing a merge.
1241 (and (not vc-git-patch-string)
1242 (if only (list "--only" "--") '("-a")))))
1243 (if vc-async-checkin
1244 (progn (vc-wait-for-process-before-save
1245 (apply #'vc-do-async-command buffer root
1246 vc-git-program (nconc args files))
1247 "Finishing checking in files...")
1248 (with-current-buffer buffer
1249 (vc-run-delayed
1250 (vc-compilation-mode 'git)
1251 (funcall post)))
1252 (vc-set-async-update buffer))
1253 (apply #'vc-git-command nil 0 files args)
1254 (funcall post)))))
1238 1255
1239(defun vc-git--stash-staged-changes (files) 1256(defun vc-git--stash-staged-changes (files)
1240 "Stash only the staged changes to FILES." 1257 "Stash only the staged changes to FILES."
diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el
index a18c463c848..5c0758b93b2 100644
--- a/lisp/vc/vc-hg.el
+++ b/lisp/vc/vc-hg.el
@@ -1181,25 +1181,42 @@ If toggling on, also insert its message into the buffer."
1181 "Major mode for editing Hg log messages. 1181 "Major mode for editing Hg log messages.
1182It is based on `log-edit-mode', and has Hg-specific extensions.") 1182It is based on `log-edit-mode', and has Hg-specific extensions.")
1183 1183
1184(autoload 'vc-wait-for-process-before-save "vc-dispatcher")
1185
1184(defun vc-hg-checkin (files comment &optional _rev) 1186(defun vc-hg-checkin (files comment &optional _rev)
1185 "Hg-specific version of `vc-backend-checkin'. 1187 "Hg-specific version of `vc-backend-checkin'.
1186REV is ignored." 1188REV is ignored."
1187 (apply #'vc-hg-command nil 0 files 1189 (let ((args (nconc (list "commit" "-m")
1188 (nconc (list "commit" "-m") 1190 (vc-hg--extract-headers comment))))
1189 (vc-hg--extract-headers comment)))) 1191 (if vc-async-checkin
1192 (let ((buffer (vc-hg--async-buffer)))
1193 (vc-wait-for-process-before-save
1194 (apply #'vc-hg--async-command buffer (nconc args files))
1195 "Finishing checking in files...")
1196 (with-current-buffer buffer
1197 (vc-run-delayed
1198 (vc-compilation-mode 'hg)))
1199 (vc-set-async-update buffer))
1200 (apply #'vc-hg-command nil 0 files args))))
1190 1201
1191(defun vc-hg-checkin-patch (patch-string comment) 1202(defun vc-hg-checkin-patch (patch-string comment)
1192 (let ((patch-file (make-temp-file "hg-patch"))) 1203 (let ((patch-file (make-temp-file "hg-patch")))
1193 (write-region patch-string nil patch-file) 1204 (write-region patch-string nil patch-file)
1194 (unwind-protect 1205 (unwind-protect
1195 (progn 1206 (let ((args (list "update"
1207 "--merge" "--tool" "internal:local"
1208 "tip")))
1196 (apply #'vc-hg-command nil 0 nil 1209 (apply #'vc-hg-command nil 0 nil
1197 (nconc (list "import" "--bypass" patch-file "-m") 1210 (nconc (list "import" "--bypass" patch-file "-m")
1198 (vc-hg--extract-headers comment))) 1211 (vc-hg--extract-headers comment)))
1199 (vc-hg-command nil 0 nil 1212 (if vc-async-checkin
1200 "update" 1213 (let ((buffer (vc-hg--async-buffer)))
1201 "--merge" "--tool" "internal:local" 1214 (apply #'vc-hg--async-command buffer args)
1202 "tip")) 1215 (with-current-buffer buffer
1216 (vc-run-delayed
1217 (vc-compilation-mode 'hg)))
1218 (vc-set-async-update buffer))
1219 (apply #'vc-hg-command nil 0 nil args)))
1203 (delete-file patch-file)))) 1220 (delete-file patch-file))))
1204 1221
1205(defun vc-hg--extract-headers (comment) 1222(defun vc-hg--extract-headers (comment)
@@ -1543,15 +1560,14 @@ call \"hg push -r REVS\" to push the specified revisions REVS."
1543(defun vc-hg-merge-branch () 1560(defun vc-hg-merge-branch ()
1544 "Prompt for revision and merge it into working directory. 1561 "Prompt for revision and merge it into working directory.
1545This runs the command \"hg merge\"." 1562This runs the command \"hg merge\"."
1546 (let* ((root (vc-hg-root default-directory)) 1563 (let ((buffer (vc-hg--async-buffer))
1547 (buffer (format "*vc-hg : %s*" (expand-file-name root))) 1564 (branch (vc-read-revision "Revision to merge: ")))
1548 ;; Disable pager. 1565 (apply #'vc-hg--async-command buffer
1549 (process-environment (cons "HGPLAIN=1" process-environment))
1550 (branch (vc-read-revision "Revision to merge: ")))
1551 (apply #'vc-do-async-command buffer root vc-hg-program
1552 (append '("--config" "ui.report_untrusted=0" "merge") 1566 (append '("--config" "ui.report_untrusted=0" "merge")
1553 (unless (string= branch "") (list branch)))) 1567 (and (not (string-empty-p branch)) (list branch))))
1554 (with-current-buffer buffer (vc-run-delayed (vc-compilation-mode 'hg))) 1568 (with-current-buffer buffer
1569 (vc-run-delayed
1570 (vc-compilation-mode 'hg)))
1555 (vc-set-async-update buffer))) 1571 (vc-set-async-update buffer)))
1556 1572
1557(defun vc-hg-prepare-patch (rev) 1573(defun vc-hg-prepare-patch (rev)
@@ -1571,15 +1587,33 @@ This runs the command \"hg merge\"."
1571 "A wrapper around `vc-do-command' for use in vc-hg.el. 1587 "A wrapper around `vc-do-command' for use in vc-hg.el.
1572This function differs from `vc-do-command' in that it invokes 1588This function differs from `vc-do-command' in that it invokes
1573`vc-hg-program', and passes `vc-hg-global-switches' to it before FLAGS." 1589`vc-hg-program', and passes `vc-hg-global-switches' to it before FLAGS."
1590 (vc-hg--command-1 #'vc-do-command
1591 (list (or buffer "*vc*")
1592 okstatus vc-hg-program file-or-list)
1593 flags))
1594
1595(defun vc-hg--async-command (buffer &rest args)
1596 "Wrapper around `vc-do-async-command' like `vc-hg-command'."
1597 (vc-hg--command-1 #'vc-do-async-command
1598 (list buffer (vc-hg-root default-directory)
1599 vc-hg-program)
1600 args))
1601
1602(defun vc-hg--async-buffer ()
1603 "Buffer passed to `vc-do-async-command' by vg-hg.el commands.
1604Intended for use via the `vc-hg--async-command' wrapper."
1605 (format "*vc-hg : %s*"
1606 (expand-file-name (vc-hg-root default-directory))))
1607
1608(defun vc-hg--command-1 (fun args flags)
1574 ;; Disable pager. 1609 ;; Disable pager.
1575 (let ((process-environment (cons "HGPLAIN=1" process-environment)) 1610 (let ((process-environment (cons "HGPLAIN=1" process-environment)))
1576 (flags (append '("--config" "ui.report_untrusted=0") flags))) 1611 (apply fun (append args
1577 (apply #'vc-do-command (or buffer "*vc*") 1612 '("--config" "ui.report_untrusted=0")
1578 okstatus vc-hg-program file-or-list 1613 (if (stringp vc-hg-global-switches)
1579 (if (stringp vc-hg-global-switches) 1614 (cons vc-hg-global-switches flags)
1580 (cons vc-hg-global-switches flags) 1615 (append vc-hg-global-switches
1581 (append vc-hg-global-switches 1616 flags))))))
1582 flags)))))
1583 1617
1584(defun vc-hg-root (file) 1618(defun vc-hg-root (file)
1585 (vc-find-root file ".hg")) 1619 (vc-find-root file ".hg"))
diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el
index f58290ccd69..dcb32e9da67 100644
--- a/lisp/vc/vc.el
+++ b/lisp/vc/vc.el
@@ -1002,6 +1002,24 @@ the URL-REGEXP of the association."
1002 :value-type ,vc-cloneable-backends-custom-type) 1002 :value-type ,vc-cloneable-backends-custom-type)
1003 :version "31.1") 1003 :version "31.1")
1004 1004
1005(defcustom vc-async-checkin nil
1006 "If non-nil, checkin operations should be done asynchronously.
1007
1008This is useful to set as a directory local variable in repositories
1009where the VCS in use performs checkin operations slowly.
1010For example, Git is slow when committing changes to very large files,
1011and Mercurial can be slow when there is a very large number of files.
1012
1013While an asynchronous checkin operation is in progress, Emacs installs a
1014`before-save-hook' to switch back to a synchronous checkin if you ask to
1015save buffers under the current VC tree. This is to avoid nondeterminism
1016regarding exactly what changes get checked in.
1017
1018Not supported by all backends."
1019 :type 'boolean
1020 :safe #'booleanp
1021 :version "31.1")
1022
1005 1023
1006;; File property caching 1024;; File property caching
1007 1025
@@ -1857,26 +1875,33 @@ Runs the normal hooks `vc-before-checkin-hook' and `vc-checkin-hook'."
1857 (lambda () 1875 (lambda ()
1858 (vc-call-backend backend 'log-edit-mode)) 1876 (vc-call-backend backend 'log-edit-mode))
1859 (lambda (files comment) 1877 (lambda (files comment)
1860 (message "Checking in %s..." (vc-delistify files))
1861 ;; "This log message intentionally left almost blank". 1878 ;; "This log message intentionally left almost blank".
1862 ;; RCS 5.7 gripes about white-space-only comments too. 1879 ;; RCS 5.7 gripes about whitespace-only comments too.
1863 (or (and comment (string-match "[^\t\n ]" comment)) 1880 (unless (and comment (string-match "[^\t\n ]" comment))
1864 (setq comment "*** empty log message ***")) 1881 (setq comment "*** empty log message ***"))
1865 (with-vc-properties 1882 (cl-labels ((do-it ()
1866 files 1883 ;; We used to change buffers to get local value of
1867 ;; We used to change buffers to get local value of 1884 ;; `vc-checkin-switches', but the (singular) local
1868 ;; vc-checkin-switches, but 'the' local buffer is 1885 ;; buffer is not well defined for filesets.
1869 ;; not a well-defined concept for filesets. 1886 (if patch-string
1870 (progn 1887 (vc-call-backend backend 'checkin-patch
1871 (if patch-string 1888 patch-string comment)
1872 (vc-call-backend backend 'checkin-patch patch-string comment) 1889 (vc-call-backend backend 'checkin
1873 (vc-call-backend backend 'checkin files comment rev)) 1890 files comment rev))
1874 (mapc #'vc-delete-automatic-version-backups files)) 1891 (mapc #'vc-delete-automatic-version-backups files)))
1875 `((vc-state . up-to-date) 1892 (if (and vc-async-checkin
1876 (vc-checkout-time . ,(file-attribute-modification-time 1893 ;; Backends which support `vc-async-checkin'.
1877 (file-attributes file))) 1894 (memq backend '(Git Hg)))
1878 (vc-working-revision . nil))) 1895 ;; Rely on `vc-set-async-update' to update properties.
1879 (message "Checking in %s...done" (vc-delistify files))) 1896 (do-it)
1897 (message "Checking in %s..." (vc-delistify files))
1898 (with-vc-properties files (do-it)
1899 `((vc-state . up-to-date)
1900 (vc-checkout-time
1901 . ,(file-attribute-modification-time
1902 (file-attributes file)))
1903 (vc-working-revision . nil)))
1904 (message "Checking in %s...done" (vc-delistify files)))))
1880 'vc-checkin-hook 1905 'vc-checkin-hook
1881 backend 1906 backend
1882 patch-string)) 1907 patch-string))