(define-generic-mode): Doc fix.
[bpt/emacs.git] / lisp / pcvs-info.el
CommitLineData
3afbc435 1;;; pcvs-info.el --- internal representation of a fileinfo entry
5b467bf4 2
e7cff550 3;; Copyright (C) 1991, 92, 93, 94, 95, 96, 97, 98, 99, 2000 Free Software Foundation, Inc.
5b467bf4
SM
4
5;; Author: Stefan Monnier <monnier@cs.yale.edu>
6;; Keywords: pcl-cvs
5b467bf4
SM
7
8;; This file is part of GNU Emacs.
9
10;; GNU Emacs is free software; you can redistribute it and/or modify
11;; it under the terms of the GNU General Public License as published by
12;; the Free Software Foundation; either version 2, or (at your option)
13;; any later version.
14
15;; GNU Emacs is distributed in the hope that it will be useful,
16;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18;; GNU General Public License for more details.
19
20;; You should have received a copy of the GNU General Public License
21;; along with GNU Emacs; see the file COPYING. If not, write to the
22;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23;; Boston, MA 02111-1307, USA.
24
25;;; Commentary:
26
27;; The cvs-fileinfo data structure:
28;;
29;; When the `cvs update' is ready we parse the output. Every file
30;; that is affected in some way is added to the cookie collection as
31;; a "fileinfo" (as defined below in cvs-create-fileinfo).
32
33;;; Code:
34
35(eval-when-compile (require 'cl))
36(require 'pcvs-util)
37;;(require 'pcvs-defs)
38
39;;;;
40;;;; config variables
41;;;;
42
43(defcustom cvs-display-full-path t
44 "*Specifies how the filenames should look like in the listing.
45If t, their full path name will be displayed, else only the filename."
46 :group 'pcl-cvs
47 :type '(boolean))
48
5b467bf4
SM
49(defcustom cvs-allow-dir-commit nil
50 "*Allow `cvs-mode-commit' on directories.
51If you commit without any marked file and with the cursor positioned
52on a directory entry, cvs would commit the whole directory. This seems
53to confuse some users sometimes."
54 :group 'pcl-cvs
55 :type '(boolean))
56
5b467bf4
SM
57;;;;
58;;;; Faces for fontification
59;;;;
60
61(defface cvs-header-face
62 '((((class color) (background dark))
58b64ac7 63 (:foreground "lightyellow" :weight bold))
5b467bf4 64 (((class color) (background light))
58b64ac7
RS
65 (:foreground "blue4" :weight bold))
66 (t (:weight bold)))
5b467bf4
SM
67 "PCL-CVS face used to highlight directory changes."
68 :group 'pcl-cvs)
69
70(defface cvs-filename-face
71 '((((class color) (background dark))
72 (:foreground "lightblue"))
73 (((class color) (background light))
74 (:foreground "blue4"))
75 (t ()))
76 "PCL-CVS face used to highlight file names."
77 :group 'pcl-cvs)
78
79(defface cvs-unknown-face
80 '((((class color) (background dark))
81 (:foreground "red"))
82 (((class color) (background light))
83 (:foreground "red"))
58b64ac7 84 (t (:slant italic)))
5b467bf4
SM
85 "PCL-CVS face used to highlight unknown file status."
86 :group 'pcl-cvs)
87
88(defface cvs-handled-face
89 '((((class color) (background dark))
90 (:foreground "pink"))
91 (((class color) (background light))
92 (:foreground "pink"))
93 (t ()))
94 "PCL-CVS face used to highlight handled file status."
95 :group 'pcl-cvs)
96
97(defface cvs-need-action-face
98 '((((class color) (background dark))
99 (:foreground "orange"))
100 (((class color) (background light))
101 (:foreground "orange"))
58b64ac7 102 (t (:slant italic)))
5b467bf4
SM
103 "PCL-CVS face used to highlight status of files needing action."
104 :group 'pcl-cvs)
105
106(defface cvs-marked-face
107 '((((class color) (background dark))
58b64ac7 108 (:foreground "green" :weight bold))
5b467bf4 109 (((class color) (background light))
58b64ac7
RS
110 (:foreground "green3" :weight bold))
111 (t (:weight bold)))
5b467bf4
SM
112 "PCL-CVS face used to highlight marked file indicator."
113 :group 'pcl-cvs)
114
115(defface cvs-msg-face
58b64ac7 116 '((t (:slant italic)))
5b467bf4
SM
117 "PCL-CVS face used to highlight CVS messages."
118 :group 'pcl-cvs)
119
1fe28d30
SM
120(defvar cvs-fi-up-to-date-face 'cvs-handled-face)
121(defvar cvs-fi-unknown-face 'cvs-unknown-face)
53921158 122(defvar cvs-fi-conflict-face 'font-lock-warning-face)
5b467bf4
SM
123
124;; There is normally no need to alter the following variable, but if
125;; your site has installed CVS in a non-standard way you might have
126;; to change it.
127
128(defvar cvs-bakprefix ".#"
129 "The prefix that CVS prepends to files when rcsmerge'ing.")
130
5b467bf4
SM
131(easy-mmode-defmap cvs-status-map
132 '(([(mouse-2)] . cvs-mouse-toggle-mark))
1fe28d30 133 "Local keymap for text properties of status")
5b467bf4
SM
134
135;; Constructor:
136
137(defstruct (cvs-fileinfo
138 (:constructor nil)
139 (:copier nil)
140 (:constructor -cvs-create-fileinfo (type dir file full-log
141 &key marked subtype
142 merge
143 base-rev
144 head-rev))
145 (:conc-name cvs-fileinfo->))
146 marked ;; t/nil.
147 type ;; See below
148 subtype ;; See below
149 dir ;; Relative directory the file resides in.
150 ;; (concat dir file) should give a valid path.
151 file ;; The file name sans the directory.
152 base-rev ;; During status: This is the revision that the
153 ;; working file is based on.
154 head-rev ;; During status: This is the highest revision in
155 ;; the repository.
156 merge ;; A cons cell containing the (ancestor . head) revisions
157 ;; of the merge that resulted in the current file.
158 ;;removed ;; t if the file no longer exists.
159 full-log ;; The output from cvs, unparsed.
160 ;;mod-time ;; Not used.
161
162 ;; In addition to the above, the following values can be extracted:
163
164 ;; handled ;; t if this file doesn't require further action.
165 ;; full-path ;; The complete relative filename.
166 ;; pp-name ;; The printed file name
167 ;; backup-file;; For MERGED and CONFLICT files after a \"cvs update\",
168 ;; this is a full path to the backup file where the
169 ;; untouched version resides.
170
171 ;; The meaning of the type field:
f1180544 172
5b467bf4
SM
173 ;; Value ---Used by--- Explanation
174 ;; update status
175 ;; NEED-UPDATE x file needs update
176 ;; MODIFIED x x modified by you, unchanged in repository
177 ;; MERGED x x successful merge
178 ;; ADDED x x added by you, not yet committed
179 ;; MISSING x rm'd, but not yet `cvs remove'd
180 ;; REMOVED x x removed by you, not yet committed
181 ;; NEED-MERGE x need merge
182 ;; CONFLICT x conflict when merging
183 ;; ;;MOD-CONFLICT x removed locally, changed in repository.
184 ;; DIRCHANGE x x A change of directory.
185 ;; UNKNOWN x An unknown file.
186 ;; UP-TO-DATE x The file is up-to-date.
187 ;; UPDATED x x file copied from repository
188 ;; PATCHED x x diff applied from repository
189 ;; COMMITTED x x cvs commit'd
190 ;; DEAD An entry that should be removed
191 ;; MESSAGE x x This is a special fileinfo that is used
192 ;; to display a text that should be in
193 ;; full-log."
194 ;; TEMP A temporary message that should be removed
5b467bf4
SM
195 )
196(defun cvs-create-fileinfo (type dir file msg &rest keys)
197 (cvs-check-fileinfo (apply #'-cvs-create-fileinfo type dir file msg keys)))
198
199;; Fake selectors:
200
201(defun cvs-fileinfo->full-path (fileinfo)
202 "Return the full path for the file that is described in FILEINFO."
203 (let ((dir (cvs-fileinfo->dir fileinfo)))
204 (if (eq (cvs-fileinfo->type fileinfo) 'DIRCHANGE)
205 (if (string= dir "") "." (directory-file-name dir))
206 ;; Here, I use `concat' rather than `expand-file-name' because I want
207 ;; the resulting path to stay relative if `dir' is relative.
5b467bf4
SM
208 (concat dir (cvs-fileinfo->file fileinfo)))))
209
210(defun cvs-fileinfo->pp-name (fi)
211 "Return the filename of FI as it should be displayed."
212 (if cvs-display-full-path
213 (cvs-fileinfo->full-path fi)
214 (cvs-fileinfo->file fi)))
215
216(defun cvs-fileinfo->backup-file (fileinfo)
217 "Construct the file name of the backup file for FILEINFO."
218 (let* ((dir (cvs-fileinfo->dir fileinfo))
219 (file (cvs-fileinfo->file fileinfo))
220 (default-directory (file-name-as-directory (expand-file-name dir)))
221 (files (directory-files "." nil
222 (concat "^" (regexp-quote cvs-bakprefix)
223 (regexp-quote file) "\\.")))
224 bf)
225 (dolist (f files bf)
226 (when (and (file-readable-p f)
227 (or (null bf) (file-newer-than-file-p f bf)))
228 (setq bf (concat dir f))))))
229
230;; (defun cvs-fileinfo->handled (fileinfo)
231;; "Tell if this requires further action"
232;; (memq (cvs-fileinfo->type fileinfo) '(UP-TO-DATE DEAD)))
233
234\f
235;; Predicate:
236
5b467bf4
SM
237(defun cvs-check-fileinfo (fi)
238 "Check FI's conformance to some conventions."
239 (let ((check 'none)
240 (type (cvs-fileinfo->type fi))
241 (subtype (cvs-fileinfo->subtype fi))
242 (marked (cvs-fileinfo->marked fi))
243 (dir (cvs-fileinfo->dir fi))
244 (file (cvs-fileinfo->file fi))
245 (base-rev (cvs-fileinfo->base-rev fi))
246 (head-rev (cvs-fileinfo->head-rev fi))
247 (full-log (cvs-fileinfo->full-log fi)))
9d4b3027 248 (if (and (setq check 'marked) (memq marked '(t nil))
5b467bf4
SM
249 (setq check 'base-rev) (or (null base-rev) (stringp base-rev))
250 (setq check 'head-rev) (or (null head-rev) (stringp head-rev))
251 (setq check 'full-log) (stringp full-log)
252 (setq check 'dir)
253 (and (stringp dir)
254 (not (file-name-absolute-p dir))
255 (or (string= dir "")
256 (string= dir (file-name-as-directory dir))))
257 (setq check 'file)
258 (and (stringp file)
259 (string= file (file-name-nondirectory file)))
260 (setq check 'type) (symbolp type)
261 (setq check 'consistency)
262 (case type
263 (DIRCHANGE (and (null subtype) (string= "." file)))
264 ((NEED-UPDATE ADDED MISSING DEAD MODIFIED MESSAGE UP-TO-DATE
265 REMOVED NEED-MERGE CONFLICT UNKNOWN MESSAGE)
266 t)))
267 fi
268 (error "Invalid :%s in cvs-fileinfo %s" check fi))))
269
270\f
f1180544 271;;;;
5b467bf4 272;;;; State table to indicate what you can do when.
f1180544 273;;;;
5b467bf4
SM
274
275(defconst cvs-states
276 `((NEED-UPDATE update diff)
277 (UP-TO-DATE update nil remove diff safe-rm revert)
278 (MODIFIED update commit undo remove diff merge diff-base)
279 (ADDED update commit remove)
280 (MISSING remove undo update safe-rm revert)
281 (REMOVED commit add undo safe-rm)
282 (NEED-MERGE update undo diff diff-base)
283 (CONFLICT merge remove undo commit diff diff-base)
284 (DIRCHANGE remove update diff ,(if cvs-allow-dir-commit 'commit) tag)
285 (UNKNOWN ignore add remove)
286 (DEAD )
287 (MESSAGE))
288 "Fileinfo state descriptions for pcl-cvs.
289This is an assoc list. Each element consists of (STATE . FUNS)
290- STATE (described in `cvs-create-fileinfo') is the key
291- FUNS is the list of applicable operations.
292 The first one (if any) should be the \"default\" action.
293Most of the actions have the obvious meaning.
294`safe-rm' indicates that the file can be removed without losing
295 any information.")
296
297;;;;
298;;;; Utility functions
299;;;;
300
5b467bf4
SM
301(defun cvs-applicable-p (fi-or-type func)
302 "Check if FUNC is applicable to FI-OR-TYPE.
303If FUNC is nil, always return t.
304FI-OR-TYPE can either be a symbol (a fileinfo-type) or a fileinfo."
305 (let ((type (if (symbolp fi-or-type) fi-or-type
306 (cvs-fileinfo->type fi-or-type))))
307 (and (not (eq type 'MESSAGE))
308 (eq (car (memq func (cdr (assq type cvs-states)))) func))))
309
db1f981d 310(defun cvs-add-face (str face &optional keymap &rest props)
c8bc0b5f
SM
311 (when keymap
312 (when (keymapp keymap)
313 (setq props (list* 'keymap keymap props)))
314 (setq props (list* 'mouse-face 'highlight props)))
315 (add-text-properties 0 (length str) (list* 'font-lock-face face props) str)
5b467bf4
SM
316 str)
317
5b467bf4
SM
318(defun cvs-fileinfo-pp (fileinfo)
319 "Pretty print FILEINFO. Insert a printed representation in current buffer.
320For use by the cookie package."
321 (cvs-check-fileinfo fileinfo)
322 (let ((type (cvs-fileinfo->type fileinfo))
323 (subtype (cvs-fileinfo->subtype fileinfo)))
324 (insert
325 (case type
326 (DIRCHANGE (concat "In directory "
327 (cvs-add-face (cvs-fileinfo->full-path fileinfo)
db1f981d
SM
328 'cvs-header-face t
329 'cvs-goal-column t)
5b467bf4
SM
330 ":"))
331 (MESSAGE
cb3430a1
SM
332 (cvs-add-face (format "Message: %s" (cvs-fileinfo->full-log fileinfo))
333 'cvs-msg-face))
5b467bf4
SM
334 (t
335 (let* ((status (if (cvs-fileinfo->marked fileinfo)
336 (cvs-add-face "*" 'cvs-marked-face)
337 " "))
338 (file (cvs-add-face (cvs-fileinfo->pp-name fileinfo)
6efa25a1
KG
339 'cvs-filename-face t
340 'cvs-goal-column t))
5b467bf4
SM
341 (base (or (cvs-fileinfo->base-rev fileinfo) ""))
342 (head (cvs-fileinfo->head-rev fileinfo))
343 (type
344 (let ((str (case type
345 ;;(MOD-CONFLICT "Not Removed")
346 (DEAD "")
347 (t (capitalize (symbol-name type)))))
1fe28d30
SM
348 (face (let ((sym (intern
349 (concat "cvs-fi-"
350 (downcase (symbol-name type))
351 "-face"))))
352 (or (and (boundp sym) (symbol-value sym))
353 'cvs-need-action-face))))
5b467bf4
SM
354 (cvs-add-face str face cvs-status-map)))
355 (side (or
356 ;; maybe a subtype
357 (when subtype (downcase (symbol-name subtype)))
358 ;; or the head-rev
359 (when (and head (not (string= head base))) head)
360 ;; or nothing
cb3430a1 361 "")))
6efa25a1
KG
362 (format "%-11s %s %-11s %-11s %s"
363 side status type base file)))))))
5b467bf4
SM
364
365
366(defun cvs-fileinfo-update (fi fi-new)
367 "Update FI with the information provided in FI-NEW."
368 (let ((type (cvs-fileinfo->type fi-new))
369 (merge (cvs-fileinfo->merge fi-new)))
370 (setf (cvs-fileinfo->type fi) type)
371 (setf (cvs-fileinfo->subtype fi) (cvs-fileinfo->subtype fi-new))
372 (setf (cvs-fileinfo->full-log fi) (cvs-fileinfo->full-log fi-new))
373 (setf (cvs-fileinfo->base-rev fi) (cvs-fileinfo->base-rev fi-new))
374 (setf (cvs-fileinfo->head-rev fi) (cvs-fileinfo->head-rev fi-new))
375 (cond
376 (merge (setf (cvs-fileinfo->merge fi) merge))
377 ((memq type '(UP-TO-DATE NEED-UPDATE))
378 (setf (cvs-fileinfo->merge fi) nil)))))
379
5b467bf4
SM
380(defun cvs-fileinfo< (a b)
381 "Compare fileinfo A with fileinfo B and return t if A is `less'.
382The ordering defined by this function is such that directories are
383sorted alphabetically, and inside every directory the DIRCHANGE
384fileinfo will appear first, followed by all files (alphabetically)."
385 (let ((subtypea (cvs-fileinfo->subtype a))
386 (subtypeb (cvs-fileinfo->subtype b)))
387 (cond
5b467bf4
SM
388 ;; Sort according to directories.
389 ((string< (cvs-fileinfo->dir a) (cvs-fileinfo->dir b)) t)
390 ((not (string= (cvs-fileinfo->dir a) (cvs-fileinfo->dir b))) nil)
391
392 ;; The DIRCHANGE entry is always first within the directory.
393 ((eq (cvs-fileinfo->type b) 'DIRCHANGE) nil)
394 ((eq (cvs-fileinfo->type a) 'DIRCHANGE) t)
395
396 ;; All files are sorted by file name.
397 ((string< (cvs-fileinfo->file a) (cvs-fileinfo->file b))))))
398
1fe28d30
SM
399;;;
400;;; Look at CVS/Entries to quickly find a first approximation of the status
401;;;
402
403(defun cvs-fileinfo-from-entries (dir &optional all)
404 "List of fileinfos for DIR, extracted from CVS/Entries.
405Unless ALL is optional, returns only the files that are not up-to-date.
406DIR can also be a file."
407 (let* ((singlefile
408 (cond
409 ((equal dir "") nil)
410 ((file-directory-p dir) (setq dir (file-name-as-directory dir)) nil)
411 (t (prog1 (file-name-nondirectory dir)
412 (setq dir (or (file-name-directory dir) ""))))))
413 (file (expand-file-name "CVS/Entries" dir))
414 (fis nil))
415 (if (not (file-readable-p file))
416 (push (cvs-create-fileinfo (if singlefile 'UNKNOWN 'DIRCHANGE)
417 dir (or singlefile ".") "") fis)
418 (with-temp-buffer
419 (insert-file-contents file)
420 (goto-char (point-min))
421 ;; Select the single file entry in case we're only interested in a file.
422 (cond
423 ((not singlefile)
424 (push (cvs-create-fileinfo 'DIRCHANGE dir "." "") fis))
425 ((re-search-forward
426 (concat "^[^/]*/" (regexp-quote singlefile) "/.*") nil t)
427 (setq all t)
428 (goto-char (match-beginning 0))
429 (narrow-to-region (point) (match-end 0)))
430 (t
431 (push (cvs-create-fileinfo 'UNKNOWN dir singlefile "") fis)
432 (narrow-to-region (point-min) (point-min))))
433 (while (looking-at "\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/\\([^/]*\\)/")
434 (if (/= (match-beginning 1) (match-end 1))
435 (setq fis (append (cvs-fileinfo-from-entries
436 (concat dir (file-name-as-directory
437 (match-string 2)))
438 all)
439 fis))
440 (let ((f (match-string 2))
441 (rev (match-string 3))
442 (date (match-string 4))
443 timestamp
444 (type 'MODIFIED)
445 (subtype nil))
446 (cond
447 ((equal (substring rev 0 1) "-")
448 (setq type 'REMOVED rev (substring rev 1)))
449 ((not (file-exists-p (concat dir f))) (setq type 'MISSING))
450 ((equal rev "0") (setq type 'ADDED rev nil))
451 ((equal date "Result of merge") (setq subtype 'MERGED))
452 ((let ((mtime (nth 5 (file-attributes (concat dir f))))
453 (system-time-locale "C"))
38fdf6aa
SM
454 (setq timestamp (format-time-string "%c" mtime 'utc))
455 ;; Solaris sometimes uses "Wed Sep 05", not "Wed Sep 5".
456 ;; See "grep '[^a-z_]ctime' cvs/src/*.c" for reference.
457 (if (= (aref timestamp 8) ?0)
458 (setq timestamp (concat (substring timestamp 0 8)
459 " " (substring timestamp 9))))
460 (equal timestamp date))
1fe28d30
SM
461 (setq type (if all 'UP-TO-DATE)))
462 ((equal date (concat "Result of merge+" timestamp))
463 (setq type 'CONFLICT)))
464 (when type
465 (push (cvs-create-fileinfo type dir f ""
466 :base-rev rev :subtype subtype)
467 fis))))
468 (forward-line 1))))
469 fis))
470
5b467bf4
SM
471(provide 'pcvs-info)
472
ab5796a9 473;;; arch-tag: d85dde07-bdc2-400a-882f-92f398c7b0ba
3afbc435 474;;; pcvs-info.el ends here