1 ;;; patcher-diff.el --- Diff utilities
3 ;; Copyright (C) 2008, 2009, 2010 Didier Verna
4 ;; Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2007 Didier Verna
6 ;; Author: Didier Verna <didier@xemacs.org>
7 ;; Maintainer: Didier Verna <didier@xemacs.org>
8 ;; Created: Sun Feb 14 16:58:16 2010
9 ;; Last Revision: Fri Dec 2 22:11:00 2011
13 ;; This file is part of Patcher.
15 ;; Patcher is free software; you can redistribute it and/or modify
16 ;; it under the terms of the GNU General Public License version 3,
17 ;; as published by the Free Software Foundation.
19 ;; Patcher is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program; if not, write to the Free Software
26 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
31 ;; Contents management by FCM version 0.1.
38 (eval-when-compile (require 'patcher-cutil))
39 (require 'patcher-util)
40 (require 'patcher-project)
41 (require 'patcher-instance)
42 (require 'patcher-source)
43 (require 'patcher-change-log)
47 ;; ==========================================================================
49 ;; ==========================================================================
51 (defun* patcher-run-after-diff-hook
53 &aux (after-diff-hook (patcher-project-option project :after-diff-hook)))
54 ;; Run PROJECT's after-diff-hook in current buffer, between MIN and MAX.
56 (patcher-with-progression "Running the after-diff hook"
57 (mapcar (lambda (func)
58 (funcall func min max))
64 ;; ==========================================================================
65 ;; Diff creation and parsing
66 ;; ==========================================================================
68 (defun patcher-parse-diff (project min max)
69 ;; Parse PROJECT's diff output in current buffer between MIN and MAX.
70 ;; For each diffed file, create extents with the following properties:
71 ;; 'patcher-project = PROJECT, and
72 ;; 'patcher-change-log = <absolute filename> for ChangeLog files, or
73 ;; 'patcher-source = <absolute filename> for source files.
74 (patcher-with-progression "Parsing the diff output"
77 (let* ((diff-header (patcher-project-option project :diff-header t))
78 (change-logs-updating (patcher-project-option project
79 :change-logs-updating))
80 (regexp (nth 0 diff-header))
81 (old-file-match (nth 1 diff-header))
82 (new-file-match (nth 2 diff-header))
87 (while (re-search-forward regexp max t)
88 (setq beg (match-beginning 0))
89 (setq old-file (match-string old-file-match))
90 (setq new-file (match-string new-file-match))
91 (if (string= old-file "/dev/null")
93 (setq old-absfile (expand-file-name old-file default-directory)))
94 (if (string= new-file "/dev/null")
96 (setq new-absfile (expand-file-name new-file default-directory)))
97 (setq end (or (save-excursion
98 (when (re-search-forward regexp max t)
101 ;; #### NOTE: the extent properties below are set in relation with
102 ;; NEW-FILE, unless we're facing a deletion, in which case we use
104 (when change-logs-updating
105 (setq change-log (patcher-locate-change-log project
106 (or new-absfile old-absfile))))
107 (set-extent-properties (make-extent beg end)
110 patcher-project ,project
117 ,(or new-absfile old-absfile))))))))
119 (patcher-define-error 'diff
120 "Patcher diff error."
123 (put 'patcher-diff 'lisp-indent-function 2)
125 (project command &key (erase t) (progression "Diff'ing the project"))
126 ;; Create a PROJECT diff with COMMAND in process buffer.
127 ;; If ERASE (the default), erase the process buffer first.
128 ;; Otherwise, insert the diff at current point.
129 ;; Use PROGRESSION message to make people wait.
130 ;; After the diff is created, run the diff line filter, the after diff hook
131 ;; and parse the diff output.
132 ;; Throw a PATCHER-DIFF error if COMMAND fails.
133 (multiple-value-bind (min max)
134 (patcher-condition-case nil
135 (patcher-call-command project command
137 :progression progression
139 (patcher-project-option project :ignore-diff-status))
141 (patcher-error 'diff command)))
142 (with-current-buffer (patcher-project-process-buffer project)
143 ;; We need a marker in case the hook modifies the diff output.
145 (setq max (point-marker))
146 (let ((diff-line-filter (patcher-project-option project
148 (when diff-line-filter
149 (operate-on-matching-lines diff-line-filter t nil min max)))
150 (patcher-run-after-diff-hook project min max)
151 (patcher-parse-diff project min max))))
153 (defun patcher-diff-change-logs (project)
154 ;; Create PROJECT's ChangeLog diff.
155 ;; This function assumes that ChangeLog files are already known and filled
157 (patcher-save-change-logs project)
158 (patcher-diff project (patcher-command project
159 (or (patcher-project-option project
160 :change-logs-diff-command)
161 (patcher-project-diff-command project))
162 (mapcar #'patcher-file-relative-name
163 (patcher-project-change-logs project)))
164 :progression "Generating the ChangeLogs diff")
165 (patcher-detect-missing-change-logs project))
167 (defun* patcher-convert-change-logs-diff
169 ;; Here so that it triggers an error if nil, before doing anything else.
170 &aux (change-logs-diff-command (patcher-project-option project
171 :change-logs-diff-command t))
172 (buffer (patcher-project-process-buffer project))
174 ;; Convert PROJECT's ChangeLog diffs in process buffer.
175 ;; Conversion is done directly in place with the ChangeLog-specific diff
177 (patcher-with-progression "Converting the ChangeLogs diff"
178 (with-current-buffer buffer
179 ;; #### WARNING: deleting extents from within mapcar-extents seems
180 ;; unsafe. Besides, if I modify the extents'contents instead of deleting
181 ;; and recreating them, map(car)-extents goes into an infinite loop, on
182 ;; all extents over and over again.
183 (dolist (extent (patcher-change-log-extents))
184 (setq change-log (extent-property extent 'patcher-change-log))
185 (goto-char (extent-start-position extent))
186 (patcher-delete-extent-and-region extent)
187 (patcher-diff project
188 (patcher-command project change-logs-diff-command
189 (list (patcher-file-relative-name change-log)))
191 :progression (format "Processing %s" change-log))))))
193 (defun patcher-diff-all (project)
194 ;; Create PROJECT's global diff with both ChangeLog and source files.
195 ;; This function assumes that ChangeLog files are already known and filled
197 (patcher-save-sources project)
198 (patcher-save-change-logs project)
199 (patcher-diff project
200 (patcher-command project (patcher-project-diff-command project) t)
201 :progression "Generating global diff")
202 (patcher-detect-inconsistent-sources project)
203 (patcher-detect-inconsistent-change-logs project)
204 (when (patcher-project-option project :change-logs-diff-command)
205 (patcher-convert-change-logs-diff project)))
207 (defun* patcher-diff-specification
209 &aux (buffer (patcher-project-process-buffer project))
212 ;; Diff PROJECT's specification and deduce source and ChangeLog files.
213 ;; Return 2 values: the lists of involved source and ChangeLog files.
214 (patcher-save-sources project)
215 (patcher-save-change-logs project)
216 (patcher-diff project (patcher-command project
217 (patcher-project-diff-command project)
218 (patcher-project-specification project)))
219 (setq sources (patcher-sources buffer))
221 (patcher-error "Your source files do not differ from the archive."))
222 (when (patcher-project-option project :change-logs-updating)
223 (patcher-with-progression "Detecting ChangeLog files"
224 ;; Note that while we can rely on the diff to deduce the source files,
225 ;; this is not possible for ChangeLog files, even when the updating is
226 ;; manual, because they might not be there (for instance if
227 ;; `specification' is explicit about files). So all in all, the only
228 ;; safe way to deduce ChangeLog files is to compute them from the source
230 (dolist (source sources)
231 (let ((change-log (patcher-locate-change-log project source)))
232 (unless (member change-log change-logs)
233 (patcher-endpush change-log change-logs))))))
234 (values sources change-logs))
239 ;; ==========================================================================
241 ;; ==========================================================================
243 (globally-declare-boundp 'font-lock-always-fontify-immediately)
245 (put 'patcher-insert-diff 'lisp-indent-function 1)
246 (defun* patcher-insert-diff
247 (project point &optional (kind :sources)
248 &aux (buffer (patcher-project-process-buffer project))
249 (function (patcher-project-option project
250 :diff-prologue-function)))
251 ;; Insert PROJECT's diff at POINT in current buffer.
252 ;; The diff comes from the process buffer and can be of different KINDs:
253 ;; - :sources (the default) means to filter out only source diffs,
254 ;; - :change-logs means to filter out only ChangeLog diffs,
255 ;; - :mixed means to keep both (the whole process buffer is used here).
256 ;; Prepend the diff with an appropriate prologue.
257 ;; Depending on KIND, create a patcher-diff or patcher-change-logs extent
263 (patcher-project-command-name project)
265 :source-files (patcher-project-sources-string project)
266 :change-log-files (patcher-project-change-logs-string project)
267 :source-diff (patcher-command project
268 (patcher-project-diff-command project))
269 :change-log-diff (patcher-command project
270 (patcher-project-option project
271 :change-logs-diff-command))))
272 (let ((font-lock-always-fontify-immediately t))
275 (patcher-source-contents buffer))
277 (patcher-change-log-contents buffer))
279 (buffer-substring nil nil buffer)))))
280 (set-extent-properties (make-extent point (point))
283 ,(if (eq kind :change-logs)
288 (provide 'patcher-diff)
290 ;;; patcher-diff.el ends here