diff options
| author | Miles Bader | 2000-09-11 13:51:21 +0000 |
|---|---|---|
| committer | Miles Bader | 2000-09-11 13:51:21 +0000 |
| commit | 7530b6da4e245c83407efdddfa87b496246ab80d (patch) | |
| tree | 51cba920e2b6b35a3d07f981f3e341f187698ba1 /lisp/diff-mode.el | |
| parent | f74b07059d4bd017536a77a792b0575d6c2f1a1b (diff) | |
| download | emacs-7530b6da4e245c83407efdddfa87b496246ab80d.tar.gz emacs-7530b6da4e245c83407efdddfa87b496246ab80d.zip | |
(diff-apply-hunk): Function basically rewritten. Now understands
non-unified diffs. Some functionality moved into `diff-hunk-text' and
`diff-find-text'. Add OTHER-FILE, DRY-RUN, POPUP, and NOERROR
arguments. If DRY-RUN is true, don't actually modify anything. Only
reposition point in the patched file if the patch succeeds. Only pop
up another window if POPUP is true. Emit a message describing what
happened if successful, and at what line-offset. Automatically detect
reversed hunks and do something appropriate.
(diff-hunk-text, diff-find-text): New functions.
(diff-filter-lines): Function removed.
(diff-test-hunk): New function.
(diff-goto-source): Rewritten in terms of diff-apply-hunk.
Diffstat (limited to 'lisp/diff-mode.el')
| -rw-r--r-- | lisp/diff-mode.el | 269 |
1 files changed, 210 insertions, 59 deletions
diff --git a/lisp/diff-mode.el b/lisp/diff-mode.el index b737754f3db..5e5d4d7e625 100644 --- a/lisp/diff-mode.el +++ b/lisp/diff-mode.el | |||
| @@ -4,7 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> | 5 | ;; Author: Stefan Monnier <monnier@cs.yale.edu> |
| 6 | ;; Keywords: patch diff | 6 | ;; Keywords: patch diff |
| 7 | ;; Revision: $Id: diff-mode.el,v 1.9 2000/08/16 19:56:10 monnier Exp $ | 7 | ;; Revision: $Id: diff-mode.el,v 1.11 2000/09/07 20:14:27 fx Exp $ |
| 8 | 8 | ||
| 9 | ;; This file is part of GNU Emacs. | 9 | ;; This file is part of GNU Emacs. |
| 10 | 10 | ||
| @@ -42,7 +42,6 @@ | |||
| 42 | 42 | ||
| 43 | ;; - Reverse doesn't work with normal diffs. | 43 | ;; - Reverse doesn't work with normal diffs. |
| 44 | ;; - (nitpick) The mark is not always quite right in diff-goto-source. | 44 | ;; - (nitpick) The mark is not always quite right in diff-goto-source. |
| 45 | ;; - diff-apply-hunk only works on unified diffs. | ||
| 46 | 45 | ||
| 47 | ;; Todo: | 46 | ;; Todo: |
| 48 | 47 | ||
| @@ -471,21 +470,6 @@ Non-nil OLD means that we want the old file." | |||
| 471 | (if (null file) (error "Can't find the file") | 470 | (if (null file) (error "Can't find the file") |
| 472 | (list file line span))))) | 471 | (list file line span))))) |
| 473 | 472 | ||
| 474 | (defun diff-goto-source (&optional other-file) | ||
| 475 | "Jump to the corresponding source line. | ||
| 476 | `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg | ||
| 477 | is give) determines whether to jump to the old or the new file. | ||
| 478 | If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument]) | ||
| 479 | then `diff-jump-to-old-file-flag' is also set, for the next invocations." | ||
| 480 | (interactive "P") | ||
| 481 | (save-excursion | ||
| 482 | (let ((loc (diff-find-source-location other-file))) | ||
| 483 | (pop-to-buffer (find-file-noselect (car loc))) | ||
| 484 | (ignore-errors | ||
| 485 | (goto-line (+ (cadr loc) (caddr loc))) | ||
| 486 | (push-mark (point) t t) | ||
| 487 | (goto-line (cadr loc)))))) | ||
| 488 | |||
| 489 | (defun diff-mouse-goto-source (event) | 473 | (defun diff-mouse-goto-source (event) |
| 490 | "Run `diff-goto-source' for the diff at a mouse click." | 474 | "Run `diff-goto-source' for the diff at a mouse click." |
| 491 | (interactive "e") | 475 | (interactive "e") |
| @@ -896,57 +880,224 @@ Only works for unified diffs." | |||
| 896 | nil t) | 880 | nil t) |
| 897 | (equal (match-string 1) (match-string 2))))) | 881 | (equal (match-string 1) (match-string 2))))) |
| 898 | 882 | ||
| 899 | 883 | (defun diff-hunk-text (hunk dest) | |
| 900 | (defun diff-filter-lines (char) | 884 | "Returns the literal source text from HUNK, if DEST is nil, otherwise |
| 885 | the destination text." | ||
| 886 | (with-current-buffer "foo" | ||
| 887 | (erase-buffer) | ||
| 888 | (insert hunk) | ||
| 889 | (goto-char (point-min)) | ||
| 890 | (let ((src nil) | ||
| 891 | (dst nil) | ||
| 892 | (divider nil) | ||
| 893 | (num-pfx-chars 2)) | ||
| 894 | (cond ((looking-at "^@@") | ||
| 895 | ;; unified diff | ||
| 896 | (setq num-pfx-chars 1) | ||
| 897 | (forward-line 1) | ||
| 898 | (setq src (point) dst (point))) | ||
| 899 | ((looking-at "^\\*\\*") | ||
| 900 | ;; context diff | ||
| 901 | (forward-line 2) | ||
| 902 | (setq src (point)) | ||
| 903 | (re-search-forward "^--- " nil t) | ||
| 904 | (forward-line 0) | ||
| 905 | (setq divider (point)) | ||
| 906 | (forward-line 1) | ||
| 907 | (setq dst (point))) | ||
| 908 | ((looking-at "^[0-9]+a[0-9,]+$") | ||
| 909 | ;; normal diff, insert | ||
| 910 | (forward-line 1) | ||
| 911 | (setq dst (point))) | ||
| 912 | ((looking-at "^[0-9,]+d[0-9]+$") | ||
| 913 | ;; normal diff, delete | ||
| 914 | (forward-line 1) | ||
| 915 | (setq src (point))) | ||
| 916 | ((looking-at "^[0-9,]+c[0-9,]+$") | ||
| 917 | ;; normal diff, change | ||
| 918 | (forward-line 1) | ||
| 919 | (setq src (point)) | ||
| 920 | (re-search-forward "^---$" nil t) | ||
| 921 | (forward-line 0) | ||
| 922 | (setq divider (point)) | ||
| 923 | (forward-line 1) | ||
| 924 | (setq dst (point))) | ||
| 925 | (t | ||
| 926 | (error "Unknown diff hunk type"))) | ||
| 927 | |||
| 928 | (if (if dest (null dst) (null src)) | ||
| 929 | ;; Implied empty text | ||
| 930 | "" | ||
| 931 | |||
| 932 | ;; Explicit text | ||
| 933 | |||
| 934 | ;; Delete unused text region | ||
| 935 | (let ((keep (if dest dst src)) | ||
| 936 | (kill (or divider (if dest src dst)))) | ||
| 937 | (when (and kill (> kill keep)) | ||
| 938 | (delete-region kill (point-max))) | ||
| 939 | (delete-region (point-min) keep)) | ||
| 940 | |||
| 941 | ;; Remove line-prefix characters, and unneeded lines (for | ||
| 942 | ;; unified diffs). | ||
| 943 | (let ((kill-char (if dest ?- ?+))) | ||
| 901 | (goto-char (point-min)) | 944 | (goto-char (point-min)) |
| 902 | (while (not (eobp)) | 945 | (while (not (eobp)) |
| 903 | (if (eq (char-after) char) | 946 | (if (eq (char-after) kill-char) |
| 904 | (delete-region (point) (progn (forward-line 1) (point))) | 947 | (delete-region (point) (progn (forward-line 1) (point))) |
| 905 | (delete-char 1) | 948 | (delete-char num-pfx-chars) |
| 906 | (forward-line 1)))) | 949 | (forward-line 1)))) |
| 907 | 950 | ||
| 908 | (defun diff-apply-hunk (&optional reverse) | 951 | (buffer-substring-no-properties (point-min) (point-max)))))) |
| 909 | "Apply the current hunk. | 952 | |
| 910 | With a prefix argument, REVERSE the hunk. | 953 | (defun diff-find-text (text line) |
| 911 | FIXME: Only works for unified diffs." | 954 | "Return the buffer position of the nearest occurance of TEXT to line LINE. |
| 912 | (interactive "P") | 955 | If TEXT isn't found, nil is returned." |
| 913 | (save-excursion | 956 | (goto-line line) |
| 914 | (let ((loc (diff-find-source-location nil))) | 957 | (let* ((orig (point)) |
| 915 | (diff-beginning-of-hunk) | 958 | (forw (and (search-forward text nil t) |
| 916 | (unless (looking-at diff-hunk-header-re) (error "Help! Mom!")) | ||
| 917 | (goto-char (1+ (match-end 0))) | ||
| 918 | ;; Extract the SRC and DEST strings. | ||
| 919 | (let ((text (buffer-substring (point) (progn (diff-end-of-hunk) (point)))) | ||
| 920 | src dest) | ||
| 921 | (with-temp-buffer | ||
| 922 | (insert text) | ||
| 923 | (diff-filter-lines ?+) | ||
| 924 | (setq src (buffer-string)) | ||
| 925 | (erase-buffer) | ||
| 926 | (insert text) | ||
| 927 | (diff-filter-lines ?-) | ||
| 928 | (setq dest (buffer-string))) | ||
| 929 | ;; Exchange the two strings if we're reversing the patch. | ||
| 930 | (if reverse (let ((tmp src)) (setq src dest) (setq dest tmp))) | ||
| 931 | ;; Look for SRC in the file. | ||
| 932 | (pop-to-buffer (find-file-noselect (car loc))) | ||
| 933 | (goto-line (cadr loc)) | ||
| 934 | (let* ((pos (point)) | ||
| 935 | (forw (and (search-forward src nil t) | ||
| 936 | (match-beginning 0))) | 959 | (match-beginning 0))) |
| 937 | (back (and (goto-char (+ pos (length src))) | 960 | (back (and (goto-char (+ orig (length text))) |
| 938 | (search-backward src nil t) | 961 | (search-backward text nil t) |
| 939 | (match-beginning 0)))) | 962 | (match-beginning 0)))) |
| 940 | ;; Choose the closest match. | 963 | ;; Choose the closest match. |
| 941 | (setq pos (if (and forw back) | 964 | (if (and forw back) |
| 942 | (if (> (- forw pos) (- pos back)) back forw) | 965 | (if (> (- forw orig) (- orig back)) back forw) |
| 943 | (or back forw))) | 966 | (or back forw)))) |
| 944 | (unless pos (error "Can't find the text to patch")) | 967 | |
| 945 | ;; Do it! | 968 | (defun diff-apply-hunk (&optional reverse other-file dry-run popup noerror) |
| 946 | (goto-char pos) | 969 | "Apply the current hunk to the source file. |
| 947 | (delete-char (length src)) | 970 | By default, the new source file is patched, but if the variable |
| 948 | (insert dest)))))) | 971 | `diff-jump-to-old-file-flag' is non-nil, then the old source file is |
| 972 | patched instead (some commands, such as `diff-goto-source' can change | ||
| 973 | the value of this variable when given an appropriate prefix argument). | ||
| 974 | |||
| 975 | With a prefix argument, REVERSE the hunk. | ||
| 976 | If OTHER-FILE is non-nil, patch the old file by default, and reverse the | ||
| 977 | sense of `diff-jump-to-old-file-flag'. | ||
| 978 | If DRY-RUN is non-nil, don't actually modify anything, just see whether | ||
| 979 | it's possible to do so. | ||
| 980 | If POPUP is non-nil, pop up the patched file in another window; if POPUP | ||
| 981 | is `select' then select the new window too. | ||
| 982 | If NOERROR is non-nil, then no error is signaled in the case where the hunk | ||
| 983 | cannot be found in the source file (other errors may still be signaled). | ||
| 984 | |||
| 985 | Return values are `t' if the hunk was sucessfully applied (or could be | ||
| 986 | applied, in the case where DRY-RUN was non-nil), `reversed' if the hunk | ||
| 987 | was applied backwards, or nil if the hunk couldn't be found and NOERROR | ||
| 988 | was non-nil." | ||
| 989 | (interactive (list current-prefix-arg nil nil t)) | ||
| 990 | |||
| 991 | (when other-file | ||
| 992 | ;; OTHER-FILE inverts the sense of the hunk | ||
| 993 | (setq reverse (not reverse))) | ||
| 994 | (when diff-jump-to-old-file-flag | ||
| 995 | ;; The global variable `diff-jump-to-old-file-flag' inverts the | ||
| 996 | ;; sense of OTHER-FILE (in `diff-find-source-location') | ||
| 997 | (setq reverse (not reverse))) | ||
| 998 | |||
| 999 | (let* ((loc (diff-find-source-location other-file)) | ||
| 1000 | (buf (find-file-noselect (car loc))) | ||
| 1001 | (patch-line (cadr loc)) | ||
| 1002 | (hunk | ||
| 1003 | (save-excursion | ||
| 1004 | (diff-beginning-of-hunk) | ||
| 1005 | (unless (looking-at diff-hunk-header-re) | ||
| 1006 | (error "Malformed hunk")) | ||
| 1007 | (buffer-substring (point) (progn (diff-end-of-hunk) (point))))) | ||
| 1008 | (src (diff-hunk-text hunk reverse)) | ||
| 1009 | (dst (diff-hunk-text hunk (not reverse))) | ||
| 1010 | (pos | ||
| 1011 | (with-current-buffer buf (diff-find-text src patch-line))) | ||
| 1012 | (reversed-pos | ||
| 1013 | (and (null pos) | ||
| 1014 | (with-current-buffer buf (diff-find-text dst patch-line))))) | ||
| 1015 | |||
| 1016 | (when (and reversed-pos popup) | ||
| 1017 | ;; A reversed patch was detected, perhaps apply it in reverse | ||
| 1018 | ;; (this is only done in `interactive' mode, when POPUP is non-nil). | ||
| 1019 | (if (or dry-run | ||
| 1020 | (save-window-excursion | ||
| 1021 | (pop-to-buffer buf) | ||
| 1022 | (goto-char reversed-pos) | ||
| 1023 | (if reverse | ||
| 1024 | (y-or-n-p | ||
| 1025 | "Hunk hasn't been applied yet, so can't reverse it; apply it now? ") | ||
| 1026 | (y-or-n-p "Hunk has already been applied; undo it? ")))) | ||
| 1027 | |||
| 1028 | ;; Set up things to reverse the diff | ||
| 1029 | (let ((swap dst)) | ||
| 1030 | (setq pos reversed-pos) | ||
| 1031 | (setq src dst) | ||
| 1032 | (setq dst swap)) | ||
| 1033 | |||
| 1034 | ;; The user has chosen not to apply the reversed hunk, but we | ||
| 1035 | ;; don't want to given an error message, so set things up so | ||
| 1036 | ;; nothing else gets done down below | ||
| 1037 | (message "(Nothing done)") | ||
| 1038 | (setq noerror t))) | ||
| 1039 | |||
| 1040 | (if (null pos) | ||
| 1041 | ;; POS is nil, so we couldn't find the source text. | ||
| 1042 | (unless noerror | ||
| 1043 | (error "Can't find the text to patch")) | ||
| 1044 | |||
| 1045 | (let ((reversed (if reversed-pos (not reverse) reverse))) | ||
| 1046 | (unless dry-run | ||
| 1047 | ;; Apply the hunk | ||
| 1048 | (with-current-buffer buf | ||
| 1049 | (goto-char pos) | ||
| 1050 | (delete-char (length src)) | ||
| 1051 | (insert dst))) | ||
| 1052 | |||
| 1053 | (when popup | ||
| 1054 | ;; Show a message describing what was done | ||
| 1055 | (let ((real-line | ||
| 1056 | (1+ (with-current-buffer buf (count-lines (point-min) pos)))) | ||
| 1057 | (msg | ||
| 1058 | (if dry-run | ||
| 1059 | (if reversed "already applied" "not yet applied") | ||
| 1060 | (if reversed "undone" "applied")))) | ||
| 1061 | (cond ((= real-line patch-line) | ||
| 1062 | (message "Hunk %s" msg)) | ||
| 1063 | ((= real-line (1+ patch-line)) | ||
| 1064 | (message "Hunk %s at offset 1 line" msg)) | ||
| 1065 | (t | ||
| 1066 | (message "Hunk %s at offset %d lines" | ||
| 1067 | msg | ||
| 1068 | (- real-line patch-line))))) | ||
| 1069 | |||
| 1070 | ;; Display BUF in a window, and maybe select it | ||
| 1071 | (cond ((eq popup 'select) | ||
| 1072 | (pop-to-buffer buf) | ||
| 1073 | (goto-char pos)) | ||
| 1074 | (t | ||
| 1075 | (set-window-point (display-buffer buf) pos)))) | ||
| 1076 | |||
| 1077 | ;; Return an appropriate indicator of success | ||
| 1078 | (if reversed 'reversed t))))) | ||
| 1079 | |||
| 949 | 1080 | ||
| 1081 | (defun diff-test-hunk (&optional reverse) | ||
| 1082 | "See whether it's possible to apply the current hunk. | ||
| 1083 | With a prefix argument, REVERSE the hunk." | ||
| 1084 | (interactive "P") | ||
| 1085 | (diff-apply-hunk reverse nil t t)) | ||
| 1086 | |||
| 1087 | (defun diff-goto-source (&optional other-file) | ||
| 1088 | "Jump to the corresponding source line. | ||
| 1089 | `diff-jump-to-old-file-flag' (or its opposite if the OTHER-FILE prefix arg | ||
| 1090 | is give) determines whether to jump to the old or the new file. | ||
| 1091 | If the prefix arg is bigger than 8 (for example with \\[universal-argument] \\[universal-argument]) | ||
| 1092 | then `diff-jump-to-old-file-flag' is also set, for the next invocations." | ||
| 1093 | (interactive "P") | ||
| 1094 | (or (diff-apply-hunk nil other-file t 'select t) | ||
| 1095 | ;; couldn't actually find the hunk, just obey the hunk line number | ||
| 1096 | (let ((loc (diff-find-source-location other-file))) | ||
| 1097 | (find-file-other-window (car loc)) | ||
| 1098 | (goto-line (cadr loc)) | ||
| 1099 | (error "Hunk text not found")))) | ||
| 1100 | |||
| 950 | 1101 | ||
| 951 | ;; provide the package | 1102 | ;; provide the package |
| 952 | (provide 'diff-mode) | 1103 | (provide 'diff-mode) |