* 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)
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))
("^\\(---\\|\\+\\+\\+\\|\\*\\*\\*\\) \\([^\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
(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)
--- /dev/null
+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
+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