master 6faf3cb68be: Improve diff-unified->context

Helmut Eller <[email protected]> Wed, 22 Jul 2026 11:47:14 -0400 (EDT)
Newsgroups gmane.emacs.diffs
Message-ID <[email protected]>
branch: master
commit 6faf3cb68be73090d36cc110d4ab8044a5d8b7f8
Author: Helmut Eller <[email protected]>
Commit: Helmut Eller <[email protected]>

    Improve diff-unified->context
    
    Preserve comments in hunk headers (usually a function name).
    
    * lisp/vc/diff-mode.el (diff-unified->context): Parse the comment in the
    hunk header and insert it in the corresponding hunk header in the
    context diff.
    (diff-context->unified): Change analogously.
    * test/lisp/vc/diff-mode-tests.el (diff-mode-tests-undo-unified->context):
    New test.
    (diff-mode-tests--context-patch, diff-mode-tests--unified-patch): New
    variables.
---
 lisp/vc/diff-mode.el            | 19 ++++++------
 test/lisp/vc/diff-mode-tests.el | 69 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 79 insertions(+), 9 deletions(-)

diff --git a/lisp/vc/diff-mode.el b/lisp/vc/diff-mode.el
index 0ee055424c8..c27abda1af9 100644
--- a/lisp/vc/diff-mode.el
+++ b/lisp/vc/diff-mode.el
@@ -1293,7 +1293,7 @@ else cover the whole buffer."
       (goto-char start)
       (while (and (re-search-forward
                    (concat "^\\(\\(---\\) .+\n\\(\\+\\+\\+\\) .+\\|"
-                           diff-hunk-header-re-unified ".*\\)$")
+                           diff-hunk-header-re-unified "\\( .*\\)?\\)$")
                    nil t)
 		  (< (point) end))
 	(combine-after-change-calls
@@ -1308,13 +1308,14 @@ else cover the whole buffer."
 		  (lines1 (or (match-string 5) "1"))
 		  (line2 (match-string 6))
 		  (lines2 (or (match-string 7) "1"))
+                  (comment (match-string 8))
 		  ;; Variables to use the special undo function.
 		  (old-undo buffer-undo-list)
 		  (old-end (marker-position end))
 		  (start (match-beginning 0))
 		  (reversible t))
 	      (replace-match
-	       (concat "***************\n*** " line1 ","
+	       (concat "***************" comment "\n*** " line1 ","
 		       (number-to-string (+ (string-to-number line1)
 					    (string-to-number lines1)
 					    -1))
@@ -1417,7 +1418,7 @@ With a prefix argument, convert unified format to context format."
           (inhibit-read-only t))
       (save-excursion
         (goto-char start)
-        (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}.*\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)\\(?: \\(.*\\)\\|$\\)" nil t)
+        (while (and (re-search-forward "^\\(\\(\\*\\*\\*\\) .+\n\\(---\\) .+\\|\\*\\{15\\}\\( .*\\)?\n\\*\\*\\* \\([0-9]+\\),\\(-?[0-9]+\\) \\*\\*\\*\\*\\)$" nil t)
                     (< (point) end))
           (combine-after-change-calls
             (if (match-beginning 2)
@@ -1427,15 +1428,14 @@ With a prefix argument, convert unified format to context format."
                   (replace-match "+++" t t nil 3)
                   (replace-match "---" t t nil 2))
               ;; we matched a hunk header
-              (let ((line1s (match-string 4))
-                    (line1e (match-string 5))
+              (let ((comment (match-string 4))
+                    (line1s (match-string 5))
+                    (line1e (match-string 6))
                     (pt1 (match-beginning 0))
                     ;; Variables to use the special undo function.
                     (old-undo buffer-undo-list)
                     (old-end (marker-position end))
-                    ;; We currently throw away the comment that can follow
-                    ;; the hunk header.  FIXME: Preserve it instead!
-                    (reversible (not (match-end 6))))
+                    (reversible t))
                 (replace-match "")
                 (unless (re-search-forward
                          diff-context-mid-hunk-header-re nil t)
@@ -1490,7 +1490,8 @@ With a prefix argument, convert unified format to context format."
                             " +" line2s ","
                             (number-to-string (- (string-to-number line2e)
                                                  (string-to-number line2s)
-                                                 -1)) " @@"))
+                                                 -1))
+                            " @@" (or comment "")))
                   (set-marker pt2 nil)
                   ;; The whole procedure succeeded, let's replace the myriad
                   ;; of undo elements with just a single special one.
diff --git a/test/lisp/vc/diff-mode-tests.el b/test/lisp/vc/diff-mode-tests.el
index 36291fee0a5..875f5cf8bfe 100644
--- a/test/lisp/vc/diff-mode-tests.el
+++ b/test/lisp/vc/diff-mode-tests.el
@@ -792,5 +792,74 @@ index 0000000..3456789
           (should (equal call-initial "created.txt"))
           (should (equal call-mustmatch nil)))))))
 
+(defvar diff-mode-tests--unified-patch
+  "--- /tmp/a.el	2026-07-14 08:27:52.751202012 +0200
++++ /tmp/b.el	2026-07-14 08:28:01.087191220 +0200
+@@ -2,7 +2,7 @@ (defun foo ()
+   x
+   y
+   z
+-  a)
++  b)
+ ;;
+ ;;
+ ;;
+@@ -19,4 +19,4 @@ (defun bar ()
+   x
+   y
+   z
+-  aa)
++  bb)
+")
+
+(defvar diff-mode-tests--context-patch
+  "*** /tmp/a.el	2026-07-14 08:27:52.751202012 +0200
+--- /tmp/b.el	2026-07-14 08:28:01.087191220 +0200
+*************** (defun foo ()
+*** 2,8 ****
+    x
+    y
+    z
+!   a)
+  ;;
+  ;;
+  ;;
+--- 2,8 ----
+    x
+    y
+    z
+!   b)
+  ;;
+  ;;
+  ;;
+*************** (defun bar ()
+*** 19,22 ****
+    x
+    y
+    z
+!   aa)
+--- 19,22 ----
+    x
+    y
+    z
+!   bb)
+")
+
+(ert-deftest diff-mode-tests-undo-unified->context ()
+  (let* ((unified diff-mode-tests--unified-patch)
+         (context diff-mode-tests--context-patch)
+	 (b (generate-new-buffer "test.diff")))
+    (unwind-protect
+	(with-current-buffer b
+	  (insert unified)
+	  (diff-mode)
+	  (undo-boundary)
+	  (diff-unified->context (point-min) (point-max))
+          (should (equal (buffer-string) context))
+	  (undo-boundary)
+	  (undo)
+	  (should (equal (buffer-string) unified)))
+      (kill-buffer b))))
+
 (provide 'diff-mode-tests)
 ;;; diff-mode-tests.el ends here