1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-05 22:20:24 -08:00

New commands to rewind decentralized VCS branches

* lisp/vc/vc.el (vc--remove-revisions-from-end): New function.
(vc-uncommit-revisions-from-end, vc-delete-revisions-from-end):
* lisp/vc/log-view.el (log-view-uncommit-revisions-from-end)
(log-view-delete-revisions-from-end): New commands (bug#79408).
(log-view-mode-map): Bind them.
* doc/emacs/maintaining.texi (VC Change Log):
* doc/emacs/vc1-xtra.texi (VC Auto-Reverting):
* etc/NEWS: Document them.
This commit is contained in:
Sean Whitton 2025-11-21 11:22:30 +00:00
parent dcc909917b
commit 11b68c6223
7 changed files with 194 additions and 2 deletions

View file

@ -877,6 +877,7 @@ Miscellaneous Commands and Features of VC
* Editing VC Commands:: Editing the VC shell commands that Emacs will run.
* Preparing Patches:: Preparing and composing patches from within VC.
* VC Auto-Reverting:: Updating buffer contents after VCS operations.
* Rewinding Branches:: Commands to delete revisions from ends of branches.
Customizing VC

View file

@ -1273,6 +1273,15 @@ the revision at point, or the changes from all marked revisions
@item R
Undo the effects of old revisions; either the revision at point, or all
marked revisions (@code{log-view-revert-or-delete-revisions}).
@item x
Delete revisions newer than the revision at point from the current
branch without touching the working tree
(@code{log-view-uncommit-revisions-from-end}).
@item X
Delete revisions newer than the revision at point from the current
branch (@code{log-view-delete-revisions-from-end}).
@end table
@vindex vc-log-show-limit

View file

@ -21,6 +21,7 @@
* Editing VC Commands:: Editing the VC shell commands that Emacs will run.
* Preparing Patches:: Preparing and composing patches from within VC.
* VC Auto-Reverting:: Updating buffer contents after VCS operations.
* Rewinding Branches:: Commands to delete revisions from ends of branches.
@end menu
@node Change Logs and VC
@ -678,6 +679,60 @@ contents, regardless of whether Emacs initiated those operations.
@xref{VC Mode Line}, for details regarding Auto Revert mode in buffers
visiting tracked files (which is what @code{vc-auto-revert-mode} enables).
@node Rewinding Branches
@subsubsection Rewinding Branches
@cindex rewinding a branch (VC)
@table @kbd
@item M-x vc-delete-revisions-from-end
Delete revisions from the end of the current branch.
@item M-x vc-uncommit-revisions-from-end
Delete revisions from the end of the current branch without touching the
working tree.
@end table
@findex vc-delete-revisions-from-end
For decentralized version control systems (@pxref{VCS Repositories}),
these commands provide ways to move the current branch back to an
earlier revision. @code{vc-delete-revisions-from-end} prompts for a
revision, then removes all revisions from the end of the branch up to
but not including the specified revision. We say that the branch is
@dfn{rewound} back to the specified revision.
This command removes the changes made by the revisions from the
working tree. Therefore, if there are any uncommitted changes, they
must be reverted, first (@pxref{VC Undo}). This command will prompt you
to do that if necessary. If you supply a prefix argument, Emacs will
delete uncommitted changes without prompting.
@cindex uncommitting revisions
@findex vc-uncommit-revisions-from-end
To ``uncommit'' a revision means to remove it from the revision
history without removing its changes from the working tree. It is as
though you had made the changes but had not yet checked them in. The
command @code{vc-uncommit-revisions-from-end} prompts for a revision,
and then uncommits all revisions from the end of the branch up to but
not including the specified revision. The branch is rewound back to the
specified revision but the changes are left behind in the working tree.
When rewinding the current branch, if all the revisions deleted from
the revision history are among those you have pulled or pushed, then
these operations do not permanently delete anything: a simple
@w{@kbd{C-x v +}} (@pxref{Pulling / Pushing}) will bring the revisions
back. On the other hand, if there are new revisions on the end of the
branch that have not yet been pushed, then these commands will delete
them permanently. Emacs tries to detect this situation and ask you if
you are sure you want to delete them.
Alternative ways to access this functionality are the
@code{log-view-uncommit-revisions-from-end} and
@code{log-view-delete-revisions-from-end} commands, bound to @kbd{x} and
@kbd{X}, respectively, in Log View mode buffers (@pxref{VC Change Log}).
Compared to using the commands described here directly, the Log View
mode commands can make it easier to be sure you are rewinding back to
the revision you intend.
@node Customizing VC
@subsection Customizing VC

View file

@ -2393,6 +2393,13 @@ From Log View buffers, you can use 'C' to cherry-pick the revision at
point or all marked revisions, and 'R' to undo the revision at point or
all marked revisions.
+++
*** New commands to rewind branches.
In Log View mode, 'x' deletes revisions newer than the revision at point
from the history of the current branch, though without undoing the
changes made by those revisions to the working tree. 'X' is similar
except that it does remove the changes from the working tree.
*** New command 'log-edit-done-strip-cvs-lines'.
This command strips all lines beginning with "CVS:" from the buffer.
It is intended to be added to the 'log-edit-done-hook' so that

View file

@ -2235,7 +2235,8 @@ With a prefix argument, try to REVERSE the hunk."
This command is useful in buffers generated by \\[vc-diff] and \\[vc-root-diff],
especially when preparing to commit the patch with \\[vc-next-action].
You can use \\<diff-mode-map>\\[diff-hunk-kill] to temporarily remove changes that you intend to
You can use \\<diff-mode-map>\\[diff-hunk-kill] \
to temporarily remove changes that you intend to
include in a separate commit or commits, and you can use this command
to permanently drop changes you didn't intend, or no longer want.

View file

@ -115,6 +115,7 @@
(autoload 'vc-find-revision "vc")
(autoload 'vc-diff-internal "vc")
(autoload 'vc--pick-or-revert "vc")
(autoload 'vc--remove-revisions-from-end "vc")
(autoload 'vc--prompt-other-working-tree "vc")
(defvar cvs-minor-wrap-function)
@ -142,7 +143,9 @@
"TAB" #'log-view-msg-next
"<backtab>" #'log-view-msg-prev
"C" #'log-view-cherry-pick
"R" #'log-view-revert-or-delete-revisions)
"R" #'log-view-revert-or-delete-revisions
"x" #'log-view-uncommit-revisions-from-end
"X" #'log-view-delete-revisions-from-end)
(easy-menu-define log-view-mode-menu log-view-mode-map
"Log-View Display Menu."
@ -828,6 +831,36 @@ See also `vc-revert-or-delete-revision'."
t current-prefix-arg))
(log-view--pick-or-revert directory nil t interactive delete))
(defun log-view-uncommit-revisions-from-end ()
"Uncommit revisions newer than the revision at point.
The revision at point must be on the current branch. The newer
revisions are deleted from the revision history but the changes made by
those revisions to files in the working tree are not undone.
To delete revisions from the revision history and also undo the changes
in the working tree, see `log-edit-delete-revisions-from-end'."
(interactive)
(vc--remove-revisions-from-end (log-view-current-tag)
nil t log-view-vc-backend)
(revert-buffer))
(defun log-view-delete-revisions-from-end (&optional discard)
"Delete revisions newer than the revision at point.
The revision at point must be on the current branch. The newer
revisions are deleted from the revision history and the changes made by
those revisions to files in the working tree are undone.
If the are uncommitted changes, prompts to discard them.
With a prefix argument (when called from Lisp, with optional argument
DISCARD non-nil), discard any uncommitted changes without prompting.
To delete revisions from the revision history without undoing the
changes in the working tree, see `log-edit-uncommit-revisions-from-end'."
(interactive "P")
(vc--remove-revisions-from-end (log-view-current-tag)
(if discard 'discard t)
t log-view-vc-backend)
(revert-buffer))
;; These are left unbound by default. A user who doesn't like the DWIM
;; behavior of `log-view-revert-or-delete-revisions' can unbind that and
;; bind these two commands instead.

View file

@ -2429,6 +2429,92 @@ with a prefix argument."
(interactive (list (vc-read-revision "Revision to delete: ")))
(vc--pick-or-revert rev t nil t nil nil backend))
(defun vc--remove-revisions-from-end (rev delete prompt backend)
"Delete revisions newer than REV.
DELETE non-nil means to remove the changes from the working tree.
DELETE `discard' means to silently discard uncommitted changes.
PROMPT non-nil means to always get confirmation. (This is passed by
`log-view-uncommit-revisions-from-end' and `log-view-delete-revisions'
because they have single-letter bindings and don't otherwise prompt, so
might be easy to use accidentally.)
BACKEND is the VC backend."
(let ((backend (or backend (vc-responsible-backend default-directory))))
(unless (eq (vc-call-backend backend 'revision-granularity)
'repository)
(error "Requires VCS with whole-repository revision granularity"))
(unless (vc-find-backend-function backend 'revision-published-p)
(signal 'vc-not-supported (list 'revision-published-p backend)))
;; Rewinding the end of the branch to REV does not in itself mean
;; rewriting public history because a subsequent pull will generally
;; undo the rewinding. Rewinding and then making new commits before
;; syncing with the upstream will necessitate merging, but that's
;; just part of the normal workflow with a distributed VCS.
;; Therefore we don't prompt about deleting published revisions (and
;; so we ignore `vc-allow-rewriting-published-history').
;; We do care about deleting *unpublished* revisions, however,
;; because that could potentially mean losing work permanently.
(when (if (vc-call-backend backend 'revision-published-p
(vc-call-backend backend
'working-revision-symbol))
(and prompt
(not (y-or-n-p
(format "Uncommit revisions newer than %s?"
rev))))
;; FIXME: Actually potentially not all revisions newer than
;; REV would be permanently deleted -- only those which are
;; unpushed. So this prompt is a little misleading.
(not (yes-or-no-p
(format "Permanently delete revisions newer than %s?"
rev))))
(user-error "Aborted"))
(if delete
;; FIXME: As discussed in bug#79408, instead of just failing if
;; the user declines reverting the changes, we would leave
;; behind some sort of conflict for the user to resolve, like we
;; do when there is a merge conflict.
(let ((root (vc-root-dir)))
(when (vc-dir-status-files root nil backend)
(if (eq delete 'discard)
(vc-revert-file root)
(let ((vc-buffer-overriding-fileset `(,backend (,root))))
(vc-revert))))
(vc-call-backend backend 'delete-revisions-from-end rev))
(vc-call-backend backend 'uncommit-revisions-from-end rev))))
;;;###autoload
(defun vc-uncommit-revisions-from-end (rev &optional backend)
"Delete revisions newer than REV without touching the working tree.
REV must be on the current branch. The newer revisions are deleted from
the revision history but the changes made by those revisions to files in
the working tree are not undone.
When called interactively, prompts for REV.
BACKEND is the VC backend.
To delete revisions from the revision history and also undo the changes
in the working tree, see `vc-delete-revisions-from-end'."
(interactive (list
(vc-read-revision "Uncommit revisions newer than revision: ")))
(vc--remove-revisions-from-end rev nil nil backend))
;;;###autoload
(defun vc-delete-revisions-from-end (rev &optional discard backend)
"Delete revisions newer than REV.
REV must be on the current branch. The newer revisions are deleted from
the revision history and the changes made by those revisions to files in
the working tree are undone.
When called interactively, prompts for REV.
If the are uncommitted changes, prompts to discard them.
With a prefix argument (when called from Lisp, with optional argument
DISCARD non-nil), discard any uncommitted changes without prompting.
BACKEND is the VC backend.
To delete revisions from the revision history without undoing the
changes in the working tree, see `vc-uncommit-revisions-from-end'."
(interactive (list
(vc-read-revision "Delete revisions newer than revision: ")
current-prefix-arg))
(vc--remove-revisions-from-end rev (if discard 'discard t) nil backend))
(declare-function diff-bounds-of-hunk "diff-mode")
(defun vc-default-checkin-patch (_backend patch-string comment)