(tmm-add-prompt): If tmm-completion-prompt is nil,
[bpt/emacs.git] / lisp / ediff-ptch.el
1 ;;; ediff-ptch.el --- Ediff's patch support
2
3 ;; Copyright (C) 1996 Free Software Foundation, Inc.
4
5 ;; Author: Michael Kifer <kifer@cs.sunysb.edu>
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24
25 ;;; Code:
26
27 (defvar ediff-last-dir-patch nil
28 "Last directory used by an Ediff command for file to patch.")
29
30 (defvar ediff-backup-extension
31 (if (memq system-type '(vax-vms axp-vms emx ms-dos windows-nt windows-95))
32 "_orig" ".orig")
33 "Default backup extension for the patch program.")
34
35 (defvar ediff-patch-default-directory nil
36 "*Default directory to look for patches.")
37
38 (defvar ediff-context-diff-label-regexp
39 (concat "\\(" ; context diff 2-liner
40 "^\\*\\*\\* \\([^ \t]+\\)[^*]+[\t ]*\n--- \\([^ \t]+\\)"
41 "\\|" ; GNU unified format diff 2-liner
42 "^--- \\([^ \t]+\\)[^-]+[\t ]*\n\\+\\+\\+ \\([^ \t]+\\)"
43 "\\)")
44 "*Regexp matching filename 2-liners at the start of each context diff.")
45
46 (defvar ediff-patch-program "patch"
47 "*Name of the program that applies patches.")
48 (defvar ediff-patch-options ""
49 "*Options to pass to ediff-patch-program.")
50
51 ;; The buffer of the patch file. Local to control buffer.
52 (ediff-defvar-local ediff-patchbufer nil "")
53
54 ;; The buffer where patch displays its diagnostics.
55 (ediff-defvar-local ediff-patch-diagnostics nil "")
56
57 ;; Map of patch buffer. Has the form:
58 ;; ((filename1 marker1 marker2) (filename2 marker1 marker2) ...)
59 ;; where filenames are files to which patch would have applied the patch;
60 ;; marker1 delimits the beginning of the corresponding patch and marker2 does
61 ;; it for the end.
62 (ediff-defvar-local ediff-patch-map nil "")
63
64 ;; strip prefix from filename
65 ;; returns /dev/null, if can't strip prefix
66 (defsubst ediff-file-name-sans-prefix (filename prefix)
67 (save-match-data
68 (if (string-match (concat "^" prefix) filename)
69 (substring filename (match-end 0))
70 (concat "/null/" filename))))
71
72
73
74 ;; no longer used
75 ;; return the number of matches of regexp in buf starting from the beginning
76 (defun ediff-count-matches (regexp buf)
77 (ediff-eval-in-buffer buf
78 (let ((count 0) opoint)
79 (save-excursion
80 (goto-char (point-min))
81 (while (and (not (eobp))
82 (progn (setq opoint (point))
83 (re-search-forward regexp nil t)))
84 (if (= opoint (point))
85 (forward-char 1)
86 (setq count (1+ count)))))
87 count)))
88
89 ;; Scan BUF (which is supposed to contain a patch) and make a list of the form
90 ;; ((filename1 marker1 marker2) (filename2 marker1 marker2) ...)
91 ;; where filenames are files to which patch would have applied the patch;
92 ;; marker1 delimits the beginning of the corresponding patch and marker2 does
93 ;; it for the end. This list is then assigned to ediff-patch-map.
94 ;; Returns the number of elements in the list ediff-patch-map
95 (defun ediff-map-patch-buffer (buf)
96 (ediff-eval-in-buffer buf
97 (let ((count 0)
98 (mark1 (move-marker (make-marker) (point-min)))
99 (mark1-end (point-min))
100 (possible-file-names '("/dev/null" . "/dev/null"))
101 mark2-end mark2 filenames
102 beg1 beg2 end1 end2
103 patch-map opoint)
104 (save-excursion
105 (goto-char (point-min))
106 (setq opoint (point))
107 (while (and (not (eobp))
108 (re-search-forward ediff-context-diff-label-regexp nil t))
109 (if (= opoint (point))
110 (forward-char 1) ; ensure progress towards the end
111 (setq mark2 (move-marker (make-marker) (match-beginning 0))
112 mark2-end (match-end 0)
113 beg1 (match-beginning 2)
114 end1 (match-end 2)
115 beg2 (match-beginning 3)
116 end2 (match-end 3))
117 ;; possible-file-names is holding the new file names until we
118 ;; insert the old file name in the patch map
119 ;; It is a pair (filename from 1st header line . fn from 2nd line)
120 (setq possible-file-names
121 (cons (if (and beg1 end1)
122 (buffer-substring beg1 end1)
123 "/dev/null")
124 (if (and beg2 end2)
125 (buffer-substring beg2 end2)
126 "/dev/null")))
127 ;; check for any `Index:' or `Prereq:' lines, but don't use them
128 (if (re-search-backward "^Index:" mark1-end 'noerror)
129 (move-marker mark2 (match-beginning 0)))
130 (if (re-search-backward "^Prereq:" mark1-end 'noerror)
131 (move-marker mark2 (match-beginning 0)))
132
133 (goto-char mark2-end)
134
135 (if filenames
136 (setq patch-map (cons (list filenames mark1 mark2) patch-map)))
137 (setq mark1 mark2
138 mark1-end mark2-end
139 filenames possible-file-names))
140 (setq opoint (point)
141 count (1+ count))))
142 (setq mark2 (point-max-marker)
143 patch-map (cons (list possible-file-names mark1 mark2) patch-map))
144 (setq ediff-patch-map (nreverse patch-map))
145 count)))
146
147 ;; Fix up the file names in the list using the argument FILENAME
148 ;; Algorithm: find the first file's directory and cut it out from each file
149 ;; name in the patch. Prepend the directory of FILENAME to each file in the
150 ;; patch. In addition, the first file in the patch is replaced by FILENAME.
151 ;; Each file is actually a file-pair of files found in the context diff header
152 ;; In the end, for each pair, we select the shortest existing file.
153 ;; Note: Ediff doesn't recognize multi-file patches that are separated
154 ;; with the `Index:' line. It treats them as a single-file patch.
155 ;;
156 ;; Executes inside the patch buffer
157 (defun ediff-fixup-patch-map (filename)
158 (setq filename (expand-file-name filename))
159 (let ((actual-dir (if (file-directory-p filename)
160 ;; directory part of filename
161 (file-name-as-directory filename)
162 (file-name-directory filename)))
163 ;; directory part of the first file in the patch
164 (base-dir1 (file-name-directory (car (car (car ediff-patch-map)))))
165 (base-dir2 (file-name-directory (cdr (car (car ediff-patch-map)))))
166 )
167
168 ;; chop off base-dirs
169 (mapcar (function (lambda (triple)
170 (or (string= (car (car triple)) "/dev/null")
171 (setcar (car triple)
172 (ediff-file-name-sans-prefix
173 (car (car triple)) base-dir1)))
174 (or (string= (cdr (car triple)) "/dev/null")
175 (setcdr (car triple)
176 (ediff-file-name-sans-prefix
177 (cdr (car triple)) base-dir2)))
178 ))
179 ediff-patch-map)
180
181 ;; take the given file name into account
182 (or (file-directory-p filename)
183 (string= "/dev/null" filename)
184 (progn
185 (setcar (car ediff-patch-map)
186 (cons (file-name-nondirectory filename)
187 (file-name-nondirectory filename)))))
188
189 ;; prepend actual-dir
190 (mapcar (function (lambda (triple)
191 (if (and (string-match "^/null/" (car (car triple)))
192 (string-match "^/null/" (cdr (car triple))))
193 ;; couldn't strip base-dir1 and base-dir2
194 ;; hence, something wrong
195 (progn
196 (with-output-to-temp-buffer ediff-msg-buffer
197 (princ
198 (format "
199 The patch file contains a context diff for
200 %s
201 %s
202
203 However, Ediff cannot infer the name of the actual file
204 to be patched on your system. If you know the correct file name,
205 please enter it now.
206
207 If you don't know and still would like to apply patches to
208 other files, enter /dev/null
209 "
210 (substring (car (car triple)) 6)
211 (substring (cdr (car triple)) 6))))
212 (let ((directory t)
213 user-file)
214 (while directory
215 (setq user-file
216 (read-file-name
217 "Please enter file name: "
218 actual-dir actual-dir t))
219 (if (not (file-directory-p user-file))
220 (setq directory nil)
221 (setq directory t)
222 (beep)
223 (message "%s is a directory" user-file)
224 (sit-for 2)))
225 (setcar triple (cons user-file user-file))))
226 (setcar (car triple)
227 (expand-file-name
228 (concat actual-dir (car (car triple)))))
229 (setcdr (car triple)
230 (expand-file-name
231 (concat actual-dir (cdr (car triple))))))
232 ))
233 ediff-patch-map)
234 ;; check for the shorter existing file in each pair and discard the other
235 ;; one
236 (mapcar (function (lambda (triple)
237 (let* ((file1 (car (car triple)))
238 (file2 (cdr (car triple)))
239 (f1-exists (file-exists-p file1))
240 (f2-exists (file-exists-p file2)))
241 (cond
242 ((and (< (length file2) (length file1))
243 f2-exists)
244 (setcar triple file2))
245 ((and (< (length file1) (length file2))
246 f1-exists)
247 (setcar triple file1))
248 ((and f1-exists f2-exists
249 (string= file1 file2))
250 (setcar triple file1))
251 ((and f1-exists f2-exists)
252 (with-output-to-temp-buffer ediff-msg-buffer
253 (princ (format "
254 Ediff has inferred that
255 %s
256 %s
257 are possible targets for applying the patch.
258 Both files seem to be plausible alternatives.
259
260 Please advice:
261 Type `y' to use %s as the target;
262 Type `n' to use %s as the target.
263 "
264 file1 file2 file2 file1)))
265 (setcar triple
266 (if (y-or-n-p (format "Use %s ? " file2))
267 file2 file1)))
268 (f2-exists (setcar triple file2))
269 (f1-exists (setcar triple file1))
270 (t
271 (with-output-to-temp-buffer ediff-msg-buffer
272 (princ (format "
273 Ediff inferred that
274 %s
275 %s
276 are possible alternative targets for this patch.
277
278 However, these files do not exist.
279
280 Please enter an alternative patch target ...
281 "
282 file1 file2)))
283 (let ((directory t)
284 target)
285 (while directory
286 (setq target (read-file-name
287 "Please enter a patch target: "
288 actual-dir actual-dir t))
289 (if (not (file-directory-p target))
290 (setq directory nil)
291 (beep)
292 (message "%s is a directory" target)
293 (sit-for 2)))
294 (setcar triple target)))))))
295 ediff-patch-map)
296 ))
297
298 (defun ediff-show-patch-diagnostics ()
299 (interactive)
300 (cond ((window-live-p ediff-window-A)
301 (set-window-buffer ediff-window-A ediff-patch-diagnostics))
302 ((window-live-p ediff-window-B)
303 (set-window-buffer ediff-window-B ediff-patch-diagnostics))
304 (t (display-buffer ediff-patch-diagnostics 'not-this-window))))
305
306 (defun ediff-get-patch-buffer ()
307 "Obtain patch buffer. If patch is already in a buffer---use it.
308 Else, read patch file into a new buffer."
309 (let ((dir (cond (ediff-patch-default-directory) ; try patch default dir
310 (ediff-use-last-dir ediff-last-dir-patch)
311 (t default-directory)))
312 patch-buf)
313 (if (y-or-n-p "Is the patch already in a buffer? ")
314 (setq patch-buf
315 (get-buffer
316 (read-buffer
317 "Which buffer contains the patch? "
318 (current-buffer) 'must-match)))
319 (setq patch-buf
320 (find-file-noselect
321 (read-file-name "Which file contains the patch? " dir))))
322
323 (ediff-eval-in-buffer patch-buf
324 (goto-char (point-min))
325 (or (ediff-get-visible-buffer-window patch-buf)
326 (progn
327 (pop-to-buffer patch-buf 'other-window)
328 (select-window (previous-window)))))
329 (ediff-map-patch-buffer patch-buf)
330 patch-buf))
331
332 ;; Dispatch the right patch file function: regular or meta-level,
333 ;; depending on how many patches are in the patch file.
334 ;; At present, there is no support for meta-level patches.
335 ;; Should return either the ctl buffer or the meta-buffer
336 (defun ediff-dispatch-file-patching-job (patch-buf filename
337 &optional startup-hooks)
338 (ediff-eval-in-buffer patch-buf
339 ;; relativize names in the patch with respect to source-file
340 (ediff-fixup-patch-map filename)
341 (if (< (length ediff-patch-map) 2)
342 (ediff-patch-file-internal
343 patch-buf
344 (if (and (not (string-match "^/dev/null" (car (car ediff-patch-map))))
345 (> (length (car (car ediff-patch-map))) 1))
346 (car (car ediff-patch-map))
347 filename)
348 startup-hooks)
349 (ediff-multi-patch-internal patch-buf startup-hooks))
350 ))
351
352
353 (defun ediff-patch-buffer-internal (patch-buf buf-to-patch-name
354 &optional startup-hooks)
355 (let* ((buf-to-patch (get-buffer buf-to-patch-name))
356 (file-name-ok (if buf-to-patch (buffer-file-name buf-to-patch)))
357 (buf-mod-status (buffer-modified-p buf-to-patch))
358 (multifile-patch-p (> (length (ediff-eval-in-buffer patch-buf
359 ediff-patch-map)) 1))
360 default-dir file-name ctl-buf)
361 (if file-name-ok
362 (setq file-name file-name-ok)
363 (if multifile-patch-p
364 (error
365 "Can't apply multi-file patches to buffers that visit no files"))
366 (ediff-eval-in-buffer buf-to-patch
367 (setq default-dir default-directory)
368 (setq file-name (ediff-make-temp-file buf-to-patch))
369 (set-visited-file-name file-name)
370 (setq buffer-auto-save-file-name nil) ; don't create auto-save file
371 ;;don't confuse the user with a new bufname
372 (rename-buffer buf-to-patch-name)
373 (set-buffer-modified-p nil)
374 (set-visited-file-modtime) ; sync buffer and temp file
375 (setq default-directory default-dir)
376 ))
377
378 ;; dispatch a patch function
379 (setq ctl-buf (ediff-dispatch-file-patching-job
380 patch-buf file-name startup-hooks))
381
382 (if file-name-ok
383 ()
384 ;; buffer wasn't visiting any file,
385 ;; so we will not run meta-level ediff here
386 (ediff-eval-in-buffer ctl-buf
387 (delete-file (buffer-file-name ediff-buffer-A))
388 (delete-file (buffer-file-name ediff-buffer-B))
389 (ediff-eval-in-buffer ediff-buffer-A
390 (if default-dir (setq default-directory default-dir))
391 (set-visited-file-name nil)
392 (rename-buffer buf-to-patch-name)
393 (set-buffer-modified-p buf-mod-status))
394 (ediff-eval-in-buffer ediff-buffer-B
395 (setq buffer-auto-save-file-name nil) ; don't create auto-save file
396 (if default-dir (setq default-directory default-dir))
397 (set-visited-file-name nil)
398 (rename-buffer (ediff-unique-buffer-name
399 (concat buf-to-patch-name "_patched") ""))
400 (set-buffer-modified-p t))))
401 ))
402
403 (defun ediff-patch-file-internal (patch-buf source-filename
404 &optional startup-hooks)
405 (setq source-filename (expand-file-name source-filename))
406
407 (let* ((backup-extension
408 ;; if the user specified a -b option, extract the backup
409 ;; extension from there; else use ediff-backup-extension
410 (substring ediff-patch-options
411 (if (string-match "-b[ \t]+" ediff-patch-options)
412 (match-end 0) 0)
413 (if (string-match "-b[ \t]+[^ \t]+" ediff-patch-options)
414 (match-end 0) 0)))
415 (shell-file-name ediff-shell)
416 (patch-diagnostics (get-buffer-create "*ediff patch diagnostics*"))
417 ;; ediff-find-file may use a temp file to do the patch
418 ;; so, we save source-filename and true-source-filename as a var
419 ;; that initially is source-filename but may be changed to a temp
420 ;; file for the purpose of patching.
421 (true-source-filename source-filename)
422 (target-filename source-filename)
423 target-buf buf-to-patch file-name-magic-p ctl-buf backup-style)
424
425 ;; if the user didn't specify a backup extension, use
426 ;; ediff-backup-extension
427 (if (string= backup-extension "")
428 (setq backup-extension ediff-backup-extension))
429 (if (string-match "-V" ediff-patch-options)
430 (error
431 "Ediff doesn't take the -V option in `ediff-patch-options'--sorry"))
432
433 ;; Make a temp file, if source-filename has a magic file handler (or if
434 ;; it is handled via auto-mode-alist and similar magic).
435 ;; Check if there is a buffer visiting source-filename and if they are in
436 ;; sync; arrange for the deletion of temp file.
437 (ediff-find-file 'true-source-filename 'buf-to-patch
438 'ediff-last-dir-patch 'startup-hooks)
439
440 ;; Check if source file name has triggered black magic, such as file name
441 ;; handlers or auto mode alist, and make a note of it.
442 ;; true-source-filename should be either the original name or a
443 ;; temporary file where we put the after-product of the file handler.
444 (setq file-name-magic-p (not (equal (file-truename true-source-filename)
445 (file-truename source-filename))))
446
447 ;; Checkout orig file, if necessary, so that the patched file could be
448 ;; checked back in.
449 (if (ediff-file-checked-in-p (buffer-file-name buf-to-patch))
450 (ediff-toggle-read-only buf-to-patch))
451
452 (ediff-eval-in-buffer patch-diagnostics
453 (insert-buffer patch-buf)
454 (message "Applying patch ... ")
455 ;; fix environment for gnu patch, so it won't make numbered extensions
456 (setq backup-style (getenv "VERSION_CONTROL"))
457 (setenv "VERSION_CONTROL" nil)
458 ;; always pass patch the -f option, so it won't ask any questions
459 (shell-command-on-region
460 (point-min) (point-max)
461 (format "%s -f %s -b %s %s"
462 ediff-patch-program ediff-patch-options
463 backup-extension
464 (expand-file-name true-source-filename))
465 t)
466 ;; restore environment for gnu patch
467 (setenv "VERSION_CONTROL" backup-style))
468
469 (message "Applying patch ... done")
470 (message "")
471
472 (switch-to-buffer patch-diagnostics)
473 (sit-for 0) ; synchronize - let the user see diagnostics
474
475 (or (file-exists-p (concat true-source-filename backup-extension))
476 (error "Patch appears to have failed"))
477
478 ;; If black magic is involved, apply patch to a temp copy of the
479 ;; file. Otherwise, apply patch to the orig copy. If patch is applied
480 ;; to temp copy, we name the result old-name_patched for local files
481 ;; and temp-copy_patched for remote files. The orig file name isn't
482 ;; changed, and the temp copy of the original is later deleted.
483 ;; Without magic, the original file is renamed (usually into
484 ;; old-name_orig) and the result of patching will have the same name as
485 ;; the original.
486 (if (not file-name-magic-p)
487 (ediff-eval-in-buffer buf-to-patch
488 (set-visited-file-name (concat source-filename backup-extension))
489 (set-buffer-modified-p nil))
490
491 ;; Black magic in effect.
492 ;; If orig file was remote, put the patched file in the temp directory.
493 ;; If orig file is local, put the patched file in the directory of
494 ;; the orig file.
495 (setq target-filename
496 (concat
497 (if (ediff-file-remote-p (file-truename source-filename))
498 true-source-filename
499 source-filename)
500 "_patched"))
501
502 (rename-file true-source-filename target-filename t)
503
504 ;; arrange that the temp copy of orig will be deleted
505 (rename-file (concat true-source-filename backup-extension)
506 true-source-filename t))
507
508 ;; make orig buffer read-only
509 (setq startup-hooks
510 (cons 'ediff-set-read-only-in-buf-A startup-hooks))
511
512 ;; set up a buf for the patched file
513 (setq target-buf (find-file-noselect target-filename))
514
515 (setq ctl-buf
516 (ediff-buffers-internal
517 buf-to-patch target-buf nil
518 startup-hooks 'epatch))
519 (ediff-eval-in-buffer ctl-buf
520 (setq ediff-patchbufer patch-buf
521 ediff-patch-diagnostics patch-diagnostics))
522
523 (bury-buffer patch-diagnostics)
524 (message "Type `P', if you need to see patch diagnostics")
525 ctl-buf))
526
527 (defun ediff-multi-patch-internal (patch-buf &optional startup-hooks)
528 (let (meta-buf)
529 (setq startup-hooks
530 ;; this sets various vars in the meta buffer inside
531 ;; ediff-prepare-meta-buffer
532 (cons (` (lambda ()
533 ;; tell what to do if the user clicks on a session record
534 (setq ediff-session-action-function
535 'ediff-patch-file-form-meta
536 ediff-meta-patchbufer patch-buf)
537 ))
538 startup-hooks))
539 (setq meta-buf (ediff-prepare-meta-buffer
540 'ediff-filegroup-action
541 (ediff-eval-in-buffer patch-buf
542 ;; nil replaces a regular expression
543 (cons (list nil (format "%S" patch-buf))
544 ediff-patch-map))
545 "*Ediff Session Group Panel"
546 'ediff-redraw-directory-group-buffer
547 'ediff-multifile-patch
548 startup-hooks))
549 (ediff-show-meta-buffer meta-buf)
550 ))
551
552
553
554
555 ;;; Local Variables:
556 ;;; eval: (put 'ediff-defvar-local 'lisp-indent-hook 'defun)
557 ;;; eval: (put 'ediff-eval-in-buffer 'lisp-indent-hook 1)
558 ;;; End:
559
560 (provide 'ediff-ptch)
561
562 ;;; ediff-ptch.el ends here