New delete-revision VC backend API function (bug#79408)

* lisp/vc/vc-git.el (vc-git--assert-revision-on-branch): New
function, factored out.
(vc-git-revision-published-p): Use it.
(vc-git-delete-revision):
* lisp/vc/vc-hg.el (vc-hg-delete-revision): New functions.
* lisp/vc/vc.el: Specify delete-revision backend API function
(bug#79408).
This commit is contained in:
Sean Whitton 2025-10-27 21:12:56 +00:00
parent bf02d7e19b
commit 3dc022a023
3 changed files with 103 additions and 5 deletions

View file

@ -2208,15 +2208,19 @@ This requires git 1.8.4 or later, for the \"-L\" option of \"git log\"."
(format "Summary: %s\n(cherry picked from commit %s)\n"
comment rev))))
(defun vc-git--assert-revision-on-branch (rev branch)
"Signal an error unless REV is on BRANCH."
;; 'git branch --contains' is a porcelain command whose output could
;; change in the future.
(unless (zerop (vc-git-command nil 1 nil "merge-base"
"--is-ancestor" rev branch))
(error "Revision %s does not exist on branch %s" rev branch)))
(defun vc-git-revision-published-p (rev)
"Whether we think REV has been pushed such that it is public history.
Considers only the current branch. Does not fetch."
(let ((branch (vc-git--current-branch)))
;; 'git branch --contains' is a porcelain command whose output could
;; change in the future.
(unless (zerop (vc-git-command nil 1 nil "merge-base"
"--is-ancestor" rev branch))
(error "Revision %s does not exist on branch %s" rev branch))
(vc-git--assert-revision-on-branch rev branch)
(and
;; BRANCH has an upstream.
(with-temp-buffer
@ -2311,6 +2315,23 @@ Rebase may --autosquash your other squash!/fixup!/amend!; proceed?")))
(vc-git-command nil 0 nil "rebase" "--autostash" "--autosquash" "-i"
(format "%s~1" rev))))
(defun vc-git-delete-revision (rev)
"Rebase current branch to remove REV."
(vc-git--assert-revision-on-branch rev (vc-git--current-branch))
(with-temp-buffer
(vc-git-command t 0 nil "log" "--merges" (format "%s~1.." rev))
(unless (bobp)
(error "There have been merges since %s; cannot delete revision"
rev)))
(unless (zerop (vc-git-command nil 1 nil "rebase"
rev "--onto" (format "%s~1" rev)))
;; FIXME: Ideally we would leave some sort of conflict for the user
;; to resolve, instead of just giving up. We would want C-x v v to
;; do 'git rebase --continue' like how it can currently be used to
;; conclude a merge after resolving conflicts.
(vc-git-command nil 0 nil "rebase" "--abort")
(error "Merge conflicts while trying to delete %s; aborting" rev)))
(defvar vc-git-extra-menu-map
(let ((map (make-sparse-keymap)))
(define-key map [git-grep]

View file

@ -1839,6 +1839,73 @@ Always has to fetch, like `vc-hg-incoming-revision' does."
(vc-hg-command t 0 nil "log" (format "--rev=outgoing() and %s" rev))
(bobp)))
(defun vc-hg-delete-revision (rev)
"Use `hg histedit' to delete REV from the history of the current branch.
`hg histedit' will fail unless
- REV is an ancestor of the working directory;
- all commits back to REV are not yet public; and
- there aren't any merges in the history to be edited."
(with-temp-buffer
;; Resolve REV to a full changeset hash.
(vc-hg-command t 0 nil "log" "--limit=1"
(format "--rev=%s" rev) "--template={node}")
(when (bobp)
;; If REV is not found, hg exits 255, so this should never happen.
(error "'hg log' unexpectedly gave no output"))
(setq rev (buffer-string)))
(let ((repo default-directory)
(commands (make-nearby-temp-file "hg-commands")))
(unwind-protect
(let ((coding-system-for-write
;; On MS-Windows, we must encode command-line arguments
;; in the system codepage.
(if (eq system-type 'windows-nt)
locale-coding-system
coding-system-for-write)))
(with-temp-file commands
(let ((default-directory repo))
(vc-hg-command t 0 nil "log" (format "--rev=.:%s" rev)
"--template=pick {node}\n"))
(goto-char (point-min))
(unless (re-search-forward (format "^pick %s\n\\'" rev) nil t)
(error "'hg log' output parse failure"))
(replace-match (format "drop %s\n" rev)))
(unless (zerop
(vc-hg-command
nil 1 nil
"--config=extensions.histedit="
;; Without this, --commands is ignored and histedit
;; starts a curses interface. (Actually redundant
;; with the HGPLAIN=1 set by vc-hg--command-1.)
"--config=ui.interface=text"
;; Request validation of the commands file:
;; stop if any commits in the history back to REV
;; are missing, somehow.
"--config=histedit.dropmissing=False"
;; Prevent Mercurial trying to open Meld or similar.
;; FIXME: According to
;; <https://repo.mercurial-scm.org/hg/help/merge-tools>,
;; this can be overridden by user's "merge-patterns"
;; settings.
"--config=ui.merge=internal:fail"
"histedit"
(format "--rev=%s" rev) "--commands" commands))
;; FIXME: Ideally we would leave some sort of conflict for
;; the user to resolve, instead of just giving up.
;; We would want C-x v v to do 'hg histedit --continue' like
;; how it can currently be used to conclude a merge after
;; resolving conflicts.
(vc-hg-command nil 0 nil "--config=extensions.histedit="
"histedit" "--abort")
(error "Merge conflicts while trying to delete %s; aborting"
rev)))
(delete-file commands))))
(provide 'vc-hg)
;;; vc-hg.el ends here

View file

@ -398,6 +398,16 @@
;; Relocate the working tree, assumed to be one that uses the same
;; backing repository as this working tree, at FROM to TO.
;; Callers must ensure that FROM is not the current working tree.
;;
;; - delete-revision (rev)
;;
;; Remove REV from the revision history of the current branch.
;; For a distributed VCS, this means a rebase operation to rewrite the
;; history of the current branch so that it no longer contains REV (or
;; its changes). For a centralized VCS this may mean something
;; different; for example CVS has true undos (not yet implemented in
;; Emacs). A distributed VCS that implements this must also implement
;; revision-published-p.
;; HISTORY FUNCTIONS
;;