commit 9ccef794a8ed55a8f96e68c0dd1e53cb07e85baa (HEAD, refs/remotes/origin/master) Author: Sean Whitton Date: Thu Oct 2 21:48:31 2025 +0100 VC: New commands for cherry-picking (bug#79408) * lisp/vc/diff-mode.el (diff-buffer-file-names): New function. * lisp/vc/log-view.el (vc--pick-or-revert) (vc--prompt-other-working-tree): Autoload. (vc-parent-buffer-name, vc-log-short-style) (vc-print-log-internal): Declare. (log-view--pick-or-revert): New function. (log-view-revision-cherry-pick, log-view-revision-revert): New commands. (log-view-mode-map, log-view-mode-menu): Bind them. * lisp/vc/vc-dispatcher.el (vc-start-logentry): If get-file-buffer returns nil, use the current buffer as the parent buffer. * lisp/vc/vc.el (diff-buffer-file-names, diff-reverse-direction): Declare. (vc--pick-or-revert): New function. (vc-revision-cherry-pick, vc-revision-revert): New commands. * lisp/vc/vc-hooks.el (vc-menu-map): Bind them. * doc/emacs/maintaining.texi (VC Change Log, VC Undo) (Copying Between Branches): * etc/NEWS: Document the new commands. diff --git a/doc/emacs/emacs.texi b/doc/emacs/emacs.texi index b32c704bd12..7a5107ee359 100644 --- a/doc/emacs/emacs.texi +++ b/doc/emacs/emacs.texi @@ -857,10 +857,11 @@ VC Directory Mode Version Control Branches -* Switching Branches:: How to get to another existing branch. -* Pulling / Pushing:: Receiving/sending changes from/to elsewhere. -* Merging:: Transferring changes between branches. -* Creating Branches:: How to start a new branch. +* Switching Branches:: How to get to another existing branch. +* Pulling / Pushing:: Receiving/sending changes from/to elsewhere. +* Merging:: Transferring changes between branches. +* Creating Branches:: How to start a new branch. +* Copying Between Branches:: Copying the changes made by revisions. @ifnottex Miscellaneous Commands and Features of VC diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index e7a05a3556b..9d768cc1a77 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1264,6 +1264,15 @@ Unmark the entry at point (@code{log-view-unmark-entry}). @item U Unmark all marked entries (@code{log-view-unmark-all-entries}). + +@item C +Copy changes to a currently checked out branch; either the changes from +the revision at point, or the changes from all marked revisions +(@code{log-view-revision-cherry-pick}). + +@item R +Undo the effects of old revisions; either the revision at point, or all +marked revisions (@code{log-view-revision-revert}). @end table @vindex vc-log-show-limit @@ -1307,6 +1316,9 @@ also prompt for a specific VCS shell command to run for this purpose. @item C-x v u Revert the work file(s) in the current VC fileset to the last revision (@code{vc-revert}). + +@item M-x vc-revision-revert +Undo the effects of an older commit. @end table @kindex C-x v u @@ -1330,6 +1342,21 @@ unlocked; you must lock again to resume editing. You can also use @kbd{C-x v u} to unlock a file if you lock it and then decide not to change it. +@findex vc-revision-revert +@cindex reverting commits + To discard changes that have already been committed, by yourself or +someone else, you can use @w{@kbd{M-x vc-revision-revert}}. This is +called @dfn{reverting} a commit. The command prompts for a revision to +revert, and then the VC backend reverts it. Most backends implement +this by making a new commit which undoes the changes made by the +revision. + + An alternative way to access this functionality is to the +@code{log-view-revision-revert} command, bound to @kbd{R} in Log View +mode buffers (@pxref{VC Change Log}). Compared to using @w{@kbd{M-x +vc-revision revert}} directly, this can make it easier to be sure you +are reverting the revision you intend. + @node VC Ignore @subsection Ignore Version Control Files @@ -1642,10 +1669,11 @@ supports checking out different branches and committing into new or different branches. @menu -* Switching Branches:: How to get to another existing branch. -* Pulling / Pushing:: Receiving/sending changes from/to elsewhere. -* Merging:: Transferring changes between branches. -* Creating Branches:: How to start a new branch. +* Switching Branches:: How to get to another existing branch. +* Pulling / Pushing:: Receiving/sending changes from/to elsewhere. +* Merging:: Transferring changes between branches. +* Creating Branches:: How to start a new branch. +* Copying Between Branches:: Copying the changes made by revisions. @end menu @node Switching Branches @@ -1857,6 +1885,51 @@ revision. on that branch. To leave the branch, you must explicitly select a different revision with @kbd{C-u C-x v v}. +@node Copying Between Branches +@subsubsection Copying Changes Made By Revisions Between Branches + +@table @kbd +@item M-x vc-revision-cherry-pick +Copy a single revision to branch checked out in this working tree. +@end table + +@cindex cherry-pick +@cindex revision, cherry-picks of + Sometimes it is useful to copy a revision from one branch to another. +This means creating a new revision with the same changes, log message +and authorship information as an existing revision that can be found on +another branch. This is often called @dfn{cherry-picking} the revision +from one branch to another. + +@cindex revisions, backporting + The most common case is copying a revision from a branch that won't be +merged (@pxref{Merging}) into your current branch. For example, your +project might have a feature-frozen branch that accepts only bug fixes. +Someone (possibly you) fixes a bug on the main development branch. You +can then cherry-pick that revision onto the feature-frozen branch in +order to fix the bug there, too. This is called @dfn{backporting} the +revision, or backporting the fix. + +@findex vc-revision-cherry-pick + You can use the command @kbd{M-x vc-revision-cherry-pick} to +cherry-pick revisions. It prompts for a revision to cherry-pick. It +then pops up a buffer for you to edit the log message for the new +revision. Normally the VC backend generates a log message including a +reference to the revision you want to copy, so that the copy can be +traced. If you wish, you can delete this reference before typing +@kbd{C-c C-c} to conclude the cherry-pick. + + Alternatively you can invoke the command with a prefix argument, +i.e. @w{@kbd{C-u M-x vc-revision-cherry-pick}}. In this case the log +message from the source revision is used unmodified, and the cherry-pick +happens immediately, without popping up a buffer for log message edits. + + An alternative way to access this functionality is the +@code{log-view-revision-cherry-pick} command, bound to @kbd{C} in Log +View mode buffers (@pxref{VC Change Log}). Compared to using +@w{@kbd{M-x vc-revision-cherry-pick}} directly, this can make it easier +to be sure you are cherry-picking the revision you intend. + @ifnottex @include vc1-xtra.texi @end ifnottex diff --git a/etc/NEWS b/etc/NEWS index 8cabafd57cf..09497e499ab 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2227,6 +2227,14 @@ after VCS operations, the new mode is a more reliable way to ensure that Emacs reverts buffers visiting tracked files when VCS operations change the contents of those files. ++++ +*** New commands to cherry-pick and revert revisions. +The commands 'vc-revision-cherry-pick' and 'vc-revision-revert' let you +copy revisions between branches, and revert revisions. +From Log View buffers, you can use 'C' to cherry-pick the revision at +point or all marked revisions, and 'R' to revert the revision at point +or all marked revisions. + *** 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 diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el index f207f87811c..2fb552597fd 100644 --- a/lisp/vc/diff-mode.el +++ b/lisp/vc/diff-mode.el @@ -1175,6 +1175,21 @@ PREFIX is only used internally: don't use it." (cons (cons fs file) diff-remembered-files-alist))) file))))))) +(defun diff-buffer-file-names (&optional old noprompt) + "Return file names corresponding to all of this buffer's hunks. +Optional arguments OLD and NOPROMPT are passed on to +`diff-find-file-name', which see." + (save-excursion + (cl-loop initially + (goto-char (point-min)) + (ignore-errors (diff-file-next)) + when (and (looking-at diff-file-header-re) + (diff-find-file-name old noprompt)) + collect it + until (eq (prog1 (point) + (ignore-errors (diff-file-next))) + (point))))) + (defun diff-ediff-patch () "Call `ediff-patch-file' on the current buffer." diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index 68ce4f1baa5..0e2d506a23a 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -114,6 +114,8 @@ (require 'log-edit) (autoload 'vc-find-revision "vc") (autoload 'vc-diff-internal "vc") +(autoload 'vc--pick-or-revert "vc") +(autoload 'vc--prompt-other-working-tree "vc") (defvar cvs-minor-wrap-function) (defvar cvs-force-command) @@ -138,7 +140,9 @@ "p" #'log-view-msg-prev "w" #'log-view-copy-revision-as-kill "TAB" #'log-view-msg-next - "" #'log-view-msg-prev) + "" #'log-view-msg-prev + "C" #'log-view-revision-cherry-pick + "R" #'log-view-revision-revert) (easy-menu-define log-view-mode-menu log-view-mode-map "Log-View Display Menu." @@ -161,6 +165,11 @@ ["Toggle Details at Point" log-view-toggle-entry-display :active log-view-expanded-log-entry-function] "-----" + ["Cherry-Pick Revision(s)" log-view-revision-cherry-pick + :help "Copy changes from revision(s) to a branch"] + ["Revert Revision(s)" log-view-revision-revert + :help "Undo the effects of old revision(s)"] + "-----" ["Next Log Entry" log-view-msg-next :help "Go to the next count'th log message"] ["Previous Log Entry" log-view-msg-prev @@ -686,9 +695,114 @@ If called interactively, annotate the version at point." (log-view-current-tag) nil nil nil log-view-vc-backend))) -;; -;; diff -;; +;;;; +;;;; Cherry-picks and reverts +;;;; + +(defvar vc-parent-buffer-name) +(defvar vc-log-short-style) +(declare-function vc-print-log-internal "vc") + +(defun log-view--pick-or-revert (directory no-comment reverse) + "Copy changes from revision at point or all marked revisions. +DIRECTORY is the destination, the root of the target working tree. +NO-COMMENT non-nil means use the log messages of the revisions +unmodified, instead of using the backend's default cherry-pick comment +for that revision. +NO-COMMENT non-nil with zero or one revisions marked also means don't +prompt to edit the log message. +REVERSE non-nil means to make commit(s) undoing the effects of the +revisions, instead." + (let ((default-directory directory) + (marked (log-view-get-marked))) + (if (length> marked 1) + (progn + (save-excursion + (dolist (rev (if reverse (reverse marked) marked)) + ;; Unmark each revision *before* copying it. + ;; Then if there is a conflict such that a cherry-pick + ;; fails, after resolving that conflict and committing the + ;; cherry-pick, the right revisions will be marked to + ;; resume the original multiple cherry-pick operation. + (log-view-goto-rev rev) + (log-view-unmark-entry 1) + (vc--pick-or-revert rev + reverse + (if no-comment + (vc-call-backend log-view-vc-backend + 'get-change-comment + nil rev) + t) + nil + log-view-vc-backend))) + (when (vc-find-backend-function log-view-vc-backend + 'modify-change-comment) + (let (vc-log-short-style) + (vc-print-log-internal log-view-vc-backend + (list default-directory) + nil nil (length marked))) + (setq-local vc-log-short-style nil ; For \\`g'. + vc-parent-buffer-name nil) + (message (substitute-command-keys "Use \ +\\[log-view-modify-change-comment] to modify any of these messages")))) + (let ((rev (or (car marked) (log-view-current-tag)))) + (vc--pick-or-revert rev + reverse + (and no-comment + (vc-call-backend log-view-vc-backend + 'get-change-comment + nil rev)) + nil + log-view-vc-backend))))) + +(defun log-view-revision-cherry-pick (directory &optional no-comment) + "Copy changes from revision at point to current branch. +If there are marked revisions, use those instead of the revision at point. + +When called interactively, prompts for the target working tree to which +to copy the revision(s); the current working tree is the default choice. +When called from Lisp, DIRECTORY is the root of the target working tree. + +When copying a single revision, prompts for editing the log message for +the new commit, except with optional argument NO-COMMENT non-nil +(interactively, with a prefix argument). +When copying multiple revisions, never prompts to edit log messages. + +Normally a log message for each new commit is generated by the backend, +including references to the source commits so that the copy can be +traced. With optional argument NO-COMMENT non-nil (interactively, with +a prefix argument), use the log messages from the source revisions +unmodified. + +See also `vc-revision-cherry-pick'." + (interactive + (list (vc--prompt-other-working-tree log-view-vc-backend + "Cherry-pick to working tree" + 'allow-empty) + current-prefix-arg)) + (log-view--pick-or-revert directory no-comment nil)) + +(defun log-view-revision-revert (directory) + "Undo the effects of the revision at point. +When revisions are marked, undo the effects of each of them. +When called interactively, prompts for the target working tree in which +to revert; the current working tree is the default choice. +When called from Lisp, DIRECTORY is the root of the target working tree. + +When reverting a single revision, prompts for editing the log message +for the new commit. +When reverting multiple revisions, never prompts to edit log messages. + +See also `vc-revision-revert'." + (interactive (list (vc--prompt-other-working-tree + (vc-responsible-backend default-directory) + "Revert in working tree" + 'allow-empty))) + (log-view--pick-or-revert directory nil t)) + +;;;; +;;;; diff +;;;; (defun log-view-diff (beg end) "Get the diff between two revisions. diff --git a/lisp/vc/vc-dispatcher.el b/lisp/vc/vc-dispatcher.el index c4ad148bdf1..78173786705 100644 --- a/lisp/vc/vc-dispatcher.el +++ b/lisp/vc/vc-dispatcher.el @@ -836,12 +836,10 @@ AFTER-HOOK specifies the local value for `vc-log-after-operation-hook'. BACKEND, if non-nil, specifies a VC backend for the Log Edit buffer. PATCH-STRING is a patch to check in. DIFF-FUNCTION is `log-edit-diff-function' for the Log Edit buffer." - (let ((parent (if (and (length= files 1) - (not (vc-dispatcher-browsing))) - (get-file-buffer (car files)) - (current-buffer)))) - (unless parent - (error "Unable to determine VC parent buffer")) + (let ((parent (or (and (length= files 1) + (not (vc-dispatcher-browsing)) + (get-file-buffer (car files))) + (current-buffer)))) (if (and comment (not initial-contents)) (set-buffer (get-buffer-create logbuf)) (pop-to-buffer (get-buffer-create logbuf))) diff --git a/lisp/vc/vc-hooks.el b/lisp/vc/vc-hooks.el index 2972f139d06..48e84d6aee1 100644 --- a/lisp/vc/vc-hooks.el +++ b/lisp/vc/vc-hooks.el @@ -1010,11 +1010,16 @@ other commands receive global bindings where they had none before." (defvar vc-menu-map (let ((map (make-sparse-keymap "Version Control"))) - ;;(define-key map [show-files] - ;; '("Show Files under VC" . (vc-directory t))) + (define-key map [vc-revision-revert] + '(menu-item "Revert Revision" vc-revision-revert + :help "Undo the effects of a revision")) + (define-key map [vc-revision-cherry-pick] + '(menu-item "Cherry-Pick Revision" vc-revision-cherry-pick + :help "Copy the changes from a single revision to this branch")) + (define-key map [separator1] menu-bar-separator) (define-key map [vc-retrieve-tag] - '(menu-item "Retrieve Tag" vc-retrieve-tag - :help "Retrieve tagged version or branch")) + '(menu-item "Retrieve Tag" vc-retrieve-tag + :help "Retrieve tagged version or branch")) (define-key map [vc-create-tag] '(menu-item "Create Tag" vc-create-tag :help "Create version tag")) @@ -1027,7 +1032,7 @@ other commands receive global bindings where they had none before." (define-key map [vc-create-branch] '(menu-item "Create Branch..." vc-create-branch :help "Make a new branch")) - (define-key map [separator1] menu-bar-separator) + (define-key map [separator2] menu-bar-separator) (define-key map [vc-annotate] '(menu-item "Annotate" vc-annotate :help "Display the edit history of the current file using colors")) @@ -1058,7 +1063,7 @@ other commands receive global bindings where they had none before." (define-key map [vc-print-root-log] '(menu-item "Show Top of the Tree History " vc-print-root-log :help "List the change log for the current tree in a window")) - (define-key map [separator2] menu-bar-separator) + (define-key map [separator3] menu-bar-separator) (define-key map [vc-insert-header] '(menu-item "Insert Header" vc-insert-headers :help "Insert headers into a file for use with a version control system.")) diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 55606f29bea..7c28e4092dd 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -2006,9 +2006,15 @@ Type \\[vc-next-action] to check in changes.") (files backend &optional comment initial-contents rev patch-string register) "Check in FILES. -COMMENT is a comment string; if omitted, a buffer is popped up to accept -a comment. If INITIAL-CONTENTS is non-nil, then COMMENT is used as the -initial contents of the log entry buffer. +There are three calling conventions for the COMMENT and INITIAL-CONTENTS +optional arguments: +- COMMENT a string, INITIAL-CONTENTS nil means use that comment string + without prompting the user to edit it. +- COMMENT a string, INITIAL-CONTENTS non-nil means use that comment + string as the initial contents of the log entry buffer but stop for + editing. +- COMMENT t means check in immediately with an empty comment, and ignore + INITIAL-CONTENTS. The optional argument REV may be a string specifying the new revision level (only supported for some older VCSes, like RCS and CVS). @@ -2105,6 +2111,120 @@ have changed; continue with old fileset?" (current-buffer)))) backend patch-string))) +(declare-function diff-buffer-file-names "diff-mode") +(declare-function diff-reverse-direction "diff-mode") + +(defun vc--pick-or-revert (rev reverse comment initial-contents backend) + "Copy a single revision REV to branch checked out in this working tree. +REVERSE means to undo the effects of REV, instead. +COMMENT is a comment string; if omitted, a buffer is popped up to accept +a comment. If INITIAL-CONTENTS is non-nil, then COMMENT is used as the +initial contents of the log entry buffer. If COMMENT is t then use +BACKEND's default cherry-pick comment for REV without prompting. +BACKEND is the VC backend to use." + (let* ((backend (or backend (vc-responsible-backend default-directory))) + ;; `vc-*-prepare-patch' will always give us a patch with file + ;; names relative to the VC root, so switch to there now. + ;; In particular this is needed for `diff-buffer-file-names' to + ;; work properly. + (default-directory (vc-call-backend backend 'root default-directory)) + (patch (vc-call-backend backend 'prepare-patch rev)) + files whole-patch-string diff-patch-string) + (with-current-buffer (plist-get patch :buffer) + (diff-mode) + (with-restriction + (or (plist-get patch :patch-start) (point-min)) + (or (plist-get patch :patch-end) (point-max)) + (when reverse + (diff-reverse-direction (point-min) (point-max))) + (setq files (diff-buffer-file-names nil t) + diff-patch-string (buffer-string))) + ;; In the case of reverting we mustn't copy the original + ;; authorship information. The author of the revert is the + ;; current user, and its timestamp is now. + (setq whole-patch-string + (if reverse diff-patch-string (buffer-string)))) + (unless (stringp comment) + (cl-psetq comment (vc-call-backend backend 'cherry-pick-comment + files rev reverse) + initial-contents (not (eq comment t)))) + (vc-start-logentry files comment initial-contents + (format "Edit log message for %s revision." + (if reverse + "new" + ;; ^ "reverted revision" would mean + ;; REV, not the revision we are about + ;; to create. We could use + ;; "reverting revision" but it reads + ;; oddly. + "copied")) + "*vc-cherry-pick*" + (lambda () + (vc-call-backend backend 'log-edit-mode)) + (lambda (_files comment) + (vc-call-backend backend 'checkin-patch + whole-patch-string comment)) + nil + backend + diff-patch-string))) + +;; No bindings in `vc-prefix-map' for the following two commands because +;; we expect users will usually use `log-view-revision-cherry-pick' and +;; `log-view-revision-revert', which do have bindings. + +;;;###autoload +(defun vc-revision-cherry-pick (rev &optional comment initial-contents backend) + "Copy the changes from a single revision REV to the current branch. +When called interactively, prompts for REV. +Typically REV is a revision from another branch, where that branch is +one that will not be merged into the branch checked out in this working +tree. + +Normally a log message for the new commit is generated by the backend +and includes a reference to REV so that the copy can be traced. +When called interactively with a prefix argument, use REV's log message +unmodified, and also skip editing it. + +When called from Lisp, there are three calling conventions for the +COMMENT and INITIAL-CONTENTS optional arguments: +- COMMENT a string, INITIAL-CONTENTS nil means use that comment string + without prompting the user to edit it. +- COMMENT a string, INITIAL-CONTENTS non-nil means use that comment + string as the initial contents of the log entry buffer but stop for + editing. +- COMMENT t means use BACKEND's default cherry-pick comment for REV + without prompting for editing, and ignore INITIAL-CONTENTS. + +Optional argument BACKEND is the VC backend to use." + (interactive (let ((rev (vc-read-revision "Revision to copy: ")) + (backend (vc-responsible-backend default-directory))) + (list rev + (and current-prefix-arg + (vc-call-backend backend 'get-change-comment + nil rev)) + nil + backend))) + (vc--pick-or-revert rev nil comment initial-contents backend)) + +;;;###autoload +(defun vc-revision-revert (rev &optional comment initial-contents backend) + "Undo the effects of revision REV. +When called interactively, prompts for REV. + +When called from Lisp, there are three calling conventions for the +COMMENT and INITIAL-CONTENTS optional arguments: +- COMMENT a string, INITIAL-CONTENTS nil means use that comment string + without prompting the user to edit it. +- COMMENT a string, INITIAL-CONTENTS non-nil means use that comment + string as the initial contents of the log entry buffer but stop for + editing. +- COMMENT t means use BACKEND's default revert comment for REV without + prompting for editing, and ignore INITIAL-CONTENTS. + +Optional argument BACKEND is the VC backend to use." + (interactive (list (vc-read-revision "Revision to revert: "))) + (vc--pick-or-revert rev t comment initial-contents backend)) + (declare-function diff-bounds-of-hunk "diff-mode") (defun vc-default-checkin-patch (_backend patch-string comment) commit 5ee1e205e1663409c9d0a196bd9bbec9b36cf53a Author: Eli Zaretskii Date: Sat Oct 4 17:18:12 2025 +0300 ; Improve the documentation of the last commit * lisp/vc/log-view.el (log-view-copy-revision-as-kill): * doc/emacs/maintaining.texi (VC Change Log): * etc/NEWS: Improve wording of last commit. (Bug#79493) diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 2f8a15a0b0b..e7a05a3556b 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1245,9 +1245,11 @@ earlier revision (@code{log-view-diff-changeset}). This shows the changes to all files made in that revision. @item w -Copy the revision of the log entry at point, or all marked revisions, to -the kill ring, as if you had used @kbd{M-w} -(@code{log-view-copy-revision-as-kill}). +Copy to the kill ring (@pxref{Kill Ring}) the revision ID of the log +entry at point, as if you had used @kbd{M-w} +(@code{log-view-copy-revision-as-kill}). If several revisions are +marked, the command copies to the kill ring the IDs of all of them, +separated by spaces. @item @key{RET} In a compact-style log buffer (e.g., the one created by @kbd{C-x v L}), diff --git a/etc/NEWS b/etc/NEWS index 96d85ea6b0d..8cabafd57cf 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2395,8 +2395,10 @@ In addition, a new command 'U' removes all marks. +++ *** New command 'w' in Log View mode. -'w' now copies the revision of the log entry at point to the kill ring. -If there are marked revisions, it copies those, instead. +The new command 'log-view-copy-revision-as-kill', by default bound to +'w' in Log View mode, copies to the kill ring the ID of the revision at +point in the log entry. If there are marked revisions, it copies the +IDs of those, instead. ** Diff mode diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index ca84f4b7452..68ce4f1baa5 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -751,8 +751,8 @@ considered file(s)." fr to))) (defun log-view-copy-revision-as-kill () - "Copy the revision at point to the kill ring. -If there are marked revisions, use those, separated by spaces." + "Copy the ID of the revision at point to the kill ring. +If there are marked revisions, copy the IDs of those, separated by spaces." (interactive) (let ((revisions (log-view-get-marked))) (if (length> revisions 1) commit d0c63b84276fd17ec330bec44cf82876ab48e489 Author: Sean Whitton Date: Sat Oct 4 14:41:16 2025 +0100 Improve log-view-copy-revision-as-kill * lisp/vc/log-view.el (log-view-copy-revision-as-kill): Signal user-error if there is no revision at point. * doc/emacs/maintaining.texi (VC Change Log): * etc/NEWS: Shorten docs for the new command. diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 5af0c0f938a..2f8a15a0b0b 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1245,9 +1245,9 @@ earlier revision (@code{log-view-diff-changeset}). This shows the changes to all files made in that revision. @item w -Copy the revision of the marked log entries into the kill ring, as if -you had killed them with @kbd{M-w}. Multiple entries will be separated -by a space. +Copy the revision of the log entry at point, or all marked revisions, to +the kill ring, as if you had used @kbd{M-w} +(@code{log-view-copy-revision-as-kill}). @item @key{RET} In a compact-style log buffer (e.g., the one created by @kbd{C-x v L}), diff --git a/etc/NEWS b/etc/NEWS index cc6580e79bb..96d85ea6b0d 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2393,12 +2393,10 @@ You can get back the old behavior with something like this: In addition, a new command 'U' removes all marks. ---- -*** A new command 'log-view-copy-revision-as-kill' added. -The new 'log-view-copy-revision-as-kill' command copies the revision of -the log entry at point to the kill-ring. When multiple log entries are -marked the command copies all revisions in a single space separated -string. The command is bound by default to `w' in log-view-mode-map. ++++ +*** New command 'w' in Log View mode. +'w' now copies the revision of the log entry at point to the kill ring. +If there are marked revisions, it copies those, instead. ** Diff mode diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index 06c241161a9..ca84f4b7452 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -751,16 +751,18 @@ considered file(s)." fr to))) (defun log-view-copy-revision-as-kill () - "Copy the revision under point, as a string, to the `kill-ring'." + "Copy the revision at point to the kill ring. +If there are marked revisions, use those, separated by spaces." (interactive) (let ((revisions (log-view-get-marked))) (if (length> revisions 1) (let ((found (string-join revisions " "))) (kill-new found) (message "%s" found)) - (when-let* ((rev (or (car revisions) (cadr (log-view-current-entry))))) - (kill-new rev) - (message "%s" rev))))) + (if-let* ((rev (or (car revisions) (log-view-current-tag)))) + (progn (kill-new rev) + (message "%s" rev)) + (user-error "No revision at point"))))) (provide 'log-view) commit 2ce33b66c513c451d5808a3e6583e84ea073524c Author: Timo Myyrä Date: Sun Sep 21 11:12:55 2025 +0300 New command log-view-copy-revision-as-kill * lisp/vc/log-view.el (log-view-copy-revision-as-kill): New command. (log-view-mode-map): Bind it. * doc/emacs/maintaining.texi (VC Change Log): * etc/NEWS: Document it. diff --git a/doc/emacs/maintaining.texi b/doc/emacs/maintaining.texi index 27ed511adb9..5af0c0f938a 100644 --- a/doc/emacs/maintaining.texi +++ b/doc/emacs/maintaining.texi @@ -1244,6 +1244,11 @@ Display the changeset diff between the revision at point and the next earlier revision (@code{log-view-diff-changeset}). This shows the changes to all files made in that revision. +@item w +Copy the revision of the marked log entries into the kill ring, as if +you had killed them with @kbd{M-w}. Multiple entries will be separated +by a space. + @item @key{RET} In a compact-style log buffer (e.g., the one created by @kbd{C-x v L}), toggle between showing and hiding the full log entry for the revision at diff --git a/etc/NEWS b/etc/NEWS index d44a46a9c86..cc6580e79bb 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -2393,6 +2393,13 @@ You can get back the old behavior with something like this: In addition, a new command 'U' removes all marks. +--- +*** A new command 'log-view-copy-revision-as-kill' added. +The new 'log-view-copy-revision-as-kill' command copies the revision of +the log entry at point to the kill-ring. When multiple log entries are +marked the command copies all revisions in a single space separated +string. The command is bound by default to `w' in log-view-mode-map. + ** Diff mode +++ diff --git a/lisp/vc/log-view.el b/lisp/vc/log-view.el index 863f0ddcce3..06c241161a9 100644 --- a/lisp/vc/log-view.el +++ b/lisp/vc/log-view.el @@ -136,6 +136,7 @@ "f" #'log-view-find-revision "n" #'log-view-msg-next "p" #'log-view-msg-prev + "w" #'log-view-copy-revision-as-kill "TAB" #'log-view-msg-next "" #'log-view-msg-prev) @@ -749,6 +750,18 @@ considered file(s)." log-view-vc-fileset))) fr to))) +(defun log-view-copy-revision-as-kill () + "Copy the revision under point, as a string, to the `kill-ring'." + (interactive) + (let ((revisions (log-view-get-marked))) + (if (length> revisions 1) + (let ((found (string-join revisions " "))) + (kill-new found) + (message "%s" found)) + (when-let* ((rev (or (car revisions) (cadr (log-view-current-entry))))) + (kill-new rev) + (message "%s" rev))))) + (provide 'log-view) ;;; log-view.el ends here commit 3271d9fc9654e1d9aa92f19c969f4b3d5b9e6272 Author: Elías Gabriel Pérez Date: Sun Sep 28 16:32:17 2025 -0600 hideshow.el: Minor optimizations * lisp/progmodes/hideshow.el (hs-hideable-region-p): New function. (hs--make-indicators-overlays, hs--add-indicators) (hs-hide-block-at-point, hs-hide-all, hs-hide-block) (hs-hide-initial-comment-block): Use it. (Bug#79533) diff --git a/lisp/progmodes/hideshow.el b/lisp/progmodes/hideshow.el index d270b5677e3..2d4042be808 100644 --- a/lisp/progmodes/hideshow.el +++ b/lisp/progmodes/hideshow.el @@ -620,6 +620,16 @@ Skip \"internal\" overlays if `hs-allow-nesting' is non-nil." (when (overlay-get ov 'hs) (delete-overlay ov))))) +(defun hs-hideable-region-p (beg end) + "Return t if region in BEG and END can be hidden." + ;; Check if BEG and END are not in the same line number, + ;; since using `count-lines' is slow, only check if both + ;; positions do not share the same BOL. + (save-excursion + (let ((pos1 (progn (goto-char beg) (line-beginning-position))) + (pos2 (progn (goto-char end) (line-beginning-position)))) + (and (< pos1 pos2) (not (= pos1 pos2)))))) + (defun hs-make-overlay (b e kind &optional b-offset e-offset) "Return a new overlay in region defined by B and E with type KIND. KIND is either `code' or `comment'. Optional fourth arg B-OFFSET @@ -690,40 +700,44 @@ point." (defun hs--make-indicators-overlays (beg) "Helper function to make the indicators overlays." (let ((hiddenp (eq 'hs (get-char-property (pos-eol) 'invisible)))) - (when-let* ((o (make-overlay - (if hs-indicator-type beg (pos-eol)) - (1+ (if hs-indicator-type beg (pos-eol))))) - (fringe-type (if hiddenp 'hs-show 'hs-hide)) - (face-or-icon (if hiddenp 'hs-indicator-show 'hs-indicator-hide))) - - (overlay-put o 'hs-indicator t) - (overlay-put o 'hs-indicator-block-start beg) - (overlay-put o 'evaporate t) - (overlay-put o 'priority -50) - - (overlay-put - o 'before-string - (pcase hs-indicator-type - ;; Fringes - ('fringe - (propertize - "+" 'display - `(left-fringe ,fringe-type ,face-or-icon))) - ;; Margins - ('margin - (propertize - "+" 'display - `((margin left-margin) - ,(or (plist-get (icon-elements face-or-icon) 'image) - (icon-string face-or-icon))) - 'face face-or-icon - 'keymap hs-indicators-map)) - ;; EOL string - ('nil - (propertize - (icon-string face-or-icon) - 'mouse-face 'highlight - 'keymap hs-indicators-map))))))) + ;; If we are going to use the EOL indicators, then + ;; ignore the invisible lines which mostly are already + ;; hidden blocks. + (when (or hs-indicator-type (not hiddenp)) + (let* ((o (make-overlay + (if hs-indicator-type beg (pos-eol)) + (1+ (if hs-indicator-type beg (pos-eol))))) + (fringe-type (if hiddenp 'hs-show 'hs-hide)) + (face-or-icon (if hiddenp 'hs-indicator-show 'hs-indicator-hide))) + + (overlay-put o 'hs-indicator t) + (overlay-put o 'hs-indicator-block-start beg) + (overlay-put o 'evaporate t) + (overlay-put o 'priority -50) + + (overlay-put + o 'before-string + (pcase hs-indicator-type + ;; Fringes + ('fringe + (propertize + "+" 'display + `(left-fringe ,fringe-type ,face-or-icon))) + ;; Margins + ('margin + (propertize + "+" 'display + `((margin left-margin) + ,(or (plist-get (icon-elements face-or-icon) 'image) + (icon-string face-or-icon))) + 'face face-or-icon + 'keymap hs-indicators-map)) + ;; EOL string + ('nil + (propertize + (icon-string face-or-icon) + 'mouse-face 'highlight + 'keymap hs-indicators-map)))))))) (defun hs--add-indicators (&optional beg end) "Add hideable indicators from BEG to END." @@ -749,11 +763,7 @@ point." (scan-error (throw 'hs-indicator-error nil))) (point)))) ;; Check if block is longer than 1 line. - (_ (< b-beg b-end)) - ;; If we are going to use the EOL indicators, then - ;; ignore the invisible lines which mostly are already - ;; hidden blocks. - (_ (> (count-lines b-beg b-end (not hs-indicator-type)) 1))) + (_ (hs-hideable-region-p b-beg b-end))) (hs--make-indicators-overlays b-beg)) ;; Only 1 indicator per line (forward-line 1)) @@ -879,7 +889,7 @@ and then further adjusted to be at the end of the line." (p (car-safe block)) (q (cdr-safe block)) ov) - (if (and block (< p q) (> (count-lines p q) 1)) + (if (hs-hideable-region-p p q) (progn (cond ((and hs-allow-nesting (setq ov (hs-overlay-at p))) (delete-overlay ov)) @@ -1136,7 +1146,7 @@ If `hs-hide-comments-when-hiding-all' is non-nil, also hide the comments." ;; found a comment, probably (let ((c-reg (hs-inside-comment-p))) (when (and c-reg (car c-reg)) - (if (> (count-lines (car c-reg) (nth 1 c-reg)) 1) + (if (hs-hideable-region-p (car c-reg) (nth 1 c-reg)) (hs-hide-block-at-point t c-reg) (goto-char (nth 1 c-reg)))))) (progress-reporter-update spew (point))) @@ -1163,7 +1173,7 @@ Upon completion, point is repositioned and the normal hook (let ((c-reg (hs-inside-comment-p))) (cond ((and c-reg (or (null (nth 0 c-reg)) - (<= (count-lines (car c-reg) (nth 1 c-reg)) 1))) + (not (hs-hideable-region-p (car c-reg) (nth 1 c-reg))))) (message "(not enough comment lines to hide)")) ((or c-reg (funcall hs-looking-at-block-start-p-func) @@ -1259,7 +1269,7 @@ This can be useful if you have huge RCS logs in those comments." (when c-reg (let ((beg (car c-reg)) (end (cadr c-reg))) ;; see if we have enough comment lines to hide - (when (> (count-lines beg end) 1) + (when (hs-hideable-region-p beg end) (hs-hide-comment-region beg end))))))) ;;;###autoload commit b8555ebff41bc06a7a35234d613a28a053b95c35 Author: Augusto Stoffel Date: Sat Sep 27 15:32:05 2025 +0200 Add 'auto-mode-alist' entry for Containerfile * lisp/progmodes/dockerfile-ts-mode.el: Also match Containerfile and Containerfile.* in the 'auto-mode-alist' entry. (Bug#79524) diff --git a/lisp/progmodes/dockerfile-ts-mode.el b/lisp/progmodes/dockerfile-ts-mode.el index 40259792b52..f02f97b6eb1 100644 --- a/lisp/progmodes/dockerfile-ts-mode.el +++ b/lisp/progmodes/dockerfile-ts-mode.el @@ -218,7 +218,11 @@ is t or contains the mode name." (when (treesit-available-p) (add-to-list 'auto-mode-alist ;; NOTE: We can't use `rx' here, as it breaks bootstrap. - '("\\(?:Dockerfile\\(?:\\..*\\)?\\|\\.[Dd]ockerfile\\)\\'" + ;; (rx (or (and (or "Dockerfile" "Containerfile") + ;; (? "." (* nonl))) + ;; (and "." (and (any "Dd") "ocker") "file")) + ;; eos) + '("\\(?:\\(?:\\(?:Contain\\|Dock\\)erfile\\)\\(?:\\..*\\)?\\|\\.[Dd]ockerfile\\)\\'" . dockerfile-ts-mode-maybe)) ;; To be able to toggle between an external package and core ts-mode: (defvar treesit-major-mode-remap-alist)