From 4e7cb37b8440bf63ce5ef715282bfbf9b263128a Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Sat, 27 Sep 2025 21:36:51 +0100 Subject: [PATCH] VC prepare-patch: New :patch-start & :patch-end plist entries * lisp/vc/vc.el (prepare-patch): Specify :patch-start and :patch-end plist entries. * lisp/vc/vc-git.el (vc-git-prepare-patch): Use -n1 to avoid passing a revision range to git-format-patch, which is a bit simpler. Catch search-failed errors and signal an error with a more helpful message. Properly handle Subject: header by looking for continuation lines. Return :patch-start and :patch-end entries in the plist. * lisp/vc/vc-hg.el (vc-hg-prepare-patch): Always pass --git to 'hg export' for consistency. Catch search-failed errors and signal an error with a more helpful message. Return a :patch-start entry in the plist. --- lisp/vc/vc-git.el | 53 +++++++++++++++++++++++++++++------------------ lisp/vc/vc-hg.el | 20 +++++++++++------- lisp/vc/vc.el | 22 +++++++++++--------- 3 files changed, 57 insertions(+), 38 deletions(-) diff --git a/lisp/vc/vc-git.el b/lisp/vc/vc-git.el index 5002c21747f..54475542ac4 100644 --- a/lisp/vc/vc-git.el +++ b/lisp/vc/vc-git.el @@ -2311,26 +2311,39 @@ Rebase may --autosquash your other squash!/fixup!/amend!; proceed?"))) (defun vc-git-prepare-patch (rev) (with-current-buffer (generate-new-buffer " *vc-git-prepare-patch*") - (vc-git-command - t 0 '() "format-patch" - "--no-numbered" "--stdout" - ;; From gitrevisions(7): ^ means the th parent - ;; (i.e. ^ is equivalent to ^1). As a - ;; special rule, ^0 means the commit itself and - ;; is used when is the object name of a tag - ;; object that refers to a commit object. - (concat rev "^.." rev)) - (let (subject) - ;; Extract the subject line - (goto-char (point-min)) - (search-forward-regexp "^Subject: \\(.+\\)") - (setq subject (match-string 1)) - ;; Jump to the beginning for the patch - (search-forward-regexp "\n\n") - ;; Return the extracted data - (list :subject subject - :buffer (current-buffer) - :body-start (point))))) + (vc-git-command t 0 nil "format-patch" + "--no-numbered" "--stdout" "-n1" rev) + (condition-case _ + (let (subject body-start patch-start patch-end) + (goto-char (point-min)) + (re-search-forward "^Subject: \\(.*\\)") + (setq subject (match-string 1)) + (while (progn (forward-line 1) + (looking-at "[\s\t]\\(.*\\)")) + (setq subject (format "%s %s" subject (match-string 1)))) + (goto-char (point-min)) + (re-search-forward "\n\n") + (setq body-start (point)) + (if ;; If the user has added any of these to + ;; `vc-git-diff-switches' then they expect to see the + ;; diffstat in *vc-diff* buffers. + (cl-intersection '("--stat" + "--patch-with-stat" + "--compact-summary") + (vc-switches 'git 'diff) + :test #'equal) + (progn (re-search-forward "^---$") + (setq patch-start (pos-bol 2))) + (re-search-forward "^diff --git a/") + (setq patch-start (pos-bol))) + (re-search-forward "^-- $") + (setq patch-end (pos-bol)) + (list :subject subject + :body-start body-start + :patch-start patch-start + :patch-end patch-end + :buffer (current-buffer))) + (search-failed (error "git-format-patch output parse failure"))))) ;; grep-compute-defaults autoloads grep. (declare-function grep-read-regexp "grep" ()) diff --git a/lisp/vc/vc-hg.el b/lisp/vc/vc-hg.el index a646ba079dd..0d1f1703081 100644 --- a/lisp/vc/vc-hg.el +++ b/lisp/vc/vc-hg.el @@ -1673,14 +1673,18 @@ This runs the command \"hg merge\"." (defun vc-hg-prepare-patch (rev) (with-current-buffer (generate-new-buffer " *vc-hg-prepare-patch*") - (vc-hg-command t 0 '() "export" "--rev" rev) - (let (subject) - ;; Extract the subject line - (goto-char (point-min)) - (search-forward-regexp "^[^#].*") - (setq subject (match-string 0)) - ;; Return the extracted data - (list :subject subject :buffer (current-buffer))))) + (vc-hg-command t 0 nil "export" "--git" "--rev" rev) + (condition-case _ + (let (subject patch-start) + (goto-char (point-min)) + (re-search-forward "^[^#].*") + (setq subject (match-string 0)) + (re-search-forward "\n\ndiff --git a/") + (setq patch-start (pos-bol)) + (list :subject subject + :patch-start patch-start + :buffer (current-buffer))) + (search-failed (error "'hg export' output parse failure"))))) ;;; Internal functions diff --git a/lisp/vc/vc.el b/lisp/vc/vc.el index 782f9ae12a2..006d2098c2f 100644 --- a/lisp/vc/vc.el +++ b/lisp/vc/vc.el @@ -670,15 +670,17 @@ ;; ;; - prepare-patch (rev) ;; -;; Prepare a patch and return a property list with the keys -;; `:subject' indicating the patch message as a string, `:buffer' -;; with a buffer object that contains the entire patch message and -;; `:body-start' and `:body-end' demarcating what part of said -;; buffer should be inserted into an inline patch. If the two last -;; properties are omitted, `point-min' and `point-max' will -;; respectively be used instead. If supported by the backend, the -;; patch should contain authorship identity and date information, and -;; REV's log message. +;; Prepare a patch and return a property list with the keys `:subject' +;; with the summary line (first line) of the patch message as a +;; string; `:buffer' with a buffer object that contains the entire +;; patch message; `:body-start' and `:body-end' demarcating the part +;; of that buffer which should be inserted inline into a mail message +;; body; and `:patch-start' and `:patch-end' demarcating the part of +;; the buffer that is purely the patch, excluding any log message. +;; If any of these *-start and *-end properties are omitted, they +;; default to (point-min) and (point-max), respectively. +;; If supported by the backend, the patch should contain authorship +;; identity and date information, and REV's log message. ;; ;; - clone (remote directory rev) ;; @@ -4210,7 +4212,7 @@ If nil, no default will be used. This option may be set locally." :buffer (current-buffer))))) (defun vc-prepare-patch-prompt-revisions () - "Prompt the user for a list revisions. + "Prompt the user for a list of revisions. Prepare a default value, depending on the current context. With a numerical prefix argument, use the last N revisions as the default value. If the current buffer is a log-view buffer, use