X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/dd24cb375fc84ea98e672da58b5ba814ef8ba472..409cc4a3ea9e7461572a04f021ff3993e9a516f6:/lisp/diff-mode.el diff --git a/lisp/diff-mode.el b/lisp/diff-mode.el index 7ea02352b0..4c566b344a 100644 --- a/lisp/diff-mode.el +++ b/lisp/diff-mode.el @@ -1,7 +1,7 @@ ;;; diff-mode.el --- a mode for viewing/editing context diffs ;; Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, -;; 2005, 2006 Free Software Foundation, Inc. +;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc. ;; Author: Stefan Monnier ;; Keywords: convenience patch diff @@ -10,7 +10,7 @@ ;; GNU Emacs is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation; either version 2, or (at your option) +;; the Free Software Foundation; either version 3, or (at your option) ;; any later version. ;; GNU Emacs is distributed in the hope that it will be useful, @@ -46,13 +46,15 @@ ;; of a hunk. Show then the changes between and and make it ;; possible to apply them to , , or . ;; Or maybe just make it into a ".rej to diff3-markers converter". +;; Maybe just use `wiggle' (by Neil Brown) to do it for us. ;; ;; - Refine hunk on a word-by-word basis. -;; +;; +;; - in diff-apply-hunk, strip context in replace-match to better +;; preserve markers and spacing. ;; - Handle `diff -b' output in context->unified. ;;; Code: - (eval-when-compile (require 'cl)) (defvar add-log-buffer-file-name-function) @@ -110,6 +112,8 @@ when editing big diffs)." ("N" . diff-file-next) ("p" . diff-hunk-prev) ("P" . diff-file-prev) + ("\t" . diff-hunk-next) + ([backtab] . diff-hunk-prev) ("k" . diff-hunk-kill) ("K" . diff-file-kill) ;; From compilation-minor-mode. @@ -118,8 +122,8 @@ when editing big diffs)." ("\C-m" . diff-goto-source) ([mouse-2] . diff-goto-source) ;; From XEmacs' diff-mode. -;; Standard M-w is useful, so don't change M-W. -;; ("W" . widen) + ;; Standard M-w is useful, so don't change M-W. + ;;("W" . widen) ;;("." . diff-goto-source) ;display-buffer ;;("f" . diff-goto-source) ;find-file ("o" . diff-goto-source) ;other-window @@ -128,14 +132,14 @@ when editing big diffs)." ;;("h" . diff-show-header) ;;("j" . diff-show-difference) ;jump to Nth diff ;;("q" . diff-quit) -;; Not useful if you have to metafy them. -;; (" " . scroll-up) -;; ("\177" . scroll-down) -;; Standard M-a is useful, so don't change M-A. -;; ("A" . diff-ediff-patch) -;; Standard M-r is useful, so don't change M-r or M-R. -;; ("r" . diff-restrict-view) -;; ("R" . diff-reverse-direction) + ;; Not useful if you have to metafy them. + ;;(" " . scroll-up) + ;;("\177" . scroll-down) + ;; Standard M-a is useful, so don't change M-A. + ;;("A" . diff-ediff-patch) + ;; Standard M-r is useful, so don't change M-r or M-R. + ;;("r" . diff-restrict-view) + ;;("R" . diff-reverse-direction) ("q" . quit-window)) "Basic keymap for `diff-mode', bound to various prefix keys.") @@ -151,6 +155,8 @@ when editing big diffs)." ("\C-c\C-s" . diff-split-hunk) ("\C-c\C-t" . diff-test-hunk) ("\C-c\C-u" . diff-context->unified) + ;; `d' because it duplicates the context :-( --Stef + ("\C-c\C-d" . diff-unified->context) ("\C-c\C-w" . diff-refine-hunk) ("\C-c\C-f" . next-error-follow-minor-mode)) "Keymap for `diff-mode'. See also `diff-mode-shared-map'.") @@ -160,12 +166,23 @@ when editing big diffs)." '("Diff" ["Jump to Source" diff-goto-source t] ["Apply hunk" diff-apply-hunk t] + ["Test applying hunk" diff-test-hunk t] ["Apply diff with Ediff" diff-ediff-patch t] - ["-----" nil nil] + "-----" ["Reverse direction" diff-reverse-direction t] ["Context -> Unified" diff-context->unified t] ["Unified -> Context" diff-unified->context t] ;;["Fixup Headers" diff-fixup-modifs (not buffer-read-only)] + "-----" + ["Split hunk" diff-split-hunk t] + ["Refine hunk" diff-refine-hunk t] + ["Kill current hunk" diff-hunk-kill t] + ["Kill current file's hunks" diff-file-kill t] + "-----" + ["Previous Hunk" diff-hunk-prev t] + ["Next Hunk" diff-hunk-next t] + ["Previous File" diff-file-prev t] + ["Next File" diff-file-next t] )) (defcustom diff-minor-mode-prefix "\C-c=" @@ -386,13 +403,20 @@ when editing big diffs)." ;; The return value is used by easy-mmode-define-navigation. (goto-char (or end (point-max))))) -(defun diff-beginning-of-hunk () +(defun diff-beginning-of-hunk (&optional try-harder) + "Move back to beginning of hunk. +If TRY-HARDER is non-nil, try to cater to the case where we're not in a hunk +but in the file header instead, in which case move forward to the first hunk." (beginning-of-line) (unless (looking-at diff-hunk-header-re) (forward-line 1) (condition-case () (re-search-backward diff-hunk-header-re) - (error (error "Can't find the beginning of the hunk"))))) + (error + (if (not try-harder) + (error "Can't find the beginning of the hunk") + (diff-beginning-of-file-and-junk) + (diff-hunk-next)))))) (defun diff-beginning-of-file () (beginning-of-line) @@ -421,7 +445,7 @@ when editing big diffs)." If the prefix ARG is given, restrict the view to the current file instead." (interactive "P") (save-excursion - (if arg (diff-beginning-of-file) (diff-beginning-of-hunk)) + (if arg (diff-beginning-of-file) (diff-beginning-of-hunk 'try-harder)) (narrow-to-region (point) (progn (if arg (diff-end-of-file) (diff-end-of-hunk)) (point))) @@ -449,18 +473,37 @@ If the prefix ARG is given, restrict the view to the current file instead." (diff-end-of-hunk) (kill-region start (point))))) +(defun diff-beginning-of-file-and-junk () + "Go to the beginning of file-related diff-info. +This is like `diff-beginning-of-file' except it tries to skip back over leading +data such as \"Index: ...\" and such." + (let ((start (point)) + (file (condition-case err (progn (diff-beginning-of-file) (point)) + (error err))) + ;; prevhunk is one of the limits. + (prevhunk (save-excursion (ignore-errors (diff-hunk-prev) (point)))) + err) + (when (consp file) + ;; Presumably, we started before the file header, in the leading junk. + (setq err file) + (diff-file-next) + (setq file (point))) + (let ((index (save-excursion + (re-search-backward "^Index: " prevhunk t)))) + (when index (setq file index)) + (if (<= file start) + (goto-char file) + ;; File starts *after* the starting point: we really weren't in + ;; a file diff but elsewhere. + (goto-char start) + (signal (car err) (cdr err)))))) + (defun diff-file-kill () "Kill current file's hunks." (interactive) - (diff-beginning-of-file) + (diff-beginning-of-file-and-junk) (let* ((start (point)) - (prevhunk (save-excursion - (ignore-errors - (diff-hunk-prev) (point)))) - (index (save-excursion - (re-search-backward "^Index: " prevhunk t))) (inhibit-read-only t)) - (when index (setq start index)) (diff-end-of-file) (if (looking-at "^\n") (forward-char 1)) ;`tla' generates such diffs. (kill-region start (point)))) @@ -581,14 +624,16 @@ If the OLD prefix arg is passed, tell the file NAME of the old file." (list (if old (match-string 2) (match-string 4)) (if old (match-string 4) (match-string 2))))))))) -(defun diff-find-file-name (&optional old) +(defun diff-find-file-name (&optional old prefix) "Return the file corresponding to the current patch. -Non-nil OLD means that we want the old file." +Non-nil OLD means that we want the old file. +PREFIX is only used internally: don't use it." (save-excursion (unless (looking-at diff-file-header-re) (or (ignore-errors (diff-beginning-of-file)) (re-search-forward diff-file-header-re nil t))) (let ((fs (diff-hunk-file-names old))) + (if prefix (setq fs (mapcar (lambda (f) (concat prefix f)) fs))) (or ;; use any previously used preference (cdr (assoc fs diff-remembered-files-alist)) @@ -603,13 +648,22 @@ Non-nil OLD means that we want the old file." ((or (null files) (setq file (do* ((files files (cdr files)) (file (car files) (car files))) - ((or (null file) (file-exists-p file)) + ;; Use file-regular-p to avoid + ;; /dev/null, directories, etc. + ((or (null file) (file-regular-p file)) file)))) file)) ;; .rej patches implicitly apply to (and (string-match "\\.rej\\'" (or buffer-file-name "")) (let ((file (substring buffer-file-name 0 (match-beginning 0)))) (when (file-exists-p file) file))) + ;; If we haven't found the file, maybe it's because we haven't paid + ;; attention to the PCL-CVS hint. + (and (not prefix) + (boundp 'cvs-pcl-cvs-dirchange-re) + (save-excursion + (re-search-backward cvs-pcl-cvs-dirchange-re nil t)) + (diff-find-file-name old (match-string 1))) ;; if all else fails, ask the user (let ((file (read-file-name (format "Use file %s: " (or (first fs) "")) nil (first fs) t (first fs)))) @@ -635,11 +689,11 @@ Non-nil OLD means that we want the old file." (defun diff-unified->context (start end) "Convert unified diffs to context diffs. START and END are either taken from the region (if a prefix arg is given) or -else cover the whole bufer." +else cover the whole buffer." (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) (list (region-beginning) (region-end)) (list (point-min) (point-max)))) - (unless (markerp end) (setq end (copy-marker end))) + (unless (markerp end) (setq end (copy-marker end t))) (let (;;(diff-inhibit-after-change t) (inhibit-read-only t)) (save-excursion @@ -729,7 +783,7 @@ With a prefix argument, convert unified format to context format." (list (point-min) (point-max) current-prefix-arg))) (if to-context (diff-unified->context start end) - (unless (markerp end) (setq end (copy-marker end))) + (unless (markerp end) (setq end (copy-marker end t))) (let ( ;;(diff-inhibit-after-change t) (inhibit-read-only t)) (save-excursion @@ -797,11 +851,11 @@ With a prefix argument, convert unified format to context format." (defun diff-reverse-direction (start end) "Reverse the direction of the diffs. START and END are either taken from the region (if a prefix arg is given) or -else cover the whole bufer." +else cover the whole buffer." (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) (list (region-beginning) (region-end)) (list (point-min) (point-max)))) - (unless (markerp end) (setq end (copy-marker end))) + (unless (markerp end) (setq end (copy-marker end t))) (let (;;(diff-inhibit-after-change t) (inhibit-read-only t)) (save-excursion @@ -859,7 +913,7 @@ else cover the whole bufer." (defun diff-fixup-modifs (start end) "Fixup the hunk headers (in case the buffer was modified). START and END are either taken from the region (if a prefix arg is given) or -else cover the whole bufer." +else cover the whole buffer." (interactive (if (or current-prefix-arg (and transient-mark-mode mark-active)) (list (region-beginning) (region-end)) (list (point-min) (point-max)))) @@ -973,13 +1027,17 @@ See `after-change-functions' for the meaning of BEG, END and LEN." "Major mode for viewing/editing context diffs. Supports unified and context diffs as well as (to a lesser extent) normal diffs. + When the buffer is read-only, the ESC prefix is not necessary. If you edit the buffer manually, diff-mode will try to update the hunk headers for you on-the-fly. You can also switch between context diff and unified diff with \\[diff-context->unified], or vice versa with \\[diff-unified->context] and you can also reverse the direction of -a diff with \\[diff-reverse-direction]." +a diff with \\[diff-reverse-direction]. + + \\{diff-mode-map}" + (set (make-local-variable 'font-lock-defaults) diff-font-lock-defaults) (set (make-local-variable 'outline-regexp) diff-outline-regexp) (set (make-local-variable 'imenu-generic-expression) @@ -1004,13 +1062,13 @@ a diff with \\[diff-reverse-direction]." (add-hook 'after-change-functions 'diff-after-change-function nil t) (add-hook 'post-command-hook 'diff-post-command-hook nil t)) ;; Neat trick from Dave Love to add more bindings in read-only mode: - (let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map))) + (lexical-let ((ro-bind (cons 'buffer-read-only diff-mode-shared-map))) (add-to-list 'minor-mode-overriding-map-alist ro-bind) ;; Turn off this little trick in case the buffer is put in view-mode. (add-hook 'view-mode-hook - `(lambda () - (setq minor-mode-overriding-map-alist - (delq ',ro-bind minor-mode-overriding-map-alist))) + (lambda () + (setq minor-mode-overriding-map-alist + (delq ro-bind minor-mode-overriding-map-alist))) nil t)) ;; add-log support (set (make-local-variable 'add-log-current-defun-function) @@ -1031,7 +1089,7 @@ a diff with \\[diff-reverse-direction]." (add-hook 'after-change-functions 'diff-after-change-function nil t) (add-hook 'post-command-hook 'diff-post-command-hook nil t))) -;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Handy hook functions ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun diff-delete-if-empty () ;; An empty diff file means there's no more diffs to integrate, so we @@ -1069,6 +1127,92 @@ Only works for unified diffs." nil t) (equal (match-string 1) (match-string 2))))) +(defun diff-sanity-check-context-hunk-half (lines) + (let ((count lines)) + (while + (cond + ((and (memq (char-after) '(?\s ?! ?+ ?-)) + (memq (char-after (1+ (point))) '(?\s ?\t))) + (decf count) t) + ((or (zerop count) (= count lines)) nil) + ((memq (char-after) '(?! ?+ ?-)) + (if (not (and (eq (char-after (1+ (point))) ?\n) + (y-or-n-p "Try to auto-fix whitespace loss damage? "))) + (error "End of hunk ambiguously marked") + (forward-char 1) (insert " ") (forward-line -1) t)) + ((< lines 0) + (error "End of hunk ambiguously marked")) + ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? ")) + (error "Abort!")) + ((eolp) (insert " ") (forward-line -1) t) + (t (insert " ") (delete-region (- (point) 2) (- (point) 1)) t)) + (forward-line)))) + +(defun diff-sanity-check-hunk () + (let (;; Every modification is protected by a y-or-n-p, so it's probably + ;; OK to override a read-only setting. + (inhibit-read-only t)) + (save-excursion + (cond + ((not (looking-at diff-hunk-header-re)) + (error "Not recognizable hunk header")) + + ;; A context diff. + ((eq (char-after) ?*) + (if (not (looking-at "\\*\\{15\\}\\(?: .*\\)?\n\\*\\*\\* \\([0-9]+\\),\\([0-9]+\\) \\*\\*\\*\\*")) + (error "Unrecognized context diff first hunk header format") + (forward-line 2) + (diff-sanity-check-context-hunk-half + (1+ (- (string-to-number (match-string 2)) + (string-to-number (match-string 1))))) + (if (not (looking-at "--- \\([0-9]+\\),\\([0-9]+\\) ----$")) + (error "Unrecognized context diff second hunk header format") + (forward-line) + (diff-sanity-check-context-hunk-half + (1+ (- (string-to-number (match-string 2)) + (string-to-number (match-string 1)))))))) + + ;; A unified diff. + ((eq (char-after) ?@) + (if (not (looking-at + "@@ -[0-9]+,\\([0-9]+\\) \\+[0-9]+,\\([0-9]+\\) @@")) + (error "Unrecognized unified diff hunk header format") + (let ((before (string-to-number (match-string 1))) + (after (string-to-number (match-string 2)))) + (forward-line) + (while + (case (char-after) + (?\s (decf before) (decf after) t) + (?- + (if (and (looking-at diff-file-header-re) + (zerop before) (zerop after)) + ;; No need to query: this is a case where two patches + ;; are concatenated and only counting the lines will + ;; give the right result. Let's just add an empty + ;; line so that our code which doesn't count lines + ;; will not get confused. + (progn (save-excursion (insert "\n")) nil) + (decf before) t)) + (?+ (decf after) t) + (t + (cond + ((and (zerop before) (zerop after)) nil) + ((or (< before 0) (< after 0)) + (error (if (or (zerop before) (zerop after)) + "End of hunk ambiguously marked" + "Hunk seriously messed up"))) + ((not (y-or-n-p "Try to auto-fix whitespace loss and word-wrap damage? ")) + (error "Abort!")) + ((eolp) (insert " ") (forward-line -1) t) + (t (insert " ") + (delete-region (- (point) 2) (- (point) 1)) t)))) + (forward-line))))) + + ;; A plain diff. + (t + ;; TODO. + ))))) + (defun diff-hunk-text (hunk destp char-offset) "Return the literal source text from HUNK as (TEXT . OFFSET). If DESTP is nil, TEXT is the source, otherwise the destination text. @@ -1195,7 +1339,13 @@ SRC and DST are the two variants of text as returned by `diff-hunk-text'. SWITCHED is non-nil if the patch is already applied." (save-excursion (let* ((other (diff-xor other-file diff-jump-to-old-file)) - (char-offset (- (point) (progn (diff-beginning-of-hunk) (point)))) + (char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) + (point)))) + ;; Check that the hunk is well-formed. Otherwise diff-mode and + ;; the user may disagree on what constitutes the hunk + ;; (e.g. because an empty line truncates the hunk mid-course), + ;; leading to potentially nasty surprises for the user. + (_ (diff-sanity-check-hunk)) (hunk (buffer-substring (point) (save-excursion (diff-end-of-hunk) (point)))) (old (diff-hunk-text hunk reverse char-offset)) @@ -1247,6 +1397,7 @@ SWITCHED is non-nil if the patch is already applied." (t "Hunk %s at offset %d lines")) msg line-offset))) +(defvar diff-apply-hunk-to-backup-file nil) (defun diff-apply-hunk (&optional reverse) "Apply the current hunk to the source file and go to the next. @@ -1263,6 +1414,18 @@ With a prefix argument, REVERSE the hunk." (cond ((null line-offset) (error "Can't find the text to patch")) + ((with-current-buffer buf + (and buffer-file-name + (backup-file-name-p buffer-file-name) + (not diff-apply-hunk-to-backup-file) + (not (set (make-local-variable 'diff-apply-hunk-to-backup-file) + (yes-or-no-p (format "Really apply this hunk to %s? " + (file-name-nondirectory + buffer-file-name))))))) + (error "%s" + (substitute-command-keys + (format "Use %s\\[diff-apply-hunk] to apply it to the other file" + (if (not reverse) "\\[universal-argument] "))))) ((and switched ;; A reversed patch was detected, perhaps apply it in reverse. (not (save-window-excursion @@ -1346,7 +1509,8 @@ For use in `add-log-current-defun-function'." (defun diff-refine-hunk () "Refine the current hunk by ignoring space differences." (interactive) - (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk) (point)))) + (let* ((char-offset (- (point) (progn (diff-beginning-of-hunk 'try-harder) + (point)))) (opts (case (char-after) (?@ "-bu") (?* "-bc") (t "-b"))) (line-nb (and (or (looking-at "[^0-9]+\\([0-9]+\\)") (error "Can't find line number"))