]> git.eshelyaron.com Git - emacs.git/commitdiff
Fix fontification outside hunks in Git patches
authorStefan Kangas <stefankangas@gmail.com>
Mon, 3 Mar 2025 17:37:43 +0000 (18:37 +0100)
committerEshel Yaron <me@eshelyaron.com>
Tue, 4 Mar 2025 21:04:38 +0000 (22:04 +0100)
* lisp/vc/diff-mode.el (diff-font-lock-keywords): Don't fontify lines in
Git patches starting with + or - as added/removed, if they are either
before the first hunk, or in the email signature.  (Bug#75884)
(diff-buffer-type): Move definition up.
(diff--indicator-added-re, diff--indicator-removed-re): New variables.
(diff--git-preamble-end, diff--git-footer-start)
(diff--indicator-matcher-helper, diff--indicator-added-matcher)
(diff--indicator-removed-matcher): New functions.
* test/lisp/vc/diff-mode-tests.el (diff-mode-test-git-patch)
(diff-mode-test-git-patch/before-first-hunk)
(diff-mode-test-git-patch/signature): New tests.
* test/lisp/vc/diff-mode-resources/git.patch: New file.

(cherry picked from commit 10abb87f0519b3d53d6b7078703d3a0120e3aaa8)

lisp/vc/diff-mode.el
test/lisp/vc/diff-mode-resources/git.patch [new file with mode: 0644]
test/lisp/vc/diff-mode-tests.el

index 6a38ce3addd1e5284115e9b83123c76e363ad685..9f45c8eba723f52f33dc0c62e32679954aba8c4b 100644 (file)
@@ -480,6 +480,59 @@ If non-nil, use the face `diff-changed-unspecified'.  Otherwise,
 use the face `diff-removed' for removed lines, and the face
 `diff-added' for added lines.")
 
+(defvar diff-buffer-type nil)
+
+(defvar diff--indicator-added-re
+  (rx bol
+      (group (any "+>"))
+      (group (zero-or-more nonl) "\n")))
+
+(defvar diff--indicator-removed-re
+  (rx bol
+      (group (any "<-"))
+      (group (zero-or-more nonl) "\n")))
+
+(defun diff--git-preamble-end ()
+  (save-excursion
+    (goto-char (point-min))
+    (re-search-forward "^diff --git .+ .+$" nil t)
+    (forward-line 2)
+    (point)))
+
+(defun diff--git-footer-start ()
+  (save-excursion
+    (goto-char (point-max))
+    (re-search-backward "^-- $" nil t)
+    (point)))
+
+(defun diff--indicator-matcher-helper (limit regexp)
+  "Fontify added/removed lines from point to LIMIT using REGEXP.
+
+If this is a Git patch, don't fontify lines before the first hunk, or in
+the email signature at the end."
+  (catch 'return
+    (when (eq diff-buffer-type 'git)
+      (let ((preamble-end (diff--git-preamble-end))
+            (footer-start (diff--git-footer-start))
+            (beg (point))
+            (end limit))
+        (cond ((or (<= end preamble-end)
+                   (>= beg footer-start))
+               (throw 'return nil))
+              ;; end is after preamble, adjust beg:
+              ((< beg preamble-end)
+               (goto-char preamble-end))
+              ;; beg is before footer, adjust end:
+              ((> end footer-start)
+               (setq limit footer-start)))))
+    (re-search-forward regexp limit t)))
+
+(defun diff--indicator-added-matcher (limit)
+  (diff--indicator-matcher-helper limit diff--indicator-added-re))
+
+(defun diff--indicator-removed-matcher (limit)
+  (diff--indicator-matcher-helper limit diff--indicator-removed-re))
+
 (defvar diff-font-lock-keywords
   `((,(concat "\\(" diff-hunk-header-re-unified "\\)\\(.*\\)$")
      (1 'diff-hunk-header) (6 'diff-function))
@@ -494,9 +547,9 @@ use the face `diff-removed' for removed lines, and the face
     ("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\t\n]+?\\)\\(?:\t.*\\| \\(\\*\\*\\*\\*\\|----\\)\\)?\n"
      (0 'diff-header)
      (2 (if (not (match-end 3)) 'diff-file-header) prepend))
-    ("^\\([-<]\\)\\(.*\n\\)"
+    (diff--indicator-removed-matcher
      (1 diff-indicator-removed-face) (2 'diff-removed))
-    ("^\\([+>]\\)\\(.*\n\\)"
+    (diff--indicator-added-matcher
      (1 diff-indicator-added-face) (2 'diff-added))
     ("^\\(!\\)\\(.*\n\\)"
      (1 (if diff-use-changed-face
@@ -561,7 +614,6 @@ See https://lists.gnu.org/r/emacs-devel/2007-11/msg01990.html")
 (defconst diff-separator-re "^--+ ?$")
 
 (defvar diff-narrowed-to nil)
-(defvar diff-buffer-type nil)
 
 (defun diff-hunk-style (&optional style)
   (when (looking-at diff-hunk-header-re)
diff --git a/test/lisp/vc/diff-mode-resources/git.patch b/test/lisp/vc/diff-mode-resources/git.patch
new file mode 100644 (file)
index 0000000..05ec90d
--- /dev/null
@@ -0,0 +1,51 @@
+From 1234567890abcdef1234567890abcdef12345678 Mon Sep 17 00:00:00 2001
+From: Alyssa P. Hacker <alyssa.p.hacker@example.com>
+Date: Sun, 3 Mar 2025 10:30:00 -0400
+Subject: [PATCH] Subtle bug fixes and slight improvements
+
+- This is not a removed line
++ This is not an added line
+
+---
+ src/main.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+diff --git a/src/main.py b/src/main.py
+index 9f6c5fe43e47eab441232e54456c5c2b06297b65..7b3f91a8b4ed923c8f43183276e3ab36fe04f6c9 100644
+--- a/src/main.py
++++ b/src/main.py
+@@ -2,25 +2,24 @@
+
+ def main():
+     # Initialize the magic number generator
+-    magic_number = 42
+-    print("Magic number: ", magic_number)
+
+-    # TODO: Fix the infinite loop
+-    while True:
+-        print("This loop will never end")
++    magic_number = 73  # After reconsidering, 73 seems more appropriate
++    print("Updated magic number: ", magic_number)
+
++    # The infinite loop was probably not the best approach
++    # while True:
++    #     print("This loop will never end.")
+
+     # This part of the code handles other important tasks
+     print("Processing other tasks...")
+
+     # Error handling has been updated for clarity
+-    if not fixed_it_yet:
+-        print("ERROR: Still broken!")
++    if not fixed_it_yet:  # This should be fine now
++        print("ERROR: No longer an issue.")
+
+     # Exiting the function on a positive note
+-    print("Goodbye, cruel world!")
++    print("Goodbye, world!")
+
+ if __name__ == "__main__":
+     main()
+
+-- 
+2.40.0
index cd3f613f5320b534dd4eeb78dfe1fbc40c33939d..bbd66824e483b899cf1505065f245772f3e3a524 100644 (file)
@@ -557,5 +557,45 @@ baz"))))
 +1
 ")))))
 
+(ert-deftest diff-mode-test-git-patch ()
+  (let ((file (ert-resource-file "git.patch")))
+   (with-temp-buffer
+     (insert-file-contents file)
+     (diff-mode)
+     (font-lock-ensure)
+     (goto-char (point-min))
+     (re-search-forward "magic_number = 42")
+     (should (eq (get-text-property (match-beginning 0) 'face)
+                 'diff-removed))
+     (re-search-forward "magic_number = 73")
+     (should (eq (get-text-property (match-beginning 0) 'face)
+                 'diff-added)))))
+
+(ert-deftest diff-mode-test-git-patch/before-first-hunk ()
+  (let ((file (ert-resource-file "git.patch")))
+   (with-temp-buffer
+     (insert-file-contents file)
+     (diff-mode)
+     (font-lock-ensure)
+     (goto-char (point-min))
+     (re-search-forward "This is not a removed line")
+     (should (eq (get-text-property (match-beginning 0) 'face)
+                  'diff-context))
+     (re-search-forward "This is not an added line")
+     (font-lock-ensure)
+     (should (eq (get-text-property (match-beginning 0) 'face)
+                 'diff-context)))))
+
+(ert-deftest diff-mode-test-git-patch/signature ()
+  (let ((file (ert-resource-file "git.patch")))
+   (with-temp-buffer
+     (insert-file-contents file)
+     (diff-mode)
+     (font-lock-ensure)
+     (goto-char (point-max))
+     (re-search-backward "^-- $")
+     (should (eq (get-text-property (match-beginning 0) 'face)
+                  'diff-context)))))
+
 (provide 'diff-mode-tests)
 ;;; diff-mode-tests.el ends here