2 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
5 ;; Version: #Revision: 4.0 $
6 ;; Author: This file is based on diff.el by
7 ;; sunpitt!wpmstr!fbresz@Sun.COM 1/27/89.
8 ;; It has been completely rewritten in July 1994 by
9 ;; Sandy Rutherford <sandy@ibm550.sissa.it>
10 ;; It has mostly been demolished in March 2001 by
11 ;; Mike Sperber <mike@xemacs.org> to use
12 ;; Stefan Monnier's diff-mode.
14 ;; Description: Call unix diff utility.
16 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
18 ;;; Copyright (C) 1990 Free Software Foundation, Inc.
19 ;;; Copyright (C) 1994 Sandy Rutherford
20 ;;; Copyright (C) 2001 Mike Sperber
22 ;;; This file is based on diff.el by sunpitt!wpmstr!fbresz@Sun.COM 1/27/89.
23 ;;; It has been completely rewritten in July 1994 by
24 ;;; Sandy Rutherford <sandy@ibm550.sissa.it>
25 ;;; It has mostly been demolished in March 2001 by
26 ;;; Mike Sperber <mike@xemacs.org> to use
28 ;;; This program is free software; you can redistribute it and/or modify
29 ;;; it under the terms of the GNU General Public License as published by
30 ;;; the Free Software Foundation; either version 1, or (at your option)
31 ;;; any later version.
33 ;;; This program is distributed in the hope that it will be useful,
34 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
35 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
36 ;;; GNU General Public License for more details.
38 ;;; A copy of the GNU General Public License can be obtained from this
39 ;;; program's author (send electronic mail to sandy@ibm550.sissa.it) or
40 ;;; from the Free Software Foundation, Inc., 675 Mass Ave, Cambridge,
51 "Handling output from Unix diff utility"
54 ;; should be in to loaddefs.el now.
56 (defcustom diff-switches "-c"
57 "*A list of switches (strings) to pass to the diff program."
62 (defcustom diff-do-narrow nil
63 "*If non-nil diff buffers are initialized narrowed to each difference."
67 (defcustom diff-load-hooks nil
68 "Hooks to run after loading diff.el"
72 ;;; Internal variables
74 (defconst diff-emacs-19-p
75 (let ((ver emacs-major-version))
78 (or diff-emacs-19-p (require 'emacs-19))
80 (defvar diff-old-file nil)
81 ;; A list whose car is the name of the old file, and whose cdr indicates
82 ;; whether we should delete the buffer on quit.
83 (defvar diff-new-file nil)
84 ;; Same as diff-old-file, except for the new file.
86 (defvar diff-temp-template (expand-file-name "diff" (temp-directory)))
88 (defun diff-read-args (oldprompt newprompt switchprompt
89 &optional file-for-backup)
90 ;; Grab the args for diff. OLDPROMPT and NEWPROMPT are the prompts
91 ;; for the old & new filenames, SWITCHPROMPT for the list of
92 ;; switches. If FILE_FOR_BACKUP is provided (it must be a string if
93 ;; so), then it will be used to try & work out a file & backup to
94 ;; diff, & in this case the prompting order is backwards. %s in a
95 ;; prompt has a guess substituted into it. This is nasty.
98 (setq newf file-for-backup
99 newf (if (and newf (file-exists-p newf))
101 (format newprompt (file-name-nondirectory newf))
103 (read-file-name (format newprompt "") nil nil t))
104 oldf (file-newest-backup newf)
105 oldf (if (and oldf (file-exists-p oldf))
107 (format oldprompt (file-name-nondirectory oldf))
109 (read-file-name (format oldprompt "")
110 (file-name-directory newf) nil t)))
111 ;; Else we aren't trying to be bright...
112 (setq oldf (read-file-name (format oldprompt "") nil nil t)
114 (format newprompt (file-name-nondirectory oldf))
115 nil (file-name-directory oldf) t)))
116 (list oldf newf (diff-read-switches switchprompt))))
118 (defun diff-read-switches (switchprompt)
119 ;; Read and return a list of switches
120 (if current-prefix-arg
121 (let ((default (if (listp diff-switches)
122 (mapconcat 'identity diff-switches " ")
125 (read-string (format switchprompt default) default)))))
127 (defun diff-fix-switches (switch-spec)
128 ;; Parse a string into a list of switches or leave it be if it's
130 (if (stringp switch-spec)
131 (let (result (start 0))
132 (while (string-match "\\(\\S-+\\)" switch-spec start)
133 (setq result (cons (substring switch-spec (match-beginning 1)
136 start (match-end 0)))
140 (defun diff-get-file-buffer (file)
141 ;; Returns \(BUFFER . DEL-P\), where DEL-P is t if diff is expected
142 ;; to delete the buffer, and nil otherwise.
143 (let* ((buff (get-file-buffer file))
145 (if (and buff (buffer-modified-p buff))
148 "Buffer %s is modified. Diffing against buffer contents."
151 ;; Call find-file-noselect even if we already have the buffer,
152 ;; as it will run verify-buffer-file-modtime.
153 (cons (find-file-noselect file) del-p)))
155 (defun diff-cleanup-buffers ()
156 ;; Cleans up diff buffers by deleting buffers that we don't expect
157 ;; the user to care about.
158 (let ((files (list diff-old-file diff-new-file)))
160 (let ((ent (car files))
163 (setq buff (get-file-buffer (car ent)))
164 (not (buffer-modified-p buff))
166 (setq files (cdr files)))
167 (if (get-buffer "*Diff Header*")
168 (kill-buffer "*Diff Header*"))))
170 (defun diff-latest-backup-file (file)
171 "Return the latest existing backup of FILE, or nil."
172 ;; First try simple backup, then the highest numbered of the
174 ;; Ignore the value of version-control because we look for existing
175 ;; backups, which maybe were made earlier or by another user with
176 ;; a different value of version-control.
177 (let* ((file (expand-file-name file))
178 (handler (find-file-name-handler file 'diff-latest-backup-file)))
180 (funcall handler 'diff-latest-backup-file file)
182 (let ((bak (make-backup-file-name file)))
183 (if (file-exists-p bak) bak))
184 (let* ((dir (file-name-directory file))
185 (base-versions (concat (file-name-nondirectory file) ".~"))
186 (bv-length (length base-versions)))
189 (file-name-all-completions base-versions dir)
190 ;; bv-length is a fluid var for backup-extract-version:
193 (> (backup-extract-version fn1)
194 (backup-extract-version fn2))))))))))))
196 (defun diff-run-diff (switches old old-temp new new-temp)
197 ;; Actually run the diff process with SWITCHES on OLD and NEW.
198 ;; OLD-TEMP and NEW-TEMP are names of temp files that can be used
199 ;; to dump the data out to.
200 (insert "diff " (mapconcat 'identity switches " ") " " old
202 (apply 'call-process "diff" nil t nil
203 (append switches (list old-temp new-temp))))
206 (defun diff-fix-file-names (old old-temp new new-temp pattern)
207 ;; Replaces any temp file names with the real names of files.
210 (let ((files (list old new))
211 (temps (list old-temp new-temp))
212 buffer-read-only case-fold-search)
213 (goto-char (point-min))
214 (if (re-search-forward pattern nil t)
215 (narrow-to-region (point-min) (match-beginning 0)))
217 (let ((regexp (concat "[ \t\n]\\("
218 (regexp-quote (car temps))
220 (goto-char (point-min))
222 (while (re-search-forward regexp nil t)
223 (goto-char (match-beginning 1))
224 (delete-region (point) (match-end 1))
225 (insert (car files))))
226 (setq files (cdr files)
227 temps (cdr temps)))))))
230 (defun diff (old new &optional switches)
231 "Find and display the differences between OLD and NEW files.
232 Interactively you are prompted with the current buffer's file name for NEW
233 and what appears to be its backup for OLD."
234 ;; Support for diffing directories is rather limited. It needs work.
235 (interactive (diff-read-args "Diff original file (%s) "
236 "Diff new file (%s) "
237 "Switches for diff (%s) "
239 (setq switches (diff-fix-switches (or switches diff-switches))
240 old (expand-file-name old)
241 new (expand-file-name new))
242 (let ((curr-buff (current-buffer))
243 doing-dirs old-temp new-temp old-buffer new-buffer flag)
244 (let ((fdp-old (file-directory-p old))
245 (fdp-new (file-directory-p new)))
247 ((null (or fdp-new fdp-old)))
249 (setq old (expand-file-name (file-name-nondirectory new) old)))
251 (setq new (expand-file-name (file-name-nondirectory old) new)))
252 (t (setq doing-dirs t))))
253 ;; (message "diff %s %s %s..."
254 ;; (mapconcat (function identity) switches " ") new old)
255 (message "diff %s %s %s..."
256 (mapconcat (function identity) switches " ") old new)
258 (setq diff-old-file nil
260 (setq old-temp (make-temp-name (concat diff-temp-template "1"))
261 new-temp (make-temp-name (concat diff-temp-template "2"))
262 old-buffer (diff-get-file-buffer old)
263 new-buffer (diff-get-file-buffer new)
264 diff-old-file (cons old (cdr old-buffer))
265 diff-new-file (cons new (cdr new-buffer))))
266 (let (case-fold-search)
269 (if (string-match "[ecu]" x)
270 (setq flag (aref x (match-beginning 0))))))
274 (set-buffer (get-buffer-create "*Diff Output*"))
275 (setq default-directory (file-name-directory new))
276 (let (buffer-read-only)
277 (if (fboundp 'buffer-disable-undo)
278 (buffer-disable-undo (current-buffer))
279 ;; old style (Emacs 18.55 and earlier)
280 (buffer-disable-undo (current-buffer)))
284 (diff-run-diff switches old old new new)
286 (set-buffer (car old-buffer))
287 (write-region (point-min) (point-max) old-temp nil 'quiet)
288 (set-buffer (car new-buffer))
289 (write-region (point-min) (point-max) new-temp nil 'quiet))
290 (diff-run-diff switches old old-temp new new-temp))
291 ;; Need to replace file names
292 (if (and (not doing-dirs) (memq flag '(?c ?u)))
293 (diff-fix-file-names old old-temp new new-temp
294 diff-hunk-header-re))
296 (goto-char (point-min))
300 (diff-restrict-view)))
301 (display-buffer (current-buffer))))
303 (delete-file old-temp)
306 (delete-file new-temp)
308 (set-buffer curr-buff))))
311 (defun diff-backup (file &optional switches)
312 "Diff this file with its backup file or vice versa.
313 Uses the latest backup, if there are several numerical backups.
314 If this file is a backup, diff it with its original.
315 The backup file is the first file given to `diff'."
316 (interactive (list (read-file-name "Diff (file with backup): ")
317 (and current-prefix-arg
318 (diff-read-switches "Diff switches: "))))
320 (if (backup-file-name-p file)
322 ori (file-name-sans-versions file))
323 (setq bak (or (diff-latest-backup-file file)
324 (error "No backup found for %s" file))
326 (diff bak ori switches)))
328 ;;; Run any load hooks
329 (run-hooks 'diff-load-hook)