diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index d8776fcc249..a7119327df3 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -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] diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index 446f0c63d1e..cf1653a9051 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -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 + ;; , + ;; 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 diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index b66a1121734..c76511fac2f 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -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 ;;