Function: vc-next-action

vc-next-action is an autoloaded, interactive and byte-compiled function defined in vc.el.gz.

Signature

(vc-next-action VERBOSE)

Documentation

Do the next logical version control operation on the current fileset.

This requires that all files in the current VC fileset be in the sufficiently similar states. If they are not, signal an error. Also signal an error if files in the fileset are ignored by the VCS.

For modern merging-based version control systems:
  If every file in the fileset is not registered for version
   control, register the fileset (but don't commit). If VERBOSE is
   non-nil (interactively, the prefix argument), ask for the VC
   backend with which to register the fileset.
  If every work file in the VC fileset is either added or modified,
   pop up a *vc-log* buffer to commit the fileset changes.
  (If some are added or modified and some are unregistered, offer to
   register the unregistered ones, first.)
  For a centralized version control system, if any work file in
   the VC fileset is out of date, offer to update the fileset.

For old-style locking-based version control systems, like RCS:
  If every file is not registered, register the file(s); with a prefix
   argument, allow to specify the VC backend for registration.
  If every file is registered and unlocked, check out (lock)
   the file(s) for editing.
  If every file is locked by you and has changes, pop up a
   *vc-log* buffer to check in the changes. Leave a
   read-only copy of each changed file after checking in.
  If every file is locked by you and unchanged, unlock them.
  If every file is locked by someone else, offer to steal the lock.
  If files are unlocked, but have changes, offer to either claim the
   lock or revert to the last checked-in version.

If this command is invoked from a patch buffer under diff-mode, it will apply the diffs from the patch and pop up a *vc-log* buffer to check-in the resulting changes.

When using this command to register a new file (or files), it will automatically deduce which VC repository to register it with, using the most specific one.

If VERBOSE is non-nil (interactively, the prefix argument), you can specify another VC backend for the file(s), or (for centralized VCS only) the revision ID or branch ID from which to check out the file(s).

View in manual

Probably introduced at or before Emacs version 19.29.

Key Bindings

Source Code

;; Defined in /usr/src/emacs/lisp/vc/vc.el.gz
;; Here's the major entry point.

;;;###autoload
(defun vc-next-action (verbose)
  "Do the next logical version control operation on the current fileset.
This requires that all files in the current VC fileset be in the
sufficiently similar states.  If they are not, signal an error.
Also signal an error if files in the fileset are ignored by the VCS.

For modern merging-based version control systems:
  If every file in the fileset is not registered for version
   control, register the fileset (but don't commit).  If VERBOSE is
   non-nil (interactively, the prefix argument), ask for the VC
   backend with which to register the fileset.
  If every work file in the VC fileset is either added or modified,
   pop up a *vc-log* buffer to commit the fileset changes.
  (If some are added or modified and some are unregistered, offer to
   register the unregistered ones, first.)
  For a centralized version control system, if any work file in
   the VC fileset is out of date, offer to update the fileset.

For old-style locking-based version control systems, like RCS:
  If every file is not registered, register the file(s); with a prefix
   argument, allow to specify the VC backend for registration.
  If every file is registered and unlocked, check out (lock)
   the file(s) for editing.
  If every file is locked by you and has changes, pop up a
   *vc-log* buffer to check in the changes.  Leave a
   read-only copy of each changed file after checking in.
  If every file is locked by you and unchanged, unlock them.
  If every file is locked by someone else, offer to steal the lock.
  If files are unlocked, but have changes, offer to either claim the
   lock or revert to the last checked-in version.

If this command is invoked from a patch buffer under `diff-mode', it
will apply the diffs from the patch and pop up a *vc-log* buffer to
check-in the resulting changes.

When using this command to register a new file (or files), it
will automatically deduce which VC repository to register it
with, using the most specific one.

If VERBOSE is non-nil (interactively, the prefix argument),
you can specify another VC backend for the file(s),
or (for centralized VCS only) the revision ID or branch ID
from which to check out the file(s)."
  (interactive "P")
  (let* ((vc-fileset (vc-deduce-fileset nil t 'state-model-only-files))
         (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.
         (state (nth 3 vc-fileset))
         ;; The backend should check that the checkout-model is consistent
         ;; among all the `files'.
	 (model (nth 4 vc-fileset)))

    ;; If a buffer has unsaved changes, a checkout would discard those
    ;; changes, so treat the buffer as having unlocked changes.
    (when (and (not (eq model 'implicit)) (eq state 'up-to-date))
      (dolist (file files)
        (let ((buffer (get-file-buffer file)))
          (and buffer
               (buffer-modified-p buffer)
               (setq state 'unlocked-changes)))))

    ;; Do the right thing.
    (cond
     ((eq state 'ignored)
      (error "Fileset files are ignored by the version-control system"))
     ;; Fileset comes from a diff-mode buffer, see
     ;; 'diff-vc-deduce-fileset', and the buffer is the patch to apply.
     ((eq model 'patch)
      (vc-checkin files backend nil nil nil (buffer-string)))
     ((or (null state) (eq state 'unregistered))
      (cond (verbose
             (let ((backend (vc-read-backend "Backend to register to: ")))
               (vc-register (cons backend (cdr vc-fileset)))))
            (t
             (vc-register vc-fileset))))
     ((eq state 'missing)
      (vc-delete-file files))
     ;; Files are up-to-date, or need a merge and user specified a revision
     ((or (eq state 'up-to-date) (and verbose (eq state 'needs-update)))
      (cond
       (verbose
	;; Go to a different revision.
	(let* ((revision
                ;; FIXME: Provide completion.
                (read-string "Branch, revision, or backend to move to: "))
               (revision-downcase (downcase revision)))
	  (if (member
	       revision-downcase
	       (mapcar (lambda (arg) (downcase (symbol-name arg)))
                       vc-handled-backends))
	      (let ((vsym (intern-soft revision-downcase)))
		(dolist (file files) (vc-transfer-file file vsym)))
	    (dolist (file files)
              (vc-checkout file revision)))))
       ((not (eq model 'implicit))
	;; check the files out
	(dolist (file files) (vc-checkout file)))
       (t
        ;; do nothing
        (message "Fileset is up-to-date"))))
     ;; Files have local changes
     ((memq state '(added missing removed edited))
      (let ((ready-for-commit files))
	;; CVS, SVN and bzr don't care about read-only (bug#9781).
	;; RCS does, SCCS might (someone should check...).
	(when (memq backend '(RCS SCCS))
	  ;; If files are edited but read-only, give user a chance to correct.
	  (dolist (file files)
	    ;; If committing a mix of removed and edited files, the
	    ;; fileset has state = 'edited.  Rather than checking the
	    ;; state of each individual file in the fileset, it seems
	    ;; simplest to just check if the file exists.  Bug#9781.
	    (when (and (file-exists-p file) (not (file-writable-p file)))
	      ;; Make the file-buffer read-write.
	      (unless (y-or-n-p (format "%s is edited but read-only; make it writable and continue? " file))
		(error "Aborted"))
	      ;; Maybe we somehow lost permissions on the directory.
	      (condition-case nil
		  (set-file-modes file (logior (file-modes file) 128))
		(error (error "Unable to make file writable")))
	      (let ((visited (get-file-buffer file)))
		(when visited
		  (with-current-buffer visited
		    (read-only-mode -1)))))))
	;; Allow user to revert files with no changes
        ;; FIXME: This will never do anything because STATE will never
        ;; be `up-to-date' in this branch of the cond.
        ;; How did the code end up like this?  --spwhitton
	(save-excursion
          (let (to-revert)
            (dolist (file files)
              (let ((visited (get-file-buffer file)))
                ;; For files with locking, if the file does not contain
                ;; any changes, just let go of the lock, i.e. revert.
                (when (and (not (eq model 'implicit))
			   (eq state 'up-to-date)
			   ;; If buffer is modified, that means the user just
			   ;; said no to saving it; in that case, don't revert,
			   ;; because the user might intend to save after
			   ;; finishing the log entry and committing.
			   (not (and visited (buffer-modified-p visited))))
                  (push file to-revert))))
            (vc-revert-files backend to-revert)
            (setq ready-for-commit
                  (cl-nset-difference ready-for-commit to-revert))))
	;; Remaining files need to be committed
	(if (not ready-for-commit)
	    (message "No files remain to be committed")
          ;; In the case there actually are any unregistered files then
          ;; `vc-deduce-backend', via `vc-only-files-state-and-model',
          ;; has already prompted the user to approve registering them.
          ;;
          ;; FIXME: We should be able to use `vc-backend' instead of
          ;; `vc-registered' here given that `vc-deduce-backend' just
          ;; determined a state for all of the files.  However, there
          ;; are case(s) where the cached information is out-of-date.
          ;; For example, if we used C-x v v on a directory in *vc-dir*
          ;; and thereby newly registered files within that directory,
          ;; only that directory's name will have been passed to
          ;; `vc-register', and so `vc-backend' will still consider them
          ;; unregistered, even though `vc-dir-deduce-fileset' will
          ;; return `added' for their states.
	  (let ((register (cl-remove-if #'vc-registered fileset-only-files)))
            (if (not verbose)
                (vc-checkin ready-for-commit backend nil nil nil nil register)
	      (let* ((revision (read-string "New revision or backend: "))
                     (revision-downcase (downcase revision)))
                (if (member
		     revision-downcase
		     (mapcar (lambda (arg) (downcase (symbol-name arg)))
			     vc-handled-backends))
		    (let ((vsym (intern revision-downcase)))
		      (dolist (file files) (vc-transfer-file file vsym)))
		  (vc-checkin ready-for-commit backend
                              nil nil revision nil register))))))))
     ;; locked by somebody else (locking VCSes only)
     ((stringp state)
      ;; In the old days, we computed the revision once and used it on
      ;; the single file.  Then, for the 2007-2008 fileset rewrite, we
      ;; computed the revision once (incorrectly, using a free var) and
      ;; used it on all files.  To fix the free var bug, we can either
      ;; use `(car files)' or do what we do here: distribute the
      ;; revision computation among `files'.  Although this may be
      ;; tedious for those backends where a "revision" is a trans-file
      ;; concept, it is nonetheless correct for both those and (more
      ;; importantly) for those where "revision" is a per-file concept.
      ;; If the intersection of the former group and "locking VCSes" is
      ;; non-empty [I vaguely doubt it --ttn], we can reinstate the
      ;; pre-computation approach of yore.
      (dolist (file files)
        (vc-steal-lock
         file (if verbose
                  (read-string (format "%s revision to steal: " file))
                (vc-working-revision file))
         state)))
     ;; conflict
     ((eq state 'conflict)
      ;; FIXME: Is it really the UI we want to provide?
      ;; In my experience, the conflicted files should be marked as resolved
      ;; one-by-one when saving the file after resolving the conflicts.
      ;; I.e. stating explicitly that the conflicts are resolved is done
      ;; very rarely.
      (vc-mark-resolved backend files))
     ;; needs-update
     ((eq state 'needs-update)
      (dolist (file files)
	(if (yes-or-no-p (format
			  "%s is not up-to-date.  Get latest revision? "
			  (file-name-nondirectory file)))
	    (vc-checkout file t)
	  (when (and (not (eq model 'implicit))
		     (yes-or-no-p "Lock this revision? "))
	    (vc-checkout file)))))
     ;; needs-merge
     ((eq state 'needs-merge)
      (dolist (file files)
	(when (yes-or-no-p (format
			  "%s is not up-to-date.  Merge in changes now? "
			  (file-name-nondirectory file)))
	  (vc-maybe-resolve-conflicts
           file (vc-call-backend backend 'merge-news file)))))

     ;; unlocked-changes
     ((eq state 'unlocked-changes)
      (dolist (file files)
	(when (not (equal buffer-file-name file))
	  (find-file-other-window file))
	(if (save-window-excursion
	      (vc-diff-internal nil
				(cons (car vc-fileset)
                                      (cons (cadr vc-fileset) (list file)))
				(vc-working-revision file) nil)
	      (goto-char (point-min))
	      (let ((inhibit-read-only t))
		(insert
		 (format "Changes to %s since last lock:\n\n" file)))
	      (beep)
	      (yes-or-no-p (concat "File has unlocked changes.  "
				   "Claim lock retaining changes? ")))
	    (progn (vc-call-backend backend 'steal-lock file)
		   (clear-visited-file-modtime)
		   (write-file buffer-file-name)
		   (vc-mode-line file backend))
	  (if (not (yes-or-no-p
		    "Revert to checked-in revision, instead? "))
	      (error "Checkout aborted")
	    (vc-revert-buffer-internal t t)
	    (vc-checkout file)))))
     ;; Unknown fileset state
     (t
      (error "Fileset is in an unknown state %s" state)))))