1 ;;; xetla.el --- Arch (tla) interface for XEmacs
3 ;; Copyright (C) 2003-2004 by Stefan Reichoer (GPL)
4 ;; Copyright (C) 2004 2005 Steve Youngs (BSD)
6 ;; Author: Steve Youngs <steve@eicq.org>
7 ;; Maintainer: Steve Youngs <steve@eicq.org>
9 ;; Keywords: arch archive tla
11 ;; Based on xtla.el by: Stefan Reichoer, <stefan@xsteve.at>
13 ;; This file is part of XEtla.
15 ;; Redistribution and use in source and binary forms, with or without
16 ;; modification, are permitted provided that the following conditions
19 ;; 1. Redistributions of source code must retain the above copyright
20 ;; notice, this list of conditions and the following disclaimer.
22 ;; 2. Redistributions in binary form must reproduce the above copyright
23 ;; notice, this list of conditions and the following disclaimer in the
24 ;; documentation and/or other materials provided with the distribution.
26 ;; 3. Neither the name of the author nor the names of any contributors
27 ;; may be used to endorse or promote products derived from this
28 ;; software without specific prior written permission.
30 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
31 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
32 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
33 ;; DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
34 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
35 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
36 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
37 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
38 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
39 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
40 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 ;; Contributions from:
45 ;; Matthieu Moy <Matthieu.Moy@imag.fr>
46 ;; Masatake YAMATO <jet@gyve.org>
47 ;; Milan Zamazal <pdm@zamazal.org>
48 ;; Martin Pool <mbp@sourcefrog.net>
49 ;; Robert Widhopf-Fenk <hack@robf.de>
50 ;; Mark Triggs <mst@dishevelled.net>
53 ;; The main commands are available with the prefix key C-x T.
54 ;; Type C-x T C-h for a list.
56 ;; M-x xetla-inventory shows a xetla inventory
57 ;; In this inventory buffer the following commands are available:
58 ;; e ... xetla-edit-log
59 ;; = ... xetla-changes
60 ;; l ... xetla-changelog
63 ;; To Edit a logfile issue: M-x xetla-edit-log
64 ;; In this mode you can hit C-c C-d to show the changes
66 ;; After that you issue M-x xetla-commit (bound to C-c C-c) to commit the files
68 ;; M-x xetla-archives starts the interactive archive browser
70 ;; M-x xetla-make-archive creates a new archive directory
71 ;; Many commands are available from here. Look at the menus, they're
72 ;; very helpful to begin.
74 ;; M-x xetla-bookmarks RET
75 ;; Is another good starting point. This is the place where you put the
76 ;; project you work on most often, and you can get a new version, see
77 ;; the missing patches, and a few other useful features from here.
78 ;; Use `a' to add a bookmark. Add your own projects, and your
79 ;; contributor's projects too. Select several related projects with
80 ;; `m' (unselect with M-u or M-del). Make them partners with 'M-p'.
81 ;; Now, with your cursor on a bookmark, view the uncommitted changes,
82 ;; the missing patches from your archive and your contributors with
85 ;; M-x xetla-file-ediff RET
86 ;; Is an wrapper to xetla file-diff, ediff to view the changes
90 ;; xetla-tag-insert inserts a arch-tag entry generated with uuidgen
92 ;; If you find xetla.el useful, and you have some ideas to improve it
93 ;; please share them with us (Patches are preferred :-))
97 ;;; XEtla/Xtla safety code
98 ;; You can run into some problems if you have both XEtla and Xtla
99 ;; installed at the same time. The following attempts to guard
100 ;; against it by warning the user and giving them a couple of
101 ;; functions for disabling one of XEtla or Xtla in their current
102 ;; session. Dangerous stuff!!
103 (defvar xetla-dont-warn-about-xtla nil)
105 (when (and (featurep 'xtla)
106 (not xetla-dont-warn-about-xtla))
107 (xetla-warn-about-xtla))
109 (defconst xetla-warn-about-xtla-text
110 "We have detected that you have both XEtla and Xtla installed.
112 This is never a very good idea (unless you _really_ know what you are
113 doing) because both packages share similar key bindings and install
116 Our suggestion is that you remove either XEtla or Xtla and just use the
117 other. For your convenience there is `xetla-attempt-xetla-removal' and
118 `xetla-attempt-xtla-removal', which will attempt to disable one of the
119 packages for the current session only.
121 Use those functions at your own risk. The best answer is to not load
122 one of XEtla or Xtla in the first place.
124 To disable this warning: (setq xetla-dont-warn-about-xtla t).")
126 (defun xetla-warn-about-xtla ()
127 "Pop up a big fat warning about trying to use XEtla and xtla together."
128 (get-buffer-create "*XEtla/Xtla Warning*")
129 (with-current-buffer "*XEtla/Xtla Warning*"
131 (insert xetla-warn-about-xtla-text))
132 (pop-to-buffer "*XEtla/Xtla Warning*"))
134 (defun xetla-attempt-xetla-removal ()
135 "Attempt to disable xetla.
137 **** This is dangerous, use at your own risk. ****
139 This function attempts to unload all the XEtla features, remove the
140 `xetla-find-file-hook', remove entries from `auto-mode-alist', and
141 finally, remove the XEtla lisp directory from the `load-path'. After
142 all of this has happened, Xtla should run without problems. Please
143 note that we do _NOT_ guarantee that this will work perfectly and you
144 really should physically remove either XEtla or Xtla.
146 Use of this function is only valid in the current session, in other
147 words, it ain't saved."
149 ;; unload the features
150 (when (featurep 'xetla-tips) (unload-feature 'xetla-tips t))
151 (when (featurep 'xetla-browse) (unload-feature 'xetla-browse t))
152 (when (featurep 'xetla) (unload-feature 'xetla t))
153 (when (featurep 'xetla-core) (unload-feature 'xetla-core t))
154 (when (featurep 'xetla-defs) (unload-feature 'xetla-defs t))
155 (when (featurep 'xetla-autoloads) (unload-feature 'xetla-autoloads t))
157 (remove-hook 'find-file-hooks 'xetla-find-file-hook)
158 ;; clean out auto-mode-alist
159 (setq auto-mode-alist
160 (delete '("\\+\\+log\\." . xetla-log-edit-mode) auto-mode-alist))
161 (setq auto-mode-alist
162 (delete '("/\\(=tagging-method\\|\\.arch-inventory\\)$" . xetla-inventory-file-mode)
164 ;; clean out the load-path
166 (remove (file-name-directory (locate-library "xetla")) load-path))
167 ;; did I miss anything?
168 ;; what can we do about key bindings?
170 (message "To ensure correct key bindings, please reload Xtla"))
172 (defun xetla-attempt-xtla-removal ()
173 "Attempt to disable Xtla.
175 **** This is dangerous, use at your own risk. ****
177 This function attempts to unload all the Xtla features, remove the
178 `tla-find-file-hook', remove entries from `auto-mode-alist', and
179 finally, remove the XEtla lisp directory from the `load-path'. After
180 all of this has happened, XEtla should run without problems. Please
181 note that we do _NOT_ guarantee that this will work perfectly and you
182 really should physically remove either XEtla or Xtla.
184 Use of this function is only valid in the current session, in other
185 words, it ain't saved."
187 ;; unload the features
188 (when (featurep 'xtla-tips) (unload-feature 'xtla-tips t))
189 (when (featurep 'xtla-browse) (unload-feature 'xtla-browse t))
190 (when (featurep 'xtla) (unload-feature 'xtla t))
191 (when (featurep 'xtla-core) (unload-feature 'xtla-core t))
192 (when (featurep 'xtla-defs) (unload-feature 'xtla-defs t))
193 (when (featurep 'xtla-emacs) (unload-feature 'xtla-emacs t))
194 (when (featurep 'xtla-xemacs) (unload-feature 'xtla-xemacs t))
195 (when (featurep 'xtla-autoloads) (unload-feature 'xtla-autoloads t))
197 (remove-hook 'find-file-hooks 'tla-find-file-hook)
198 ;; clean out auto-mode-alist
199 (setq auto-mode-alist
200 (delete '("\\+\\+log\\." . tla-log-edit-mode) auto-mode-alist))
201 (setq auto-mode-alist
202 (delete '("/\\(=tagging-method\\|\\.arch-inventory\\)$" . tla-inventory-file-mode)
204 ;; clean out the load-path
206 (remove (file-name-directory (locate-library "xtla")) load-path))
207 ;; did I miss anything?
208 ;; what can we do about key bindings?
209 (message "To ensure correct key bindings, please reload XEtla"))
211 ;;; End XEtls/Xtla safety code
215 (when (locate-library "xetla-version")
216 (require 'xetla-version)))
218 (eval-when-compile (require 'cl))
220 ;; gnus is optional. Load it at compile-time to avoid warnings.
222 (autoload 'gnus-article-part-wrapper "gnus-art")
223 (autoload 'gnus-article-show-summary "gnus-art" nil t)
224 (autoload 'gnus-summary-select-article-buffer "gnus-sum" nil t)
225 (autoload 'mm-save-part-to-file "mm-decode")
226 (autoload 'mml-attach-file "mml" nil t))
230 (require 'font-lock))
239 (require 'xetla-defs)
240 (require 'xetla-core))
243 (when (locate-library "smerge-mode")
244 (require 'smerge-mode))
246 (when (locate-library "hl-line")
250 (autoload 'dired "dired" nil t)
251 (autoload 'dired-make-relative "dired")
252 (autoload 'dired-other-window "dired" nil t)
253 (autoload 'minibuffer-prompt-end "completer")
254 (autoload 'regexp-opt "regexp-opt")
255 (autoload 'reporter-submit-bug-report "reporter")
256 (autoload 'view-file-other-window "view-less" nil t)
257 (autoload 'view-mode "view-less" nil t)
258 (autoload 'with-electric-help "ehelp"))
260 ;; --------------------------------------
261 ;; Internal variables
262 ;; --------------------------------------
263 (defvar xetla-edit-arch-command nil)
264 (defvar xetla-pre-commit-window-configuration nil)
265 (defvar xetla-log-edit-file-name nil)
266 (defvar xetla-log-edit-file-buffer nil)
267 (defvar xetla-my-id-history nil)
268 (defvar xetla-memorized-log-header nil)
269 (defvar xetla-memorized-log-message nil)
270 (defvar xetla-memorized-version nil)
272 (defvar xetla-buffer-archive-name nil)
273 (defvar xetla-buffer-category-name nil)
274 (defvar xetla-buffer-branch-name nil)
275 (defvar xetla-buffer-version-name nil)
276 (defvar xetla-buffer-refresh-function nil
277 "Variable should be local to each buffer.
278 Function used to refresh the current buffer")
279 (defvar xetla-buffer-marked-file-list nil
280 "List of marked files in the current buffer.")
281 (defvar xetla-get-revision-info-at-point-function nil
282 "Variable should be local to each buffer.
283 Function used to get the revision info at point")
285 (defvar xetla-mode-line-process "")
286 (defvar xetla-mode-line-process-status "")
289 (put 'xetla-default-button 'mouse-face 'highlight)
290 (put 'xetla-default-button 'evaporate t)
291 ;;(put 'xetla-default-button 'rear-nonsticky t)
292 ;;(put 'xetla-default-button 'front-nonsticky t)
296 "Displays a welcome message."
298 (let* ((name "*xetla-welcome*")
299 (buffer (get-buffer name)))
300 (if buffer (xetla-switch-to-buffer buffer)
301 (xetla-switch-to-buffer
302 (setq buffer (get-buffer-create name)))
303 (insert " *** Welcome to XEtla ! ***
305 XEtla is the XEmacs frontend to the revision control system GNU/arch (tla).
307 As a starting point, you should look at the \"Tools\" menu, there is a
308 \"XEtla\" entry with a lot of interesting commands.
310 There is also a manual for XEtla. It should be available using the
311 Info system, however it is still just a skeleton file with no
312 information in it yet. Well, you know how much hackers just love
313 doing documentation. :-)
315 Hope you'll enjoy it !
320 "[" (xetla-insert-button "Bookmarks" 'xetla-bookmarks)
322 "[" (xetla-insert-button "Inventory" 'xetla-inventory)
324 "[" (xetla-insert-button "Browse Archives" (if (fboundp 'xetla-browse)
328 "[" (xetla-insert-button "Browse Revisions" 'xetla-revisions)
330 "[" (xetla-insert-button "Report Bug" 'xetla-submit-bug-report)
334 (local-set-key [?q] (lambda () (interactive)
335 (kill-buffer (current-buffer)))))
336 (xetla-message-with-bouncing
337 (concat "XEtla core development team: "
338 "Steve Youngs <steve@eicq.org>, "
339 "Sebastian Freundt <freundt@math.tu-berlin.de> "
340 " --- We hope you have as much fun using XEtla "
341 "as we have had in hacking it for you."))))
343 (defun xetla-insert-button (label function)
344 "Insert a button labeled with LABEL and launching FUNCTION.
345 Helper function for `xetla'."
346 (xetla-face-add label 'bold
347 (let ((map (make-sparse-keymap)))
348 (define-key map [return] function)
349 (define-key map "\C-m" function)
350 (define-key map [button2] function)
354 (defun xetla-face-add-with-condition (condition text face1 face2)
355 "If CONDITION then add TEXT the face FACE1, else add FACE2."
357 (xetla-face-add text face1)
358 (xetla-face-add text face2)))
360 (defun xetla-face-set-temporary-during-popup (face begin end menu &optional prefix)
361 "Put FACE on BEGIN and END in the buffer during Popup MENU.
362 PREFIX is passed to `popup-menu'."
366 (setq o (make-extent begin end))
367 (set-extent-face o face)
369 (popup-menu menu prefix))
372 (defconst xetla-mark (xetla-face-add "*" 'xetla-mark)
373 "Fontified string used for marking.")
375 ;; --------------------------------------
377 ;; --------------------------------------
378 (defmacro xetla-toggle-list-entry (list entry)
379 "Either add or remove from the value of LIST the value ENTRY."
380 `(if (member ,entry ,list)
381 (setq ,list (delete ,entry ,list))
382 (add-to-list ',list ,entry)))
384 ;; --------------------------------------
385 ;; Common used functions for many xetla modes
386 ;; --------------------------------------
387 (defun xetla-kill-all-buffers ()
388 "Kill all xetla buffers."
391 (dolist (type-cons xetla-buffers-tree)
392 (dolist (path-buffer (cdr type-cons))
393 (setq number (1+ number))
394 (kill-buffer (cadr path-buffer))))
395 (message "Killed %d buffer%s" number
396 (if (> number 1) "s" "")))
397 (setq xetla-buffers-tree nil))
399 (defvar xetla-buffer-previous-window-config nil
400 "Window-configuration to return to on buffer quit.
402 If nil, nothing is done special. Otherwise, must be a
403 window-configuration. `xetla-buffer-quit' will restore this
404 window-configuration.")
406 (make-variable-buffer-local 'xetla-buffer-previous-window-config)
408 (defun xetla-buffer-quit ()
409 "Quit the current buffer.
411 If `xetla-buffer-quit-mode' is 'kill, then kill the buffer. Otherwise,
414 ;; Value is buffer local => keep it before killing the buffer!
415 (let ((prev-wind-conf xetla-buffer-previous-window-config))
416 (if (eq xetla-buffer-quit-mode 'kill)
417 (kill-buffer (current-buffer))
420 (set-window-configuration prev-wind-conf))))
422 (defun xetla-edit-=tagging-method-file ()
423 "Edit the {arch}/=tagging-method file."
425 (find-file (expand-file-name "{arch}/=tagging-method" (xetla-tree-root))))
427 (defun xetla-edit-.arch-inventory-file (&optional dir)
428 "Edit DIR/.arch-inventory file.
429 `default-directory' is used as DIR if DIR is nil.
430 If it is called interactively and the prefix argument is given via DIR,
431 use the directory of a file associated with the point to find .arch-inventory.
432 In the case no file is associated with the point, it reads the directory name
433 with `read-directory-name'."
435 (list (if (not (interactive-p))
437 (let ((file (xetla-get-file-info-at-point)))
439 (if (not (file-name-absolute-p file))
440 (concat default-directory
441 (file-name-directory file))
442 (file-name-directory file))
443 (expand-file-name (read-directory-name
444 "Directory containing \".arch-inventory\": ")))))))
445 (let* ((dir (or dir default-directory))
446 (file (expand-file-name ".arch-inventory" dir))
447 (newp (not (file-exists-p file))))
450 (when (and newp (y-or-n-p
451 (format "Insert arch tag to \"%s\"? " file)))
452 (xetla-tag-insert)))))
454 (defun xetla-ewoc-delete (cookie elem)
455 "Remove element from COOKIE the element ELEM."
457 '(lambda (x) (not (eq x (ewoc-data elem))))))
459 (defun xetla-generic-refresh ()
460 "Call the function specified by `xetla-buffer-refresh-function'."
462 (let ((xetla-read-directory-mode 'never)
463 (xetla-read-project-tree-mode 'never))
464 (funcall xetla-buffer-refresh-function)))
466 (defun xetla-get-info-at-point ()
467 "Get the version information that point is on."
468 (when (fboundp xetla-get-revision-info-at-point-function)
469 (funcall xetla-get-revision-info-at-point-function)))
471 (defvar xetla-window-config nil
472 "Used for inter-function communication.")
474 (defun xetla-ediff-buffers (bufferA bufferB)
475 "Wrapper around `ediff-buffers'.
477 Calls `ediff-buffers' on BUFFERA and BUFFERB."
478 (let ((xetla-window-config (current-window-configuration)))
479 (ediff-buffers bufferA bufferB
480 '(xetla-ediff-startup-hook) 'xetla-ediff)))
482 (defun xetla-insert-right-justified (string count &optional face)
483 "Insert a string with a right-justification.
485 Inserts STRING preceded by spaces so that the line ends exactly at
486 COUNT characters (or after if STRING is too long).
487 If FACE is non-nil, insert the string fontified with FACE."
488 (insert-char ?\ (max 0 (- count (length string))))
489 (insert (if face (xetla-face-add string face) string))
492 (defun xetla-generic-popup-menu (event prefix)
493 "Generic function to popup a menu.
495 The menu is defined in the text property under the point which is
496 given by mouse. EVENT is the mouse event that called the function.
497 PREFIX is passed to `xetla-generic-popup-menu-by-keyboard'."
499 (mouse-set-point event)
500 (xetla-generic-popup-menu-by-keyboard prefix))
503 (defun xetla-generic-popup-menu-by-keyboard (prefix)
504 "Popup a menu defined in the text property under the point.
506 PREFIX is passed to `popup-menu'."
508 (if (get-text-property (point) 'menu)
509 (let* ((menu (get-text-property (point) 'menu))
510 (p (previous-single-property-change (point) 'menu nil
512 (n (next-single-property-change (point) 'menu nil
514 (b (if (and p (get-text-property p 'menu)) p (point)))
515 (e (if n n (point))))
516 (xetla-face-set-temporary-during-popup 'xetla-highlight
520 (error "No context-menu under the point")))
524 ;; (xetla-message-with-bouncing
525 ;; (concat "Author: Stefan Reichoer <stefan@xsteve.at>, "
526 ;; "Contributions from: "
527 ;; "Matthieu Moy <Matthieu.Moy@imag.fr>, "
528 ;; "Masatake YAMATO <jet@gyve.org>, "
529 ;; "Milan Zamazal <pdm@zamazal.org>, "
530 ;; "Martin Pool <mbp@sourcefrog.net>, "
531 ;; "Robert Widhopf-Fenk <hack@robf.de>, "
532 ;; "Mark Triggs <mst@dishevelled.net>"))
533 ;; (xetla-message-with-rolling
534 ;; (concat "Author: Stefan Reichoer <stefan@xsteve.at>, "
535 ;; "Contributions from: "
536 ;; "Matthieu Moy <Matthieu.Moy@imag.fr>, "
537 ;; "Masatake YAMATO <jet@gyve.org>, "
538 ;; "Milan Zamazal <pdm@zamazal.org>, "
539 ;; "Martin Pool <mbp@sourcefrog.net>, "
540 ;; "Robert Widhopf-Fenk <hack@robf.de>, "
541 ;; "Mark Triggs <mst@dishevelled.net>"))
542 (defvar xetla-message-long-default-interval 0.2
543 "Default animation step interval.
545 Used in `xetla-message-with-bouncing' and `xetla-message-with-rolling'")
547 (defvar xetla-message-long-border-interval 1.0
548 "Animation step interval when bouncing in `xetla-message-with-bouncing'.")
550 (defun* xetla-message-with-bouncing (&rest msg)
551 "Similar to `message' but display the message in bouncing animation to show long line."
552 (setq msg (apply 'format msg))
553 (let* ((width (- (window-width (minibuffer-window))
554 (+ 1 (length "[<] ") (length " [>]"))))
555 (msglen (length msg))
557 (steps (- msglen width))
564 (setq submsg (substring msg i (+ i width)))
565 (message "[<] %s [ ]" submsg)
566 (unless (sit-for (cond
567 ((eq i 0) xetla-message-long-border-interval)
568 (t xetla-message-long-default-interval)))
569 (return-from xetla-message-with-bouncing)))
573 (setq submsg (substring msg j (+ j width)))
574 (message "[ ] %s [>]" submsg)
575 (unless (sit-for (cond
576 ((eq i 0) xetla-message-long-border-interval)
577 (t xetla-message-long-default-interval)))
578 (return-from xetla-message-with-bouncing)))
579 (garbage-collect)))))
581 (defun* xetla-message-with-rolling (&rest msg)
582 "Similar to `message' but display the message in rolling animation to show long line."
583 (setq msg (concat " <MESSAGE>: "
586 (let* ((width (- (window-width (minibuffer-window))
587 (+ 1 (length "[<] "))))
588 (msglen (length msg))
590 (normal-range (- msglen width)))
595 (setq submsg (if (< i normal-range)
596 (substring msg i (+ i width))
597 ;; Rolling is needed.
598 (concat (substring msg i)
599 (substring msg 0 (- (+ i width) msglen)))))
600 (message "[<] %s" submsg)
601 (unless (sit-for (cond
602 ((eq i 0) xetla-message-long-border-interval)
603 (t xetla-message-long-default-interval)))
604 (return-from xetla-message-with-rolling)))
605 (garbage-collect)))))
607 ;; --------------------------------------
608 ;; Name read engine helpers
609 ;; --------------------------------------
611 ;; Extended version of xetla-read-name
613 (defun xetla-name-read-help ()
614 "Displays a help message with keybindings for the minibuffer prompt."
616 (set-buffer (get-buffer-create "*Help*"))
617 (let ((inhibit-read-only t))
619 (kill-all-local-variables)
622 (insert "This buffer describes the name reading engine for xetla
624 You are prompted for a fully qualified archive, category, branch,
625 version, or revision, which means a string like
626 \"John.Smith@rt.fm-arch/xetla-revolutionary-1.0\". Completion is
627 available with TAB. Only the item being entered is proposed for
628 completion, which means that if you're typing the archive name,
629 pressing TAB will give you the list of archives. If you started to
630 type the category name, you'll get the list of category for this
633 Here's a list of other interesting bindings available in the
637 (let ((interesting (mapcar (lambda (pair) (cdr pair))
638 xetla-name-read-extension-keydefs)))
639 (dolist (func interesting)
640 (let* ((keys (where-is-internal func xetla-name-read-minibuf-map))
643 (when (not (eq 'menu-bar (aref (car keys) 0)))
644 (setq keys1 (if (string= keys1 "") (key-description (car keys))
646 (key-description (car keys))))))
647 (setq keys (cdr keys)))
648 (insert (format "%s%s\t`%s'\n" keys1
649 (make-string (max 0 (- 5 (length keys1))) ?\ )
650 (symbol-name func))))))
651 (goto-char (point-min))
652 (xetla-funcall-if-exists
653 help-setup-xref (list 'xetla-name-read-help)
655 (display-buffer (current-buffer))
656 (toggle-read-only 1))
658 (defun xetla-name-read-inline-help ()
659 "Displays a help message in echo area."
661 (let ((interesting (mapcar (lambda (pair) (cdr pair))
662 xetla-name-read-extension-keydefs))
664 (dolist (func interesting)
665 (let* ((keys (where-is-internal func xetla-name-read-minibuf-map))
667 (func (symbol-name func)))
669 (when (not (eq 'menu-bar (aref (car keys) 0)))
670 (setq keys1 (if (string= keys1 "") (key-description (car keys))
672 (key-description (car keys))))))
673 (setq keys (cdr keys)))
674 (setq func (progn (string-match "xetla-name-read-\\(.+\\)"
676 (match-string 1 func)))
677 (setq line (concat line (format "%s => `%s'" keys1 func) " "))))
678 (xetla-message-with-rolling line)
684 (defun xetla-read-revision-with-default-tree (&optional prompt tree)
685 "Read revision name with `xetla-name-read'.
686 PROMPT is passed to `xetla-name-read' without changing.
687 Default version associated with TREE, a directory is used as default arguments
688 for`xetla-name-read'."
689 (setq tree (xetla-tree-root (or tree default-directory) t))
690 (let ((tree-rev (xetla-tree-version-list tree)))
691 (xetla-name-read prompt
692 (if tree-rev (xetla-name-archive tree-rev) 'prompt)
693 (if tree-rev (xetla-name-category tree-rev) 'prompt)
694 (if tree-rev (xetla-name-branch tree-rev) 'prompt)
695 (if tree-rev (xetla-name-version tree-rev) 'prompt)
699 ;; Version for the tree of default directory
701 (defvar xetla-name-read-insert-version-associated-with-default-directory nil)
702 (defun xetla-name-read-insert-version-associated-with-default-directory (&optional force)
703 "Insert the version for the tree of the directory specified by .
705 If FORCE is non-nil, insert the version even if the minibuffer isn't empty."
707 (let ((version-for-tree
709 (xetla-tree-version-list
710 (if xetla-name-read-insert-version-associated-with-default-directory
711 xetla-name-read-insert-version-associated-with-default-directory
714 (xetla-name-read-arguments 'archive)
715 (xetla-name-read-arguments 'category)
716 (xetla-name-read-arguments 'branch)
717 (xetla-name-read-arguments 'version))))
718 (if (and (window-minibuffer-p (selected-window))
719 (or force (equal "" (buffer-substring))))
720 (insert version-for-tree))))
725 (defun xetla-name-read-insert-default-archive (&optional force)
726 "Insert default archive name into the minibuffer if it is empty.
728 If FORCE is non-nil, insert the archive name even if the minibuffer
731 (if (and (window-minibuffer-p (selected-window))
732 (or (equal "" (buffer-substring)) force)
734 (xetla-name-read-arguments 'archive)
736 (insert (xetla-my-default-archive))))
741 (defvar xetla-name-read-insert-info-at-point nil)
742 (defvar xetla-name-read-insert-info-at-point-extent nil)
743 (defun xetla-name-read-insert-info-at-point (&optional force)
744 "Insert the info(maybe revision) under the point to the minibuffer.
746 If FORCE is non-nil, insert the version even if the minibuffer isn't
750 (or xetla-name-read-insert-info-at-point
751 (xetla-name-read-insert-version-associated-with-default-directory))))
752 (when (and (window-minibuffer-p (selected-window))
753 (or (equal "" (buffer-substring)) force)
755 (insert info-at-point))))
757 (defun xetla-name-read-insert-info-at-point-init ()
758 "This function retrieves the info at point.
760 Further call to `xetla-name-read-insert-info-at-point-final' will
761 actuall insert the value computed here."
762 (setq xetla-name-read-insert-info-at-point
763 (let ((raw-info (cadr (xetla-get-info-at-point)))
764 (b (previous-single-property-change (point) 'menu))
765 (e (next-single-property-change (point) 'menu)))
768 (setq xetla-name-read-insert-info-at-point-extent
769 (make-extent (1- b) e))
770 (set-extent-property xetla-name-read-insert-info-at-point-extent
771 'face 'xetla-highlight))
773 (xetla-name-split raw-info) t
774 (xetla-name-read-arguments 'archive)
775 (xetla-name-read-arguments 'category)
776 (xetla-name-read-arguments 'branch)
777 (xetla-name-read-arguments 'version)
778 (xetla-name-read-arguments 'revision))))))
780 (defun xetla-name-read-insert-info-at-point-final (&optional no-use)
781 "Called when exitting the minibuffer prompt.
783 Cancels the effect of `xetla-name-read-insert-info-at-point-init'.
785 Argument NO-USE is ignored."
786 (when xetla-name-read-insert-info-at-point-extent
787 (delete-extent xetla-name-read-insert-info-at-point-extent)
788 (setq xetla-name-read-insert-info-at-point-extent nil)))
793 (defvar xetla-name-read-insert-partner-ring-position nil)
794 (defun xetla-name-read-insert-partner-init ()
795 "Initialize \"Insert Partner Version\" menu used in `xetla-name-read'."
796 (setq xetla-name-read-insert-partner-ring-position nil)
798 (setq xetla-name-read-partner-menu (cons "Insert Partner Version" nil))
799 (let ((partners (reverse (xetla-partner-list))))
801 (setq p (xetla-name-mask
802 (xetla-name-split p) t
803 (xetla-name-read-arguments 'archive)
804 (xetla-name-read-arguments 'category)
805 (xetla-name-read-arguments 'branch)
806 (xetla-name-read-arguments 'version)
807 (xetla-name-read-arguments 'revision)))
808 (setcdr xetla-name-read-partner-menu
811 `(lambda () (interactive)
813 (minibuffer-prompt-end) (point-max))
815 (cdr xetla-name-read-partner-menu))))
817 (fset 'xetla-name-read-partner-menu (cons 'keymap xetla-name-read-partner-menu)))
819 (defun xetla-name-read-insert-partner-previous ()
820 "Insert the previous partner version into miniffer."
822 (let* ((partners (xetla-partner-list))
823 (plen (length partners))
824 (pos (if xetla-name-read-insert-partner-ring-position
825 (if (eq xetla-name-read-insert-partner-ring-position 0)
827 (1- xetla-name-read-insert-partner-ring-position))
829 (pversion (when partners (xetla-name-mask
830 (xetla-name-split (nth pos partners)) t
831 (xetla-name-read-arguments 'archive)
832 (xetla-name-read-arguments 'category)
833 (xetla-name-read-arguments 'branch)
834 (xetla-name-read-arguments 'version)
835 (xetla-name-read-arguments 'revision)))))
836 (when (and (window-minibuffer-p (selected-window))
839 (delete-region (minibuffer-prompt-end) (point-max))
841 (setq xetla-name-read-insert-partner-ring-position pos))))
843 (defun xetla-name-read-insert-partner-next ()
844 "Insert the next partner version into the miniffer."
846 (let* ((partners (xetla-partner-list))
847 (plen (length partners))
848 (pos (if xetla-name-read-insert-partner-ring-position
849 (if (eq xetla-name-read-insert-partner-ring-position (1- plen))
851 (1+ xetla-name-read-insert-partner-ring-position))
853 (pversion (when partners (xetla-name-mask
854 (xetla-name-split (nth pos partners)) t
855 (xetla-name-read-arguments 'archive)
856 (xetla-name-read-arguments 'category)
857 (xetla-name-read-arguments 'branch)
858 (xetla-name-read-arguments 'version)
859 (xetla-name-read-arguments 'revision)))))
860 (when (and (window-minibuffer-p (selected-window))
863 (delete-region (minibuffer-prompt-end) (point-max))
865 (setq xetla-name-read-insert-partner-ring-position pos))))
870 (defun xetla-name-read-insert-ancestor (&optional force)
871 "Insert the ancestor name into the minibuffer if it is empty.
873 If FORCE is non-nil, insert the ancestor even if the minibuffer isn't
876 (let* ((version (xetla-tree-version-list default-directory))
877 (ancestor (when (and version
878 (not (eq this-command 'xetla-compute-direct-ancestor)))
879 (xetla-compute-direct-ancestor
880 (xetla-name-mask version nil
881 t t t t "base-0")))))
883 (window-minibuffer-p (selected-window))
884 (or (equal "" (buffer-substring)) force)
886 (xetla-name-read-arguments 'archive)
888 (insert (xetla-name-mask
892 (xetla-name-read-arguments 'category)
895 (xetla-name-read-arguments 'branch)
898 (xetla-name-read-arguments 'version)
901 (xetla-name-read-arguments 'revision)
902 '(prompt maybe)))))))
905 ;; Partners in Bookmark
907 (defvar xetla-name-read-insert-bookmark-ring-position nil)
908 (defun xetla-name-read-insert-bookmark-init ()
909 "Initialize \"Insert Version in Bookmark\" menu used in `xetla-name-read'."
910 (setq xetla-name-read-insert-bookmark-ring-position nil)
912 (setq xetla-name-read-bookmark-menu (cons "Insert Version in Bookmark" nil))
913 (let* ((default-version (xetla-tree-version-list default-directory 'no-error))
914 (bookmarks (when default-version
915 (nreverse (xetla-bookmarks-get-partner-versions default-version)))))
917 (setq p (xetla-name-mask
919 (xetla-name-read-arguments 'archive)
920 (xetla-name-read-arguments 'category)
921 (xetla-name-read-arguments 'branch)
922 (xetla-name-read-arguments 'version)
923 (xetla-name-read-arguments 'revision)))
924 (setcdr xetla-name-read-bookmark-menu
927 `(lambda () (interactive)
929 (minibuffer-prompt-end) (point-max))
931 (cdr xetla-name-read-bookmark-menu))))
933 (fset 'xetla-name-read-bookmark-menu (cons 'keymap xetla-name-read-bookmark-menu)))
935 (defun xetla-name-read-insert-bookmark-previous ()
936 "Insert the previous partner version in the bookmark into minibuffer."
938 (let* ((default-version (xetla-tree-version-list default-directory))
939 (bookmarks (when default-version
940 (nreverse (xetla-bookmarks-get-partner-versions default-version))))
941 (plen (length bookmarks))
942 (pos (if xetla-name-read-insert-bookmark-ring-position
943 (if (eq xetla-name-read-insert-bookmark-ring-position 0)
945 (1- xetla-name-read-insert-bookmark-ring-position))
947 (pversion (when bookmarks (xetla-name-mask
948 (nth pos bookmarks) t
949 (xetla-name-read-arguments 'archive)
950 (xetla-name-read-arguments 'category)
951 (xetla-name-read-arguments 'branch)
952 (xetla-name-read-arguments 'version)
953 (xetla-name-read-arguments 'revision)))))
954 (when (and (window-minibuffer-p (selected-window))
957 (delete-region (minibuffer-prompt-end) (point-max))
959 (setq xetla-name-read-insert-bookmark-ring-position pos))))
961 (defun xetla-name-read-insert-bookmark-next ()
962 "Insert the next partner version in the bookmark into the miniffer."
964 (let* ((default-version (xetla-tree-version-list default-directory))
965 (bookmarks (when default-version
966 (nreverse (xetla-bookmarks-get-partner-versions default-version))))
967 (plen (length bookmarks))
968 (pos (if xetla-name-read-insert-bookmark-ring-position
969 (if (eq xetla-name-read-insert-bookmark-ring-position (1- plen))
971 (1+ xetla-name-read-insert-bookmark-ring-position))
973 (pversion (when bookmarks (xetla-name-mask
974 (nth pos bookmarks) t
975 (xetla-name-read-arguments 'archive)
976 (xetla-name-read-arguments 'category)
977 (xetla-name-read-arguments 'branch)
978 (xetla-name-read-arguments 'version)
979 (xetla-name-read-arguments 'revision)))))
980 (when (and (window-minibuffer-p (selected-window))
983 (delete-region (minibuffer-prompt-end) (point-max))
985 (setq xetla-name-read-insert-bookmark-ring-position pos))))
987 (add-hook 'xetla-name-read-init-hook
988 'xetla-name-read-insert-info-at-point-init)
989 (add-hook 'xetla-name-read-final-hook
990 'xetla-name-read-insert-info-at-point-final)
991 (add-hook 'xetla-name-read-error-hook
992 'xetla-name-read-insert-info-at-point-final)
993 (add-hook 'xetla-name-read-init-hook
994 'xetla-name-read-insert-partner-init)
995 (add-hook 'xetla-name-read-init-hook
996 'xetla-name-read-insert-bookmark-init)
998 (defun xetla-tree-root (&optional location no-error)
999 "Return the tree root for LOCATION, nil if not in a local tree.
1000 Computation is done from withing Emacs, by looking at an {arch}
1001 directory in a parent buffer of LOCATION. This is therefore very
1004 If NO-ERROR is non-nil, don't raise an error if LOCATION is not an
1005 arch managed tree (but return nil)."
1006 (setq location (or location default-directory))
1007 (let ((pwd location))
1008 (while (not (or (string= pwd "/")
1009 (file-exists-p (concat (file-name-as-directory pwd) "{arch}"))))
1010 (setq pwd (expand-file-name (concat (file-name-as-directory pwd) ".."))))
1011 (if (file-exists-p (concat pwd "/{arch}/=tagging-method"))
1013 (replace-regexp-in-string "/+$" "/" pwd))
1016 (error "%S is not in an arch-managed tree!" location)))))
1018 (defun xetla-read-project-tree-maybe (&optional prompt directory)
1019 "Return a directory name which is the root of some project tree.
1020 Either prompt from the user or use the current directory. The
1021 recommended usage is
1023 (defun xetla-some-feature (...)
1024 (let ((default-directory (xetla-read-project-tree-maybe
1025 \"Run some feature in\")))
1026 (code-for-some-feature))
1028 The behavior can be changed according to the value of
1029 `xetla-read-project-tree-mode'.
1031 PROMPT is used as a user prompt, and DIRECTORY is the default
1033 (let ((root (xetla-tree-root (or directory default-directory) t))
1034 (default-directory (or (xetla-tree-root
1035 (or directory default-directory) t)
1038 (prompt (or prompt "Use directory: ")))
1039 (case xetla-read-project-tree-mode
1040 (always (xetla-tree-root (read-directory-name prompt)))
1042 (xetla-tree-root (read-directory-name prompt))))
1044 (error "Not in a project tree")))
1045 (t (error "Wrong value for xetla-prompt-for-directory")))))
1047 (defun xetla-read-directory-maybe (&optional prompt directory force)
1048 "Read a directory name inside an arch managed tree.
1050 Return a directory name which is a subdirectory or the root of some
1051 project tree. Works in a way similar to
1052 `xetla-read-project-tree-maybe', but is customized with the variable
1053 `xetla-read-directory-mode'.
1055 PROMPT is the user prompt, and DIRECTORY is the default directory."
1056 (let ((root (xetla-tree-root (or directory default-directory) t))
1057 (default-directory (or directory default-directory))
1058 (prompt (or prompt "Use directory: ")))
1059 (case xetla-read-directory-mode
1060 (always (read-directory-name prompt))
1063 (read-directory-name prompt))
1067 (read-directory-name prompt))))
1068 (never (if root (or directory default-directory)
1069 (error "Not in a project tree")))
1070 (t (error "Wrong value for xetla-read-directory-mode")))))
1072 (defun xetla-save-some-buffers (&optional tree)
1073 "Save all buffers visiting a file in TREE."
1075 (tree (or (xetla-tree-root tree t)
1078 (error "Not in a project tree"))
1079 (dolist (buffer (buffer-list))
1080 (with-current-buffer buffer
1081 (when (buffer-modified-p)
1082 (let ((file (buffer-file-name)))
1084 (let ((root (xetla-tree-root (file-name-directory file) t))
1085 (tree-exp (expand-file-name tree)))
1087 (string= (file-name-as-directory root) tree-exp)
1088 ;; buffer is modified and in the tree TREE.
1089 (or xetla-do-not-prompt-for-save
1090 (y-or-n-p (concat "Save buffer "
1094 (save-buffer))))))))
1097 (defun xetla-revert-some-buffers (&optional tree)
1098 "Reverts all buffers visiting a file in TREE that aren't modified.
1099 To be run after an update or a merge."
1100 (let ((tree (xetla-tree-root tree)))
1101 (dolist (buffer (buffer-list))
1102 (with-current-buffer buffer
1103 (when (not (buffer-modified-p))
1104 (let ((file (buffer-file-name)))
1106 (let ((root (xetla-uniquify-file-name
1107 (xetla-tree-root (file-name-directory file) t)))
1108 (tree-exp (xetla-uniquify-file-name
1109 (expand-file-name tree))))
1110 (when (and (string= root tree-exp)
1111 ;; buffer is modified and in the tree TREE.
1112 xetla-automatically-revert-buffers)
1113 ;; Keep the buffer if the file doesn't exist
1114 (if (file-exists-p file)
1115 (revert-buffer t t)))))))))))
1117 ;; --------------------------------------
1118 ;; xetla help system for commands that get input from the user via the minibuffer
1119 ;; --------------------------------------
1121 ;; GENERIC: This functionality should be in emacs itself. >> Masatake
1122 ;; to check: we should use some other binding for this, perhaps f1 C-m
1123 (defun xetla-display-command-help (command)
1124 "Help system for commands that get input via the minibuffer.
1126 This is an internal function called by `xetla-show-command-help'.
1128 COMMAND is the last command executed."
1131 (let ((cmd-help (when (fboundp command)
1132 (documentation command))))
1133 (delete-region (point-min) (point-max))
1134 (insert (if cmd-help
1135 (format "Help for %S:\n%s" command cmd-help)
1136 (format "No help available for %S" command)))))
1137 " *xetla-command-help*"))
1139 (defvar xetla-command-stack nil)
1141 (defun xetla-minibuffer-setup ()
1142 "Function called in `minibuffer-setup-hook'.
1144 Memorize last command run."
1145 (push this-command xetla-command-stack))
1147 (defun xetla-minibuffer-exit ()
1148 "Function called in `minibuffer-exit-hook'.
1150 Cancels the effect of `xetla-minibuffer-setup'."
1151 (pop xetla-command-stack))
1153 (defun xetla-show-command-help ()
1154 "Help system for commands that get input via the minibuffer.
1156 When the user is asked for input in the minibuffer, a help for the
1157 command will be shown, if the user hits \\<minibuffer-local-map>\\[xetla-show-command-help].
1158 This functionality is not only for xetla commands available it is
1159 available for all Emacs commands."
1161 (xetla-display-command-help (car xetla-command-stack)))
1163 (when xetla-install-command-help-system
1164 (define-key minibuffer-local-map [f1]
1165 'xetla-show-command-help)
1166 (define-key minibuffer-local-completion-map [f1]
1167 'xetla-show-command-help)
1168 (define-key minibuffer-local-must-match-map [f1]
1169 'xetla-show-command-help)
1170 (define-key minibuffer-local-map [(control meta ?h)]
1171 'xetla-show-command-help)
1172 (define-key minibuffer-local-completion-map [(control meta ?h)]
1173 'xetla-show-command-help)
1174 (define-key minibuffer-local-must-match-map [(control meta ?h)]
1175 'xetla-show-command-help)
1176 (add-hook 'minibuffer-setup-hook 'xetla-minibuffer-setup)
1177 (add-hook 'minibuffer-exit-hook 'xetla-minibuffer-exit))
1179 ;; --------------------------------------
1180 ;; Top level xetla commands
1181 ;; --------------------------------------
1182 (defcustom xetla-make-log-function 'xetla-default-make-log-function
1183 "*Function used to create the log buffer.
1185 Must return a string which is the absolute name of the log file. This
1186 function is called only when the log file doesn't exist already. The
1187 default is `xetla-default-make-log-function', which just calls \"xetla
1188 make-log\". If you want to override this function, you may just write
1189 a wrapper around `xetla-default-make-log-function'."
1193 (defun xetla-make-log ()
1194 "Create the log file and return its filename.
1196 If the file exists, its name is returned. Otherwise, the log file is
1197 created by the function specified by `xetla-make-log-function', which,
1198 by default, calls \"xetla make-log\"."
1200 (let* ((version (xetla-tree-version-list))
1201 (file (concat (xetla-tree-root) "++log."
1202 (xetla-name-category version) "--"
1203 (xetla-name-branch version) "--"
1204 (xetla-name-version version) "--"
1205 (xetla-name-archive version))))
1206 (if (file-exists-p file)
1208 (funcall xetla-make-log-function))))
1210 (defun xetla-default-make-log-function ()
1211 "Candidate (and default value) for `xetla-make-log-function'.
1212 Calls \"xetla make-log\" to generate the log file."
1213 (xetla-run-tla-sync '("make-log")
1215 (lambda (output error status arguments)
1216 (xetla-buffer-content output))))
1218 (defun xetla-pop-to-inventory ()
1219 "Call `xetla-inventory' with a prefix arg."
1221 (xetla-inventory nil t))
1223 (defvar xetla-inventory-cookie nil)
1224 (defvar xetla-inventory-list nil
1225 "Full list for the inventory.")
1227 (defun xetla-inventory-goto-file (file)
1228 "Put cursor on FILE. nil return means the file hasn't been found."
1229 (goto-char (point-min))
1230 (let ((current (ewoc-locate xetla-inventory-cookie)))
1231 (while (and current (not (string= (caddr (ewoc-data current))
1233 (setq current (ewoc-next xetla-inventory-cookie current)))
1234 (when current (xetla-inventory-cursor-goto current))
1238 (defun xetla-inventory-make-toggle-fn-and-var (variable function)
1239 "Define the VARIABLE and the toggle FUNCTION for type TYPE."
1240 (make-variable-buffer-local variable)
1241 (eval `(defun ,function ()
1243 (setq ,variable (not ,variable))
1244 (xetla-inventory-redisplay))))
1246 (dolist (type-arg xetla-inventory-file-types-manipulators)
1247 (xetla-inventory-make-toggle-fn-and-var (cadr type-arg) (caddr type-arg)))
1249 (defun xetla-inventory-redisplay ()
1250 "Refresh *xetla-inventory* buffer."
1251 (let* ((elem (ewoc-locate xetla-inventory-cookie))
1252 (file (when elem (caddr (ewoc-data elem))))
1254 (xetla-inventory-display)
1256 (xetla-inventory-goto-file file))
1258 (xetla-inventory-cursor-goto (ewoc-locate xetla-inventory-cookie))))
1261 (defun xetla-inventory-set-toggle-variables (new-value)
1262 "Set all xetla-inventory-display-* variables.
1263 If NEW-VALUE is 'toggle set the values to (not xetla-inventory-display-*
1264 Otherwise set it to NEW-VALUE."
1265 (dolist (type-arg xetla-inventory-file-types-manipulators)
1266 (eval `(setq ,(cadr type-arg)
1267 (if (eq new-value 'toggle)
1268 (not ,(cadr type-arg))
1271 (defun xetla-inventory-set-all-toggle-variables ()
1272 "Set all inventory toggle variables to t."
1274 (xetla-inventory-set-toggle-variables t)
1275 (xetla-inventory-redisplay))
1277 (defun xetla-inventory-reset-all-toggle-variables ()
1278 "Set all inventory toggle variables to nil."
1280 (xetla-inventory-set-toggle-variables nil)
1281 (xetla-inventory-redisplay))
1283 (defun xetla-inventory-toggle-all-toggle-variables ()
1284 "Toggle the value of all inventory toggle variables."
1286 (xetla-inventory-set-toggle-variables 'toggle)
1287 (xetla-inventory-redisplay))
1291 (defun xetla-inventory (&optional directory arg)
1292 "Show a xetla inventory at DIRECTORY.
1293 When called with a prefix arg, pop to the inventory buffer.
1294 DIRECTORY defaults to the current one when within an arch managed tree,
1295 unless prefix argument ARG is non-nil."
1296 (interactive (list (xetla-read-directory-maybe
1297 "Run inventory in (directory): "
1298 nil current-prefix-arg)
1299 current-prefix-arg))
1300 (let ((default-directory (or directory default-directory)))
1302 (pop-to-buffer (xetla-get-buffer-create 'inventory directory))
1303 (switch-to-buffer (xetla-get-buffer-create 'inventory directory))))
1304 (xetla-inventory-mode)
1306 ;; We have to provide all file types or xetla inventory won't display
1308 '("inventory" "--both" "--kind" "--source" "--backups" "--junk"
1309 "--unrecognized" "--precious")
1311 (lambda (output error status arguments)
1312 (let ((list (split-string (xetla-buffer-content output) "\n"))
1313 (inventory-list '()))
1316 (when (string-match "\\([A-Z]\\)\\([\\? ]\\) +\\([^ ]\\) \\(.*\\)"
1318 (let ((xetla-type (string-to-char (match-string 1 item)))
1319 (question (string= (match-string 2 item) "?"))
1320 (escaped-filename (match-string 4 item))
1321 (type (string-to-char (match-string 3 item))))
1322 (push (list xetla-type
1324 (xetla-unescape escaped-filename)
1328 (setq inventory-list (reverse inventory-list))
1329 (set (make-local-variable 'xetla-inventory-list)
1331 (xetla-inventory-display)))))
1333 (defun xetla-inventory-display ()
1334 "Display the inventory.
1335 This function creates the ewoc from the variable `xetla-inventory-list',
1336 selecting only files to print."
1338 (let (buffer-read-only)
1340 (set (make-local-variable 'xetla-inventory-cookie)
1341 (ewoc-create 'xetla-inventory-printer))
1342 (xetla-inventory-insert-headers)
1343 (dolist (elem xetla-inventory-list)
1344 (let ((type (car elem)))
1345 (if (eval (cadr (assoc type
1346 xetla-inventory-file-types-manipulators)))
1347 (ewoc-enter-last xetla-inventory-cookie elem)))))
1348 (goto-char (point-min)))
1350 (defun xetla-inventory-chose-face (type)
1351 "Return a face adapted to TYPE, which can be J, S, P, T or U."
1353 (?P 'xetla-precious)
1354 (?U 'xetla-unrecognized)
1357 (?T 'xetla-nested-tree)))
1359 (defun xetla-inventory-printer (elem)
1360 "Ewoc printer for `xetla-inventory-cookie'.
1362 (let* ((type (nth 0 elem))
1363 (question (nth 1 elem))
1365 (file-type (nth 3 elem))
1366 (face (xetla-inventory-chose-face type)))
1367 (insert (if (member file xetla-buffer-marked-file-list)
1368 (concat " " xetla-mark " ") " "))
1369 (insert (xetla-face-add (format "%c%s "
1371 (if question "?" " "))
1375 (case file-type (?d "/") (?> "@") (t "")))
1377 'xetla-inventory-item-map
1378 xetla-inventory-item-menu))))
1380 (defun xetla-inventory-mark-file ()
1381 "Mark file at point in inventory mode.
1383 Adds it to the variable `xetla-buffer-marked-file-list', and move cursor
1386 (let ((current (ewoc-locate xetla-inventory-cookie))
1387 (file (xetla-get-file-info-at-point)))
1388 (add-to-list 'xetla-buffer-marked-file-list file)
1389 (ewoc-refresh xetla-inventory-cookie)
1390 (xetla-inventory-cursor-goto (or (ewoc-next xetla-inventory-cookie
1394 (defun xetla-inventory-unmark-file ()
1395 "Unmark file at point in inventory mode."
1397 (let ((current (ewoc-locate xetla-inventory-cookie))
1398 (file (xetla-get-file-info-at-point)))
1399 (setq xetla-buffer-marked-file-list
1400 (delete file xetla-buffer-marked-file-list))
1401 (ewoc-refresh xetla-inventory-cookie)
1402 (xetla-inventory-cursor-goto (or (ewoc-next xetla-inventory-cookie
1406 (defun xetla-inventory-unmark-all ()
1407 "Unmark all files in inventory mode."
1409 (let ((current (ewoc-locate xetla-inventory-cookie)))
1410 (setq xetla-buffer-marked-file-list nil)
1411 (ewoc-refresh xetla-inventory-cookie)
1412 (xetla-inventory-cursor-goto current)))
1414 (defvar xetla-get-file-info-at-point-function nil
1415 "Function used to get the file at point, anywhere.")
1417 (defun xetla-get-file-info-at-point ()
1418 "Gets the filename at point, according to mode.
1419 Actually calls the function `xetla-get-file-info-at-point-function'."
1420 (when xetla-get-file-info-at-point-function
1421 (funcall xetla-get-file-info-at-point-function)))
1423 (defvar xetla-generic-select-files-function nil
1424 "Function called by `xetla-generic-select-files'.
1425 Must be local to each buffer.")
1427 (defun xetla-generic-select-files (msg-singular
1431 no-group ignore-marked
1434 "Get the list of files at point, and ask confirmation of the user.
1436 This is a generic function calling
1437 `xetla-generic-select-files-function', defined locally for each xetla
1438 buffer. The behavior should be the following:
1440 Prompt with either MSG-SINGULAR, MSG-PLURAL, MSG-ERR OR MSG-PROMPT. If
1441 NO-GROUP is nil and if the cursor is on the beginning of a group, all
1442 the files belonging to this message are selected. If some files are
1443 marked \(i.e. `xetla-buffer-marked-file-list' is non-nil) and
1444 IGNORE-MARKED is non-nil, the list of marked files is returned. If
1445 NO-PROMPT is non-nil, don't ask for confirmation. If Y-OR-N is
1446 non-nil, then this function is used instead of `y-or-n-p'."
1447 (when xetla-generic-select-files-function
1448 (funcall xetla-generic-select-files-function
1449 msg-singular msg-plural msg-err msg-prompt no-group
1450 ignore-marked no-prompt y-or-n)))
1452 (defun xetla-generic-find-file-at-point ()
1453 "Opens the file at point.
1455 The filename is obtained with `xetla-get-file-info-at-point', so, this
1456 function should be useable in all modes seting
1457 `xetla-get-file-info-at-point-function'"
1459 (let* ((file (xetla-get-file-info-at-point)))
1462 (error "No file at point"))
1464 (find-file file)))))
1466 (xetla-make-bymouse-function xetla-generic-find-file-at-point)
1468 (defun xetla-generic-find-file-other-window ()
1469 "Visit the current inventory file in the other window."
1471 (let ((file (xetla-get-file-info-at-point)))
1474 (find-file-other-window file))
1475 (error "No file at point"))))
1477 (defun xetla-generic-view-file ()
1478 "Visit the current inventory file in view mode."
1480 (let ((file (xetla-get-file-info-at-point)))
1482 (view-file-other-window file)
1483 (error "No file at point"))))
1485 (defun xetla-inventory-get-file-info-at-point ()
1486 "Gets the file at point in inventory mode."
1487 (caddr (ewoc-data (ewoc-locate xetla-inventory-cookie))))
1489 (defun xetla-inventory-insert-headers ()
1490 "Insert the header (top of buffer) for *xetla-inventory*."
1491 (let* ((tree-version (xetla-name-construct
1492 (xetla-tree-version-list nil 'no-error)))
1493 (tagging-method (xetla-id-tagging-method nil))
1495 (xetla-face-add (make-string
1496 (max (+ (length "Directory: ") (length default-directory))
1497 (+ (length "Default Tree Version: ") (length tree-version))
1498 (+ (length "ID Tagging Method: ") (length tagging-method)))
1502 xetla-inventory-cookie
1504 "Directory: " (xetla-face-add default-directory 'xetla-local-directory
1505 (let ((map (make-sparse-keymap))
1508 (dired ,default-directory))))
1509 (define-key map [return] func)
1510 (define-key map "\C-m" func)
1511 (define-key map [button2] func)
1514 "Run Dired Here") "\n"
1515 "Default Tree Version: " (xetla-face-add tree-version 'xetla-archive-name
1516 'xetla-inventory-default-version-map
1517 (xetla-partner-create-menu
1518 'xetla-generic-set-tree-version
1519 "Change the Default Tree Version")) "\n"
1520 "ID Tagging Method: " (xetla-face-add tagging-method 'xetla-tagging-method
1521 'xetla-inventory-tagging-method-map
1522 xetla-inventory-tagging-method-menu) "\n"
1524 (concat "\n" separator))))
1526 (defvar xetla-buffer-source-buffer nil
1527 "Buffer from where a command was called.")
1530 (defun xetla-edit-log (&optional insert-changelog source-buffer)
1531 "Edit the xetla log file.
1533 With an optional prefix argument INSERT-CHANGELOG, insert the last
1534 group of entries from the ChangeLog file. SOURCE-BUFFER, if non-nil,
1535 is the buffer from which the function was called. It is used to get
1536 the list of marked files, and potentially run a selected file commit."
1538 (setq xetla-pre-commit-window-configuration
1539 (current-window-configuration))
1540 (setq xetla-log-edit-file-name (xetla-make-log))
1541 (xetla-switch-to-buffer
1542 (find-file-noselect xetla-log-edit-file-name))
1543 (when insert-changelog
1544 (goto-char (point-max))
1545 (let ((buf (find-file-noselect (find-change-log))))
1546 (insert-buffer buf))
1547 (when (re-search-forward "^2" nil t)
1548 (delete-region (point-at-bol)
1550 (when (re-search-forward "^2" nil t)
1551 (delete-region (point-at-bol) (point-max)))
1552 (goto-char (point-min)))
1553 (xetla-log-edit-mode)
1554 (set (make-local-variable 'xetla-buffer-source-buffer)
1559 (defun xetla-add-log-entry ()
1560 "Add new xetla log ChangeLog style entry."
1563 (xetla-add-log-entry-internal)))
1565 (defun xetla-add-log-entry-internal ()
1566 "Similar to `add-change-log-entry'.
1568 Inserts the entry in the arch log file instead of the ChangeLog."
1569 ;; This is mostly copied from add-log.el. Perhaps it would be better to
1570 ;; split add-change-log-entry into several functions and then use them, but
1571 ;; that wouldn't work with older versions of Emacs.
1573 (let* ((defun (add-log-current-defun))
1574 (buf-file-name (if (and (boundp 'add-log-buffer-file-name-function)
1575 add-log-buffer-file-name-function)
1576 (funcall add-log-buffer-file-name-function)
1578 (buffer-file (if buf-file-name (expand-file-name buf-file-name)))
1579 (file-name (xetla-make-log))
1580 ;; Set ENTRY to the file name to use in the new entry.
1581 (entry (add-log-file-name buffer-file file-name))
1587 (goto-char (point-min))
1588 (when (re-search-forward "^Patches applied:" nil t)
1589 (narrow-to-region (point-min) (match-beginning 0))
1591 (goto-char (point-min)))
1592 (re-search-forward "\n\n\\|\\'")
1596 (if (looking-at "\n*[^\n* \t]")
1597 (skip-chars-forward "\n")
1598 (if (and (boundp 'add-log-keep-changes-together)
1599 add-log-keep-changes-together)
1600 (goto-char (point-max))
1601 (forward-paragraph))) ; paragraph delimits entries for file
1605 ;; Now insert the new line for this entry.
1606 (cond ((re-search-forward "^\\s *\\*\\s *$" bound t)
1607 ;; Put this file name into the existing empty entry.
1610 ((let (case-fold-search)
1612 (concat (regexp-quote (concat "* " entry))
1613 ;; Don't accept `foo.bar' when
1614 ;; looking for `foo':
1615 "\\(\\s \\|[(),:]\\)")
1617 ;; Add to the existing entry for the same file.
1618 (re-search-forward "^\\s *$\\|^\\s \\*")
1619 (goto-char (match-beginning 0))
1620 ;; Delete excess empty lines; make just 2.
1621 (while (and (not (eobp)) (looking-at "^\\s *$"))
1622 (delete-region (point) (point-at-bol 2)))
1625 (indent-relative-maybe))
1627 ;; Make a new entry.
1628 (if xetla-log-insert-last
1630 (goto-char (point-max))
1631 (re-search-backward "^.")
1636 (while (looking-at "\\sW")
1638 (while (and (not (eobp)) (looking-at "^\\s *$"))
1639 (delete-region (point) (point-at-bol 2)))
1642 (indent-to left-margin)
1644 (if entry (insert entry))))
1645 (if narrowing (widen))
1646 ;; Now insert the function name, if we have one.
1647 ;; Point is at the entry for this file,
1648 ;; either at the end of the line or at the first blank line.
1651 ;; Make it easy to get rid of the function name.
1653 (unless (save-excursion
1654 (beginning-of-line 1)
1655 (looking-at "\\s *$"))
1657 ;; See if the prev function name has a message yet or not
1658 ;; If not, merge the two entries.
1659 (let ((pos (point-marker)))
1660 (if (and (skip-syntax-backward " ")
1661 (skip-chars-backward "):")
1663 (progn (delete-region (+ 1 (point)) (+ 2 (point))) t)
1664 (> fill-column (+ (current-column) (length defun) 3)))
1665 (progn (delete-region (point) pos)
1669 (set-marker pos nil))
1670 (insert defun "): "))
1671 ;; No function name, so put in a colon unless we have just a star.
1672 (unless (save-excursion
1673 (beginning-of-line 1)
1674 (looking-at "\\s *\\(\\*\\s *\\)?$"))
1677 (defvar xetla-changes-cookie nil
1678 "Ewoc cookie for the changes buffer.
1680 Element should look like
1682 (file \"filename\" \"M\" \"/\")
1683 (file \"newname\" \"M\" \"/\" \"filename\")
1684 (subtree \"name\" related-buffer changes?)
1685 (message \"doing such or such thing\")")
1687 (defun xetla-changes-delete-messages (&optional immediate)
1688 "Remove messages from the ewoc list of modifications.
1690 if IMMEDIATE is non-nil, refresh the display too."
1691 (when xetla-changes-cookie
1692 (ewoc-filter xetla-changes-cookie
1694 (not (eq (car elem) 'message))))))
1696 (defvar xetla-changes-summary nil
1697 "Wether the current buffer display only a summary or a full diff.")
1699 (defvar xetla-changes-buffer-master-buffer nil
1700 "Master buffer for a nested *xetla-changes* buffer.")
1702 (defvar xetla-changes-summary nil
1703 "Wether the current buffer display only a summary or a full diff.")
1706 (defun xetla-changes (&optional summary against)
1707 "Run \"tla changes\".
1709 When called without a prefix argument: show the detailed diffs also.
1710 When called with a prefix argument SUMMARY: do not show detailed
1711 diffs. When AGAINST is non-nil, use it as comparison tree."
1713 (let* ((root (xetla-read-project-tree-maybe
1714 "Run tla changes in: "))
1715 (default-directory root)
1716 (buffer (xetla-prepare-changes-buffer
1718 (list 'last-revision root))
1719 (list 'local-tree root)
1721 default-directory)))
1722 (with-current-buffer buffer
1723 (set (make-local-variable 'xetla-changes-summary)
1725 (when xetla-switch-to-buffer-first
1726 (xetla-switch-to-buffer buffer))
1727 (xetla-save-some-buffers)
1728 (xetla-run-tla-async
1729 '("inventory" "--nested" "--trees")
1730 :related-buffer buffer
1732 `(lambda (output error status arguments)
1733 (let ((subtrees (delete ""
1735 (with-current-buffer
1736 output (buffer-string)) "\n"))))
1737 (with-current-buffer ,buffer
1738 (let ((inhibit-read-only t))
1740 xetla-changes-cookie
1742 (concat "* running tla changes in tree " ,root
1744 (ewoc-refresh xetla-changes-cookie))
1745 (dolist (subtree subtrees)
1746 (let ((buffer-sub (xetla-get-buffer-create
1748 (with-current-buffer buffer-sub
1749 (let ((inhibit-read-only t))
1751 (xetla-changes-mode)
1752 (set (make-local-variable
1753 'xetla-changes-buffer-master-buffer)
1755 (ewoc-enter-last xetla-changes-cookie
1756 (list 'subtree buffer-sub subtree
1758 (xetla-changes-internal
1760 nil ;; TODO "against" what for a nested tree?
1764 (xetla-changes-internal ,(not summary)
1766 ,root ,buffer nil)))))))
1769 (defun xetla-changes-against (&optional summary against)
1770 "Wrapper for `xetla-changes'.
1772 When called interactively, SUMMARY is the prefix arg, and AGAINST is
1773 read from the user."
1774 (interactive (list current-prefix-arg
1775 (list 'revision (xetla-name-read "Compute changes against: "
1776 'prompt 'prompt 'prompt 'prompt
1778 (xetla-changes summary against))
1781 (defun xetla-changes-last-revision (&optional summary)
1782 "Run `xetla-changes' against the last but one revision.
1784 The idea is that running this command just after a commit should be
1785 equivalent to running `xetla-changes' just before the commit.
1787 SUMMARY is passed to `xetla-changes'."
1789 (let ((default-directory (xetla-read-project-tree-maybe
1790 "Review last patch in directory: ")))
1791 (xetla-changes summary (list 'revision
1792 (xetla-name-construct (xetla-compute-direct-ancestor))))))
1794 (defvar xetla-changes-modified nil
1795 "MODIFIED revision for the changes currently displayed.
1797 Must be buffer-local.
1799 This variable has the form (type location), and can be either
1801 '(revision (\"archive\" \"category\" \"branch\" \"version\"
1806 '(local-tree \"/path/to/local/tree\")
1808 The value nil means we have no information about which local tree or
1811 (defvar xetla-changes-base nil
1812 "BASE revision for the changes currently displayed.
1814 Must be buffer-local.
1816 The values for this variable can be the same as for
1817 `xetla-changes-modified', plus the values
1819 '(last-revision \"/path/to/tree\"),
1820 used by `xetla-changes' to mean \"revision on which this local tree is
1825 '(previous-revision (\"archive\" \"category\" \"branch\" \"version\"
1827 used by commands like xetla-get-changeset, and means that the changes
1828 are against the previous revision.")
1830 (defun xetla-changes-internal (diffs against root buffer master-buffer)
1831 "Internal function to run \"tla changes\".
1833 If DIFFS is non nil, show the detailed diffs also.
1834 Run the command against tree AGAINST in directory ROOT.
1835 The output will be displayed in buffer BUFFER.
1837 BUFFER must already be in changes mode, but mustn't contain any change
1838 information. Only roots of subprojects are already in the ewoc.
1840 If MASTER-BUFFER is non-nil, this run of tla changes is done in a
1841 nested project of a bigger one. MASTER-BUFFER is the buffer in which
1842 the root of the projects is displayed."
1843 (with-current-buffer buffer
1844 (xetla-run-tla-async
1845 `("changes" ,(when diffs "--diffs")
1846 ,(case (car against)
1848 (error "Can not run tla changes against a local tree"))
1849 (previous-revision (xetla-compute-direct-ancestor
1851 (last-revision (if (string= (xetla-uniquify-file-name
1853 (xetla-uniquify-file-name
1856 (error "tla changes against last %s %s"
1857 "revision of local tree not"
1859 (revision (xetla-name-construct (cadr against)))))
1861 `(lambda (output error status arguments)
1863 (message "No changes in subtree %s" ,root)
1864 (message "No changes in %s" ,root))
1865 (with-current-buffer ,(current-buffer)
1866 (let ((inhibit-read-only t))
1867 (xetla-changes-delete-messages)
1868 (ewoc-enter-last xetla-changes-cookie
1869 (list 'message (concat "* No changes in "
1871 (when ,master-buffer
1872 (with-current-buffer ,master-buffer
1873 (ewoc-map (lambda (x)
1874 (when (and (eq (car x) 'subtree)
1875 (eq (cadr x) ,buffer))
1876 (setcar (cdddr x) 'no-changes))
1878 ;; (ewoc-refresh xetla-changes-cookie)))
1879 xetla-changes-cookie)))
1880 (ewoc-refresh xetla-changes-cookie))))
1882 `(lambda (output error status arguments)
1885 (xetla-show-error-buffer error)
1886 (goto-char (point-min))
1887 (when (search-forward "try tree-lint" nil t)
1888 (xetla-tree-lint ,root)))
1889 (xetla-show-changes-buffer output nil ,buffer ,master-buffer)
1890 (when ,master-buffer
1891 (with-current-buffer ,master-buffer
1892 (ewoc-map (lambda (x)
1893 (when (and (eq (car x) 'subtree)
1894 (eq (cadr x) ,buffer))
1895 (setcar (cdddr x) 'changes))
1897 xetla-changes-cookie)))))
1900 (defun xetla-changes-chose-face (modif)
1901 "Return a face adapted to MODIF, a string, which can be A, M, C, or D."
1903 ((string-match "A" modif) 'xetla-added)
1904 ((string-match "M" modif) 'xetla-modified)
1905 ((string-match "-" modif) 'xetla-modified)
1906 ((string-match "C" modif) 'xetla-conflict)
1907 ((string-match "D" modif) 'xetla-conflict)
1908 ((string-match "/" modif) 'xetla-move)
1909 ((string-match "=" modif) 'xetla-move)
1911 (xetla-trace "unknown modif: \"%s\"" modif)
1914 (defun xetla-changes-printer (elem)
1915 "Ewoc pretty-printer for `xetla-changes-cookie'.
1919 ((eq (car elem) 'file)
1920 (let* ((empty-mark " ")
1921 (mark (when (member (cadr elem) xetla-buffer-marked-file-list)
1922 (concat xetla-mark " ")))
1924 (modif (caddr elem))
1926 (basename (nth 4 elem))
1927 (line (concat modif dir " "
1928 (when basename (concat basename "\t"))
1932 (xetla-changes-chose-face modif))))
1935 (insert empty-mark))
1936 (insert (xetla-face-add line
1938 'xetla-changes-file-map
1939 xetla-changes-file-menu))))
1940 ((eq (car elem) 'subtree)
1941 (insert " T" (cond ((not (cadddr elem)) "?")
1942 ((eq (cadddr elem) 'changes) "M")
1943 ((eq (cadddr elem) 'no-changes) "-"))
1945 ((eq (car elem) 'message)
1946 (insert (cadr elem))))
1949 (defconst xetla-verbose-format-spec
1950 '(("added files" "A" " ")
1951 ("modified files" "M" " ")
1952 ("removed files" "D" " "))
1953 "Internal variable used to parse the output of xetla show-changeset."
1956 (defun xetla-show-changes-buffer (buffer &optional verbose-format
1957 output-buffer no-switch)
1958 "Show the *xetla-changes* buffer built from the *xetla-process* BUFFER.
1960 If VERBOSE-FORMAT is non-nil, the format of the *xetla-process* buffer
1961 should be the one of xetla show-changeset.
1963 Use OUTPUT-BUFFER to display changes if provided. That buffer must
1964 already be in changes mode.
1966 If NO-SWITCH is nil, don't switch to the created buffer."
1967 (let* ((root (with-current-buffer buffer
1968 (xetla-tree-root default-directory t)))
1969 (changes-buffer (or output-buffer (xetla-get-buffer-create
1972 (if (or no-switch xetla-switch-to-buffer-first)
1973 (set-buffer changes-buffer)
1974 (xetla-switch-to-buffer changes-buffer))
1975 (let (buffer-read-only)
1976 (xetla-changes-delete-messages)
1977 (unless output-buffer
1979 (xetla-changes-mode))
1980 (with-current-buffer buffer
1983 (goto-char (point-min))
1984 (while (re-search-forward
1985 (concat "^\\* \\(" (regexp-opt
1986 (mapcar 'car xetla-verbose-format-spec))
1989 (let* ((elem (assoc (match-string 1)
1990 xetla-verbose-format-spec))
1993 (if (string= modif "M")
1994 (while (re-search-forward "^--- orig/\\(.*\\)$"
1996 (let ((file (match-string 1)))
1997 (with-current-buffer changes-buffer
1999 xetla-changes-cookie
2000 (list 'file (xetla-unescape file)
2002 (while (looking-at "^$") (forward-line 1))
2005 (let ((file (match-string 1)))
2006 (with-current-buffer changes-buffer
2008 xetla-changes-cookie
2009 (list 'file (xetla-unescape file)
2011 (forward-line 1))))))
2012 (goto-char (point-min))
2013 (if (re-search-forward "^---" nil t)
2015 (beginning-of-line)))
2016 (setq header (buffer-substring-no-properties
2017 (goto-char (point-min))
2018 (progn (re-search-forward "^[^*]" nil t)
2022 (while (or (eq (char-after) ?*)
2023 (looking-at "^\\(.\\)\\([ /bfl>-]?\\) +\\([^\t\n]*\\)\\(\t\\(.*\\)\\)?$"))
2024 (if (eq (char-after) ?*)
2025 (let ((msg (buffer-substring-no-properties
2026 (point) (point-at-eol))))
2027 (with-current-buffer changes-buffer
2028 (ewoc-enter-last xetla-changes-cookie
2029 (list 'message msg))))
2030 (let ((file (match-string 3))
2031 (modif (match-string 1))
2032 (dir (match-string 2))
2033 (newname (match-string 5)))
2034 (with-current-buffer changes-buffer
2036 (ewoc-enter-last xetla-changes-cookie
2038 (xetla-unescape newname)
2040 (xetla-unescape file)))
2041 (ewoc-enter-last xetla-changes-cookie
2043 (xetla-unescape file)
2046 (let ((footer (concat
2047 (xetla-face-add (make-string 72 ?\ ) 'xetla-separator)
2049 (buffer-substring-no-properties
2050 (point) (point-max)))))
2051 (with-current-buffer changes-buffer
2052 (ewoc-set-hf xetla-changes-cookie header footer)
2053 (if root (cd root)))))
2055 (toggle-read-only 1)
2056 (when font-lock-mode
2057 (let ((font-lock-verbose nil))
2058 (font-lock-fontify-buffer)))
2059 (if (ewoc-nth xetla-changes-cookie 0)
2060 (goto-char (ewoc-location (ewoc-nth xetla-changes-cookie 0)))))
2062 (defun xetla-changes-save (directory)
2063 "Run \"tla changes -o\" to create a changeset.
2065 The changeset is stored in DIRECTORY."
2066 (interactive "FDirectory to store the changeset: ")
2067 (xetla-run-tla-sync (list "changes" "-o" directory)
2068 :error `(lambda (output error status arguments)
2070 (0 (message "xetla-changes-save: 0"))
2071 (1 (message (format "xetla-changes-save to %s finished" ,directory)))
2072 (otherwise (xetla-default-error-function
2073 output error status arguments))))))
2076 (defun xetla-changes-save-as-tgz (file-name)
2077 "Run \"tla changes -o\" to create .tar.gz file.
2078 The changeset is stored in the tarball 'FILE-NAME.tar.gz'."
2079 (interactive "FFile to store the changeset (without .tar.gz extension): ")
2080 (let* ((changeset-dir (expand-file-name file-name))
2081 (tgz-file-name (concat changeset-dir ".tar.gz")))
2082 (when (file-directory-p changeset-dir)
2083 (error "The changeset directory %s does already exist" changeset-dir))
2084 (when (file-exists-p tgz-file-name)
2085 (error "The changeset tarball %s does already exist" tgz-file-name))
2086 (xetla-changes-save changeset-dir)
2087 ;;create the archive: tar cfz ,,cset.tar.gz ,,cset
2088 (let ((default-directory (file-name-directory changeset-dir)))
2089 ;;(message "Calling tar cfz %s %s" tgz-file-name (file-name-nondirectory changeset-dir))
2090 (call-process "tar" nil nil nil "cfz" tgz-file-name (file-name-nondirectory changeset-dir)))
2091 (call-process "rm" nil nil nil "-rf" changeset-dir)
2092 (message "Created changeset tarball %s" tgz-file-name)))
2095 (defun xetla-delta (base modified &optional directory)
2096 "Run tla delta BASE MODIFIED.
2097 If DIRECTORY is a non-empty string, the delta is stored to it.
2098 If DIRECTORY is ask, a symbol, ask the name of directory.
2099 If DIRECTORY is nil or an empty string, just show the delta using --diffs."
2101 (xetla-name-construct
2102 (xetla-name-read "Base: "
2103 'prompt 'prompt 'prompt 'prompt 'prompt))
2104 (xetla-name-construct
2105 (xetla-name-read "Modified: "
2106 'prompt 'prompt 'prompt 'prompt 'prompt))
2107 (when current-prefix-arg
2110 (when (eq directory 'ask)
2112 (read-directory-name "Stored to: "
2113 (xetla-tree-root default-directory t)
2114 (xetla-tree-root default-directory t)
2118 (when (and directory (stringp directory) (string= directory ""))
2119 (setq directory nil))
2121 (when (and directory (file-directory-p directory))
2122 (error "%s already exists" directory))
2126 (list "delta" base modified directory)
2127 (list "delta" "--diffs" base modified)))
2128 (run-dired-p (when directory 'ask)))
2129 (xetla-run-tla-async args
2131 `(lambda (output error status arguments)
2133 (xetla-delta-show-directory ,directory ',run-dired-p)
2134 (xetla-delta-show-diff-on-buffer
2135 output ,base ,modified))))))
2137 (defun xetla-delta-show-diff-on-buffer (output base modified)
2138 "Show the result of \"delta -diffs\".
2140 OUTPUT is the output buffer of the xetla process.
2141 BASE is the name of the base revision, and MODIFIED is the name of the
2142 modified revision, (then command being run is tla delta BASE
2144 (with-current-buffer output
2146 ;; There were no changes if the last line of
2147 ;; the buffer is "* changeset report"
2149 (goto-char (point-max))
2152 (looking-at "^* changeset report")))
2156 (concat "tla delta finished: "
2157 "No changes in this arch working copy"))
2158 (setq buffer (xetla-prepare-changes-buffer
2160 (xetla-name-split base))
2162 (xetla-name-split modified))
2163 'delta default-directory))
2164 (xetla-show-changes-buffer output nil buffer)
2165 (xetla-switch-to-buffer buffer)
2166 (message "tla delta finished")))))
2168 (defun xetla-delta-show-directory (directory run-dired-p)
2169 "Called by `xetla-delta' to show a changeset in DIRECTORY.
2171 If RUN-DIRED-P is non-nil, run dired in the parent directory of the
2173 (xetla-show-changeset directory nil)
2174 (when (xetla-do-dired (concat (file-name-as-directory directory) "..") run-dired-p)
2176 (goto-char (point-min))
2177 (re-search-forward (concat
2178 (regexp-quote (file-name-nondirectory directory))
2180 (goto-char (match-beginning 0))
2181 (xetla-flash-line)))
2183 ;; (defvar xetla-get-changeset-start-time nil)
2184 ;; (defvar xetla-changeset-cache (make-hash-table :test 'equal)
2185 ;; "The cache for `xetla-get-changeset'.
2186 ;; A hashtable, where the revisions are used as keys.
2187 ;; The value is a list containing the time the cache data was recorded and
2188 ;; the text representation of the changeset.")
2191 (defun xetla-get-changeset (revision justshow &optional destination
2193 "Gets the changeset corresponding to REVISION.
2195 When JUSTSHOW is non-nil (no prefix arg), just show the diff.
2196 Otherwise, store changeset in DESTINATION.
2197 If WITHOUT-DIFF is non-nil, don't use the -diff option to show the
2200 (list (let ((current-version (xetla-tree-version nil t)))
2201 (xetla-name-construct
2202 (apply 'xetla-name-read "Revision to view: "
2204 (append (delete nil (xetla-name-split current-version))
2206 (list 'prompt 'prompt 'prompt 'prompt 'prompt)))))
2207 (not current-prefix-arg)))
2208 (let ((buffer (xetla-get-buffer 'changeset revision)))
2209 (if buffer (save-selected-window (xetla-switch-to-buffer buffer))
2210 (let* ((dest (or destination
2211 (xetla-make-temp-name "xetla-changeset")))
2212 (rev-list (xetla-name-split revision))
2213 (buffer (and justshow
2214 (xetla-prepare-changes-buffer
2215 (list 'previous-revision rev-list)
2216 (list 'revision rev-list)
2217 'changeset revision)))
2218 (xetla-switch-to-buffer-mode
2219 (if xetla-switch-to-changes-buffer
2220 xetla-switch-to-buffer-mode 'show-in-other-window)))
2221 (when (and justshow xetla-switch-to-buffer-first)
2222 (xetla-switch-to-buffer buffer))
2223 ;; (if (gethash revision xetla-changeset-cache)
2225 ;; (message (format "Using changes for revision %S from cache." revision))
2226 ;; (with-current-buffer buffer
2227 ;; (let ((buffer-read-only nil))
2228 ;; (insert (cadr (gethash revision xetla-changeset-cache))))))
2229 ;; (setq xetla-get-changeset-start-time (current-time))
2230 (xetla-run-tla-async
2231 (list "get-changeset" revision dest)
2233 `(lambda (output error status arguments)
2234 ;; (let* ((xetla-run-time (time-to-seconds (time-since xetla-get-changeset-start-time)))
2235 ;; (cache-revision (or (and (numberp xetla-cache-xetla-get-changeset)
2236 ;; (> xetla-run-time xetla-cache-xetla-get-changeset))
2237 ;; (and (not (numberp xetla-cache-xetla-get-changeset))
2238 ;; xetla-cache-xetla-get-changeset)))
2241 (xetla-show-changeset ,dest ,without-diff ,buffer)
2242 ;; (when cache-revision
2243 ;; (message (format "caching result from xetla-get-changeset, xetla-run-time=%S"
2245 ;; (with-current-buffer ,buffer
2246 ;; (puthash ,revision
2247 ;; (list (current-time)
2248 ;; (buffer-substring-no-properties (point-min) (point-max)))
2249 ;; xetla-changeset-cache)))
2250 (call-process "rm" nil nil nil "-rf" ,dest))))))))
2253 (defun xetla-prepare-changes-buffer (base modified type path)
2254 "Create and return a buffer to run \"tla changes\" or equivalent.
2256 Sets the local-variables `xetla-changes-base' and
2257 `xetla-changes-modified' are set according to BASE and MODIFIED.
2259 TYPE and PATH are passed to `xetla-get-buffer-create'."
2260 (with-current-buffer
2261 (xetla-get-buffer-create type path)
2262 (let ((inhibit-read-only t)) (erase-buffer))
2263 (xetla-changes-mode)
2264 (set (make-local-variable 'xetla-changes-base) base)
2265 (set (make-local-variable 'xetla-changes-modified) modified)
2268 (defun xetla-show-changeset (directory &optional without-diff buffer
2270 "Run tla show-changeset on DIRECTORY.
2272 If prefix argument, WITHOUT-DIFF is non-nil, just show the summary.
2273 BUFFER is the target buffer to output. If BUFFER is nil, create a new
2276 BASE and MODIFIED are the name of the base and modified. Their values
2277 will be used for the variables `xetla-changes-base' and
2278 `xetla-changes-modified'."
2279 (interactive (list (let ((changeset-dir (or (xetla-get-file-info-at-point) "")))
2280 (unless (file-directory-p (expand-file-name changeset-dir))
2281 (setq changeset-dir ""))
2282 (xetla-uniquify-file-name
2283 (read-directory-name
2284 "Changeset directory to view: " changeset-dir changeset-dir)))))
2286 (setq buffer (xetla-prepare-changes-buffer base modified
2287 'changeset directory))
2288 (if xetla-switch-to-buffer-first
2289 (xetla-switch-to-buffer buffer)))
2290 (xetla-run-tla-sync (list "show-changeset"
2291 (unless without-diff
2295 `(lambda (output error status arguments)
2296 (xetla-show-changes-buffer output (not ',without-diff)
2298 ,xetla-switch-to-buffer-first)
2299 (xetla-post-switch-to-buffer))))
2301 (defun xetla-show-changeset-from-tgz (file)
2302 "Show the archived changeset from a tar.gz FILE.
2303 Such a changeset can be created via `xetla-changes-save-as-tgz'."
2304 (interactive (list (let ((changeset-tarball (or (xetla-get-file-info-at-point) "")))
2305 (read-file-name "Changeset tarball to view: " nil changeset-tarball t changeset-tarball))))
2306 (let ((temp-dir (xetla-make-temp-name "xetla-changeset-tgz"))
2308 (message "temp-dir: %s" temp-dir)
2309 (call-process "mkdir" nil nil nil temp-dir)
2310 (call-process "tar" nil nil nil "xfz" file "-C" temp-dir)
2311 (setq changeset-dir (car (delete "." (delete ".." (directory-files temp-dir)))))
2312 (xetla-show-changeset (concat (xetla-uniquify-file-name temp-dir) changeset-dir))
2313 (call-process "rm" nil nil nil "-rf" temp-dir)))
2316 (defun xetla-apply-changeset (changeset target &optional reverse)
2317 "Call \"tla apply-changeset\".
2319 CHANGESET is the changeset to apply, TARGET is the directory in which
2320 to apply the changeset. If REVERSE is non-nil, apply the changeset in
2322 (interactive "DChangeset Directory: \nDTarget Directory: \nP")
2323 (if (file-directory-p changeset)
2324 (setq changeset (expand-file-name changeset))
2325 (error "%s is not directory" changeset))
2326 (if (file-directory-p target)
2327 (setq target (expand-file-name target))
2328 (error "%s is not directory" target))
2330 (or (xetla-save-some-buffers target)
2332 "Apply-change may delete unsaved changes. Continue anyway? ")
2333 (error "Not applying"))
2334 (xetla-apply-changeset-internal changeset target reverse)
2335 (when (y-or-n-p (format "Run inventory at `%s'? " target))
2336 (xetla-inventory target)))
2338 (defun xetla-apply-changeset-internal (changeset target reverse)
2339 "Actually call \"tla apply-changeset CHANGESET TARGET\".
2341 If REVERSE is non-nil, use --reverse too."
2342 (xetla-run-tla-sync (list "apply-changeset"
2343 (when reverse "--reverse")
2344 (when xetla-use-forward-option "--forward")
2346 :finished `(lambda (output error status arguments)
2347 ;; (xetla-show-last-process-buffer)
2348 (xetla-show-changes-buffer output)
2349 (message "tla apply-changeset finished")
2350 (xetla-revert-some-buffers ,target))))
2352 (defun xetla-apply-changeset-from-tgz (file tree)
2353 "Apply changeset in FILE to TREE."
2354 (interactive "fApply changeset from tarball: \nDApply to tree: ")
2355 (let ((target (xetla-tree-root tree))
2356 (temp-dir (xetla-make-temp-name "xetla-changeset-tgz"))
2358 (call-process "mkdir" nil nil nil temp-dir)
2359 (call-process "tar" nil nil nil "xfz" (expand-file-name file) "-C" temp-dir)
2360 (setq changeset-dir (concat (xetla-uniquify-file-name temp-dir)
2361 (car (delete "." (delete ".." (directory-files temp-dir))))))
2362 (xetla-show-changeset changeset-dir)
2363 (when (yes-or-no-p "Apply the changeset? ")
2364 (xetla-apply-changeset changeset-dir target))
2365 (call-process "rm" nil nil nil "-rf" temp-dir)))
2369 (defun xetla-file-ediff-revisions (file &optional base modified)
2370 "View changes in FILE between BASE and MODIFIED using ediff."
2371 (interactive (let ((version-list (xetla-tree-version-list)))
2372 (list (buffer-file-name)
2374 (xetla-name-read "Base revision: "
2375 (xetla-name-archive version-list)
2376 (xetla-name-category version-list)
2377 (xetla-name-branch version-list)
2378 (xetla-name-version version-list)
2381 (xetla-name-read "Modified revision: "
2382 (xetla-name-archive version-list)
2383 (xetla-name-category version-list)
2384 (xetla-name-branch version-list)
2385 (xetla-name-version version-list)
2387 (xetla-ediff-buffers
2388 (xetla-file-get-revision-in-buffer file base)
2389 (xetla-file-get-revision-in-buffer file modified)))
2392 (defun xetla-file-diff (file &optional revision)
2393 "Run \"tla file-diff\" on file FILE.
2395 In interactive mode, the file is the current buffer's file.
2396 If REVISION is specified, it must be a string representing a revision
2397 name, and the file will be diffed according to this revision."
2398 (interactive (list (buffer-file-name)))
2400 (xetla-run-tla-async (list "file-diffs" file revision)
2402 (lambda (output error status arguments)
2403 (message "No changes in this arch working copy"))
2405 (lambda (output error status arguments)
2407 (xetla-show-last-process-buffer
2410 (xetla-default-error-function
2411 output error status arguments))))))
2413 (defvar xetla-mine-string "TREE")
2414 (defvar xetla-his-string "MERGE-SOURCE")
2417 (defvar smerge-mode))
2420 (defun xetla-conflicts-finish ()
2421 "Command to delete .rej file after conflicts resolution.
2422 Asks confirmation if the file still has diff3 markers."
2424 (if (and (boundp 'smerge-mode) smerge-mode)
2428 (goto-char (point-min))
2429 (xetla-funcall-if-exists smerge-find-conflict))
2430 (not (y-or-n-p (concat "Buffer still has diff3 markers. "
2431 "Delete .rej file anyway? "))))
2432 (error "Not deleting .rej file"))
2433 (xetla-funcall-if-exists smerge-mode -1))
2434 (when (not (y-or-n-p (concat "Buffer is not in in smerge-mode. "
2435 "Delete .rej file anyway? ")))
2436 (error "Not deleting .rej file")))
2437 (let ((rejfile (concat (buffer-file-name) ".rej")))
2438 (if (file-exists-p rejfile)
2440 (delete-file rejfile)
2441 (message "deleted file %s" rejfile))
2442 (error (format "%s: no such file" rejfile)))))
2445 (defun xetla-view-conflicts (buffer)
2446 "*** WARNING: semi-deprecated function.
2447 Use this function if you like, but M-x smerge-mode RET is actually
2448 better for the same task ****
2450 Graphical view of conflicts after xetla star-merge -three-way. The
2451 buffer given as an argument must be the content of a file with
2452 conflicts markers like.
2458 >>>>>>> MERGE-SOURCE
2460 Priority is given to your file by default. (This means all conflicts
2461 will be rejected if you do nothing)."
2462 (interactive (list (find-file (read-file-name "View conflicts in: "))))
2463 (let ((mine-buffer buffer)
2464 (his-buffer (get-buffer-create "*xetla-his*")))
2465 (with-current-buffer his-buffer
2467 (insert-buffer mine-buffer)
2468 (goto-char (point-min))
2469 (while (re-search-forward (concat "^<<<<<<< "
2470 (regexp-quote xetla-mine-string) "$")
2473 (delete-region (point) (progn
2474 (re-search-forward "^=======\n")))
2477 (regexp-quote xetla-his-string) "$"))
2479 (delete-region (point) (1+ (point-at-eol)))
2482 (with-current-buffer mine-buffer
2483 (goto-char (point-min))
2484 (while (re-search-forward (concat "^<<<<<<< "
2485 (regexp-quote xetla-mine-string) "$")
2488 (delete-region (point) (1+ (point-at-eol)))
2489 (re-search-forward "^=======$")
2491 (delete-region (point) (progn
2494 (regexp-quote xetla-his-string) "\n"))))
2496 (xetla-ediff-buffers mine-buffer his-buffer)
2499 (defun xetla-file-get-revision-in-file (file &optional revision)
2500 "Get the last-committed version of FILE.
2502 If REVISION is non-nil, it must be a cons representing the revision,
2503 and this revision will be used as a reference.
2505 Return (file temporary). temporary is non-nil when the file is
2506 temporary and should be deleted."
2507 (case (car revision)
2508 (local-tree (list file nil))
2509 (previous-revision (xetla-file-get-revision-in-file
2512 (xetla-compute-direct-ancestor
2514 ((last-revision revision)
2515 (let* ((default-directory (if (eq (car revision) 'last-revision)
2517 (xetla-tree-root file)))
2518 (revision (if (eq (car revision) 'revision)
2519 (xetla-name-construct (cadr revision))))
2520 (original (progn (xetla-run-tla-sync
2521 (list "file-find" file revision)
2522 :finished 'xetla-null-handler)
2523 (with-current-buffer xetla-last-process-buffer
2524 (goto-char (point-min))
2525 (re-search-forward "^[^*]")
2526 (buffer-substring-no-properties
2529 (original-to-be-removed nil)
2531 (unless (file-exists-p original)
2532 ;; Probably xetla is ran remotely or whatever. Well, get the
2533 ;; file using the old good tla file-diff | patch -R -o ...
2534 (setq original (xetla-make-temp-name "xetla-ediff")
2535 original-to-be-removed t)
2536 (xetla-run-tla-sync (list "file-diffs" file revision)
2537 :finished 'xetla-null-handler
2539 (lambda (output error status arguments)
2540 (if (not (eq status 1))
2541 (xetla-default-error-function
2542 output error status arguments))))
2543 (with-current-buffer xetla-last-process-buffer
2544 (if (= (point-min) (point-max))
2545 (setq file-unmodified-p t))
2546 (call-process-region (point-min) (point-max)
2547 xetla-patch-executable
2549 "-R" "-o" original file)))
2550 (list original file-unmodified-p original-to-be-removed)))))
2552 (defun xetla-file-revert (file &optional revision)
2553 "Revert the file FILE to the last committed version.
2555 Warning: You use version control to keep backups of your files. This
2556 function will by definition not keep any backup in the archive.
2558 Most of the time, you should not use this function. Call
2559 `xetla-file-ediff' instead, and undo the changes one by one with the key
2560 `b', then save your buffer.
2562 As a last chance, `xetla-file-revert' keeps a backup of the last-saved in
2565 If REVISION is non-nil, it must be a cons representing the revision,
2566 and this revision will be used as a reference."
2567 (interactive (list (progn (when (and (buffer-modified-p)
2568 (or xetla-do-not-prompt-for-save
2569 (y-or-n-p (format "Save buffer %s? "
2571 (current-buffer))))))
2573 (buffer-file-name))))
2574 ;; set aside a backup copy
2575 (copy-file file (car (find-backup-file-name file)) t)
2578 (xetla-run-tla-sync (list "file-diffs" file revision)
2580 (lambda (output error status arguments)
2581 (error "File %s is not modified!" (cadr arguments)))
2583 (lambda (output error status arguments)
2585 (xetla-default-error-function
2586 output error status arguments)
2587 (xetla-show-last-process-buffer
2590 (goto-char (point-min))
2591 (let ((inhibit-read-only t))
2593 (format "M %s\n" (cadr arguments))
2594 "Do you really want to revert ALL the changes listed below?\n")
2595 (if xetla-highlight (font-lock-fontify-buffer)))
2598 (let* ((file-unmo-temp (xetla-file-get-revision-in-file
2600 (list 'revision revision)
2601 (list 'last-revision (xetla-tree-root)))))
2602 (original (car file-unmo-temp)))
2603 (unless (yes-or-no-p (format "Really revert %s? " file))
2605 (error "Not reverting file %s!" file))
2607 (copy-file original file t)
2608 (let ((buf (get-file-buffer file)))
2609 (when buf (with-current-buffer buf (revert-buffer))))))
2611 (defun xetla-undo (tree &optional
2612 archive category branch version revision)
2613 ; checkdoc-params: (archive category branch version revision)
2614 "Undo whole local TREE against ARCHIVE/CATEGORY-BRANCH-VERSION-REVISION.
2615 If ARCHIVE is nil, default ARCHIVE/CATEGORY-BRANCH-VERSION-REVISION
2616 associated with TREE."
2618 (if (and (not current-prefix-arg)
2619 (y-or-n-p "Use default revision to undo? "))
2620 (list default-directory nil nil nil nil nil)
2621 (cons default-directory
2622 (xetla-read-revision-with-default-tree "Undo against archive: "
2623 default-directory))))
2624 (xetla-undo-internal tree archive category branch version revision))
2627 (defun xetla-undo-internal (tree &optional archive category branch version revision)
2628 ; checkdoc-params: (tree archive category branch version revision)
2629 "Internal function used by `xetla-undo'."
2630 (save-excursion (if archive
2631 (xetla-changes nil (xetla-name-construct
2632 archive category branch version revision))
2634 (sit-for 1) ;;xetla-changes should start before the yes-or-no-p query
2637 (format "Revert whole local tree (%s) from `%s'? "
2638 tree (xetla-name-construct
2639 archive category branch version revision))
2640 (format "Revert whole local tree (%s) from default revision? " tree)))
2641 (let ((default-directory tree))
2642 (xetla-run-tla-sync (if archive
2643 (list "undo" (xetla-name-construct
2644 archive category branch version revision))
2646 ;; TODO in case of files violating the naming
2647 ;; conventions we could offer to delete them or
2648 ;; switch to inventory-mode and do it there,
2649 ;; basically saying YES should delete them and
2650 ;; perform the undo operation again
2652 (xetla-revert-some-buffers tree)))
2654 (defun xetla-get-undo-changeset-names ()
2655 "Get the list of directories starting with \",,undo-\".
2657 This is used by xetla-redo to get the list of candidates for an undo
2660 (directory-files (xetla-tree-root default-directory t) t ",,undo-"))
2662 (defun xetla-select-changeset (dir-list)
2663 "Select a changeset.
2665 DIR-LIST is intended to be the result of
2666 `xetla-get-undo-changeset-names'."
2667 (completing-read "Select changeset: " (mapcar 'list dir-list) nil nil (car dir-list)))
2670 (defun xetla-redo (&optional target)
2672 If TARGET directroy is given, TARGET should hold undo data generated by `xetla undo'."
2674 (let* ((undo-changesets (xetla-get-undo-changeset-names))
2675 (undo-changeset (or target
2676 (when (= (length undo-changesets) 1) (car undo-changesets))
2677 (xetla-select-changeset undo-changesets))))
2678 (xetla-show-changeset undo-changeset)
2679 (when (yes-or-no-p (format "Redo the %s changeset? " undo-changeset))
2680 (xetla-run-tla-sync (list "redo" undo-changeset)))))
2684 (defun xetla-file-ediff (file &optional revision)
2685 "Interactive view of differences in FILE with ediff.
2687 Changes are computed since last commit (or REVISION if specified)."
2688 (interactive (list (progn (when (and (buffer-modified-p)
2689 (y-or-n-p (format "Save buffer %s? "
2691 (current-buffer)))))
2693 (buffer-file-name))))
2694 (let ((original (xetla-file-get-revision-in-buffer
2695 file (or revision (list 'last-revision
2696 (xetla-tree-root))))))
2697 (when (string= (with-current-buffer original (buffer-string))
2699 (error "No modification in this file"))
2700 (xetla-ediff-buffers (or (get-file-buffer file)
2701 (find-file-noselect file))
2705 (defun xetla-file-view-original (file &optional revision)
2706 "Get the last-committed version of FILE in a buffer.
2708 If REVISION is specified, it must be a cons representing the revision
2709 for which to get the original."
2710 (interactive (list (buffer-file-name)))
2711 (let ((original (xetla-file-get-revision-in-buffer
2712 file (or revision (list 'last-revision
2713 (xetla-tree-root))))))
2714 (when (string= (with-current-buffer original (buffer-string))
2716 (message "No modification in this file"))
2717 (xetla-switch-to-buffer original)))
2719 (defun xetla-buffer-for-rev (file revision)
2720 "Return an empty buffer suitable for viewing FILE in REVISION.
2722 The name of the buffer is chosen according to FILE and REVISION.
2724 REVISION may have one of the values described in the docstring of
2725 `xetla-changes-modified' or `xetla-changes-base'."
2727 (file-name-nondirectory file)
2729 ((eq (car revision) 'revision)
2730 (xetla-name-construct (cadr revision)))
2731 ((eq (car revision) 'local-tree)
2733 ((eq (car revision) 'last-revision) "original")
2734 ((eq (car revision) 'previous-revision)
2735 (xetla-name-construct-semi-qualified
2736 (xetla-compute-direct-ancestor (cadr revision))))
2740 (create-file-buffer name))))
2742 (defun xetla-file-get-revision-in-buffer (file &optional revision)
2743 "Get the last committed version of FILE in a buffer.
2745 Returned value is the buffer.
2747 REVISION can have any of the values described in the docstring of
2748 `xetla-changes-base' and `xetla-changes-modified'"
2749 (let* ((default-directory (xetla-tree-root))
2750 (file-unmo-temp (xetla-file-get-revision-in-file file revision))
2751 (original (car file-unmo-temp))
2752 (original-to-be-removed (cadr file-unmo-temp)))
2753 (if (eq (car revision) 'local-tree)
2754 (find-file-noselect original)
2755 (let ((buffer-orig (xetla-buffer-for-rev file revision)))
2756 (with-current-buffer buffer-orig
2758 (insert-file-contents original)
2759 (when original-to-be-removed
2760 (delete-file original)))
2763 (defun xetla-ediff-startup-hook ()
2764 "Passed as a startup hook for ediff.
2766 Programs ediff to return to the current window configuration after
2768 ;; ediff-after-quit-hook-internal is local to an ediff session.
2769 (add-hook 'ediff-after-quit-hook-internal
2771 (set-window-configuration
2772 ,xetla-window-config))
2775 (defun xetla-commit-check-empty-line ()
2776 "Check that the headers are followed by an empty line.
2778 Current buffer must be a log buffer. This function checks it starts
2779 with RFC822-like headers, followed by an empty line"
2781 (goto-char (point-min))
2782 (while (not (looking-at "^$"))
2783 (unless (looking-at "^[A-Za-z0-9_-]*:")
2784 (error "A blank line must follow the last header field"))
2786 ;; space and tabs are continuation line.
2787 (while (looking-at "[ \t]+")
2790 (defun xetla-commit-check-empty-headers ()
2791 "Check that the current buffer starts with non-empty headers.
2793 Also checks that the the line following headers is empty (or the
2794 notion of \"header\" would loose its meaning)."
2796 (goto-char (point-min))
2797 (while (not (looking-at "^$"))
2798 (unless (looking-at "^[A-Za-z0-9_-]*:")
2799 (error "A blank line must follow the last header field"))
2800 (when (looking-at "^\\([A-Za-z0-9_-]*\\):[ \t]*$")
2801 (let ((header (match-string 1)))
2802 (unless (string-match xetla-commit-headers-allowed-to-be-empty
2805 (when (eq (char-before) ?:) (insert " "))
2806 (error (format "Empty \"%s: \" header" header)))))
2808 ;; space and tabs are continuation line.
2809 (while (looking-at "[ \t]+")
2812 (defun xetla-commit-check-missing-space ()
2813 "Check the space after the colon in each header:
2815 Check that no header in the summary buffer miss the SPC character
2816 following the semicolon. Also checks that the the line following
2817 headers is empty (or the notion of \"header\" would loose its
2820 (goto-char (point-min))
2821 (let ((stg-changed))
2822 (while (not (looking-at "^$"))
2823 (unless (looking-at "^[A-Za-z0-9_-]*:")
2824 (error "A blank line must follow the last header field"))
2825 (when (looking-at "^\\([A-Za-z0-9_-]*\\):[^ ]")
2826 (let ((header (match-string 1)))
2827 (if xetla-commit-fix-missing-space
2829 (setq stg-changed t)
2830 (search-forward ":")
2832 (error (format "Missing space after colon for \"%s:\""
2835 ;; space and tabs are continuation line.
2836 (while (looking-at "[ \t]+")
2841 (defun xetla-commit-check-log-buffer ()
2842 "Function to call from the ++log... buffer, before comitting.
2844 \(`xetla-commit' calls it automatically). This runs the tests listed in
2845 `xetla-commit-check-log-buffer-functions'. Each function is called with
2846 no argument and can raise an error in case the log buffer isn't
2847 correctly filled in."
2848 (dolist (function xetla-commit-check-log-buffer-functions)
2849 (funcall function)))
2851 (defcustom xetla-warn-about-conflict-files t
2852 "*When non-`nil' ask whether to commit if conflict files are present.
2853 When `nil' commit anyway."
2856 (defun xetla-commit-find-conflict-files (dir)
2857 "Searches for conflict files in the current working directory."
2858 (when (file-readable-p dir)
2859 (let* ((dirs (directory-files dir t "^[^.]+$" nil 'subdirs))
2860 (cur (directory-files dir t "\\.rej"))
2861 (sub (mapcar #'xetla-commit-find-conflict-files dirs)))
2862 (setq cur (delete nil cur))
2863 (setq sub (delete nil sub))
2868 (defun xetla-commit-seal (&optional force)
2869 "Commit a `version-0' revision to seal a repo.
2871 This calls `tla commit --seal'. With optional argument FORCE, don't
2872 prompt for confirmation."
2876 (format "Do you really want to seal `%s'? "
2877 (xetla-tree-version))))
2878 (if (string-match "--version\\(-\\|fix-\\)+"
2879 (xetla-get-current-revision default-directory))
2880 (error "Revision already sealed, use `xetla-commit-fix' instead")
2882 (lambda (output error status args)
2883 (xetla-tips-popup-maybe))
2886 (defun xetla-commit-fix (&optional force)
2887 "Commit a `versionfix' revision.
2889 This calls `tla commit --fix'. With optional argument FORCE, don't
2890 prompt for confirmation."
2894 (format "Do you really want to versionfix `%s'? "
2895 (xetla-tree-version))))
2896 (if (not (string-match "--version\\(-\\|fix-\\)+"
2897 (xetla-get-current-revision default-directory)))
2898 (error "Revision not sealed")
2900 (lambda (output error status args)
2901 (xetla-tips-popup-maybe))
2905 (defun xetla-commit (&optional handler version-flag)
2908 Optional argument HANDLER is the process handler for the commit
2911 Optional argument VERSION-FLAG may be one of the symbols
2912 'seal to commit a sealed version
2913 'fix to commit a fix version
2914 If omitted it defaults to a normal commit.
2916 When the commit finishes successful, `tla-commit-done-hook' is called."
2918 (with-current-buffer
2919 (find-file-noselect (xetla-make-log))
2921 (xetla-commit-check-log-buffer)
2922 (error (progn (switch-to-buffer (current-buffer))
2924 (or (xetla-save-some-buffers)
2926 "Commit with unsaved changes is a bad idea. Continue anyway? ")
2927 (error "Not committing"))
2928 (and xetla-warn-about-conflict-files
2929 ;; fsck it, actually we dont need all the .rej files (yet), so
2930 ;; speed up would be to unwind the recursion after the first
2931 ;; occurrence of a .rej
2932 (xetla-commit-find-conflict-files ".")
2935 "Commit with unresolved conflicts is a bad idea. "
2936 "Continue anyway? "))
2937 (error "Not committing")))
2938 (let* ((file-list (and (buffer-live-p xetla-buffer-source-buffer)
2939 (with-current-buffer xetla-buffer-source-buffer
2940 xetla-buffer-marked-file-list)))
2942 (when file-list (setq arglist (append arglist (cons "--"
2944 ;; raises an error if commit isn't possible
2945 (xetla-run-tla-async
2947 (cons (when xetla-strict-commits "--strict")
2949 ((eq version-flag 'fix) "--fix")
2950 ((eq version-flag 'seal) "--seal")
2951 ((eq version-flag nil) nil)
2952 (t (error "Wrong version flag: %s" version-flag)))
2953 (when file-list (cons "--"
2955 :finished handler))))
2957 (defun xetla-import ()
2960 (with-current-buffer
2961 (find-file-noselect (xetla-make-log)))
2962 (xetla-run-tla-sync (list "import")
2963 :finished 'xetla-null-handler))
2967 (defun xetla-rm (file)
2968 "Call tla rm on file FILE. Prompts for confirmation before."
2970 (when (yes-or-no-p (format "Delete file %s? " file))
2971 (xetla-run-tla-sync (list "rm" file)
2972 :finished 'xetla-null-handler)))
2974 (defun xetla-pristines ()
2975 "Run \"tla pristine\"."
2977 (xetla-run-tla-sync '("pristines")))
2980 (defun xetla-changelog (&optional version)
2981 "Run \"tla changelog\".
2983 Display the result in an improved ChangeLog mode.
2984 With prefix arg, VERSION, display that version's changelog."
2986 (let ((default-directory (xetla-read-project-tree-maybe))
2987 (version (when current-prefix-arg
2988 (xetla-name-construct
2989 (xetla-name-read "ChangeLog for version: "
2990 'prompt 'prompt 'prompt 'prompt)))))
2991 (xetla-run-tla-sync (list "changelog" version)
2992 :finished 'xetla-null-handler)
2993 (xetla-show-last-process-buffer 'changelog 'xetla-changelog-mode)
2994 (goto-char (point-min))
2995 (view-mode nil (lambda (&rest args)
2996 (xetla-buffer-quit)))))
2999 (defun xetla-logs ()
3002 (let ((default-directory (xetla-read-project-tree-maybe))
3003 ; (details (or xetla-revisions-shows-date
3004 ; xetla-revisions-shows-creator
3005 ; xetla-revisions-shows-summary))
3007 (xetla-run-tla-async
3008 (list "logs" "--full"
3009 ; (when details "-date")
3010 ; (when details "-creator")
3011 ; (when details "-summary"))
3014 `(lambda (output error status arguments)
3015 (let ((buffer (xetla-get-buffer-create 'logs (xetla-tree-root))))
3016 (xetla-switch-to-buffer buffer)
3017 (xetla-revision-list-mode)
3018 (xetla-revisions-parse-list 'logs nil ;;,details
3019 nil ;; TODO (merges)
3021 xetla-revision-list-cookie)
3022 (set (make-local-variable 'xetla-buffer-refresh-function)
3025 (defun xetla-help-via-keyb ()
3027 (let ((ext (extent-string (extent-at (point)))))
3030 (defun xetla-help-via-mouse (event)
3032 (goto-char (event-point event))
3033 (let ((ext (extent-string (extent-at (point)))))
3036 (defconst xetla-help-extent-map
3037 (let* ((map (make-sparse-keymap 'xetla-help-extent-map)))
3038 (define-key map [button2] 'xetla-help-via-mouse)
3039 (define-key map [return] 'xetla-help-via-keyb)
3041 "A keymap for the extents in output from `tla help'.")
3043 (defun xetla-display-global-help (buffer &rest args)
3044 (switch-to-buffer buffer)
3045 (xetla-process-buffer-mode)
3046 (goto-char (point-min))
3048 (while (re-search-forward "\\(\\w+.*\\) : " nil t)
3049 (let ((extent (make-extent (match-beginning 1) (match-end 1)))
3050 (echo "RET or button2 for help on this command."))
3051 (set-extent-property extent 'face 'widget-button-face)
3052 (set-extent-property extent 'mouse-face 'highlight)
3053 (set-extent-property extent 'keymap xetla-help-extent-map)
3054 (set-extent-property extent 'help-echo echo)
3055 (set-extent-property extent 'balloon-help echo)
3056 (set-extent-property extent 'duplicable t)))))
3059 (defun xetla-help (command)
3060 "Run tla COMMAND -H."
3062 (list (completing-read
3067 `(lambda (output error status arguments)
3068 (with-current-buffer output
3069 (goto-char (point-min))
3071 (while (re-search-forward
3072 " *\\([^ ]*\\) : " nil t)
3074 (cons (list (match-string 1))
3077 (if (string= command "")
3080 :finished 'xetla-display-global-help)
3081 (xetla-run-tla-sync (list command "-H"))))
3083 (defun xetla-tree-version-list-tla ()
3084 "Return the tree version, or nil if not in a project tree."
3085 (xetla-run-tla-sync '("tree-version")
3087 (lambda (output error status arguments)
3088 (with-current-buffer output
3090 (goto-char (point-min))
3091 (re-search-forward "\\(.*\\)/\\(.*\\)--\\(.*\\)--\\(.*\\)" nil t)
3092 (list (match-string 1)
3095 (match-string 4)))))))
3097 (defun xetla-tree-version-list (&optional location no-error)
3098 "Elisp implementation of `xetla-tree-version-list-tla'.
3100 A string, LOCATION is used as a directory where
3101 \"/{arch}/++default-version\" is. If NO-ERROR is non-nil, errors are
3102 not reported; just return nil."
3103 (let ((version-string (xetla-tree-version location no-error)))
3105 (string-match "\\(.*\\)/\\(.*\\)--\\(.*\\)--\\(.*\\)" version-string)
3106 (list (match-string 1 version-string)
3107 (match-string 2 version-string)
3108 (match-string 3 version-string)
3109 (match-string 4 version-string)))))
3111 (defun xetla-tree-root-xetla ()
3112 "Run tla tree-root."
3114 (xetla-run-tla-sync '("tree-root")
3116 `(lambda (output error status arguments)
3117 (let ((result (xetla-buffer-content output)))
3118 (when ,(interactive-p)
3119 (message "tla tree-root is: %s"
3124 (defun xetla-tree-version (&optional location no-error)
3125 "Equivalent of xetla tree-version (but implemented in pure elisp).
3127 Optional argument LOCATION is the directory in which the command must
3128 be ran. If NO-ERROR is non-nil, don't raise errors if ran outside an
3130 (interactive (list nil nil))
3131 (let* ((tree-root (xetla-tree-root location no-error))
3132 (default-version-file (when tree-root
3134 "{arch}/++default-version"
3136 (version (and (boundp 'xetla-buffer-version-name)
3137 xetla-buffer-version-name)))
3138 (if (and (null version)
3139 default-version-file
3140 (file-readable-p default-version-file))
3142 (insert-file-contents default-version-file)
3143 (setq version (buffer-substring-no-properties
3145 (if (eq (char-before (point-max)) ?\n)
3148 (when (interactive-p)
3149 (message "%s" version))
3153 (defun xetla-my-id (&optional arg my-id)
3156 When called without a prefix argument ARG, just print the my-id from
3157 xetla and return it. If MY-ID is not set yet, return an empty string.
3158 When called with a prefix argument, ask for a new my-id.
3160 The my-id should have the following format:
3162 Your id is recorded in various archives and log messages as you use
3163 arch. It must consist entirely of printable characters and fit on one
3164 line. By convention, it should have the form of an email address, as
3167 Jane Hacker <jane.hacker@email.address>"
3169 (let ((id (xetla-run-tla-sync '("my-id")
3171 (lambda (output error status arguments)
3172 (xetla-buffer-content output))
3174 (lambda (output error status arguments)
3177 ;; Set the user's ID
3178 (let ((new-id (or my-id
3179 (read-string "New arch my-id: "
3180 id xetla-my-id-history id))))
3181 (if (string= id new-id)
3182 (message "Id unchanged! Id = %s" new-id)
3183 (message "Setting id to: %s" new-id)
3184 (xetla-run-tla-sync (list "my-id" new-id)
3185 :finished (lambda (output error status arguments)
3186 (message "Id changed"))
3188 (lambda (output error status arguments)
3189 (message "Could not change Id")
3190 (xetla-show-error-buffer error)
3193 (cond (id (when (interactive-p)
3194 (message "Arch my-id: %s" id))
3196 (t (when (interactive-p)
3197 (message (concat "Arch my-id has not been given yet. "
3198 "Call `%s' to set.")
3202 (defun xetla-set-my-id ()
3203 "Set xetla's my-id."
3212 (defun xetla-my-revision-library (&optional arg)
3213 "Run tla my-revision-library.
3215 When called without a prefix argument ARG, just print the
3216 my-revision-library from xetla. When called with a prefix argument, ask
3217 for a new my-revision-library.
3219 my-revision-library specifies a path, where the revision library is
3220 stored to speed up tla. For example ~/tmp/arch-lib.
3222 You can configure the parameters for the library via
3223 `xetla-library-config'."
3225 (let ((result (xetla-run-tla-sync '("my-revision-library")
3226 :finished 'xetla-status-handler
3227 :error 'xetla-null-handler))
3228 (rev-lib (xetla-get-process-output)))
3231 (xetla-library-add-interactive rev-lib)
3232 (if (and rev-lib (string= "" rev-lib))
3233 (message "Arch my-revision-library has not been given yet. Call `%s' with prefix arguments to set."
3235 (when (interactive-p) (message "Arch my-revision-library: %s" rev-lib)))
3238 (defun xetla-library-add-interactive (&optional old-rev-lib)
3239 "Prompts for argument and run `xetla-library-add'.
3241 Argument OLD-REV-LIB is the previously set revision library (a
3243 (unless old-rev-lib (setq old-rev-lib ""))
3244 (let ((new-rev-lib (expand-file-name (read-directory-name
3245 "New arch revision library: " old-rev-lib))))
3246 (if (not (string= old-rev-lib new-rev-lib))
3248 (message "Setting my-revision-library to: %s" new-rev-lib)
3249 (xetla-library-add-internal new-rev-lib))
3252 (defun xetla-library-delete (rev-lib)
3253 "Unregister revision library REV-LIB."
3254 (interactive (list (xetla-read-revision-library)))
3255 (xetla-run-tla-sync (list "my-revision-library" "--delete" rev-lib)
3256 :finished (lambda (output error status arguments)
3257 (message "Library %s removed."
3260 (defun xetla-library-add-internal (new-rev-lib)
3261 "Change the revision library path to NEW-REV-LIB."
3262 (let ((dir-attr (file-attributes new-rev-lib)))
3264 (make-directory new-rev-lib t))
3265 (xetla-run-tla-sync (list "my-revision-library" new-rev-lib)
3267 (lambda (output error status arguments)
3268 (message (xetla-buffer-content output))))
3271 (defun xetla-revision-library-list ()
3272 "Parse `xetla my-revision-library' into a list of revision libraries."
3273 (xetla-run-tla-sync '("my-revision-library")
3275 'xetla-output-buffer-split-handler))
3277 (defvar xetla-library-history nil)
3279 (defun xetla-read-revision-library (&optional prompt)
3280 "Read a revision library from keyboard.
3281 Prompt the user with PROMPT if given."
3282 (let ((list-lib (xetla-revision-library-list)))
3283 (if (null (cdr list-lib))
3285 (completing-read (or prompt
3286 (format "Revision library (default %s): "
3288 (mapcar 'list (xetla-revision-library-list))
3289 nil t nil xetla-library-history
3292 (defun xetla-library-config (&optional arg)
3293 "Run tla library-config.
3294 When called without prefix argument ARG, just print the config.
3295 When called with prefix argument ARG, let the user change the config."
3297 (let ((rev-lib (xetla-read-revision-library))
3298 (config-param (when arg
3299 (completing-read "tla library config "
3300 (mapcar 'list '("--greedy"
3305 (xetla-run-tla-sync (list "library-config" config-param rev-lib)
3306 :finished 'xetla-null-handler)
3307 (message (xetla-get-process-output))))
3309 (defun xetla-library-add (archive category branch version revision)
3310 "Add ARCHIVE-CATEGORY-BRANCH-VERSION-REVISION to the revision library."
3311 (xetla-show-last-process-buffer)
3312 (xetla-run-tla-async `("library-add"
3313 ,(xetla-name-construct archive category
3317 (defun xetla-library-find (archive category branch version revision
3319 "Find ARCHIVE-CATEGORY-BRANCH-VERSION-REVISION in the revision library.
3320 If the revision is found, return the path for it. Else return nil."
3321 (if (zerop (xetla-run-tla-sync (list "library-find" (when silent "--silent")
3322 (xetla-name-construct
3323 archive category branch
3325 :finished 'xetla-status-handler
3326 :error 'xetla-status-handler))
3327 (xetla-get-process-output)))
3329 ;; completing-read: tagline, explicit, names, implicit
3330 (defvar xetla-id-tagging-method-history nil)
3332 (defun xetla-id-tagging-method (arg)
3333 "View (and return) or change the id-tagging method.
3334 When called without prefix argument ARG: show the actual tagging method.
3335 When called with prefix argument ARG: Ask the user for the new tagging method."
3337 (let ((tm (progn (xetla-run-tla-sync '("id-tagging-method")
3339 (lambda (output error status arguments)
3340 (xetla-buffer-content output)))))
3341 (new-tagging-method))
3344 (setq new-tagging-method
3345 (xetla-id-tagging-method-read tm))
3346 (when (not (string= tm new-tagging-method))
3347 (xetla-id-tagging-method-set new-tagging-method)))
3348 (when (interactive-p)
3349 (message "Arch id tagging method: %s" tm))
3353 (defun xetla-id-tagging-method-read (old-method)
3354 "Read id tagging method.
3355 If OLD-METHOD is given, use it as the default method."
3358 (format "New id tagging method (default %s): " old-method)
3359 "New id tagging method: ")
3360 (mapcar 'list '("tagline" "explicit" "names" "implicit"))
3362 xetla-id-tagging-method-history
3365 (defun xetla-id-tagging-method-set (method)
3366 "Set the tagging method to METHOD."
3367 (message "Setting tagging method to: %s" method)
3368 (xetla-run-tla-sync (list "id-tagging-method"
3370 :finished 'xetla-null-handler))
3372 (defun xetla-archive-mirror (archive &optional category branch version from)
3373 "Synchronize the mirror for ARCHIVE.
3374 Limit to CATEGORY-BRANCH-VERSION. If FROM is provided, mirror from it."
3375 (interactive (xetla-name-read nil 'prompt))
3376 (let ((name (xetla-name-construct-semi-qualified category branch version)))
3377 (when (string= name "") (setq name nil))
3378 (xetla-run-tla-async (list "archive-mirror"
3382 :finished `(lambda (output error status arguments)
3383 (message "tla archive-mirror finished"))
3386 (defun xetla-archive-fixup (archive)
3387 "Run tla archive-fixup for ARCHIVE."
3388 (interactive (list (car (xetla-name-read "Archive to fixup: " 'prompt))))
3389 (xetla-run-tla-async (list "archive-fixup" "-A" archive)
3390 :finished `(lambda (output error status arguments)
3391 (message "tla archive-fixup %s finished" ,archive))
3395 (defun xetla-star-merge (from &optional to-tree)
3396 "Star merge from version/revision FROM to local tree TO-TREE."
3397 (interactive (list (xetla-name-construct
3398 (xetla-name-read "Star merge from (version or revision): "
3399 'prompt 'prompt 'prompt 'prompt 'maybe))
3400 (read-directory-name "In tree: ")))
3401 (let ((to-tree (when to-tree (expand-file-name to-tree))))
3402 (or (xetla-save-some-buffers (or to-tree default-directory))
3404 "Star-merge may delete unsaved changes. Continue anyway? ")
3405 (error "Not running star-merge"))
3406 (let* ((default-directory (or to-tree default-directory))
3408 (buffer (xetla-prepare-changes-buffer
3409 (list 'last-revision default-directory)
3410 (list 'local-tree default-directory)
3411 ;; TODO using xetla-changes here makes it simpler.
3412 ;; The user can just type `g' and get the real
3413 ;; changes. Maybe a 'star-merge would be better
3415 'changes default-directory)))
3416 (when xetla-switch-to-buffer-first
3417 (xetla-switch-to-buffer buffer))
3418 (when xetla-three-way-merge (add-to-list 'arglist "--three-way"))
3419 (when xetla-use-forward-option (add-to-list 'arglist "--forward"))
3420 (xetla-run-tla-async `("star-merge" ,@arglist ,from)
3421 :finished `(lambda (output error status arguments)
3422 ;; (xetla-show-last-process-buffer)
3423 (xetla-show-changes-buffer
3425 (message "tla star-merge finished")
3426 (xetla-revert-some-buffers ,to-tree))
3427 :error `(lambda (output error status arguments)
3429 ;; 2 stands for an error.
3430 (2 (xetla-default-error-function
3431 output error status arguments))
3432 ;; How about other status?
3433 (otherwise (xetla-show-changes-buffer output)
3434 output nil ,buffer)))))))
3436 (defun xetla-replay-arguments ()
3437 "Build an argument list for the replay command.
3438 Used to factorize the code of (interactive ...) between `xetla-replay-reverse'
3439 and `xetla-replay'."
3440 (list (xetla-name-construct
3441 (xetla-name-read "Relay version or revision: "
3442 'prompt 'prompt 'prompt 'prompt 'maybe))
3443 (read-directory-name "Replay in tree: ")
3444 current-prefix-arg))
3446 (defun xetla-replay-reverse (from &optional to-tree arg)
3447 "Call `xetla-replay' with the REVERSE option."
3448 (interactive (xetla-replay-arguments))
3449 (xetla-replay from to-tree arg t))
3452 (defun xetla-replay (from &optional to-tree arg reverse)
3453 "Replay the revision FROM into tree TO-TREE.
3454 If FROM is a string, it should be a fully qualified revision.
3455 If FROM is a list, it should be a list of fully qualified revisions to
3458 If ARG is non-nil, replay all the version instead of the revision only.
3459 If REVERSE is non-nil, reverse the requested revision."
3460 (interactive (xetla-replay-arguments))
3461 (let ((default-directory (or to-tree default-directory)))
3462 (or (xetla-save-some-buffers)
3464 "Replay may delete unsaved changes. Continue anyway? ")
3465 (error "Not replaying"))
3466 (xetla-show-last-process-buffer)
3467 (let ((buffer (xetla-prepare-changes-buffer
3468 (list 'last-revision default-directory)
3469 (list 'local-tree default-directory)
3470 'changes default-directory)))
3471 (when xetla-switch-to-buffer-first
3472 (xetla-switch-to-buffer buffer))
3473 (xetla-run-tla-async `("replay"
3474 ,(when xetla-use-forward-option "--forward")
3475 ,(when reverse "--reverse")
3476 ,(when xetla-use-skip-present-option "--skip-present")
3480 :finished `(lambda (output error status arguments)
3481 (xetla-show-changes-buffer output
3483 (message "tla replay finished")
3484 (xetla-revert-some-buffers ,to-tree))
3485 :error (lambda (output error status arguments)
3486 (xetla-show-error-buffer error)
3487 (xetla-show-last-process-buffer))))))
3489 (defun xetla-sync-tree (from &optional to-tree)
3490 "Synchronize the patch logs of revision FROM and tree TO-TREE."
3492 (xetla-name-construct
3493 (xetla-name-read "Sync tree with revision: "
3494 'prompt 'prompt 'prompt 'prompt 'prompt))
3495 (read-directory-name "Sync tree: ")))
3496 (let ((default-directory (or to-tree default-directory)))
3497 (or (xetla-save-some-buffers)
3499 "Update may delete unsaved changes. Continue anyway? ")
3500 (error "Not updating"))
3501 (xetla-show-last-process-buffer)
3502 (xetla-run-tla-async `("sync-tree" ,from)
3503 :finished `(lambda (output error status arguments)
3504 (xetla-show-last-process-buffer)
3505 (message "tla sync-tree finished")
3506 (xetla-revert-some-buffers ,to-tree))
3507 :error (lambda (output error status arguments)
3508 (xetla-show-changes-buffer output)))))
3510 (defun xetla-tag (source-revision tag-version)
3511 "Create a tag from SOURCE-REVISION to TAG-VERSION.
3512 Run tla tag --setup."
3514 (list (xetla-name-construct
3515 (xetla-name-read "Source revision (or version): " 'prompt 'prompt 'prompt
3517 (xetla-name-construct
3518 (xetla-name-read "Tag version: " 'prompt 'prompt 'prompt
3520 (xetla-run-tla-async (list "tag" "--setup"
3521 source-revision tag-version)))
3523 (defun xetla-set-tree-version (version)
3524 "Run tla set-tree-version VERSION."
3525 (interactive (list (xetla-name-read "Set tree version to: "
3526 'prompt 'prompt 'prompt 'prompt)))
3528 (let ((new-version (xetla-name-construct version))
3529 (old-version (xetla-tree-version)))
3530 (when (y-or-n-p (format "Switch tree version from `%s' to `%s'? "
3533 (xetla-run-tla-sync (list "set-tree-version" new-version)))))
3535 ;; --------------------------------------
3537 ;; --------------------------------------
3539 (make-face 'xetla-bookmark-name
3540 "Face used for bookmark names.")
3541 (set-face-foreground 'xetla-bookmark-name "magenta")
3543 (defvar xetla-bookmarks-loaded nil
3544 "Whether `xetla-bookmarks' have been loaded from file.")
3546 (defvar xetla-bookmarks-alist nil
3547 "Alist containing Xetla bookmarks.")
3549 (defvar xetla-bookmarks-show-details nil
3550 "Whether `xetla-bookmarks' should show bookmark details.")
3552 (defvar xetla-bookmarks-cookie nil
3555 (defvar xetla-missing-buffer-todolist nil
3556 "List of (kind info).
3559 \(separator \"label\" bookmark \"local-tree\")
3560 \(changes \"local-tree\")
3561 \(missing \"local-tree\" \"location\" \"bookmark-name\")")
3563 (defvar xetla-bookmarks-marked-list nil
3564 "A list of marked bookmarks.")
3566 (defun xetla-bookmarks-load-from-file (&optional force)
3567 "Load bookmarks from the file `xetla-bookmarks-file-name'.
3569 If FORCE is non-nil, reload the file even if it was loaded before."
3570 (when (or force (not xetla-bookmarks-loaded))
3571 (xetla-load-state (xetla-config-file-full-path
3572 xetla-bookmarks-file-name t))
3573 (setq xetla-bookmarks-loaded t)))
3575 (defun xetla-bookmarks-save-to-file ()
3576 "Save `xetla-bookmarks-alist' to the file `xetla-bookmarks-file-name'."
3577 (xetla-save-state '(xetla-bookmarks-alist)
3578 (xetla-config-file-full-path xetla-bookmarks-file-name t)
3581 (defun xetla-bookmarks-toggle-details (&optional val)
3582 "Toggle the display of bookmark details.
3583 If VAL is positive, enable bookmark details.
3584 If VAL is negative, disable bookmark details."
3586 (let ((current-bookmark (ewoc-locate xetla-bookmarks-cookie)))
3587 (setq xetla-bookmarks-show-details
3591 (not xetla-bookmarks-show-details)))
3592 (not xetla-bookmarks-show-details)))
3593 (ewoc-refresh xetla-bookmarks-cookie)
3594 (xetla-bookmarks-cursor-goto current-bookmark)))
3596 (defvar xetla-bookmarks-align 19
3597 "Position, in chars, of the `:' when displaying the bookmarks buffer.")
3599 (defun xetla-bookmarks-printer (element)
3600 "Pretty print ELEMENT, an entry of the bookmark list.
3601 This is invoked by ewoc when displaying the bookmark list."
3602 (insert (if (member element xetla-bookmarks-marked-list)
3603 (concat " " xetla-mark " ") " "))
3604 (xetla-insert-right-justified (concat (car element) ": ")
3605 (- xetla-bookmarks-align 3)
3606 'xetla-bookmark-name)
3607 (insert (xetla-face-add (xetla-name-construct
3608 (cdr (assoc 'location (cdr element))))
3609 'xetla-revision-name
3610 'xetla-bookmarks-entry-map
3611 xetla-bookmarks-entry-menu
3613 (when xetla-bookmarks-show-details
3615 (insert-char ?\ xetla-bookmarks-align)
3616 (insert (cdr (assoc 'timestamp (cdr element))))
3618 (let ((notes (assoc 'notes (cdr element))))
3620 (insert-char ?\ xetla-bookmarks-align)
3621 (insert (cdr notes))
3623 (let ((nickname (assoc 'nickname (cdr element))))
3625 (xetla-insert-right-justified "nickname: " xetla-bookmarks-align)
3626 (insert (cadr nickname))
3628 (let ((partners (assoc 'partners (cdr element))))
3630 (xetla-insert-right-justified "partners: " xetla-bookmarks-align)
3631 (insert (cadr partners))
3632 (dolist (x (cddr partners))
3634 (insert-char ?\ xetla-bookmarks-align)
3637 (let ((local-tree (assoc 'local-tree (cdr element))))
3639 (xetla-insert-right-justified "local trees: " xetla-bookmarks-align)
3640 (insert (cadr local-tree))
3641 (dolist (x (cddr local-tree))
3644 (let ((groups (assoc 'groups (cdr element))))
3646 (xetla-insert-right-justified "Groups: " xetla-bookmarks-align)
3647 (insert (cadr groups))
3648 (dolist (x (cddr groups))
3651 (let ((summary-format (assoc 'summary-format (cdr element))))
3652 (when summary-format
3653 (xetla-insert-right-justified "Summary format: " xetla-bookmarks-align)
3654 (insert "\"" (cadr summary-format) "\"")
3657 (defvar xetla-revision-list-cookie nil
3658 "Ewoc cookie for xetla-bookmark-missing.")
3660 (defun xetla-bookmarks-read-local-tree (&optional bookmark arg)
3661 "Read a local tree for BOOKMARK, and possibly add it to the bookmarks.
3662 If ARG is non-nil, user will be prompted anyway. Otherwise, just use the
3663 default if it exists."
3664 (let* ((bookmark (or bookmark
3665 (ewoc-data (ewoc-locate
3666 xetla-bookmarks-cookie))))
3667 (local-trees (assoc 'local-tree (cdr bookmark))))
3670 (let ((dir (read-directory-name
3671 (format "Local tree for \"%s\": "
3673 (when (y-or-n-p "Add this tree in your bookmarks? ")
3674 (xetla-bookmarks-add-tree bookmark dir))
3677 ;; multiple local trees.
3678 (let ((dir (completing-read
3679 (format "Local tree for \"%s\": "
3681 (mapcar #'(lambda (x) (cons x nil))
3683 nil nil nil nil (cadr local-trees))))
3684 (when (and (not (member dir (cdr local-trees)))
3685 (y-or-n-p "Add this tree in your bookmarks? "))
3686 (xetla-bookmarks-add-tree bookmark dir))
3687 (when (and (not (string=
3688 dir (cadr local-trees)))
3689 (y-or-n-p "Make this the default? "))
3690 (xetla-bookmarks-delete-tree bookmark dir)
3691 (xetla-bookmarks-add-tree bookmark dir))
3693 (t (cadr local-trees)))))
3695 (defun xetla-bookmarks-missing (&optional arg)
3696 "Show the missing patches from your partners.
3697 The missing patches are received via xetla missing.
3698 Additionally the local changes in your working copy are also shown.
3700 If prefix argument ARG is specified, the local tree is prompted even
3701 if already set in the bookmarks."
3703 (unless xetla-bookmarks-cookie
3704 (error "Wrong buffer, run: `%s' and try again"
3705 (substitute-command-keys "\\[xetla-bookmarks]")))
3706 (let ((list (or xetla-bookmarks-marked-list
3707 (list (ewoc-data (ewoc-locate
3708 xetla-bookmarks-cookie))))))
3709 (set-buffer (xetla-get-buffer-create 'missing))
3710 (xetla-revision-list-mode)
3711 (set (make-local-variable 'xetla-buffer-refresh-function)
3712 'xetla-missing-refresh)
3713 (let ((xetla-bookmarks-missing-buffer-list-elem
3718 (xetla-bookmarks-read-local-tree elem arg)))
3720 (set (make-local-variable 'xetla-missing-buffer-todolist)
3723 (mapcar (lambda (elem)
3724 (xetla-bookmarks-missing-elem
3725 (car elem) arg (cdr elem) t t))
3726 xetla-bookmarks-missing-buffer-list-elem))))
3727 (xetla-missing-refresh))))
3729 (defvar xetla-nb-active-processes 1
3730 "Number of active processes in this buffer.
3732 Used internally as a counter to launch a global handler when all
3733 processes have finished.")
3735 (defun xetla-missing-refresh ()
3736 "Refreshed a *xetla-missing* buffer.
3738 Process the variable `xetla-missing-buffer-todolist' and launches the
3739 xetla processes with the appropriate handlers to fill in the ewoc."
3741 (set (make-local-variable 'xetla-nb-active-processes) 1)
3742 (let ((buffer-read-only nil))
3744 (set (make-local-variable 'xetla-revision-list-cookie)
3745 (ewoc-create 'xetla-revision-list-printer))
3746 (xetla-kill-process-maybe (current-buffer))
3747 (dolist (item xetla-missing-buffer-todolist)
3750 ;; This item is a version that we want to check for missing patches.
3751 ;; ITEM is of the form:
3752 ;; (missing <local tree> <fully qualified version> [bookmark name])
3753 (let* ((local-tree (nth 1 item))
3754 (version (nth 2 item))
3755 (bookmark-name (nth 3 item))
3756 (text (if bookmark-name
3757 (format "Missing patches from partner %s:"
3759 (concat "Missing patches from archive " version)))
3760 (node (ewoc-enter-last xetla-revision-list-cookie
3761 (list 'separator (concat
3764 (ewoc-enter-last xetla-revision-list-cookie
3765 '(message "Checking for missing patches..."))
3766 (let ((default-directory local-tree))
3767 ;; Set the default-directory for the *xetla-missing* buffer.
3768 (cd default-directory)
3769 (setq xetla-nb-active-processes
3770 (+ xetla-nb-active-processes 1))
3771 (xetla-run-tla-async
3772 `("missing" "--full" ,(when xetla-use-skip-present-option
3773 "--skip-present");;"-summary" "-creator" "-date"
3776 `(lambda (output error status arguments)
3777 (when (and (xetla-get-buffer 'missing)
3778 (buffer-live-p (xetla-get-buffer 'missing)))
3779 (with-current-buffer (xetla-get-buffer-create 'missing)
3780 (when (ewoc-p xetla-revision-list-cookie)
3781 (let* ((cookie xetla-revision-list-cookie)
3782 (to-delete (ewoc-next cookie ,node))
3784 xetla-revision-list-cookie
3787 xetla-revision-list-cookie))
3788 (deleted (eq cur to-delete)))
3789 (xetla-revisions-parse-list
3793 'xetla-revision-compute-merged-by
3795 (ewoc--node-delete to-delete)
3796 (ewoc-refresh xetla-revision-list-cookie)
3797 (let ((loc (if deleted
3799 xetla-revision-list-cookie
3803 (goto-char (ewoc-location loc)))))))))
3805 `(lambda (output error status arguments)
3806 (when (and (xetla-get-buffer 'missing)
3807 (buffer-live-p (xetla-get-buffer 'missing)))
3808 (with-current-buffer (xetla-get-buffer-create 'missing)
3809 (when (ewoc-p xetla-revision-list-cookie)
3810 (let* ((cookie xetla-revision-list-cookie)
3811 (to-delete (ewoc-next cookie ,node)))
3812 (setcdr (ewoc-data to-delete) '("Error in xetla process"))))))
3813 (message "Abnormal exit with code %d!\n%s" status
3814 (xetla-buffer-content error)))))))
3816 ;; This item is a separator - the name of a bookmark.
3817 ;; ITEM is of the form:
3818 ;; (separator <text> bookmark <local tree>)
3819 (let* ((text (nth 1 item))
3820 (local-tree (nth 3 item)))
3821 (ewoc-enter-last xetla-revision-list-cookie
3827 ;; This item is a local-tree that should be checked for changes.
3828 ;; ITEM is of the form:
3829 ;; (changes <local tree>)
3831 (ewoc-enter-last xetla-revision-list-cookie
3832 '(message "Checking for local changes..."))))
3833 (let ((default-directory (nth 1 item)))
3834 (xetla-run-tla-async
3836 :error `(lambda (output error status arguments)
3837 (with-current-buffer ,(current-buffer)
3838 (let* ((prev (ewoc-prev
3839 xetla-revision-list-cookie
3842 xetla-revision-list-cookie))
3843 (deleted (eq cur ,to-delete)))
3844 (xetla-bookmarks-missing-parse-changes
3845 output ,(ewoc-nth xetla-revision-list-cookie
3847 (ewoc--node-delete ,to-delete)
3848 (ewoc-refresh xetla-revision-list-cookie)
3849 (let ((loc (if deleted
3851 xetla-revision-list-cookie
3855 (goto-char (ewoc-location loc)))))))
3856 :finished `(lambda (output error status arguments)
3857 (with-current-buffer ,(current-buffer)
3858 (let* ((prev (ewoc-prev
3859 xetla-revision-list-cookie
3862 xetla-revision-list-cookie))
3863 (deleted (eq cur ,to-delete)))
3864 (ewoc--node-delete ,to-delete)
3865 (ewoc-refresh xetla-revision-list-cookie)
3866 (let ((loc (if deleted
3868 xetla-revision-list-cookie
3872 (goto-char (ewoc-location loc)))))))
3874 (ewoc-set-hf xetla-revision-list-cookie ""
3875 (concat "\n" (xetla-face-add "end."
3876 'xetla-separator)))))
3877 (goto-char (point-min))
3878 ;; If all processes have been run synchronously,
3879 ;; xetla-nb-active-processes is 1 now, and we should run the
3881 (setq xetla-nb-active-processes
3882 (- xetla-nb-active-processes 1))
3883 (when (zerop xetla-nb-active-processes)
3884 (xetla-revision-compute-merged-by))
3887 (defun xetla-revision-ewoc-map (function ewoc-list)
3888 "Invoke FUNCTION on 'entry-patch nodes of EWOC-LIST.
3889 Like (ewoc-map FUNCTION EWOC-LIST), but call FUNCTION only on
3890 'entry-patch nodes. The argument passed to FUNCTION is a struct of
3891 type xetla-revisions."
3892 (ewoc-map (lambda (elem)
3893 (when (eq (car elem) 'entry-patch)
3894 (funcall function (caddr elem))))
3897 (defvar xetla-revision-merge-by-computed nil
3898 "Non-nil when the \"merged-by\" field have been computed.")
3900 (defun xetla-revision-compute-merged-by ()
3901 "Computes the field \"merged-by:\" for a revision.
3903 In a revision list buffer, with revisions containing the \"merges:\"
3904 information, compute another field \"merged-by:\", containing the
3905 reverse information. If revision-A is a merge of revision-B, then,
3906 you'll get revision-A merges: revision-B revision-B merged-by:
3909 (xetla-revision-ewoc-map (lambda (elem)
3910 (setf (xetla-revision-merged-by elem) nil))
3911 xetla-revision-list-cookie)
3912 (xetla-revision-ewoc-map 'xetla-set-merged-patches
3913 xetla-revision-list-cookie)
3914 (xetla-revision-ewoc-map (lambda (elem)
3915 (unless (xetla-revision-merged-by elem)
3916 (setf (xetla-revision-merged-by elem) 'nobody)))
3917 xetla-revision-list-cookie)
3918 (set (make-local-variable 'xetla-revision-merge-by-computed) t)
3922 (defvar xetla-merged-rev))
3924 (defun xetla-set-merged-patches (rev)
3925 "Set the \"merged-by\" field for other revisions according to REV.
3927 Adds REV to the list of all patches merged by REV."
3928 (dolist (merged-rev (xetla-revision-merges rev))
3929 (setq xetla-merged-rev merged-rev)
3930 (xetla-revision-ewoc-map
3931 `(lambda (rev-to-fill)
3932 (when (equal (xetla-name-construct
3933 (xetla-revision-revision rev-to-fill))
3935 (setf (xetla-revision-merged-by rev-to-fill)
3936 (cons ,(xetla-name-construct
3937 (xetla-revision-revision rev))
3938 (xetla-revision-merged-by rev-to-fill)))))
3939 xetla-revision-list-cookie)))
3941 (defun xetla-bookmarks-missing-elem (data arg local-tree header
3942 &optional changes-too)
3943 "Show missing patches for DATA.
3944 ARG is currently ignored but is present for backwards compatibility.
3945 LOCAL-TREE is the local tree for which missing patches should be shown.
3946 HEADER is currently ignored but is present for backwards compatibility.
3947 If CHANGES-TOO is non-nil, show changes for DATA as well as missing patches."
3948 (let* ((default-directory local-tree)
3949 (partners (assoc 'partners (cdr data)))
3950 (location (cdr (assoc 'location (cdr data)))))
3951 (xetla-switch-to-buffer (xetla-get-buffer-create 'missing))
3952 ;; The buffer was created in a context where we didn't know the
3953 ;; path to use. Set it now.
3958 ,(format "Bookmark %s (%s):"
3960 (xetla-name-construct location))
3964 (add-to-list 'item `(changes ,local-tree)))
3965 (dolist (partner (cons (xetla-name-construct
3966 (cdr (assoc 'location (cdr data)))) ; Me
3967 (cdr partners))) ; and my partners
3968 (let* ((bookmark-list
3969 (mapcar (lambda (bookmark)
3970 (and (string= partner
3971 (xetla-name-construct
3972 (cdr (assoc 'location bookmark))))
3974 xetla-bookmarks-alist))
3975 (bookmark-name (progn (while (and (not (car bookmark-list))
3976 (cdr bookmark-list))
3978 (cdr bookmark-list)))
3979 (car bookmark-list))))
3980 (add-to-list 'item `(missing ,local-tree ,partner ,bookmark-name))))
3983 (defun xetla-read-field (field)
3984 "Read the contents of FIELD from a log buffer.
3985 Must be called from a log file buffer. Returns the content of the
3986 field FIELD. FIELD is just the name of the field, without trailing
3989 (goto-char (point-min))
3990 (if (re-search-forward (concat "^" field ": ") nil t)
3991 (buffer-substring-no-properties
3993 (re-search-forward "^[^ \t]")
3994 (- (point) 2))) ;; back to the end of the last line
3998 (defun xetla-revisions-parse-list (type details merges buffer
4001 "Parse a list of revisions.
4002 TYPE can be either 'logs, 'missing, but
4003 could be extended in the future.
4005 DETAILS must be non-nil if the buffer contains date, author and
4007 MERGES must be non-nil if the buffer contains list of merged patches
4009 BUFFER is the buffer to parse.
4011 PARENT-NODE is an ewoc node to which the new items will be appened. If
4012 nil, append at the end of the ewoc list.
4013 COOKIE must be the ewoc list containing PARENT-NODE.
4015 If CALLBACK is given, it should be a function (or symbol naming a
4016 function) that will be called once the revision list has been fully
4018 (with-current-buffer (ewoc-buffer cookie)
4019 (set (make-local-variable 'xetla-revision-merge-by-computed) nil))
4020 (let ((last-node parent-node)
4022 (with-current-buffer (with-current-buffer buffer
4024 (goto-char (point-min))
4025 (re-search-forward ".*/.*--.*--.*--.*" nil t)
4027 (while (progn (> (point-max) (point)))
4028 (setq revision (buffer-substring-no-properties
4029 (point) (point-at-eol)))
4031 (let* ((rev-struct (make-xetla-revision
4032 :revision (xetla-name-split revision)))
4033 (elem (list 'entry-patch nil
4035 (when (or xetla-revisions-shows-summary
4036 xetla-revisions-shows-creator
4037 xetla-revisions-shows-date
4038 xetla-revisions-shows-merges
4039 xetla-revisions-shows-merged-by)
4040 (with-current-buffer (ewoc-buffer cookie)
4041 (setq xetla-nb-active-processes
4042 (+ xetla-nb-active-processes 1))
4044 (xetla-name-split revision)
4046 `(lambda (output error status arguments)
4047 (with-current-buffer output
4048 (setf (xetla-revision-date ,rev-struct)
4049 (xetla-read-field "Standard-date"))
4050 (setf (xetla-revision-creator ,rev-struct)
4051 (xetla-read-field "Creator"))
4052 (setf (xetla-revision-summary ,rev-struct)
4053 (xetla-read-field "Summary"))
4054 (setf (xetla-revision-merges ,rev-struct)
4056 (split-string (xetla-read-field
4058 (with-current-buffer ,(ewoc-buffer cookie)
4059 (setq xetla-nb-active-processes
4060 (- xetla-nb-active-processes 1))
4061 (when (and ',callback
4062 (zerop xetla-nb-active-processes))
4063 (funcall ',callback)))
4064 (let* ((cur (ewoc-locate xetla-revision-list-cookie)))
4065 (ewoc-refresh ,cookie)
4066 (when cur (goto-char (ewoc-location cur))))))))
4069 (ewoc-enter-after cookie last-node elem))
4070 (ewoc-enter-last cookie elem))))
4071 (kill-buffer (current-buffer)))
4072 (with-current-buffer (ewoc-buffer cookie)
4073 (setq xetla-nb-active-processes (- xetla-nb-active-processes 1))
4075 (zerop xetla-nb-active-processes))
4076 (funcall callback))))
4077 (ewoc-refresh cookie))
4079 (defun xetla-bookmarks-missing-parse-changes (buffer parent-node)
4080 "Parse the output of `xetla changes' from BUFFER and update PARENT-NODE."
4081 (with-current-buffer buffer
4083 (progn (goto-char (point-min))
4084 (when (re-search-forward "^[^\\*]" nil t)
4085 (buffer-substring-no-properties
4088 (local-tree default-directory))
4090 (with-current-buffer (xetla-get-buffer-create 'missing)
4091 (ewoc-enter-after xetla-revision-list-cookie
4097 (defun xetla-bookmarks-open-tree ()
4098 "Open a local tree in a dired buffer.
4100 With a prefix arg, prompt for a local tree to use."
4102 (let ((x-dired (if (eq xetla-switch-to-buffer-mode 'single-window)
4104 'dired-other-window))
4105 (open (if current-prefix-arg
4106 (xetla-bookmarks-read-local-tree nil 'ask)
4107 (xetla-bookmarks-read-local-tree))))
4108 (funcall x-dired open)))
4110 (defun xetla-bookmarks-find-file ()
4111 "Find a file starting from the local tree of the current bookmark.
4113 This way, you can type C-x C-f in the bookmarks buffer to open a file
4114 of a bookmarked project.
4116 With a prefix arg, prompt for the local tree to use."
4118 (let ((default-directory (xetla-uniquify-file-name
4119 (if current-prefix-arg
4120 (xetla-bookmarks-read-local-tree nil 'ask)
4121 (xetla-bookmarks-read-local-tree)))))
4122 (call-interactively 'find-file)))
4124 (defun xetla-bookmarks-tag (arg)
4125 "Run `tla tag' on the current bookmark.
4127 If multiple bookmarks are marked, create a tag for each of them. If a
4128 prefix argument ARG is given, explicitly ask for the revision to tag
4131 (unless xetla-bookmarks-cookie
4132 (error "Not in bookmarks buffer, run: `%s' and try again"
4133 (substitute-command-keys "\\[xetla-bookmarks]")))
4134 (let ((list (or xetla-bookmarks-marked-list
4135 (list (ewoc-data (ewoc-locate xetla-bookmarks-cookie))))))
4139 (xetla-name-construct
4141 (apply 'xetla-name-read "Tag from revision: "
4142 (append (cdr (assoc 'location bookmark))
4144 (cdr (assoc 'location bookmark))))))
4146 (xetla-name-construct
4147 (xetla-name-read (format "Tag version for '%s': "
4149 'prompt 'prompt 'prompt 'prompt))
4151 "Name of the bookmark for this tag: "))))
4154 (destructuring-bind (src destination name) tag
4155 (xetla-run-tla-async
4156 (list "tag" "--setup" src destination)
4158 `(lambda (output error status arguments)
4159 (xetla-bookmarks-add ,name (xetla-name-split ,destination))
4160 (xetla-bookmarks-add-partner (assoc ,name xetla-bookmarks-alist)
4163 `(lambda (output error status arguments)
4164 (error "Fail to create a tag for %s" ,src))))))
4165 (setq xetla-bookmarks-marked-list nil)
4166 (ewoc-refresh xetla-bookmarks-cookie)))
4168 (defun xetla-bookmarks-inventory ()
4169 "Run `tla inventory' on a local tree."
4171 (let ((default-directory (if current-prefix-arg
4172 (xetla-bookmarks-read-local-tree nil 'ask)
4173 (xetla-bookmarks-read-local-tree))))
4174 (xetla-inventory nil
4175 (unless (eq xetla-switch-to-buffer-mode 'single-window)
4178 (defun xetla-bookmarks-changes (arg)
4179 "Run `xetla-changes' on a local tree.
4181 Prefix argument, ARG, determines whether or not to use a full format or
4182 summary format and if it should prompt for the local tree to use.
4184 No prefix arg -- full format, default tree.
4185 One prefix arg -- summary format, default tree.
4186 Two prefix args -- full format, prompt for tree.
4187 Three prefix args -- summary format, prompt for tree."
4189 (let* ((arg (car current-prefix-arg))
4190 (default-directory (if (or (eq arg 16) (eq arg 64))
4191 (xetla-bookmarks-read-local-tree nil 'ask)
4192 (xetla-bookmarks-read-local-tree))))
4193 (if (or (eq arg 4) (eq arg 64))
4197 (defmacro xetla-make-move-fn (ewoc-direction function cookie
4198 &optional only-unmerged)
4199 "Create function to move up or down in `xetla-revision-list-cookie'.
4201 EWOC-DIRECTION is either `ewoc-next' or `ewoc-prev'.
4202 FUNCTION is the name of the function to declare.
4203 COOKIE is the ewoc to navigate in.
4204 if ONLY-UNMERGED is non-nil, then, navigate only through revisions not
4205 merged by another revision in the same list."
4206 `(defun ,function ()
4208 (let* ((elem (ewoc-locate ,cookie))
4209 (next (or (,ewoc-direction ,cookie elem) elem)))
4212 (not (and (eq (car (ewoc-data next))
4214 (eq (xetla-revision-merged-by
4215 (caddr (ewoc-data next)))
4217 (eq (car (ewoc-data next)) 'separator))
4218 (,ewoc-direction ,cookie next))
4219 (setq next (,ewoc-direction ,cookie next)))
4222 (not (and (eq (car (ewoc-data next))
4224 (eq (xetla-revision-merged-by
4225 (caddr (ewoc-data next)))
4227 (eq (car (ewoc-data next)) 'separator)))
4228 (setq next (,(if (eq ewoc-direction 'ewoc-next)
4230 'ewoc-next) ,cookie next)))
4231 (when next (goto-char (ewoc-location next)))))
4234 (xetla-make-move-fn ewoc-next xetla-revision-next
4235 xetla-revision-list-cookie)
4237 (xetla-make-move-fn ewoc-prev xetla-revision-prev
4238 xetla-revision-list-cookie)
4240 (xetla-make-move-fn ewoc-next xetla-revision-next-unmerged
4241 xetla-revision-list-cookie t)
4243 (xetla-make-move-fn ewoc-prev xetla-revision-prev-unmerged
4244 xetla-revision-list-cookie t)
4247 (defun xetla-bookmarks (&optional arg)
4248 "Display XEtla bookmarks in a buffer.
4250 With prefix argument ARG, reload the bookmarks file from disk."
4252 (xetla-bookmarks-load-from-file arg)
4253 (if (eq xetla-switch-to-buffer-mode 'single-window)
4254 (switch-to-buffer (get-buffer-create "*xetla-bookmarks*"))
4255 (pop-to-buffer (get-buffer-create "*xetla-bookmarks*")))
4256 (let ((pos (point)))
4257 (toggle-read-only -1)
4259 (set (make-local-variable 'xetla-bookmarks-cookie)
4260 (ewoc-create 'xetla-bookmarks-printer))
4261 (set (make-local-variable 'xetla-bookmarks-marked-list) nil)
4262 (dolist (elem xetla-bookmarks-alist)
4263 (ewoc-enter-last xetla-bookmarks-cookie elem))
4264 (xetla-bookmarks-mode)
4265 (if (equal pos (point-min))
4266 (if (ewoc-nth xetla-bookmarks-cookie 0)
4267 (xetla-bookmarks-cursor-goto (ewoc-nth xetla-bookmarks-cookie 0))
4268 (message "You have no bookmarks, create some in the other buffers"))
4272 (defun xetla-bookmarks-mode ()
4273 "Major mode to show XEtla bookmarks.
4276 \\{xetla-bookmarks-mode-map}"
4278 (use-local-map xetla-bookmarks-mode-map)
4279 (setq major-mode 'xetla-bookmarks-mode)
4280 (setq mode-name "xetla-bookmarks")
4281 (toggle-read-only 1)
4282 (run-hooks 'xetla-bookmarks-mode-hook))
4284 (defun xetla-bookmarks-cursor-goto (ewoc-bookmark)
4285 "Move cursor to the ewoc location of EWOC-BOOKMARK."
4287 (goto-char (ewoc-location ewoc-bookmark))
4288 (search-forward ":"))
4290 (defun xetla-bookmarks-next ()
4291 "Move the cursor to the next bookmark."
4293 (let* ((cookie xetla-bookmarks-cookie)
4294 (elem (ewoc-locate cookie))
4295 (next (or (ewoc-next cookie elem) elem)))
4296 (xetla-bookmarks-cursor-goto next)))
4298 (defun xetla-bookmarks-previous ()
4299 "Move the cursor to the previous bookmark."
4301 (let* ((cookie xetla-bookmarks-cookie)
4302 (elem (ewoc-locate cookie))
4303 (previous (or (ewoc-prev cookie elem) elem)))
4304 (xetla-bookmarks-cursor-goto previous)))
4306 (defun xetla-bookmarks-move-down ()
4307 "Move the current bookmark down."
4309 (let* ((cookie xetla-bookmarks-cookie)
4310 (elem (ewoc-locate cookie))
4311 (data (ewoc-data elem))
4312 (oldname (car data))
4313 (next (ewoc-next cookie elem)))
4315 (error "Can't go lower"))
4316 (xetla-ewoc-delete cookie elem)
4317 (goto-char (ewoc-location
4318 (ewoc-enter-after cookie next data)))
4319 (let ((list xetla-bookmarks-alist)
4322 (if (string= (caar list) oldname)
4324 (setq newlist (cons (car (cdr list)) newlist))
4325 (setq newlist (cons (car list) newlist))
4326 (setq list (cdr list)))
4327 (setq newlist (cons (car list) newlist)))
4328 (setq list (cdr list)))
4329 (setq xetla-bookmarks-alist (reverse newlist)))
4330 (search-forward ":")))
4332 (defun xetla-bookmarks-move-up ()
4333 "Move the current bookmark up."
4335 (let* ((cookie xetla-bookmarks-cookie)
4336 (elem (ewoc-locate cookie))
4337 (data (ewoc-data elem))
4338 (oldname (car data))
4339 (previous (ewoc-prev cookie elem)))
4341 (error "Can't go upper"))
4342 (xetla-ewoc-delete cookie elem)
4343 (goto-char (ewoc-location
4344 (ewoc-enter-before cookie previous data)))
4345 (let ((list xetla-bookmarks-alist)
4348 (if (string= (caar (cdr list)) oldname)
4350 (setq newlist (cons (car (cdr list)) newlist))
4351 (setq newlist (cons (car list) newlist))
4352 (setq list (cdr list)))
4353 (setq newlist (cons (car list) newlist)))
4354 (setq list (cdr list)))
4355 (setq xetla-bookmarks-alist (reverse newlist)))
4356 (search-forward ":")))
4358 (defun xetla-get-location-as-string ()
4359 "Construct an a/c-b-v-r string from the current bookmark."
4360 (let* ((elem (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4361 (location (cdr (assoc 'location elem))))
4362 (xetla-name-construct location)))
4364 (defun xetla-bookmarks-get (directory)
4365 "Run `tla get' on the bookmark under point, placing the tree in DIRECTORY."
4366 (interactive (list (expand-file-name
4367 (read-directory-name
4368 (format "Get %s in directory: " (xetla-get-location-as-string))))))
4369 (let* ((elem (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4370 (location (cdr (assoc 'location elem))))
4371 (xetla-get directory t
4372 (xetla-name-archive location)
4373 (xetla-name-category location)
4374 (xetla-name-branch location)
4375 (xetla-name-version location))))
4377 (defun xetla-bookmarks-goto ()
4378 "Browse the archive of the current bookmark."
4380 (let* ((elem (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4381 (location (cdr (assoc 'location elem)))
4382 (archive (xetla-name-archive location))
4383 (category (xetla-name-category location))
4384 (branch (xetla-name-branch location))
4385 (version (xetla-name-version location)))
4386 (cond (version (xetla-revisions archive category branch version))
4387 (branch (xetla-versions archive category branch))
4388 (category (xetla-branches archive category))
4389 (archive (xetla-categories archive))
4390 (t (error "Nothing specified for this bookmark")))))
4392 (xetla-make-bymouse-function xetla-bookmarks-goto)
4394 (defun xetla-bookmarks-star-merge (arg)
4395 "Star-merge the current bookmark to a local tree.
4396 Accepts prefix argument ARG for future extension."
4398 (let* ((elem (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4399 (location (cdr (assoc 'location elem)))
4400 (local-tree (read-directory-name "Star-merge into: ")))
4401 (xetla-star-merge (xetla-name-construct location)
4404 (defun xetla-bookmarks-replay (arg)
4405 "Replay the current bookmark to some local tree.
4406 Accepts prefix argument ARG for future extension."
4408 (let* ((elem (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4409 (location (xetla-name-construct (cdr (assoc 'location elem))))
4410 (local-tree (read-directory-name
4411 (format "Replay %s into: " location))))
4412 (xetla-replay location local-tree)))
4414 (defun xetla-bookmarks-update (arg)
4415 "Update the local tree of the current bookmark.
4416 Accepts prefix argument ARG for future extension."
4418 (let* ((buf (current-buffer))
4419 (work-list (or xetla-bookmarks-marked-list
4420 (list (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))))
4422 (mapcar (lambda (bookmark)
4423 (let ((local-trees (cdr (assoc 'local-tree bookmark))))
4424 (xetla-uniquify-file-name
4425 (cond ((null local-trees)
4426 (read-directory-name
4427 (format "Local tree for '%s'?: "
4428 (car bookmark)) nil nil t))
4429 ((not (null (cdr local-trees)))
4431 (format "Local tree for '%s'?: "
4434 (t (car local-trees))))))
4436 (mapc 'xetla-update update-trees)
4437 (with-current-buffer buf
4438 (setq xetla-bookmarks-marked-list '())
4439 (ewoc-refresh xetla-bookmarks-cookie))))
4441 (defun xetla-bookmarks-add-elem (name info)
4442 "Add the association (NAME . INFO) to the list of bookmarks, and save it.
4443 This is an internal function."
4444 (when (assoc name xetla-bookmarks-alist)
4445 (error (concat "Already got a bookmark " name)))
4446 (let ((elem (cons name info)))
4447 (add-to-list 'xetla-bookmarks-alist elem t)
4448 (xetla-bookmarks-save-to-file)
4449 (ewoc-enter-last xetla-bookmarks-cookie elem)
4452 (defun xetla-bookmarks-add (name revision-spec)
4453 "Add a bookmark named NAME for REVISION-SPEC."
4454 (interactive (let* ((fq (xetla-name-read "Version: "
4455 'prompt 'prompt 'prompt 'prompt))
4456 (n (read-string (format "Name of the bookmark for `%s': "
4457 (xetla-name-construct fq)))))
4459 (unless (get-buffer "*xetla-bookmarks*")
4461 (with-current-buffer "*xetla-bookmarks*"
4462 (let* ((info (list (cons 'location
4464 (cons 'timestamp (current-time-string)))))
4465 (xetla-bookmarks-add-elem name info))))
4467 (defun xetla-bookmarks-mark ()
4468 "Mark the bookmark at point."
4470 (let ((pos (point)))
4471 (add-to-list 'xetla-bookmarks-marked-list
4472 (ewoc-data (ewoc-locate xetla-bookmarks-cookie)))
4473 (ewoc-refresh xetla-bookmarks-cookie)
4475 (xetla-bookmarks-next))
4477 (defun xetla-bookmarks-unmark ()
4478 "Unmark the bookmark at point."
4480 (let ((pos (point)))
4481 (setq xetla-bookmarks-marked-list
4482 (delq (ewoc-data (ewoc-locate xetla-bookmarks-cookie))
4483 xetla-bookmarks-marked-list))
4484 (ewoc-refresh xetla-bookmarks-cookie)
4486 (xetla-bookmarks-next))
4488 (defun xetla-bookmarks-unmark-all ()
4489 "Unmark all bookmarks in current buffer."
4491 (let ((pos (point)))
4492 (setq xetla-bookmarks-marked-list nil)
4493 (ewoc-refresh xetla-bookmarks-cookie)
4496 (defun xetla-bookmarks-marked-are-partners ()
4497 "Make marked bookmarks mutual partners."
4499 (let ((list-arch (mapcar
4502 (xetla-name-construct
4503 (cdr (assoc 'location x)))))
4504 xetla-bookmarks-marked-list)))
4505 (dolist (book xetla-bookmarks-marked-list)
4506 (let ((myloc (xetla-name-construct
4507 (cdr (assoc 'location book)))))
4509 (dolist (arch list-arch)
4510 (unless (string= myloc arch)
4511 (xetla-bookmarks-add-partner book arch t))))))
4512 (xetla-bookmarks-save-to-file)
4513 (save-window-excursion
4516 (defun xetla-bookmarks-cleanup-local-trees ()
4517 "Remove LOCAL-TREE field from bookmarks if they don't exist."
4519 (dolist (book xetla-bookmarks-alist)
4521 (dolist (local-tree (cdr (assoc 'local-tree book)))
4522 (when (and (not (file-exists-p local-tree))
4523 (or xetla-bookmarks-cleanup-dont-prompt
4526 "Remove tree %s from bookmarks %s? "
4529 (xetla-bookmarks-delete-tree book local-tree t)))))
4530 (xetla-bookmarks-save-to-file)
4531 (save-window-excursion
4534 (defun xetla-bookmarks-delete (elem &optional force)
4535 "Delete the bookmark entry ELEM.
4536 If FORCE is non-nil, don't ask for confirmation."
4537 (interactive (list (ewoc-locate xetla-bookmarks-cookie)))
4538 (let* ((data (ewoc-data elem)))
4540 (yes-or-no-p (format "Delete bookmark \"%s\"? " (car data))))
4541 (xetla-ewoc-delete xetla-bookmarks-cookie elem)
4542 (let ((list xetla-bookmarks-alist)
4545 (unless (string= (caar list) (car data))
4546 (setq newlist (cons (car list) newlist)))
4547 (setq list (cdr list)))
4548 (setq xetla-bookmarks-alist (reverse newlist)))
4549 ;; TODO could be optimized
4550 (xetla-bookmarks-save-to-file)
4553 (defun xetla-bookmarks-find-bookmark (location)
4554 "Find the bookmark whose location is LOCATION (a string)."
4555 (let ((list xetla-bookmarks-alist)
4558 (when (string= (xetla-name-construct
4559 (cdr (assoc 'location (cdar list))))
4561 (setq result (car list))
4563 (setq list (cdr list)))
4566 (defun xetla-bookmarks-get-field (version field default)
4567 "Return VERSION'S value of FIELD, or DEFAULT if there is no value."
4568 (xetla-bookmarks-load-from-file)
4570 (dolist (elem xetla-bookmarks-alist)
4571 (let ((location (cdr (assoc 'location elem))))
4572 (when (and (string= (xetla-name-archive location)
4573 (xetla-name-archive version))
4574 (string= (xetla-name-category location)
4575 (xetla-name-category version))
4576 (string= (xetla-name-branch location)
4577 (xetla-name-branch version))
4578 (string= (xetla-name-version location)
4579 (xetla-name-version version)))
4580 (return-from dolist (or (cadr (assoc field (cdr elem))) default)))))
4583 (defmacro xetla-bookmarks-make-add-fn (name field message-already message-add)
4584 "Define a function called NAME for adding FIELD to a bookmark entry.
4585 This function will display MESSAGE-ALREADY if the user tries to add a field
4586 twice, and will display MESSAGE-ADD when a new field is successfully added."
4587 `(defun ,name (bookmark value &optional dont-save)
4588 "Adds the directory VALUE to the list of local trees of bookmark
4590 (let ((local-trees (assoc ,field (cdr bookmark))))
4592 (if (member value (cdr local-trees))
4593 (message ,message-already)
4595 (message ,message-add)
4596 (setcdr local-trees (cons value
4597 (cdr local-trees)))))
4599 (message ,message-add)
4600 (setcdr bookmark (cons (list ,field value)
4603 (xetla-bookmarks-save-to-file)
4604 (save-window-excursion
4605 (xetla-bookmarks)))))
4608 (xetla-bookmarks-make-add-fn xetla-bookmarks-add-tree
4610 "Local tree already in the list"
4611 "Local tree added to your bookmarks")
4613 (xetla-bookmarks-make-add-fn xetla-bookmarks-add-partner
4615 "Partner already in the list"
4616 "Partner added to your bookmarks")
4618 (xetla-bookmarks-make-add-fn xetla-bookmarks-add-group
4620 "Group already in the list"
4621 "Group added to your bookmarks")
4623 (xetla-bookmarks-make-add-fn xetla-bookmarks-add-nickname
4625 "Nickname already in the list"
4626 "Nickname added to your bookmark")
4628 (defmacro xetla-bookmarks-make-delete-fn (name field)
4629 "Define a function called NAME for removing FIELD from bookmark entries."
4630 `(defun ,name (bookmark value &optional dont-save)
4631 "Deletes the directory VALUE to the list of local trees of bookmark
4633 (let ((local-trees (assoc ,field (cdr bookmark))))
4635 (let ((rem-list (delete value (cdr (assoc ,field
4638 (setcdr local-trees rem-list)
4639 ;; Remove the whole ('field ...)
4640 (setcdr bookmark (delq local-trees (cdr bookmark))))))
4642 (xetla-bookmarks-save-to-file)
4643 (save-window-excursion
4644 (xetla-bookmarks)))))
4647 (xetla-bookmarks-make-delete-fn xetla-bookmarks-delete-tree
4650 (xetla-bookmarks-make-delete-fn xetla-bookmarks-delete-partner
4653 (xetla-bookmarks-make-delete-fn xetla-bookmarks-delete-group
4656 (xetla-bookmarks-make-delete-fn xetla-bookmarks-delete-nickname
4659 (defun xetla-bookmarks-add-partner-interactive ()
4660 "Add a partner to the current or marked bookmarks."
4662 (let ((bookmarks (or xetla-bookmarks-marked-list
4663 (list (ewoc-data (ewoc-locate
4664 xetla-bookmarks-cookie)))))
4665 (partner (xetla-name-construct
4666 (xetla-name-read "Version: "
4667 'prompt 'prompt 'prompt 'prompt))))
4668 (dolist (bookmark bookmarks)
4669 (xetla-bookmarks-add-partner bookmark partner t))
4670 (xetla-bookmarks-save-to-file)
4671 (save-window-excursion
4672 (xetla-bookmarks))))
4674 (defun xetla-bookmarks-add-partners-from-file ()
4675 "Add a partner to the current or marked bookmarks."
4677 (let ((bookmarks (or xetla-bookmarks-marked-list
4678 (list (ewoc-data (ewoc-locate
4679 xetla-bookmarks-cookie))))))
4680 (dolist (bookmark bookmarks)
4681 (let ((partners (xetla-partner-list
4682 (xetla-bookmarks-read-local-tree bookmark))))
4683 (dolist (partner partners)
4684 (xetla-bookmarks-add-partner bookmark partner t))))
4685 (xetla-bookmarks-save-to-file)
4686 (save-window-excursion
4687 (xetla-bookmarks))))
4689 (defun xetla-bookmarks-write-partners-to-file ()
4690 "Add the partners recorded in the bookmarks to the partner file."
4692 (let ((bookmarks (or xetla-bookmarks-marked-list
4693 (list (ewoc-data (ewoc-locate
4694 xetla-bookmarks-cookie))))))
4695 (dolist (bookmark bookmarks)
4696 (let* ((local-tree (xetla-bookmarks-read-local-tree bookmark))
4697 (partners (xetla-partner-list local-tree)))
4698 (with-current-buffer
4699 (xetla-partner-find-partner-file local-tree)
4700 (dolist (partner (cdr (assoc 'partners (cdr bookmark))))
4701 (unless (member partner partners)
4702 (insert partner "\n")))
4703 (and (buffer-modified-p)
4704 (progn (switch-to-buffer (current-buffer))
4705 (y-or-n-p (format "Save file %s? "
4706 (buffer-file-name))))
4710 (defun xetla-bookmarks-delete-partner-interactive ()
4711 "Delete a partner from the current or marked bookmarks."
4713 (let* ((bookmarks (or xetla-bookmarks-marked-list
4714 (list (ewoc-data (ewoc-locate
4715 xetla-bookmarks-cookie)))))
4716 (choices (apply 'append
4717 (mapcar #'(lambda (x)
4718 (cdr (assoc 'partners
4721 (choices-alist (mapcar #'(lambda (x) (list x)) choices))
4722 (partner (completing-read "Partner to remove: " choices-alist)))
4723 (dolist (bookmark bookmarks)
4724 (xetla-bookmarks-delete-partner bookmark partner t))
4725 (xetla-bookmarks-save-to-file)
4726 (save-window-excursion
4727 (xetla-bookmarks))))
4729 (defun xetla-bookmarks-add-tree-interactive ()
4730 "Add a local tree to the current or marked bookmarks."
4732 (let ((bookmarks (or xetla-bookmarks-marked-list
4733 (list (ewoc-data (ewoc-locate
4734 xetla-bookmarks-cookie)))))
4735 (local-tree (read-directory-name "Local tree to add: ")))
4736 (unless (file-exists-p (concat (file-name-as-directory local-tree) "{arch}"))
4737 (error (concat local-tree " is not an arch local tree.")))
4738 (dolist (bookmark bookmarks)
4739 (xetla-bookmarks-add-tree bookmark local-tree t))
4740 (xetla-bookmarks-save-to-file)
4741 (save-window-excursion
4742 (xetla-bookmarks))))
4744 (defun xetla-bookmarks-delete-tree-interactive ()
4745 "Add a local tree to the current or marked bookmarks."
4747 (let* ((bookmarks (or xetla-bookmarks-marked-list
4748 (list (ewoc-data (ewoc-locate
4749 xetla-bookmarks-cookie)))))
4750 (choices (apply 'append
4751 (mapcar #'(lambda (x)
4752 (cdr (assoc 'local-tree
4755 (choices-alist (mapcar #'(lambda (x) (list x)) choices))
4756 (local-tree (completing-read "Local tree to remove: " choices-alist)))
4757 (dolist (bookmark bookmarks)
4758 (xetla-bookmarks-delete-tree bookmark local-tree t))
4759 (xetla-bookmarks-save-to-file)
4760 (save-window-excursion
4761 (xetla-bookmarks))))
4763 (defun xetla-bookmarks-list-groups ()
4764 "Return the list of groups currently used by bookmarks."
4765 (let ((list (apply 'append
4766 (mapcar #'(lambda (x)
4769 xetla-bookmarks-alist)))
4771 ;; Make elements unique
4773 (add-to-list 'result elem))
4776 (defun xetla-bookmarks-add-group-interactive ()
4777 "Add a group entry in the current or marked bookmarks."
4779 (let* ((bookmarks (or xetla-bookmarks-marked-list
4780 (list (ewoc-data (ewoc-locate
4781 xetla-bookmarks-cookie)))))
4782 (group (completing-read "Group of bookmarks: "
4783 (mapcar #'(lambda (x) (list x))
4784 (xetla-bookmarks-list-groups)))))
4785 (dolist (bookmark bookmarks)
4786 (xetla-bookmarks-add-group bookmark group t)))
4787 (xetla-bookmarks-save-to-file)
4788 (save-window-excursion
4792 (defun xetla-bookmarks-delete-group-interactive ()
4793 "Delete a group of bookmark entry from the current or marked bookmarks."
4795 (let* ((bookmarks (or xetla-bookmarks-marked-list
4796 (list (ewoc-data (ewoc-locate
4797 xetla-bookmarks-cookie)))))
4798 (choices (apply 'append
4799 (mapcar #'(lambda (x)
4803 (choices-alist (mapcar #'(lambda (x) (list x)) choices))
4804 (group (completing-read "Group to remove: " choices-alist)))
4805 (dolist (bookmark bookmarks)
4806 (xetla-bookmarks-delete-group bookmark group t)))
4807 (xetla-bookmarks-save-to-file)
4808 (save-window-excursion
4811 (defun xetla-bookmarks-select-by-group (group)
4812 "Select all bookmarks in GROUP."
4813 (interactive (list (completing-read "Group to select: "
4814 (mapcar #'(lambda (x) (list x))
4815 (xetla-bookmarks-list-groups)))))
4816 (dolist (bookmark xetla-bookmarks-alist)
4817 (when (member group (cdr (assoc 'groups bookmark)))
4818 (add-to-list 'xetla-bookmarks-marked-list bookmark))
4820 (ewoc-refresh xetla-bookmarks-cookie))
4822 (defun xetla-bookmarks-add-nickname-interactive ()
4823 "Add a nickname to the current bookmark."
4825 (let* ((bookmark (ewoc-data (ewoc-locate
4826 xetla-bookmarks-cookie)))
4827 (prompt (format "Nickname for %s: " (xetla-name-construct
4828 (cdr (assoc 'location bookmark))))))
4829 (xetla-bookmarks-add-nickname bookmark (read-string prompt) t)
4830 (xetla-bookmarks-save-to-file)
4831 (save-window-excursion
4832 (xetla-bookmarks))))
4834 (defun xetla-bookmarks-delete-nickname-interactive ()
4835 "Delete the nickname of the current bookmark."
4837 (let* ((bookmark (ewoc-data (ewoc-locate
4838 xetla-bookmarks-cookie)))
4839 (nickname (cadr (assoc 'nickname bookmark))))
4840 (xetla-bookmarks-delete-nickname bookmark nickname t)
4841 (xetla-bookmarks-save-to-file)
4842 (save-window-excursion
4843 (xetla-bookmarks))))
4845 (defvar xetla-buffer-bookmark nil
4846 "The bookmark manipulated in the current buffer.")
4848 (defun xetla-bookmarks-edit ()
4849 "Edit the bookmark at point."
4851 (let* ((elem (ewoc-locate xetla-bookmarks-cookie))
4852 (data (ewoc-data elem)))
4853 (pop-to-buffer (concat "*xetla bookmark " (car data) "*"))
4856 (make-local-variable 'xetla-buffer-bookmark)
4857 (setq xetla-buffer-bookmark elem)
4858 (insert ";; Edit the current bookmark. C-c C-c to finish\n\n")
4859 (pp data (current-buffer))
4860 (goto-char (point-min)) (forward-line 2) (forward-char 2)
4861 (local-set-key [(control ?c) (control ?c)]
4862 #'(lambda () (interactive)
4863 (goto-char (point-min))
4864 (let* ((newval (read (current-buffer)))
4865 (elem xetla-buffer-bookmark)
4866 (oldname (car (ewoc-data elem))))
4867 (kill-buffer (current-buffer))
4868 (pop-to-buffer "*xetla-bookmarks*")
4869 (setcar (ewoc-data elem) (car newval))
4870 (setcdr (ewoc-data elem) (cdr newval))
4871 (let ((list xetla-bookmarks-alist)
4874 (if (string= (caar list) oldname)
4875 (setq newlist (cons newval newlist))
4876 (setq newlist (cons (car list) newlist)))
4877 (setq list (cdr list)))
4878 (setq xetla-bookmarks-alist (reverse newlist)))
4879 (xetla-bookmarks-save-to-file)
4880 (save-excursion (xetla-bookmarks))
4883 (defun xetla-bookmarks-get-partner-versions (version)
4884 "Return version lists of partners in bookmarks for VERSION.
4885 Each version in the returned list has a list form.
4886 If no partner, return nil.
4887 VERSION is a fully qualified version string or a list."
4888 (xetla-bookmarks-load-from-file)
4889 (when (consp version)
4890 (setq version (xetla-name-mask version t
4892 (let* ((bookmark (xetla-bookmarks-find-bookmark version))
4893 (groups (cdr (assoc 'groups bookmark)))
4894 (partners (delete nil (mapcar
4896 (when (intersection groups (cdr (assoc 'groups b)) :test 'string=)
4897 (cdr (assoc 'location b))
4899 xetla-bookmarks-alist))))
4906 (defun xetla-archives ()
4907 "Start the archive browser."
4909 (xetla-archive-tree-build-archives)
4910 (xetla-switch-to-buffer "*xetla-archives*")
4911 (let ((a-list xetla-archive-tree)
4912 (my-default-archive (xetla-my-default-archive))
4917 (toggle-read-only -1)
4920 (setq archive-name (caar a-list)
4921 archive-location (cadar a-list)
4923 defaultp (string= archive-name my-default-archive))
4924 (if defaultp (setq p (point)))
4925 (xetla-archives-insert-item archive-name archive-location defaultp))
4926 (if (> (point) (point-min))
4927 (delete-backward-char 1))
4928 (when p (goto-char p))
4929 (xetla-archive-list-mode)))
4931 (defun xetla-archives-insert-item (archive location defaultp)
4932 "Add an entry for ARCHIVE at LOCATION to the archive list.
4933 If DEFAULTP is non-nil, this item will be rendered as the default archive."
4934 (let ((start-pos (point))
4936 (insert (if defaultp xetla-mark " ")
4938 (xetla-face-add-with-condition
4940 archive 'xetla-marked 'xetla-archive-name))
4942 (insert " " location)
4944 (setq extent (make-extent start-pos (point)))
4945 (set-extent-property extent 'category 'xetla-default-button)
4946 (set-extent-property extent 'keymap xetla-archive-archive-map)
4947 (set-extent-property extent 'xetla-archive-info archive)))
4949 (defun xetla-archives-goto-archive-by-name (name)
4950 "Jump to the archive named NAME."
4951 (unless (string= (buffer-name) "*xetla-archives*")
4952 (error "`xetla-archives-goto-archive-by-name' can only be called in *xetla-archives* buffer"))
4953 (goto-char (point-min))
4954 (search-forward name)
4955 (beginning-of-line))
4957 (defun xetla-get-archive-info (&optional property)
4958 "Get some PROPERTY of the archive at point in an archive list buffer."
4960 (setq property 'xetla-archive-info))
4961 (let ((extent (car (extents-at (point)))))
4963 (extent-property extent property))))
4965 (defun xetla-my-default-archive (&optional new-default)
4966 "Set or get the default archive.
4967 When called with a prefix argument NEW-DEFAULT: Ask the user for the new
4969 If NEW-DEFAULT IS A STRING: Set the default archive to this string.
4970 When called with no argument: return the name of the default argument.
4971 When called interactively, with no argument: Show the name of the default archive."
4973 (when (or (numberp new-default) (and (listp new-default) (> (length new-default) 0)))
4974 (setq new-default (car (xetla-name-read nil 'prompt))))
4975 (cond ((stringp new-default)
4976 (message "Setting arch default archive to: %s" new-default)
4977 (xetla-run-tla-sync (list "my-default-archive" new-default)
4978 :finished 'xetla-null-handler))
4980 (xetla-run-tla-sync '("my-default-archive")
4982 `(lambda (output error status arguments)
4983 (let ((result (xetla-buffer-content output)))
4984 (when ,(interactive-p)
4985 (message "Default arch archive: %s"
4989 `(lambda (output error status arguments)
4991 (if ,(interactive-p)
4992 (message "default archive not set")
4994 (xetla-default-error-function
4995 output error status arguments)))))))
4997 (defun xetla-whereis-archive (&optional archive)
4998 "Call xetla whereis-archive on ARCHIVE."
5002 (setq archive (xetla-name-mask (xetla-name-read "Archive: " 'prompt)
5006 (xetla-run-tla-sync (list "whereis-archive" archive)
5008 (lambda (output error status arguments)
5009 (xetla-buffer-content output))))
5010 (when (interactive-p)
5011 (message "archive location for %s: %s" archive location))
5014 (defun xetla-read-location (prompt)
5015 "Read the location for an archive operation, prompting with PROMPT.
5016 The following forms are supported:
5017 * local path: e.g.: ~/archive2004
5018 * ftp path: e.g.: ftp://user:passwd@host.name.com/remote-path
5019 * sftp path: e.g.: sftp://user:passwd@host.name.com/remote-path
5020 * HTTP/WebDAV path: e.g.: http://user:passwd@host.name.com/remote-path"
5021 (read-string prompt (ffap-url-at-point)))
5023 (defun xetla-register-archive ()
5024 "Call `xetla-register-archive-internal' interactively and `xetla-archives' on success."
5026 (let* ((result (call-interactively 'xetla-register-archive-internal))
5027 (archive-registered (nth 0 result))
5028 (archive (nth 1 result))
5029 (xetla-response (nth 3 result)))
5030 (when archive-registered
5032 (xetla-archives-goto-archive-by-name
5034 (message xetla-response) ; inform the user about the response from xetla
5035 (if (string-match ".+: \\(.+\\)" xetla-response)
5036 (match-string 1 xetla-response)
5038 (xetla-flash-line))))
5040 (defun xetla-register-archive-internal (location &optional archive)
5041 "Register arch archive.
5042 LOCATION should be either a local directory or a remote path.
5043 When ffap is available the url at point is suggested for LOCATION.
5044 ARCHIVE is the name is archive. If ARCHIVE is not given or an empty string,
5045 the default name is used.
5046 The return value is a list.
5047 - The first element shows whether the archive is registered or not; t means that
5048 it is registered, already means that the archive was already
5049 registered, and nil means that it is not registered.
5050 - The second element shows archive name.
5051 - The third element shows archive location.
5052 - The fourth element is the command output string."
5053 (interactive (list (xetla-read-location "Location: ")
5054 (read-string "Archive (empty for default): ")))
5055 (if (and archive (eq 0 (length archive)))
5057 (let ((archive-registered nil)
5058 (xetla-response nil))
5059 (xetla-run-tla-sync (list "register-archive" archive location)
5061 (lambda (output error status arguments)
5062 (setq xetla-response (xetla-get-process-output))
5063 (setq archive-registered t)
5064 (message "Registered archive %s (=> %s)" archive location))
5066 (lambda (output error status arguments)
5067 (setq xetla-response (xetla-get-error-output))
5068 (when (eq status 2) ;; already registered
5069 (setq archive-registered 'already))))
5070 (list archive-registered archive location xetla-response)))
5072 (defun xetla-unregister-archive (archive ask-for-confirmation)
5073 "Delete the registration of ARCHIVE.
5074 When ASK-FOR-CONFIRMATION is non nil, ask the user for confirmation."
5075 (unless (xetla-archive-tree-get-archive archive)
5076 (xetla-archive-tree-build-archives))
5077 (let ((location (cadr (xetla-archive-tree-get-archive archive))))
5078 (when (or (not ask-for-confirmation)
5079 (yes-or-no-p (format "Delete the registration of %s(=> %s)? " archive location)))
5081 (list "register-archive" "--delete" archive)
5083 (lambda (output error status arguments)
5084 (message "Deleted the registration of %s (=> %s)" archive location))))))
5086 (defun xetla-edit-archive-location (archive)
5087 "Edit the location of ARCHIVE."
5088 (let* ((old-location (xetla-whereis-archive archive))
5089 (new-location (read-string (format "New location for %s: " archive) old-location)))
5090 (unless (string= old-location new-location)
5091 (xetla-unregister-archive archive nil)
5092 (xetla-register-archive-internal new-location archive))))
5095 (defun xetla-make-archive ()
5096 "Call `xetla-make-archive-internal' interactively then call `xetla-archives'."
5098 (call-interactively 'xetla-make-archive-internal)
5101 (defun xetla-make-archive-internal (name location &optional signed listing)
5102 "Create a new arch archive.
5103 NAME is the global name for the archive. It must be an
5104 email address with a fully qualified domain name, optionally
5105 followed by \"--\" and a string of letters, digits, periods
5107 LOCATION specifies the path, where the archive should be created.
5109 Examples for name are:
5110 foo.bar@flups.com--public
5111 foo.bar@flups.com--public-2004
5113 If SIGNED is non-nil, the archive will be created with -signed.
5114 If LISTING is non-nil, the archive will be created with -listing
5115 (Usefull for http mirrors)."
5117 (list (read-string "Archive name: ")
5120 (while (not path-ok)
5121 (setq location (xetla-read-location "Location: "))
5123 (when (eq 'local (xetla-location-type location))
5124 (setq location (expand-file-name location))
5125 (when (file-directory-p location)
5126 (message "directory already exists: %s" location)
5129 (when (not (file-directory-p
5130 (file-name-directory location)))
5131 (message "parent directory doesn't exists for %s"
5136 (y-or-n-p "Sign the archive? ")
5137 (y-or-n-p "Create .listing files? ")))
5138 (xetla-run-tla-sync (list "make-archive"
5139 (when listing "--listing")
5140 (when signed "--signed")
5143 (lambda (output error status arguments)
5144 (xetla-show-error-buffer error)
5145 (xetla-show-last-process-buffer)
5146 (error (format "xetla failed: exits-status=%s"
5149 (defun xetla-mirror-archive (&optional archive location mirror signed
5151 "Create a mirror for ARCHIVE, at location LOCATION, named MIRROR.
5152 If SIGNED is non-nil, the archive will be signed.
5153 If LISTING is non-nil, .listing files will be created (useful for HTTP
5156 (let* ((archive (or archive (car (xetla-name-read "Archive to mirror: " 'prompt))))
5157 (location (or location (xetla-read-location
5158 (format "Location of the mirror for %s: " archive))))
5159 ;;todo: take a look ath the mirror-list, when suggesting a mirror name
5160 ;;(mirror-list (xetla-get-mirrors-for-archive archive))
5161 (mirror (or mirror (read-string "Name of the mirror: "
5164 (signed (or signed (y-or-n-p "Sign mirror? ")))
5165 (listing (or listing (y-or-n-p "Create .listing files? "))))
5166 (xetla-run-tla-sync (list "make-archive"
5167 (when listing "--listing")
5168 (when signed "--signed")
5170 archive mirror location))))
5172 (defun xetla-mirror-from-archive (&optional from-archive location)
5173 "Create a mirror-from archive for FROM-ARCHIVE, at location LOCATION.
5174 The archive name FROM-ARCHIVE must end with \"-SOURCE\"."
5176 (let* ((from-archive (or from-archive
5177 (car (xetla-name-read "Mirror from archive: " 'prompt))))
5178 (location (or location (read-string
5179 (format "Location of the mirror for %s : " from-archive)))))
5180 (unless (eq (xetla-archive-type from-archive) 'source)
5181 (error "%s is not SOURCE archive" from-archive))
5182 (xetla-run-tla-sync (list "make-archive"
5184 from-archive location))))
5186 (defun xetla-get-mirrors-for-archive (archive)
5187 "Get a list of all mirrors for the given ARCHIVE."
5188 (xetla-archive-tree-build-archives)
5189 (delete nil (mapcar '(lambda (elem)
5190 (let ((a-name (car elem)))
5191 (when (and (eq (xetla-archive-type a-name) 'mirror)
5193 (substring a-name 0 (length archive))))
5195 xetla-archive-tree)))
5197 ;; in xetla-browse use: (xetla-name-archive (xetla-widget-node-get-name))
5198 ;; to get the name of an archive.
5199 ;; in xetla-archives: use (xetla-get-archive-info)
5201 ;; (xetla-get-mirrors-for-archive (xetla-get-archive-info))
5202 ;; (xetla-get-mirrors-for-archive "xsteve@nit.at-public")
5204 (defun xetla-mirror-base-name (archive)
5205 "Return the base name of the mirror ARCHIVE."
5206 (when (eq (xetla-archive-type archive) 'mirror)
5207 (substring archive 0 (string-match "-MIRROR.*$" archive))))
5209 (defun xetla-use-as-default-mirror (archive)
5210 "Use the ARCHIVE as default mirror.
5211 This function checks, if ARCHIVE is a mirror (contains -MIRROR).
5212 The default mirror ends with -MIRROR. Other mirrors have some
5213 other characters after -MIRROR (e.g.: -MIRROR-2.
5214 This function swaps the location of that -MIRROR and the -MIRROR-2.
5215 The effect of the swapping is, that the mirroring functions work
5216 per default on the default mirror."
5217 (interactive (list (xetla-name-archive (xetla-name-read "Mirror archive name: " 'prompt))))
5218 (unless (eq (xetla-archive-type archive) 'mirror)
5219 (error "%s is not a mirror" archive))
5220 (if (string-match "-MIRROR$" archive)
5221 (message "%s is already the default mirror." archive)
5222 (let* ((archive-base-name (xetla-mirror-base-name archive))
5223 (mirror-list (xetla-get-mirrors-for-archive archive-base-name))
5224 (default-mirror (concat archive-base-name "-MIRROR"))
5225 (default-mirror-present (member default-mirror mirror-list))
5226 (archive-location (xetla-whereis-archive archive))
5227 (default-mirror-location (and default-mirror-present
5228 (xetla-whereis-archive default-mirror))))
5229 (if default-mirror-present
5230 (message "swapping mirrors %s <-> %s." archive default-mirror)
5231 (message "using %s as default mirror." archive))
5232 (xetla-unregister-archive archive nil)
5233 (when default-mirror-present
5234 (xetla-unregister-archive default-mirror nil))
5235 (xetla-register-archive-internal archive-location default-mirror)
5236 (when default-mirror-present
5237 (xetla-register-archive-internal default-mirror-location archive)))))
5240 (defun xetla-archive-convert-to-source-archive (archive &optional location)
5241 "Change the name of ARCHIVE to ARCHIVE-SOURCE.
5242 Sets the archive location to LOCATION."
5244 (setq location (nth 1 (xetla-archive-tree-get-archive archive))))
5246 (error "Location for `%s' is unknown" archive))
5247 (when (eq 'source (xetla-archive-type archive))
5248 (error "%s is already source" archive))
5249 ; (unless (eq 'http (xetla-location-type location))
5250 ; (error "Read only archive is supported in xetla: " location))
5251 (xetla-unregister-archive archive nil)
5252 (xetla-register-archive-internal location (concat archive "-SOURCE")))
5257 (defun xetla-categories (archive)
5258 "List the categories of ARCHIVE."
5259 (interactive (list (xetla-name-archive
5260 (xetla-name-read nil 'prompt))))
5262 (setq archive (xetla-my-default-archive)))
5263 (xetla-archive-tree-build-categories archive)
5264 (xetla-switch-to-buffer "*xetla-categories*")
5265 (let ((list (cddr (xetla-archive-tree-get-archive archive)))
5266 category start-pos extent)
5267 (toggle-read-only -1)
5269 ;; TODO: button to invoke xetla-archives.
5270 (insert (format "Archive: %s\n%s\n" archive
5271 (make-string (+ (length archive)
5272 (length "Archive: ")) ?=)))
5275 (setq category (car (car list))
5278 (insert " " (xetla-face-add category 'xetla-category-name))
5280 (setq extent (make-extent start-pos (point)))
5281 (set-extent-property extent 'category 'xetla-default-button)
5282 (set-extent-property extent 'keymap xetla-category-category-map)
5283 (set-extent-property extent 'xetla-category-info category)
5285 (delete-backward-char 1)))
5286 (xetla-category-list-mode)
5287 (set (make-local-variable 'xetla-buffer-archive-name)
5290 (defun xetla-make-category (archive category)
5291 "In ARCHIVE, create CATEGORY."
5292 (interactive (let ((l (xetla-name-read "New Category: " 'prompt 'prompt)))
5293 (list (xetla-name-archive l)
5294 (xetla-name-category l))))
5295 (xetla-run-tla-sync (list "make-category" "-A" archive category))
5296 (let ((xetla-buffer-archive-name archive))
5297 (run-hooks 'xetla-make-category-hook)))
5302 (defun xetla-branches (archive category)
5303 "Display the branches of ARCHIVE/CATEGORY."
5304 (interactive (let ((l (xetla-name-read nil 'prompt 'prompt)))
5305 (list (xetla-name-archive l)
5306 (xetla-name-category l))))
5307 (xetla-archive-tree-build-branches archive category)
5308 (xetla-switch-to-buffer "*xetla-branches*")
5309 (let ((list (cdr (xetla-archive-tree-get-category archive category)))
5315 (toggle-read-only -1)
5317 ;; TODO: button to invoke xetla-categories and xetla-archives
5318 (setq alength (+ (length archive) (length "Archive: "))
5319 clength (+ (length category) (length "Category: ")))
5320 (insert (format "Archive: %s\nCategory: %s\n%s\n" archive category
5321 (make-string (max alength clength) ?=)))
5324 (setq branch (car (car list))
5327 (insert " " (xetla-face-add (if (string= branch "")
5329 'xetla-branch-name))
5331 (setq extent (make-extent start-pos (point)))
5332 (set-extent-property extent 'category 'xetla-default-button)
5333 (set-extent-property extent 'keymap xetla-branch-branch-map)
5334 (set-extent-property extent 'xetla-branch-info branch))
5335 (delete-backward-char 1)))
5336 (xetla-branch-list-mode)
5337 (set (make-local-variable 'xetla-buffer-archive-name)
5339 (set (make-local-variable 'xetla-buffer-category-name)
5342 (defun xetla-make-branch (archive category branch)
5343 "Make a new branch in ARCHIVE/CATEGORY called BRANCH."
5344 (interactive (let ((l (xetla-name-read "New Branch: "
5345 'prompt 'prompt 'prompt)))
5346 (list (xetla-name-archive l)
5347 (xetla-name-category l)
5348 (xetla-name-branch l))))
5349 (xetla-run-tla-sync (list "make-branch"
5350 (xetla-name-construct
5351 archive category branch)))
5352 (let ((xetla-buffer-archive-name archive)
5353 (xetla-buffer-category-name category))
5354 (run-hooks 'xetla-make-branch-hook)))
5359 (defun xetla-versions (archive category branch)
5360 "Display the versions of ARCHIVE/CATEGORY in BRANCH."
5361 (interactive (let ((l (xetla-name-read nil
5362 'prompt 'prompt 'prompt)))
5363 (list (xetla-name-archive l)
5364 (xetla-name-category l)
5365 (xetla-name-branch l))))
5366 (xetla-archive-tree-build-versions archive category branch)
5367 (xetla-switch-to-buffer "*xetla-versions*")
5368 (let ((list (cdr (xetla-archive-tree-get-branch
5369 archive category branch)))
5376 (toggle-read-only -1)
5378 ;; TODO: button to invoke xetla-categories and xetla-archives
5379 (setq alength (+ (length archive) (length "Archive: "))
5380 clength (+ (length category) (length "Category: "))
5381 blength (+ (length branch) (length "Branch: ")))
5382 (insert (format "Archive: %s\nCategory: %s\nBranch: %s\n%s\n"
5383 archive category branch
5384 (make-string (max alength clength blength) ?=)))
5387 (setq version (car (car list))
5390 (insert " " (xetla-face-add version 'xetla-version-name))
5392 (setq extent (make-extent start-pos (point)))
5393 (set-extent-property extent 'category 'xetla-default-button)
5394 (set-extent-property extent 'keymap xetla-version-version-map)
5395 (set-extent-property extent 'xetla-version-info version))
5396 (delete-backward-char 1)))
5397 (xetla-version-list-mode)
5398 (set (make-local-variable 'xetla-buffer-archive-name) archive)
5399 (set (make-local-variable 'xetla-buffer-category-name) category)
5400 (set (make-local-variable 'xetla-buffer-branch-name) branch))
5402 (defun xetla-make-version (archive category branch version)
5403 "In ARCHIVE/CATEGORY, add a version to BRANCH called VERSION."
5404 (interactive (let ((l (xetla-name-read "Version: "
5405 'prompt 'prompt 'prompt 'prompt)))
5406 (list (xetla-name-archive l)
5407 (xetla-name-category l)
5408 (xetla-name-branch l)
5409 (xetla-name-version l))))
5411 (xetla-run-tla-sync (list "make-version"
5412 (xetla-name-construct
5413 archive category branch version)))
5414 (let ((xetla-buffer-archive-name archive)
5415 (xetla-buffer-category-name category)
5416 (xetla-buffer-branch-name branch))
5417 (run-hooks 'xetla-make-version-hook)))
5424 ;; ('separator "string" kind)
5426 ;; ('entry-patch nil revision) Where "revision" is of xetla-revision
5428 ;; ('entry-change "changes")
5429 ;; The second element tells if the element is marked or not.
5430 (defun xetla-revision-list-printer (elem)
5431 "Print an element ELEM of the revision list."
5435 (let* ((struct (caddr elem))
5436 (merged-by (xetla-revision-merged-by struct))
5437 (unmerged (eq merged-by 'nobody)))
5438 (insert (if (cadr elem) (concat " " xetla-mark) " ")
5439 ;; The revision is in library?
5440 (if (and xetla-revisions-shows-library
5441 (apply 'xetla-revlib-tree-get-revision
5442 (xetla-revision-revision struct)))
5444 ;; (apply 'xetla-library-find
5445 ;; (append (caddr elem) '(t))
5448 (xetla-face-add (xetla-name-construct
5449 (xetla-revision-revision struct))
5450 (if unmerged 'xetla-unmerged
5451 'xetla-revision-name)
5452 'xetla-revision-revision-map
5453 xetla-revision-revision-menu)
5454 (if unmerged (xetla-face-add " [NOT MERGED]"
5457 (let ((summary (xetla-revision-summary struct))
5458 (creator (xetla-revision-creator struct))
5459 (date (xetla-revision-date struct)))
5460 (when (and summary xetla-revisions-shows-summary)
5461 (insert "\n " summary))
5462 (when (and creator xetla-revisions-shows-creator)
5463 (insert "\n " creator))
5464 (when (and date xetla-revisions-shows-date)
5465 (insert "\n " date)))
5466 (when (and xetla-revisions-shows-merges
5467 (xetla-revision-merges struct)
5468 (not (null (car (xetla-revision-merges struct)))))
5469 (insert "\n Merges:")
5470 (dolist (elem (xetla-revision-merges struct))
5471 (insert "\n " elem)))
5472 (when xetla-revisions-shows-merged-by
5473 (cond ((null merged-by) nil)
5475 (insert "\n Merged-by:")
5476 (dolist (elem merged-by)
5477 (insert "\n " elem)))))))
5478 (entry-change (insert (cadr elem)))
5479 (message (insert (xetla-face-add (cadr elem)
5483 (partner (insert "\n" (xetla-face-add (cadr elem)
5485 (bookmark (insert "\n" (xetla-face-add
5489 'xetla-separator) "\n")))))))
5491 (defun xetla-get-current-revision (&optional directory)
5492 "Return the current revision in DIRECTORY."
5494 (let* ((directory (or directory
5495 (xetla-read-project-tree-maybe
5496 "Get current revision in: ")))
5497 (revision (shell-command-to-string
5498 (concat "tla revisions --full "
5499 (xetla-tree-version directory)
5505 (defun xetla-tree-revisions ()
5506 "Call `xetla-revisions' in the current tree."
5508 (let* ((default-directory (xetla-read-project-tree-maybe
5509 "Run tla revisions in: "))
5510 (version (xetla-tree-version-list)))
5512 (error "Not in a project tree"))
5513 (apply 'xetla-revisions version)))
5516 (defun xetla-revisions (archive category branch version
5517 &optional update-display from-revlib)
5518 "List the revisions of ARCHIVE/CATEGORY-BRANCH-VERSION."
5519 (interactive (let ((l (xetla-name-read "Version: " 'prompt 'prompt 'prompt 'prompt)))
5521 (xetla-name-archive l)
5522 (xetla-name-category l)
5523 (xetla-name-branch l)
5524 (xetla-name-version l))))
5525 ;; TODO: Consdider the case where (and update-display from-revlib) is t.
5526 (unless (and update-display
5527 (or (xetla-revisions-tree-contains-details
5528 archive category branch version)
5529 (not (or xetla-revisions-shows-summary
5530 xetla-revisions-shows-creator
5531 xetla-revisions-shows-date))))
5533 (xetla-revlib-tree-build-revisions archive category branch version)
5534 (xetla-archive-tree-build-revisions archive category branch version)))
5535 (xetla-switch-to-buffer "*xetla-revisions*")
5536 (let ((list (cdr (if from-revlib
5537 (xetla-revlib-tree-get-version
5538 archive category branch version)
5539 (xetla-archive-tree-get-version
5540 archive category branch version))))
5547 (xetla-revision-list-mode)
5548 (toggle-read-only -1)
5549 (set (make-local-variable 'xetla-buffer-refresh-function)
5550 'xetla-revision-refresh)
5551 (setq separator (xetla-face-add
5553 (max (+ (length archive) (length "Archive: "))
5554 (+ (length category) (length "Category: "))
5555 (+ (length branch) (length "Branch: "))
5556 (+ (length version) (length "Version: ")))
5559 (ewoc-set-hf xetla-revision-list-cookie
5560 (xetla-revisions-header archive category branch version
5561 from-revlib separator)
5562 (concat "\n" separator))
5563 (if xetla-revisions-shows-library
5564 (xetla-revlib-tree-build-revisions
5565 archive category branch version nil t))
5567 (setq revision (car (car list))
5568 summary (car (cdr (car list)))
5569 creator (car (cddr (car list)))
5570 date (car (cdddr (car list)))
5572 (ewoc-enter-last xetla-revision-list-cookie
5573 (list 'entry-patch nil
5574 (make-xetla-revision
5575 :revision (list archive
5585 (goto-char (point-min))
5586 (re-search-forward "^$")
5588 (setq first (point)))
5591 (set (make-local-variable 'xetla-buffer-archive-name) archive)
5592 (set (make-local-variable 'xetla-buffer-category-name) category)
5593 (set (make-local-variable 'xetla-buffer-branch-name) branch)
5594 (set (make-local-variable 'xetla-buffer-version-name) version)
5595 (toggle-read-only t))
5597 (defun xetla-revisions-header (archive category branch version from-revlib separator)
5598 "Construct a header for the revision ARCHIVE/CATEGORY-BRANCH-VERSION.
5599 Mark the revision as contained in FROM-REVLIB and use SEPARATOR to separate
5603 (xetla-face-add archive 'xetla-archive-name) "/"
5604 (xetla-face-add category 'xetla-category-name) "--"
5605 (xetla-face-add branch 'xetla-branch-name) "--"
5606 (xetla-face-add version 'xetla-version-name) "\n"
5607 "In Revision Library: " (xetla-face-add (if from-revlib "Yes" "No") 'bold)
5613 (defun xetla-missing (local-tree location)
5614 "Search in directory LOCAL-TREE for missing patches from LOCATION.
5615 If the current buffers default directory is in an arch managed tree use that
5616 one unless called with a prefix arg. In all other cases prompt for the local
5617 tree and the location."
5618 (interactive (let ((dir
5619 (or (if (not current-prefix-arg)
5620 (xetla-tree-root nil t))
5622 (read-directory-name
5623 "Search missing patches in directory: "
5624 default-directory default-directory t nil)))))
5626 (let ((default-directory dir))
5627 (if current-prefix-arg
5630 'prompt 'prompt 'prompt 'prompt)
5631 (xetla-tree-version))))))
5632 (let ((dir (xetla-tree-root)))
5633 (pop-to-buffer (xetla-get-buffer-create 'missing))
5635 (xetla-revision-list-mode)
5636 (set (make-local-variable 'xetla-buffer-refresh-function)
5637 'xetla-missing-refresh)
5638 (set (make-local-variable 'xetla-missing-buffer-todolist)
5639 `((missing ,local-tree ,(xetla-name-construct location) nil)))
5640 (xetla-missing-refresh))
5644 ;; Rbrowse interface
5646 (defun xetla-browse-archive (archive)
5649 The interface is rather poor, but xetla-browse does a better job
5651 (interactive (let ((l (xetla-name-read nil 'prompt)))
5652 (list (xetla-name-archive l))))
5654 (setq archive (xetla-my-default-archive)))
5655 (xetla-run-tla-sync (list "rbrowse" "-A" archive)))
5657 (defun xetla-read-config-file (prompt-tree prompt-file)
5658 "Interactively read the arguments of `xetla-build-config'and `xetla-cat-config'.
5660 The string PROMPT-TREE will be used when prompting the user for a tree.
5661 The string PROMPT-FILE will be used when prompting the user for a file."
5662 (let* ((tree-root (xetla-uniquify-file-name
5663 (xetla-read-project-tree-maybe
5666 (and buffer-file-name
5667 (replace-regexp-in-string
5668 (concat "^" (regexp-quote tree-root))
5672 (replace-regexp-in-string
5673 (concat "^" (regexp-quote tree-root))
5676 (read-file-name prompt-file
5678 current-file-name)))))
5679 (when (file-name-absolute-p relative-conf-file)
5680 ;; The replace-regexp-in-string failed.
5681 (error "Configuration file must be in a %s"
5682 "subdirectory of tree-root"))
5683 (list tree-root relative-conf-file)))
5685 (defun xetla-build-config (tree-root config-file)
5686 "Run tla build-config in TREE-ROOT, outputting to CONFIG-FILE.
5687 CONFIG-FILE is the relative path-name of the configuration.
5689 When called interactively, arguments are read with the function
5690 `xetla-read-project-tree-maybe'."
5691 (interactive (xetla-read-config-file "Build configuration in directory: "
5692 "Build configuration: "))
5693 (let ((default-directory tree-root))
5694 (xetla-run-tla-async (list "build-config" config-file))))
5696 (defun xetla-cat-config (tree-root config-file snap)
5697 "Run tla cat-config in TREE-ROOT, showing CONFIG-FILE.
5698 If SNAP is non-nil, then the --snap option of tla is used.
5700 When called interactively, arguments TREE-ROOT and CONFIG-FILE are
5701 read with the function `xetla-read-project-tree-maybe'."
5702 (interactive (append (xetla-read-config-file "Cat configuration in directory: "
5703 "Cat configuration: ")
5704 (list (y-or-n-p "Include revision number? "))))
5705 (let ((default-directory tree-root))
5706 (xetla-run-tla-async
5707 (list "cat-config" (when snap "--snap") config-file))))
5712 (defun xetla-get (directory run-dired-p archive category branch
5713 &optional version revision synchronously)
5714 "Run tla get in DIRECTORY.
5715 If RUN-DIRED-P is non-nil, display the new tree in dired.
5716 ARCHIVE, CATEGORY, BRANCH, VERSION and REVISION make up the revision to be
5718 If SYNCHRONOUSLY is non-nil, run the process synchronously.
5719 Else, run the process asynchronously."
5720 ;; run-dired-p => t, nil, ask
5721 (interactive (let* ((l (xetla-name-read "Get: "
5722 'prompt 'prompt 'prompt 'maybe 'maybe))
5723 (name (xetla-name-construct l))
5724 (d (read-directory-name (format "Store \"%s\" to: " name))))
5725 (cons d (cons 'ask l))))
5726 (setq directory (expand-file-name directory))
5727 (if (file-exists-p directory)
5728 (error "Directory %s already exists" directory))
5729 (let* ((name (xetla-name-construct
5731 ;; the name element are given in interactive form
5733 ;; not interactive, but revision(and maybe version) is
5734 ;; passed tothis function.
5735 (and revision (stringp revision)))
5736 (list archive category branch version revision)
5737 (xetla-name-read "Version-Revision for Get(if necessary): "
5738 archive category branch
5739 (if version version 'maybe)
5741 (funcall (if synchronously 'xetla-run-tla-sync 'xetla-run-tla-async)
5742 (list "get" "-A" archive name directory)
5743 :finished `(lambda (output error status arguments)
5744 (let ((i (xetla-status-handler output error status arguments)))
5746 (xetla-get-do-bookmark ,directory ,archive ,category ,branch ,version)
5747 (xetla-do-dired ,directory ',run-dired-p)))))))
5749 (defun xetla-get-do-bookmark (directory archive category branch version)
5750 "Add DIRECTORY to the bookmark for ARCHIVE/CATEGORY-BRANCH-VERSION."
5751 (let ((bookmark (xetla-bookmarks-find-bookmark
5752 (xetla-name-construct
5753 archive category branch version))))
5755 (xetla-bookmarks-add-tree bookmark directory))))
5757 (defun xetla-do-dired (directory run-dired-p)
5758 "Possible run dired in DIRECTORY.
5759 If RUN-DIRED-P is 'ask, ask the user whether to run dired.
5760 If RUN-DIRED-P is nil, do not run dired.
5761 Otherwise, run dired."
5762 (setq directory (expand-file-name directory))
5764 (ask (when (y-or-n-p (format "Run dired at %s? " directory))
5767 (t (dired directory))))
5773 ;; - provide the way to run interactively
5776 (defun xetla-cache-revision (archive category branch version revision)
5777 "Cache the revision named by ARCHIVE/CATEGORY-BRANCH-VERSION-REVISION."
5778 (interactive (xetla-name-read "Revision to cache: "
5779 'prompt 'prompt 'prompt 'prompt 'prompt))
5780 (let ((result (xetla-run-tla-async (list "cacherev"
5781 (xetla-name-construct
5782 archive category branch version revision)))))
5783 ;; (xetla-show-last-process-buffer)
5789 (defun xetla-add-id (id &rest files)
5790 "Using ID, add FILES to this tree."
5791 (interactive (let ((name
5792 (read-file-name "Add file as source: "
5794 (file-name-nondirectory (or
5795 (buffer-file-name) ""))))
5796 (id (read-string "id (empty for default): ")))
5798 (if (and id (string= id ""))
5800 (setq files (mapcar 'expand-file-name files))
5801 (let* ((arch-ver (or xetla-arch-version-number
5802 (xetla-arch-version-number)))
5804 (cond ((> 2 (or (cdr-safe (assoc 'minor arch-ver)) 0))
5808 (xetla-run-tla-sync `(,add-id-string "--id" ,id . ,files)
5809 :finished 'xetla-null-handler)
5810 (xetla-run-tla-sync `(,add-id-string . ,files)
5811 :finished 'xetla-null-handler))))
5813 (defalias 'xetla-add 'xetla-add-id)
5818 (defun xetla-remove (only-id &rest files)
5819 "Remove the ids of FILES, possibly also deleting the files.
5820 If ONLY-ID is non-nil, remove the files as well as their ids. Otherwise,
5821 just remove the ids."
5822 (interactive (let* ((name
5823 (read-file-name "Remove file: "
5825 (file-name-nondirectory (or
5826 (buffer-file-name) ""))))
5827 (only-id (not (y-or-n-p (format
5828 "Delete the \"%s\" locally also? "
5830 (list only-id name)))
5831 (setq files (mapcar 'expand-file-name files))
5833 (when (equal 0 (xetla-run-tla-sync (list "id" "--explicit" f)
5834 :finished 'xetla-status-handler
5835 :error 'xetla-status-handler))
5836 (xetla-run-tla-sync (list "delete-id" f)
5837 :finished 'xetla-status-handler))
5844 (defun xetla-move (from to only-id)
5845 "Move the file FROM to TO.
5846 If ONLY-ID is non-nil, move only the ID file."
5848 (list (read-file-name "Move file: "
5850 (file-name-nondirectory
5851 (or (buffer-file-name) "")))
5853 (setq to (or to (read-file-name (format "Move file %S to: " from)
5854 nil nil nil (file-name-nondirectory from)))
5855 only-id (if (eq only-id 'ask)
5856 (not (y-or-n-p "Move the file locally also? "))
5858 from (expand-file-name from)
5859 to (expand-file-name to))
5860 (let ((buffer (get-file-buffer from))
5861 (cmd (if only-id "move-id" "mv")))
5865 (set-visited-file-name to)))
5866 (xetla-run-tla-sync (list cmd from to)
5868 `(lambda (output error status arguments)
5869 (let ((buf (find-buffer-visiting ,from)))
5871 (with-current-buffer buf
5872 (rename-buffer (file-name-nondirectory
5874 (set-visited-file-name ,to))))
5877 (defalias 'xetla-mv 'xetla-move)
5882 (defun xetla-update (tree &optional handle)
5883 "Run tla update in TREE.
5885 After running update, execute HANDLE (function taking no argument)."
5886 (interactive (list (expand-file-name
5887 (read-directory-name "Update tree: " nil nil nil ""))))
5888 (or (xetla-save-some-buffers tree)
5890 "Update may delete unsaved changes. Continue anyway? ")
5891 (error "Not updating"))
5892 (let* ((default-directory (or tree default-directory))
5893 (buffer (xetla-prepare-changes-buffer
5894 (list 'last-revision default-directory)
5895 (list 'local-tree default-directory)
5896 'changes default-directory)))
5897 (when xetla-switch-to-buffer-first
5898 (xetla-switch-to-buffer buffer))
5899 (xetla-run-tla-async `("update")
5900 :finished `(lambda (output error status arguments)
5901 ;; (xetla-show-last-process-buffer)
5902 (xetla-show-changes-buffer
5904 (message "`tla update' finished")
5905 (xetla-revert-some-buffers ,tree)
5906 (when ,handle (funcall ,handle)))
5908 (lambda (output error status arguments)
5909 (xetla-show-error-buffer error)
5910 (xetla-show-last-process-buffer)
5912 (xetla-revert-some-buffers tree)))
5918 (defun xetla-start-project (&optional archive synchronously)
5919 "Start a new project.
5920 Prompts for the root directory of the project and the fully
5921 qualified version name to use. Sets up and imports the tree and
5922 displays an inventory buffer to allow the project's files to be
5923 added and committed.
5924 If ARCHIVE is given, use it when reading version.
5925 Return a cons pair: its car is the new version name string, and
5926 its cdr is imported location.
5927 If SYNCHRONOUSLY is non-nil, run \"tla import\" synchronously.
5928 Else run it asynchronously."
5930 (let* ((base (read-directory-name "Directory containing files to import: "
5931 (or default-directory
5933 (l (xetla-name-read (format "Import `%s' to: " base)
5934 (if archive archive (xetla-my-default-archive))
5935 'prompt 'prompt 'prompt))
5936 (project (xetla-name-construct l)))
5937 (let ((default-directory (file-name-as-directory base)))
5938 (xetla-run-tla-sync (list "init-tree" project))
5940 (xetla-inventory default-directory)
5941 (message "Type %s when ready to import"
5942 (substitute-command-keys "\\[exit-recursive-edit]"))
5944 (funcall (if synchronously 'xetla-run-tla-sync 'xetla-run-tla-async)
5945 (list "import" "--setup")
5947 `(lambda (output error status arguments)
5948 (xetla-inventory ,base t)))
5949 (cons project default-directory))))
5951 (defvar xetla-partner-file-precious "/{arch}/+partner-versions"
5952 "Precious version of the partner file.
5953 We strongly suggest keeping the default value since this is a
5954 convention used by other xetla front-ends like Aba.")
5956 (defvar xetla-partner-file-source "/{arch}/=partner-versions"
5957 "Source version of the partner file.
5958 We strongly suggest keeping the default value since this is
5959 a convention used by other xetla front-ends like Aba.")
5961 ;; --------------------------------------
5962 ;; xetla partner stuff
5963 ;; --------------------------------------
5964 (defun xetla-partner-find-partner-file (&optional local-tree)
5965 "Do `find-file' xetla-partners file and return the buffer.
5966 If the file `xetla-partner-file-precious' exists, it is used in priority.
5967 Otherwise,use `xetla-partner-file-source'. The precious one is meant for user
5968 configuration, whereas the source one is used for project-wide
5969 configuration. If LOCAL-TREE is not managed by arch, return nil."
5971 (let ((default-directory (or local-tree
5972 (xetla-tree-root default-directory t))))
5973 (let* ((partner-file
5974 (cond ((not default-directory) nil)
5975 ((file-exists-p (concat (xetla-tree-root)
5976 xetla-partner-file-precious))
5977 (concat (xetla-tree-root) xetla-partner-file-precious))
5978 (t (concat (xetla-tree-root)
5979 xetla-partner-file-source))))
5980 (buffer-visiting (and partner-file (find-buffer-visiting partner-file))))
5982 (with-current-buffer buffer-visiting
5983 (if (buffer-modified-p)
5984 (if (progn (switch-to-buffer (current-buffer))
5985 (y-or-n-p (format "Save file %s? "
5986 (buffer-file-name))))
5991 (find-file-noselect partner-file))))))
5994 (defun xetla-partner-add (partner &optional local-tree)
5995 "Add a partner for this xetla working copy.
5996 Return nil if PARTNER is alerady in partners file.
5997 Look for the parners file in LOCAL-TREE.
5998 For example: Franz.Lustig@foo.bar-public/xetla-main-0.1"
5999 (interactive (list (xetla-name-construct
6001 "Version to Add Partner File: "
6002 'prompt 'prompt 'prompt 'prompt))))
6003 (let ((list (xetla-partner-list local-tree)))
6004 (if (member partner list)
6006 (with-current-buffer (xetla-partner-find-partner-file)
6007 (goto-char (point-min))
6013 (defun xetla-partner-list (&optional local-tree)
6014 "Read the partner list from partner files in LOCAL-TREE.
6015 If LOCAL-TREE is nil, use the `xetla-tree-root' of `default-directory' instead.
6016 If LOCAL-TREE is not managed by arch, return nil."
6017 (let ((buffer (xetla-partner-find-partner-file local-tree)))
6019 (with-current-buffer buffer
6020 (let ((partners (split-string (buffer-substring (point-min) (point-max)) "\n")))
6021 (remove "" partners))))))
6023 (defun xetla-partner-member (version &optional local-tree)
6024 "Predicate to check whether VERSION is in the partners file in LOCAL-TREE."
6025 (let ((list (xetla-partner-list local-tree)))
6026 (member version list)))
6028 (defun xetla-partner-read-version (&optional prompt including-self)
6029 "Specialized version for `xetla-name-read' to read a partner.
6030 - This function displays PROMPT, reads an archive/category-branch-version,
6032 - Return the result in a string form (not in a list form) and
6033 - Ask to the user whether adding the result to the partner file or not
6034 if the result is not in the partner file.
6036 If INCLUDING-SELF is non-nil, this function asks a question whether
6037 using self as partner or not. If the user answers `y' as the question,
6038 this function returns a symbol, `self'. If the user answers `n' as the
6039 question, this function runs as the same as if INCLUDING-SELF is nil."
6040 (unless prompt (setq prompt "Enter Xetla Partner: "))
6041 (if (and including-self
6042 (y-or-n-p "Select `self' as partner? "))
6044 (let ((version (xetla-name-construct
6047 'prompt 'prompt 'prompt 'prompt))))
6048 (when (and (not (xetla-partner-member version))
6049 (y-or-n-p (format "Add `%s' to Partner File? " version)))
6050 (xetla-partner-add version))
6053 ;; FIXME: Currently does nothing in XEmacs.
6054 (defun xetla-partner-create-menu (action &optional prompt)
6055 "Create the partner menu with ACTION using PROMPT as the menu name."
6056 (let ((list (xetla-partner-list)))
6057 (xetla-funcall-if-exists
6058 easy-menu-create-menu prompt
6061 (let ((v (make-vector 3 nil)))
6062 (aset v 0 item) ; name
6063 (aset v 1 `(,action ,item))
6064 (aset v 2 t) ; enable
6067 ;;(aset v 5 :selected)
6068 ;;(aset v 6 (if ...))
6072 ;; --------------------------------------
6073 ;; xetla-inventory-mode:
6074 ;; --------------------------------------
6076 (defun xetla-inventory-mode ()
6077 "Major Mode to show the inventory of a xetla working copy.
6079 This allows you to view the list of files in your local tree. You can
6080 display only some particular kinds of files with 't' keybindings:
6081 '\\<xetla-inventory-mode-map>\\[xetla-inventory-toggle-source]' to toggle show sources,
6082 '\\[xetla-inventory-toggle-precious]' to toggle show precious, ...
6084 Use '\\[xetla-inventory-mark-file]' to mark files, and '\\[xetla-inventory-unmark-file]' to unmark.
6085 If you commit from this buffer (with '\\[xetla-inventory-edit-log]'), then, the list of selected
6086 files in this buffer at the time you actually commit with
6087 \\<xetla-log-edit-mode-map>\\[xetla-log-edit-done].
6090 \\{xetla-inventory-mode-map}"
6092 ;; don't kill all local variables : this would clear the values of
6093 ;; xetla-inventory-display-*, and refresh wouldn't work well anymore.
6094 ;; (kill-all-local-variables)
6095 (use-local-map xetla-inventory-mode-map)
6096 (set (make-local-variable 'xetla-buffer-refresh-function)
6098 (make-local-variable 'xetla-buffer-marked-file-list)
6099 (easy-menu-add xetla-inventory-mode-menu)
6100 (setq major-mode 'xetla-inventory-mode)
6101 (setq mode-name "xetla-inventory")
6102 (setq mode-line-process 'xetla-mode-line-process)
6103 (set (make-local-variable 'xetla-get-file-info-at-point-function)
6104 'xetla-inventory-get-file-info-at-point)
6105 (set (make-local-variable 'xetla-generic-select-files-function)
6106 'xetla-inventory-select-files)
6107 (toggle-read-only 1)
6108 (run-hooks 'xetla-inventory-mode-hook))
6110 (defun xetla-inventory-cursor-goto (ewoc-inv)
6111 "Move cursor to the ewoc location of EWOC-INV."
6114 (progn (goto-char (ewoc-location ewoc-inv))
6116 (goto-char (point-min))))
6118 (defun xetla-inventory-next ()
6119 "Go to the next inventory item."
6121 (let* ((cookie xetla-inventory-cookie)
6122 (elem (ewoc-locate cookie))
6123 (next (or (ewoc-next cookie elem) elem)))
6124 (xetla-inventory-cursor-goto next)))
6126 (defun xetla-inventory-previous ()
6127 "Go to the previous inventory item."
6129 (let* ((cookie xetla-inventory-cookie)
6130 (elem (ewoc-locate cookie))
6131 (previous (or (ewoc-prev cookie elem) elem)))
6132 (xetla-inventory-cursor-goto previous)))
6134 (defun xetla-inventory-edit-log (&optional insert-changelog)
6135 "Wrapper around `xetla-edit-log', setting the source buffer to current buffer.
6136 If INSERT-CHANGELOG is non-nil, insert a changelog too."
6138 (xetla-edit-log insert-changelog (current-buffer)))
6140 (defun xetla-inventory-add-files (files)
6141 "Create explicit inventory ids for FILES."
6144 (if xetla-buffer-marked-file-list
6146 (unless (y-or-n-p (if (eq 1 (length xetla-buffer-marked-file-list))
6148 (car xetla-buffer-marked-file-list))
6149 (format "Add %s files? "
6150 (length xetla-buffer-marked-file-list))))
6151 (error "Not adding any file"))
6152 xetla-buffer-marked-file-list)
6153 (list (read-file-name "Add file: " default-directory
6155 (xetla-get-file-info-at-point))))))
6156 (apply 'xetla-add-id nil files)
6159 (defun xetla-inventory-remove-files (files id-only)
6160 "Remove explicit inventory ids of FILES.
6161 If ID-ONLY is nil, remove the files as well."
6164 (if xetla-buffer-marked-file-list
6166 (unless (yes-or-no-p
6167 (format "Remove %d MARKED file%s? "
6168 (length xetla-buffer-marked-file-list)
6169 (if (< (length xetla-buffer-marked-file-list) 2)
6171 (error "Not removing any file"))
6172 xetla-buffer-marked-file-list)
6173 (list (let ((file (xetla-get-file-info-at-point)))
6174 (if (yes-or-no-p (format "Remove %s? " file))
6176 (error "Not removing any file")))))))
6177 (list read-files (not (y-or-n-p (format "Delete %d %sfile%s also locally? "
6179 (if xetla-buffer-marked-file-list "MARKED " "")
6180 (if (< (length read-files) 2) "" "s")))))))
6181 (apply 'xetla-remove id-only files)
6184 (defun xetla-delete-file (file &optional recursive)
6185 "Delete FILE or directory, recursively if optional RECURSIVE is non-nil.
6186 RECURSIVE determines what to do with a non-empty directory. If RECURSIVE is:
6188 `always', delete recursively without asking.
6189 `top', ask for each directory at top level.
6190 Anything else, ask for each sub-directory."
6192 ;; This test is equivalent to
6193 ;; (and (file-directory-p fn) (not (file-symlink-p fn)))
6194 ;; but more efficient
6195 (if (not (eq t (car (file-attributes file))))
6197 (when (and recursive
6201 "^\\([^.]\\|\\.\\([^.]\\|\\..\\)\\).*")) ; Not empty.
6202 (or (eq recursive 'always)
6203 (yes-or-no-p (format "Recursive delete of %s "
6204 (dired-make-relative file)))))
6205 (if (eq recursive 'top) (setq recursive 'always)) ; Don't ask again.
6206 (while files ; Recursively delete (possibly asking).
6207 (xetla-delete-file (car files) recursive)
6208 (setq files (cdr files))))
6209 (delete-directory file))))
6211 (defun xetla-inventory-delete-files (files no-questions)
6212 "Delete FILES locally.
6213 This is here for convenience to delete left over, temporary files or files
6214 avoiding a commit or conflicting with tree-lint.
6216 It is not meant to delete xetla managed files, i.e. files with IDs will be
6217 passed to `xetla-inventory-remove-files'!
6219 When called with a prefix arg NO-QUESTIONS, just delete the files."
6222 (if xetla-buffer-marked-file-list
6224 (or current-prefix-arg
6225 (unless (yes-or-no-p
6226 (format "Delete %d files permanently? "
6227 (length xetla-buffer-marked-file-list)))
6228 (error "Not deleting any files")))
6229 xetla-buffer-marked-file-list)
6230 (if (or current-prefix-arg
6231 (yes-or-no-p (format "Delete file %S permanently? "
6232 (xetla-get-file-info-at-point))))
6233 (list (xetla-get-file-info-at-point))))
6234 current-prefix-arg))
6236 (let ((f (car files)))
6237 (if (= 0 (xetla-run-tla-sync (list "id" f)
6238 :finished 'xetla-status-handler
6239 :error 'xetla-status-handler))
6240 (if (or no-questions
6241 (y-or-n-p (format (concat "File %s is arch managed! "
6242 "Delete it with its id?") f)))
6243 (xetla-inventory-remove-files (list f) nil))
6244 (if (file-directory-p f)
6246 (delete-directory f)
6248 (if (or no-questions
6249 (y-or-n-p (format "Delete non-empty directory %S? " f)))
6250 (xetla-delete-file f 'always))))
6252 (setq files (cdr files)))
6253 (if xetla-buffer-marked-file-list
6254 (setq xetla-buffer-marked-file-list nil))
6257 (defun xetla-inventory-move ()
6258 "Rename file at the current point and update its inventory id if present."
6260 (if (eq 0 (xetla-move (xetla-get-file-info-at-point) nil 'ask))
6261 (xetla-generic-refresh)
6262 (xetla-show-last-process-buffer)))
6264 (defun xetla-inventory-revert ()
6265 "Reverts file at point."
6267 (let* ((file (xetla-get-file-info-at-point))
6268 (absolute (if (file-name-absolute-p file)
6271 (concat (file-name-as-directory
6272 default-directory) file)))))
6273 (xetla-file-revert absolute)))
6275 (defun xetla-inventory-undo (specify-revision)
6276 "Undo whole local tree associated with the current inventory buffer.
6277 If prefix arg, SPECIFY-REVISION is non-nil, read a revision and use it to undo.
6278 The changes are saved in an ,,undo directory. You can restore them again via
6279 `xetla-inventory-redo'."
6281 (let* ((tree (xetla-tree-root default-directory t))
6282 (revision (if specify-revision
6283 (xetla-read-revision-with-default-tree
6284 "Undo against archive: "
6286 (list nil nil nil nil nil))))
6287 (apply 'xetla-undo-internal (cons tree revision))))
6289 (defun xetla-inventory-maybe-undo-directory ()
6290 "Return the directory name under point if it may be an ,,undo-? directory.
6291 Return nil otherwise."
6292 (car (member (expand-file-name (xetla-get-file-info-at-point))
6293 (xetla-get-undo-changeset-names))))
6295 (defun xetla-inventory-redo ()
6296 "Redo whole local tree associated with the current inventory buffer.
6297 This function restores the saved changes from `xetla-inventory-undo'."
6299 (xetla-redo (xetla-inventory-maybe-undo-directory)))
6301 (defun xetla-file-has-conflict-p (file-name)
6302 "Return non-nil if FILE-NAME has conflicts."
6303 (let ((rej-file-name (concat default-directory
6304 (file-name-nondirectory file-name)
6306 (file-exists-p rej-file-name)))
6308 (defun xetla-inventory-find-file ()
6309 "Visit the current inventory file."
6311 (let* ((file (xetla-get-file-info-at-point)))
6314 (error "No file at point"))
6315 ((eq t (car (file-attributes file))) ; file is a directory
6316 (xetla-inventory (expand-file-name file)))
6318 (find-file file)))))
6320 (defun xetla-inventory-parent-directory ()
6321 "Go to parent directory in inventory mode."
6323 (xetla-inventory (expand-file-name "..")))
6325 (defun xetla-inventory-mirror ()
6326 "Create a mirror of version of the current tree."
6328 (let ((tree-version (xetla-tree-version-list)))
6329 (xetla-archive-mirror (xetla-name-archive tree-version)
6330 (xetla-name-category tree-version)
6331 (xetla-name-branch tree-version)
6332 (xetla-name-version tree-version))))
6334 (defun xetla-inventory-star-merge (&optional merge-partner)
6335 "Run tla star-merge.
6336 Either use a partner in the tree's \"++tla-partners\" file or ask the user
6338 (interactive (list (xetla-partner-read-version "Star-merge with: ")))
6339 (when (y-or-n-p (format "Star-merge with %s ? " merge-partner))
6340 (xetla-star-merge merge-partner)))
6342 (defun xetla-inventory-changes (summary)
6344 A prefix argument decides whether the user is asked for a diff partner
6345 and whether only a summary without detailed diffs will be shown.
6347 When called without a prefix argument: Show the changes for your tree.
6348 When called with C-u as prefix: Ask the user for a diff partner via `xetla-partner-read-version'.
6349 When called with a negative prefix: Show only a summary of the changes.
6350 When called with C- C-u as prefix: Ask the user for a diff partner, show only change summary."
6352 (let* ((ask-for-compare-partner (and summary (listp summary)))
6353 (compare-partner (if ask-for-compare-partner
6354 (xetla-partner-read-version
6355 "Compare with (default is your tree): "
6358 (if (eq 'self compare-partner)
6359 (setq compare-partner nil)
6360 (setq compare-partner (list 'revision (xetla-name-split compare-partner))))
6361 (when (listp summary)
6362 (setq summary (car summary)))
6363 (xetla-changes summary compare-partner)))
6365 (defun xetla-inventory-replay (&optional merge-partner)
6367 Either use a partner in the tree's ++xetla-partners file, or ask the user
6369 (interactive (list (xetla-partner-read-version "Replay from: ")))
6370 (when (y-or-n-p (format "Replay from %s ? " merge-partner))
6371 (xetla-replay merge-partner)))
6373 (defun xetla-inventory-update ()
6376 (xetla-update default-directory))
6378 (defun xetla-inventory-missing (&optional arg)
6379 "Run tla missing in `default-directory'.
6380 With an prefix ARG, do this for the archive of one of your partners."
6383 (let ((missing-partner (xetla-partner-read-version "Check missing against: ")))
6384 (when (y-or-n-p (format "Check missing against %s ? " missing-partner))
6385 (xetla-missing default-directory missing-partner)))
6386 (xetla-missing default-directory (xetla-tree-version))))
6388 (defun xetla-inventory-file-ediff (&optional file)
6389 "Run `ediff' on FILE."
6390 (interactive (list (caddr (ewoc-data (ewoc-locate xetla-inventory-cookie)))))
6391 (xetla-file-ediff file))
6393 (xetla-make-bymouse-function xetla-inventory-find-file)
6395 (defun xetla-inventory-delta ()
6397 Use the head revision of the version associated with the current inventory
6398 buffer as modified tree. Give the base tree interactively."
6400 (let* ((modified (xetla-tree-version-list))
6401 (modified-revision (apply 'xetla-version-head modified))
6402 (modified-fq (xetla-name-construct
6403 (xetla-name-archive modified)
6404 (xetla-name-category modified)
6405 (xetla-name-branch modified)
6406 (xetla-name-version modified)
6408 (base (xetla-name-read
6409 (format "Revision for delta to %s(HEAD) from: " modified-fq)
6410 'prompt 'prompt 'prompt 'prompt 'prompt))
6411 (base-fq (xetla-name-construct base)))
6412 (xetla-delta base-fq modified-fq 'ask)))
6415 (defun xetla-inventory-apply-changeset (reverse)
6416 "Apply changeset to the tree visited by the current inventory buffer.
6417 With a prefix argument REVERSE, reverse the changeset."
6419 (let ((inventory-buffer (current-buffer))
6420 (target (xetla-tree-root))
6421 (changeset (let ((changeset-dir (or (xetla-get-file-info-at-point) "")))
6422 (unless (file-directory-p (expand-file-name changeset-dir))
6423 (setq changeset-dir ""))
6424 (xetla-uniquify-file-name
6425 (read-directory-name
6426 "Changeset directory: " changeset-dir changeset-dir)))))
6427 (xetla-show-changeset changeset nil)
6428 (when (yes-or-no-p (format "Apply the changeset%s? "
6429 (if reverse " in REVERSE" "")))
6430 (xetla-apply-changeset changeset target reverse)
6431 (with-current-buffer inventory-buffer
6432 (xetla-generic-refresh)))))
6434 (defun xetla-inventory-apply-changeset-from-tgz (file)
6435 "Apply the changeset in FILE to the currently visited tree."
6436 (interactive (list (let ((changeset-tarball (or (xetla-get-file-info-at-point) "")))
6437 (read-file-name "Apply changeset from tarball: " nil changeset-tarball t changeset-tarball))))
6438 (let ((inventory-buffer (current-buffer))
6439 (target (xetla-tree-root)))
6440 (xetla-apply-changeset-from-tgz file target)
6441 (with-current-buffer inventory-buffer
6442 (xetla-generic-refresh))))
6444 ;; TODO: Use `xetla-inventory-select-file' in other xetla-inventory-*.
6445 ;; TODO: Mouse event check like `xetla-tree-lint-select-files'.
6446 ;; TODO: Unify with `xetla-tree-lint-select-files'.
6447 (defun xetla-inventory-select-files (prompt-singular
6448 prompt-plural msg-err
6450 msg-prompt no-group ignore-marked
6452 "Get the list of marked files and ask confirmation of the user.
6453 PROMPT-SINGULAR or PROMPT-PLURAL is used as prompt. If no file is under
6454 the point MSG-ERR is passed to `error'.
6456 MSG-PROMPT NO-GROUP IGNORE-MARKED NO-PROMPT and Y-OR-N are currently
6458 (let ((files (if xetla-buffer-marked-file-list
6459 xetla-buffer-marked-file-list
6460 (list (xetla-get-file-info-at-point)))))
6465 (if (> (length files) 1)
6468 (if (> (length files) 1)
6474 (defun xetla-inventory-make-junk (files)
6475 "Prompts and make the FILES junk.
6476 If marked files are, use them as FIELS.
6477 If not, a file under the point is used as FILES."
6480 (xetla-inventory-select-files "Make `%s' junk? "
6481 "Make %s files junk? "
6482 "Not making any file junk")))
6483 (xetla-generic-file-prefix files ",,"))
6485 (defun xetla-inventory-make-precious (files)
6486 "Prompts and make the FILES precious.
6487 If marked files are, use them as FILES.
6488 If not, a file under the point is used as FILES."
6491 (xetla-inventory-select-files "Make `%s' precious? "
6492 "Make %s files precious? "
6493 "Not making any file precious")))
6494 (xetla-generic-file-prefix files "++"))
6496 (defun xetla-generic-add-to-exclude (=tagging-method)
6497 "Exclude the file/directory under point by adding it to =TAGGING-METHOD.
6498 Adds an entry for the file to .arch-inventory or =tagging-method."
6500 (xetla-generic-add-to-* "exclude" =tagging-method))
6502 (defun xetla-generic-add-to-junk (=tagging-method)
6503 "Add the file/directory under point to =TAGGING-METHOD.
6504 Adds an entry for the file to .arch-inventory or =tagging-method."
6506 (xetla-generic-add-to-* "junk" =tagging-method))
6508 (defun xetla-generic-add-to-backup (=tagging-method)
6509 "Add the file/directory under the point to =TAGGING-METHOD.
6510 Adds an entry for the file to .arch-inventory or =tagging-method."
6512 (xetla-generic-add-to-* "backup" =tagging-method))
6514 (defun xetla-generic-add-to-precious (=tagging-method)
6515 "Add the file/directory under the point to =TAGGING-METHOD.
6516 Adds an entry for the file to .arch-inventory or =tagging-method."
6518 (xetla-generic-add-to-* "precious" =tagging-method))
6520 (defun xetla-generic-add-to-unrecognized (=tagging-method)
6521 "Add the file/directory under the point as a precious entry
6522 of .arch-inventory or =tagging-method file."
6524 (xetla-generic-add-to-* "unrecognized" =tagging-method))
6526 (defun xetla-generic-add-to-* (category =tagging-method)
6527 "Categorize currently marked files or the file under point.
6528 Each file is categorized as CATEGORY by adding it to =TAGGING-METHOD."
6529 (xetla-generic-add-files-to-*
6530 category =tagging-method
6531 (xetla-generic-select-files
6532 (format "Make `%%s' %s? " category)
6533 (format "Make %%s files %s? " category)
6534 (format "Not making any file %s? " category)
6535 (format "Make file %s: " category))))
6537 (defun xetla-generic-add-files-to-* (category =tagging-method files)
6538 "Categorize FILES as CATEGORY in =TAGGING-METHOD.
6539 If =TAGGING-METHOD is t, entries for the files are added to =tagging-method.
6540 Else, they are added to .arch-inventory.
6541 CATEGORY is one of the following strings: \"unrecognized\", \"precious\",
6542 \"backup\",\"junk\" or \"exclude\"."
6543 (let ((point (point))
6544 (basedir (expand-file-name default-directory)))
6547 (mapc (lambda (file)
6549 (xetla-edit-=tagging-method-file)
6550 (xetla-edit-.arch-inventory-file
6551 (concat basedir (file-name-directory file))))
6552 (xetla-inventory-file-add-file
6553 category (xetla-regexp-quote (file-name-nondirectory file)))
6554 (save-buffer)) files))
6555 ;; Keep the position
6557 (xetla-generic-refresh)
6558 (if (< point (point-max))
6559 (goto-char point)))))
6562 (defun xetla-generic-set-id-tagging-method (method)
6563 "Set the id tagging method of the current tree to METHOD."
6564 (interactive (list (xetla-id-tagging-method-read
6565 (xetla-id-tagging-method nil))))
6566 (xetla-id-tagging-method-set method)
6567 (xetla-generic-refresh))
6569 (defun xetla-generic-set-id-tagging-method-by-mouse (dummy-event)
6570 "Interactively set the id tagging method of the current tree.
6571 DUMMY-EVENT is ignored."
6573 (call-interactively 'xetla-generic-set-id-tagging-method))
6575 (defun xetla-generic-set-tree-version (&optional version)
6576 "Run tla set-tree-version, setting the tree to VERSION."
6579 (xetla-set-tree-version version)
6580 (call-interactively 'xetla-set-tree-version))
6581 (xetla-generic-refresh))
6583 ;; --------------------------------------
6584 ;; xetla-cat-log-mode:
6585 ;; --------------------------------------
6586 (defun xetla-cat-log-mode ()
6587 "Major Mode to show a specific log message.
6589 \\{xetla-cat-log-mode-map}"
6591 (kill-all-local-variables)
6592 (use-local-map xetla-cat-log-mode-map)
6593 (set (make-local-variable 'font-lock-defaults)
6594 '(xetla-cat-log-font-lock-keywords t))
6596 (setq major-mode 'xetla-cat-log-mode)
6597 (setq mode-name "xetla-cat-log")
6598 (toggle-read-only 1)
6599 (run-hooks 'xetla-cat-log-mode-hook))
6601 (defun xetla-cat-log (revision-spec)
6602 "Show the log for REVISION-SPEC."
6603 (interactive (list (xetla-name-construct
6604 (xetla-name-read "Revision spec: "
6605 'prompt 'prompt 'prompt 'prompt 'prompt))))
6606 (xetla-run-tla-sync (list "cat-log" revision-spec)
6607 :finished 'xetla-finish-function-without-buffer-switch)
6608 (xetla-show-last-process-buffer 'cat-log 'xetla-cat-log-mode revision-spec))
6610 (defun xetla-cat-archive-log (revision-spec)
6611 "Run cat-archive-log for REVISION-SPEC."
6612 (interactive (list (xetla-name-construct
6613 (xetla-name-read "Revision spec: "
6614 'prompt 'prompt 'prompt 'prompt 'prompt))))
6615 (xetla-run-tla-sync (list "cat-archive-log" revision-spec)
6616 :finished 'xetla-finish-function-without-buffer-switch)
6617 (xetla-show-last-process-buffer 'cat-log 'xetla-cat-log-mode revision-spec))
6619 (defun xetla-maybe-save-log (revision)
6620 "Must be called from the buffer containing the log for REVISION.
6621 Saves this buffer to the corresponding file in the log-library if
6622 `xetla-log-library-greedy' is non-nil."
6623 (if xetla-log-library-greedy
6624 (let ((dir (expand-file-name
6625 (concat (file-name-as-directory xetla-log-library)
6627 (file (xetla-name-construct-semi-qualified (cdr revision))))
6628 (unless (file-directory-p dir)
6629 (make-directory dir))
6630 (let ((name (concat " *xetla-log-rev-" (xetla-name-construct
6633 (write-file (concat (file-name-as-directory dir) file))
6634 (set-visited-file-name
6635 (concat (file-name-as-directory dir) file))
6636 (set-buffer-modified-p nil)
6637 (rename-buffer name)
6641 (defun xetla-cat-log-any (revision &optional tree async-handler)
6642 "Create a buffer containing the log file for REVISION.
6644 Either call cat-log, cat-archive-log, or read the log from the log library.
6646 REVISION must be specified as a list. If TREE is provided, try a
6647 cat-log in TREE preferably. Otherwise, try a cat-log in the local
6648 directory. If both are impossible, run cat-archive-log. (same result,
6649 but needs to retrieve something from the archive).
6651 Call the function ASYNC-HANDLER in the created buffer, with arguments
6652 (output error status arguments)."
6653 ;; (message "xetla-cat-log-any %S" revision)
6654 ;; See if the log is in the log library
6655 (when xetla-log-library-greedy
6656 (if (not (file-directory-p xetla-log-library))
6657 (make-directory xetla-log-library)))
6658 (let* ((lib-log (concat (file-name-as-directory xetla-log-library)
6659 (xetla-name-construct revision)))
6661 (or (get-file-buffer lib-log)
6662 (when (file-exists-p lib-log)
6663 (let* ((name (concat " *xetla-log("
6664 (xetla-name-construct revision) ")*")))
6665 (or (get-buffer name)
6666 ;; Surprisingly, (rename-buffer) didn't rename
6667 ;; anything here. Solution: Create a buffer with
6668 ;; the right name, and simulate a find-file.
6669 (with-current-buffer
6670 (get-buffer-create name)
6671 (insert-file-contents lib-log)
6672 (set-visited-file-name lib-log)
6673 (rename-buffer name)
6674 (set-buffer-modified-p nil)
6675 (current-buffer))))))))
6678 (funcall async-handler buffer nil 0 "cat-log")
6681 (let* ((revision-string (xetla-name-construct revision)))
6682 (let ((run-mode (if async-handler 'xetla-run-tla-async 'xetla-run-tla-sync))
6683 (handler (if async-handler
6684 `(lambda (output error status arguments)
6685 (with-current-buffer output
6686 (xetla-maybe-save-log ',revision))
6687 (funcall ,async-handler output error status
6689 `(lambda (output error status arguments)
6690 (with-current-buffer output
6691 (xetla-maybe-save-log ',revision))))))
6692 (xetla-run-tla-sync ;; Anyway, tla cat-log is fast, so, no
6693 ;; need for an asynchronous process. For some reason,
6694 ;; running it asynchronously caused a random bug when
6695 ;; running tla remotely.
6696 (list "cat-log" revision-string)
6698 ;; cat-log failed: cat-archive-log is needed
6699 :error `(lambda (output error status arguments)
6701 (list "cat-archive-log"
6703 :finished ',handler))))))))
6706 (defun xetla-log-merges (revision &optional callback)
6707 "Return a list that will contain patches merged by REVISION.
6708 When the list has been filled in, CALLBACK is called with no arguments."
6709 (let ((merges (list "")))
6712 `(lambda (output error status args)
6713 (with-current-buffer output
6714 (goto-char (point-min))
6716 (let ((list (split-string
6717 (buffer-substring-no-properties
6718 (re-search-forward "^New-patches: ")
6719 (progn (re-search-forward "^[^\t ]")
6720 (beginning-of-line) (point))))))
6722 (remove (xetla-name-construct
6725 (setcar ',merges (car list))
6726 (setcdr ',merges (cdr list)))
6727 (when ',callback (funcall ',callback))
6728 (kill-buffer nil)))))
6731 ;; --------------------------------------
6732 ;; xetla-log-edit-mode:
6733 ;; --------------------------------------
6734 (defun xetla-log-edit-next-field ()
6735 "Go to next field in a log edition."
6737 (let ((in-field (string-match "^\\([A-Z][A-Za-z]*\\(: ?\\)?\\)?$"
6739 (point-at-bol) (point)))))
6741 (string-match "^[A-Z][A-Za-z]*: $"
6742 \0 (buffer-substring
6743 (point-at-bol) (point))))
6745 (if in-field (beginning-of-line) (forward-line 1))
6746 (or (and (looking-at "^[A-Z][a-zA-Z]*: ")
6747 (goto-char (match-end 0)))
6748 (and (looking-at "^[A-Z][a-zA-Z]*:$")
6749 (goto-char (match-end 0))
6750 (progn (insert " ") t))
6751 (goto-char (point-max)))))
6753 (defun xetla-log-goto-field (field)
6754 "Go to FIELD in a log file."
6755 (goto-char (point-min))
6756 (re-search-forward field)
6758 (if (not (looking-at " "))
6762 (defun xetla-log-goto-summary ()
6763 "Go to the Summary field in a log file."
6765 (xetla-log-goto-field "^Summary:"))
6767 (defun xetla-log-goto-keywords ()
6768 "Go to the Keywords field in a log file."
6770 (xetla-log-goto-field "^Keywords:"))
6772 (defun xetla-log-goto-body ()
6773 "Go to the Body in a log file."
6775 (goto-char (point-min))
6778 (defun xetla-log-kill-body ()
6779 "Kill the content of the log file body."
6781 (xetla-log-goto-body)
6782 (kill-region (point) (point-max)))
6784 ;;;###autoload(add-to-list 'auto-mode-alist '("\\+\\+log\\." . xetla-log-edit-mode))
6787 (define-derived-mode xetla-log-edit-mode text-mode "xetla-log-edit"
6788 "Major Mode to edit xetla log messages.
6790 \\{xetla-log-edit-mode-map}
6792 (use-local-map xetla-log-edit-mode-map)
6793 (easy-menu-add xetla-log-edit-mode-menu)
6794 (set (make-local-variable 'font-lock-defaults)
6795 '(xetla-log-edit-font-lock-keywords t))
6797 (setq fill-column 73)
6798 (run-hooks 'xetla-log-edit-mode-hook))
6800 (defun xetla-log-edit-abort ()
6801 "Abort the current log edit."
6804 (set-window-configuration xetla-pre-commit-window-configuration))
6806 (autoload (quote xetla-tips-popup-maybe) "xetla-tips" "\
6807 Pops up a buffer with a tip if tips are enabled (see
6808 `xetla-tips-enabled')" nil nil)
6810 (defun xetla-log-edit-done (&optional commit version-flag)
6811 "Save the current log edit.
6813 When optional argument COMMIT is non-nil, run `tla commit'.
6815 Optional argument VERSION-FLAG is for specifying either a `seal'
6816 commit or a `fix' commit. It is a symbol and can be either `seal' or
6819 Both COMMIT and VERSION-FLAG are really meant for non-interactive
6820 use. When this function is called interactively the same thing can
6821 be achieved through prefix arguments...
6823 With a single prefix arg, run `tla commit'.
6824 With 2 prefix args, run `tla commit --seal'.
6825 With 3 prefix args, run `tla commit --fix'."
6828 (let ((log-buffer (current-buffer))
6829 (commit (car current-prefix-arg))
6831 (dir default-directory))
6832 (pop-window-configuration)
6833 (when (interactive-p)
6834 (cond ((eq commit 4)
6843 (bury-buffer log-buffer)
6845 (funcall (intern (format "xetla-commit-%s" type)))
6846 (if (string-match "--version\\(-\\|fix-\\)+"
6847 (xetla-get-current-revision dir))
6848 (error "Can't commit to sealed archive without --fix")
6849 (kill-buffer log-buffer)
6851 (lambda (output error status args)
6852 (xetla-tips-popup-maybe))))))))
6854 (defun xetla-archive-maintainer-name (version)
6855 "Return the maintainer name for a given VERSION.
6856 This function looks in the bookmarks file for the nickname field and
6858 If the nickname field is not present, just return the archive name for
6860 (xetla-bookmarks-get-field version 'nickname (xetla-name-archive version)))
6862 (defun xetla-archive-maintainer-id (archive &optional shorter)
6863 "Return my-id substring from ARCHIVE.
6864 If SHORTER is non-nil, return login name part of the my-id substring.
6865 E.g. If ARCHIVE is x@y.z-a, the result is x@y.z.
6866 If SHORTER is non-nil, the result is x."
6867 (if (string-match "\\(\\(.+\\)@.+\\)--.+" archive)
6869 (match-string 2 archive)
6870 (match-string 1 archive))))
6872 (defun xetla-archive-default-maintainer-name (version)
6873 "Return a suitable maintainer name or version name for VERSION.
6874 Either the nickname if defined in the bookmarks, or the left hand side
6875 of the email in the archive name."
6876 (or (xetla-archive-maintainer-name version)
6877 (xetla-archive-maintainer-id (xetla-name-archive version) t)))
6879 (defun xetla-merge-summary-end-of-sequence (string low high)
6880 "Pretty-print a range of merged patches.
6881 STRING is an identifier for this merge, while LOW and HIGH are the lowest
6882 and highest patches that were merged."
6887 (format "%d-%d" low high))))
6888 (if (string= string "")
6889 (concat "patch " elem)
6890 (concat string ", " elem))))
6893 (defun xetla-merge-summary-line (mergelist)
6894 "Create a suitable log summary line for a list of merges.
6895 MERGELIST is an alist in the form
6896 \((maintainer1 12 13 14 25 26)
6898 (maintainerN num42))
6899 The return value is a string in the form
6900 \"maintainer1 (patch 12-14, 25-26), maintainerN (patch-num42)\""
6903 (let ((patch-list (sort (cdar mergelist) '<))
6905 last-patch-number-low
6906 last-patch-number-high)
6907 ;; patch-list is the list of patch numbers.
6909 (unless last-patch-number-low
6910 (setq last-patch-number-low (car patch-list))
6911 (setq last-patch-number-high (- (car patch-list) 1)))
6912 (if (= (1+ last-patch-number-high) (car patch-list))
6914 (setq last-patch-number-high (car patch-list))
6916 (xetla-merge-summary-end-of-sequence
6918 last-patch-number-low
6919 last-patch-number-high))
6920 (setq last-patch-number-low (car patch-list)))
6921 (setq last-patch-number-high (car patch-list))
6922 (setq patch-list (cdr patch-list)))
6924 (xetla-merge-summary-end-of-sequence
6926 last-patch-number-low
6927 last-patch-number-high))
6928 (setq last-patch-number-low nil)
6930 (let ((maint (format "%s (%s)" (caar mergelist)
6932 (if (string= res "")
6934 (concat res ", " maint)))))
6935 (setq mergelist (cdr mergelist)))
6938 (defun xetla-merge-summary-default-format-function (string)
6939 "Return an appropriate \"Merged from\" summary line for STRING.
6941 Gets the 'summary-format field for that version in the bookmarks (or
6942 use \"Merged from %s\" by default), and calls
6943 \(format summary-format S)."
6944 (let ((format-string (xetla-bookmarks-get-field
6945 (xetla-tree-version-list)
6948 (format format-string string)))
6950 (defun xetla-merge-summary-line-for-log (&optional
6951 version-to-name-function
6952 generate-line-function
6953 format-line-function)
6954 "Generate an appropriate summary line after a merge.
6955 The generated line is of the form
6956 \"Merged from Robert (167-168, 170), Masatake (209, 213-215, 217-218)\".
6957 The names \"Robert\" and \"Masatake\" in this example are nicknames
6958 defined in the bookmarks for the corresponding versions.
6960 First, an alist A like
6961 ((\"Robert\" 167 168 170) (\"Masatake\" 209 213 214 215 217 218))
6962 is generated. If VERSION-TO-NAME-FUNCTION is non-nil, then it must be
6963 a function that is called with the version as an argument, and must
6964 return a string that will be used to instead of the nickname.
6966 Then, a string S like
6967 \"Robert (167-168, 170), Masatake (209, 213-215, 217-218)\"
6968 is generated. This is done by default by `xetla-merge-summary-line',
6969 which can be overridden by GENERATE-LINE-FUNCTION.
6971 Then, the function FORMAT-LINE-FUNCTION is called with this string S
6972 as an argument. If FORMAT-LINE-FUNCTION is nil, then,
6973 `xetla-merge-summary-default-format-function' is called. It retrieves
6974 the fields summary-format from the bookmark for the tree version, and
6975 calls (format summary-format S)."
6981 (goto-char (point-min))
6982 (while (re-search-forward "^ \\* \\(.+@.+--.+/.+--.+\\)$" nil t)
6983 (setq rev-list (xetla-name-split (match-string 1)))
6984 (setq maintainer (funcall (or version-to-name-function
6985 'xetla-archive-default-maintainer-name)
6987 (setq rev (cadr (split-string (xetla-name-revision rev-list) "-")))
6988 (add-to-list 'patch-list (list maintainer rev)))
6989 ;; patch-list has now the form
6990 ;; ((maintainer1 num1) (maintainer1 num2) ... (maintainerN num42))
6993 (let* ((elem (car patch-list))
6994 (patch-number-list (assoc (car elem) alist)))
6995 (if patch-number-list
6996 ;; This maintainer already has a patch in the list
6997 (setcdr patch-number-list
6998 (cons (string-to-number (cadr elem))
6999 (cdr patch-number-list)))
7000 ;; First patch for this maintainer. add
7001 ;; (maintainer patch-number) to the alist.
7002 (setq alist (cons (list (car elem)
7003 (string-to-number (cadr elem)))
7005 (setq patch-list (cdr patch-list)))
7006 ;; alist now has the form
7007 ;; ((maintainer1 num1 num2)
7009 ;; (maintainerN num42))
7010 ;; where numX are of type integer.
7011 (funcall (or format-line-function
7012 'xetla-merge-summary-default-format-function)
7013 (funcall (or generate-line-function
7014 'xetla-merge-summary-line) alist))))))
7016 (defun xetla-log-edit-insert-log-for-merge-and-headers ()
7017 "Call `xetla-log-edit-insert-log-for-merge' with a prefix arg."
7019 (xetla-log-edit-insert-log-for-merge t))
7021 (defun xetla-log-edit-insert-log-for-merge (arg)
7022 "Insert the output of xetla log-for-merge at POINT.
7024 When called with a prefix argument ARG, create a standard Merged from
7025 line as Summary with `xetla-merge-summary-line-for-log'."
7027 (xetla-run-tla-sync '("log-for-merge")
7029 `(lambda (output error status arguments)
7030 (let ((content (xetla-buffer-content
7032 (if (= 0 (length content))
7033 (error "There was no merge!"))
7034 (with-current-buffer ,(current-buffer)
7035 (let ((on-summary-line
7036 (= 1 (count-lines (point-min) (point))))
7039 (xetla-log-goto-body)
7040 (goto-char old-pos))
7043 (xetla-log-goto-summary)
7044 (delete-region (point) (point-at-eol))
7046 (with-current-buffer output
7047 (xetla-merge-summary-line-for-log)))
7048 (xetla-log-goto-keywords)
7049 (delete-region (point) (point-at-eol))
7051 (xetla-log-goto-summary))))))
7054 (defun xetla-log-edit-insert-memorized-log ()
7055 "Insert a memorized log message."
7057 (when xetla-memorized-log-header
7058 (xetla-log-goto-summary)
7059 (delete-region (point) (point-at-eol))
7060 (insert xetla-memorized-log-header))
7061 (when xetla-memorized-log-message
7062 (xetla-log-goto-body)
7063 (insert xetla-memorized-log-message)))
7066 ;; --------------------------------------
7067 ;; xetla-log-edit-insert-keywords:
7068 ;; --------------------------------------
7070 (defvar xetla-log-edit-keywords-marked-list)
7071 (defvar xetla-log-edit-keywords-cookie)
7072 (defvar xetla-log-edit-keywords-log-buffer)
7074 (defun xetla-log-edit-keywords-printer (elem)
7075 "If ELEM is a keyword, print it differently."
7076 (insert (if (member elem xetla-log-edit-keywords-marked-list)
7077 (concat xetla-mark " ") " ")
7080 (defun xetla-log-edit-keywords (arg)
7081 "Add keywords listed in variable `xetla-log-edit-keywords'.
7082 When called with a prefix argument ARG, delete all current keywords."
7084 (let ((current-keywords
7086 (xetla-log-goto-keywords)
7087 (buffer-substring (point) (point-at-eol))))
7088 (log-buffer (current-buffer))
7090 (setq current-keywords (replace-regexp-in-string "," " " current-keywords nil t)
7091 current-keywords (mapcar (lambda (k) (format "%s" k))
7092 (read (concat "(" current-keywords ")"))))
7093 (switch-to-buffer " *xetla-log-keywords*")
7094 (toggle-read-only 0)
7096 (make-local-variable 'xetla-log-edit-keywords)
7097 (make-local-variable 'xetla-log-edit-keywords-marked-list)
7098 (make-local-variable 'xetla-log-edit-keywords-cookie)
7099 (make-local-variable 'xetla-log-edit-keywords-log-buffer)
7100 (setq xetla-log-edit-keywords-log-buffer
7102 xetla-log-edit-keywords-marked-list
7104 xetla-log-edit-keywords-cookie
7105 (ewoc-create 'xetla-log-edit-keywords-printer
7106 "List of keywords from `xetla-log-edit-keywords':"
7107 (format "type C-c C-c to insert the marked keywords to the buffer\n%s"
7108 (buffer-name log-buffer))))
7110 (while current-keywords
7111 (add-to-list 'xetla-log-edit-keywords (car current-keywords))
7112 (setq current-keywords (cdr current-keywords)))
7114 (setq keywords xetla-log-edit-keywords)
7117 (add-to-list 'xetla-log-edit-keywords (car keywords))
7118 (ewoc-enter-last xetla-log-edit-keywords-cookie (car keywords))
7119 (setq keywords (cdr keywords))))
7121 (use-local-map xetla-log-edit-keywords-mode-map)
7122 (setq major-mode 'xetla-log-edit-keywords-mode)
7123 (setq mode-name "xetla-log-keywords")
7124 (toggle-read-only 1)
7125 (message "Type C-c C-c to finish.")
7126 (goto-char (point-min))
7129 (defun xetla-log-edit-keywords-cursor-goto (elem)
7130 "Jump to the location of ELEM."
7132 (goto-char (ewoc-location elem))
7133 (re-search-forward "^"))
7135 (defun xetla-log-edit-keywords-next ()
7136 "Go to the next keyword."
7138 (let* ((cookie xetla-log-edit-keywords-cookie)
7139 (elem (ewoc-locate cookie))
7140 (next (or (ewoc-next cookie elem) elem)))
7141 (xetla-log-edit-keywords-cursor-goto next)))
7143 (defun xetla-log-edit-keywords-previous ()
7144 "Go to the previous keyword."
7146 (let* ((cookie xetla-log-edit-keywords-cookie)
7147 (elem (ewoc-locate cookie))
7148 (previous (or (ewoc-prev cookie elem) elem)))
7149 (xetla-log-edit-keywords-cursor-goto previous)))
7151 (defun xetla-log-edit-keywords-mark ()
7152 "Mark the current keyword."
7154 (let ((pos (point)))
7155 (add-to-list 'xetla-log-edit-keywords-marked-list
7156 (ewoc-data (ewoc-locate xetla-log-edit-keywords-cookie)))
7157 (ewoc-refresh xetla-log-edit-keywords-cookie)
7159 (xetla-log-edit-keywords-next))
7161 (defun xetla-log-edit-keywords-unmark ()
7162 "Unmark the current keyword."
7164 (let ((pos (point)))
7165 (setq xetla-log-edit-keywords-marked-list
7166 (delete (ewoc-data (ewoc-locate xetla-log-edit-keywords-cookie))
7167 xetla-log-edit-keywords-marked-list))
7168 (ewoc-refresh xetla-log-edit-keywords-cookie)
7170 (xetla-log-edit-keywords-next))
7172 (defun xetla-log-edit-keywords-unmark-all ()
7173 "Unmark all marked keywords."
7175 (let ((pos (point)))
7176 (setq xetla-log-edit-keywords-marked-list nil)
7177 (ewoc-refresh xetla-log-edit-keywords-cookie)
7180 (defun xetla-log-edit-keywords-mark-all ()
7181 "Mark all keywords."
7183 (let ((pos (point)))
7184 (setq xetla-log-edit-keywords-marked-list xetla-log-edit-keywords)
7185 (ewoc-refresh xetla-log-edit-keywords-cookie)
7188 (defun xetla-log-edit-keywords-toggle-mark ()
7189 "Toggle marking of the current keyword."
7191 (let ((pos (point)))
7192 (if (member (ewoc-data (ewoc-locate xetla-log-edit-keywords-cookie))
7193 xetla-log-edit-keywords-marked-list)
7194 (xetla-log-edit-keywords-unmark)
7195 (xetla-log-edit-keywords-mark))
7196 (ewoc-refresh xetla-log-edit-keywords-cookie)
7199 (defun xetla-log-edit-keywords-insert ()
7200 "Insert marked keywords into log buffer."
7202 (let ((keywords xetla-log-edit-keywords-marked-list))
7203 (switch-to-buffer xetla-log-edit-keywords-log-buffer)
7204 (kill-buffer " *xetla-log-keywords*")
7206 (xetla-log-goto-keywords)
7207 (delete-region (point) (point-at-eol))
7208 (insert (mapconcat 'identity (reverse keywords) ", ")))))
7210 ;; --------------------------------------
7211 ;; xetla-archive-list-mode:
7212 ;; --------------------------------------
7213 (defun xetla-archive-mirror-archive ()
7214 "Mirror the archive at point."
7216 (let ((archive-info (xetla-get-archive-info)))
7218 (xetla-mirror-archive archive-info)
7221 (defun xetla-archive-synchronize-archive ()
7222 "Synchronizes the mirror for the archive at point."
7224 (let ((archive-info (xetla-get-archive-info)))
7226 (xetla-archive-mirror archive-info))))
7228 (defun xetla-archive-list-mode ()
7229 "Major Mode to show arch archives:
7230 \\{xetla-archive-list-mode-map}"
7232 (kill-all-local-variables)
7233 (use-local-map xetla-archive-list-mode-map)
7234 (easy-menu-add xetla-archive-list-mode-menu)
7235 (setq major-mode 'xetla-archive-list-mode)
7236 (setq mode-name "xetla-archives")
7238 (toggle-read-only 1)
7239 (set-buffer-modified-p nil)
7240 (set (make-local-variable 'xetla-get-revision-info-at-point-function)
7241 'xetla-get-archive-info-at-point)
7242 (run-hooks 'xetla-archive-list-mode-hook))
7244 (defun xetla-get-archive-info-at-point ()
7245 "Get archive information."
7246 (list 'archive (xetla-get-archive-info)))
7248 (defun xetla-archive-select-default ()
7249 "Select the default archive."
7251 (when (xetla-get-archive-info)
7252 (let ((pos (point)))
7253 (xetla-my-default-archive (xetla-get-archive-info))
7257 (defun xetla-archive-unregister-archive ()
7258 "Delete the registration of the selected archive."
7260 (let ((archive (xetla-get-archive-info)))
7262 (progn (xetla-unregister-archive archive t)
7264 (error "No archive under the point"))))
7266 (defun xetla-archive-edit-archive-location ()
7267 "Edit the archive location for a archive.
7268 This is done by unregistering the archive, followed by a new registration with
7271 (let ((archive (xetla-get-archive-info)))
7272 (xetla-edit-archive-location archive)
7276 (defun xetla-archive-use-as-default-mirror ()
7277 "Use the mirror archive as default mirror."
7279 (let ((archive (xetla-get-archive-info)))
7280 (xetla-use-as-default-mirror archive)
7284 (defun xetla-archive-list-categories ()
7285 "List the categories for the current archive."
7287 (let ((archive (xetla-get-archive-info)))
7289 (xetla-categories archive)
7290 (error "No archive under the point"))))
7292 (xetla-make-bymouse-function xetla-archive-list-categories)
7294 (defun xetla-archive-browse-archive ()
7295 "Browse the current archive."
7297 (let ((archive (xetla-get-archive-info)))
7299 (xetla-browse-archive archive)
7300 (error "No archive under the point"))))
7302 (defun xetla-archive-next ()
7303 "Go to the next archive."
7306 (beginning-of-line))
7308 (defun xetla-archive-previous ()
7309 "Go to the previous archive."
7312 (beginning-of-line))
7314 (defun xetla-save-archive-to-kill-ring ()
7315 "Save the name of the current archive to the kill ring."
7317 (let ((archive (or (xetla-get-archive-info)
7318 xetla-buffer-archive-name
7319 (xetla-name-archive (xetla-tree-version-list nil 'no-error)))))
7321 (error "No archive name associated with current buffer"))
7324 (message "%s" archive))
7327 ;; --------------------------------------
7328 ;; xetla-category-list-mode:
7329 ;; --------------------------------------
7330 (defun xetla-category-list-mode ()
7331 "Major Mode to show arch categories:
7332 \\{xetla-category-list-mode-map}"
7334 (kill-all-local-variables)
7335 (use-local-map xetla-category-list-mode-map)
7336 (easy-menu-add xetla-category-list-mode-menu)
7337 (setq major-mode 'xetla-category-list-mode)
7338 (setq mode-name "xetla-category")
7339 (add-hook 'xetla-make-category-hook 'xetla-category-refresh)
7341 (toggle-read-only 1)
7342 (set-buffer-modified-p nil)
7343 (set (make-local-variable 'xetla-get-revision-info-at-point-function)
7344 'xetla-get-category-info-at-point)
7345 (run-hooks 'xetla-category-list-mode-hook))
7347 (defun xetla-get-category-info-at-point ()
7348 "Get archive/category-branch information."
7349 (let ((buffer-version (xetla-name-construct
7350 xetla-buffer-archive-name
7351 (xetla-get-archive-info 'xetla-category-info))))
7352 (list 'category buffer-version)))
7354 (defun xetla-category-list-branches ()
7355 "List branches of the current category."
7357 (let ((category (xetla-get-archive-info 'xetla-category-info)))
7359 (xetla-branches xetla-buffer-archive-name category)
7360 (error "No category under the point"))))
7362 (xetla-make-bymouse-function xetla-category-list-branches)
7364 (defun xetla-category-make-category (category)
7365 "Create a new category named CATEGORY."
7366 (interactive "sCategory name: ")
7367 (xetla-make-category xetla-buffer-archive-name category))
7369 (defun xetla-category-refresh ()
7370 "Refresh the current category list."
7372 (xetla-categories xetla-buffer-archive-name))
7374 (defun xetla-category-next ()
7375 "Move to the next category."
7378 (beginning-of-line))
7380 (defun xetla-category-previous ()
7381 "Move to the previous category."
7385 (unless (looking-at "^ ")
7388 (defun xetla-category-mirror-archive ()
7389 "Mirror the current category."
7391 (let ((category (xetla-get-archive-info 'xetla-category-info)))
7393 (error "No category at point"))
7394 (xetla-archive-mirror xetla-buffer-archive-name
7398 (defun xetla-category-bookmarks-add-here (name)
7399 "Add a bookmark named NAME for this category."
7400 (interactive "sBookmark name: ")
7401 (xetla-bookmarks-add name
7402 (list xetla-buffer-archive-name
7403 (xetla-get-archive-info 'xetla-category-info)
7405 (message "bookmark %s added." name))
7407 (defun xetla-category-bookmarks-add (name)
7408 "Add a bookmark named NAME for this category."
7409 (interactive "sBookmark name: ")
7410 (xetla-bookmarks-add name
7411 (list xetla-buffer-archive-name nil nil nil))
7412 (message "bookmark %s added." name))
7414 ;; --------------------------------------
7415 ;; xetla-branch-list-mode
7416 ;; --------------------------------------
7417 (defun xetla-branch-list-mode ()
7418 "Major Mode to show arch branches:
7419 \\{xetla-branch-list-mode-map}"
7421 (kill-all-local-variables)
7422 (use-local-map xetla-branch-list-mode-map)
7423 (easy-menu-add xetla-branch-list-mode-menu)
7424 (setq major-mode 'xetla-branch-list-mode)
7425 (setq mode-name "xetla-branch")
7426 (add-hook 'xetla-make-branch-hook 'xetla-branch-refresh)
7428 (toggle-read-only 1)
7429 (set-buffer-modified-p nil)
7430 (set (make-local-variable 'xetla-get-revision-info-at-point-function)
7431 'xetla-get-branch-info-at-point)
7432 (run-hooks 'xetla-branch-list-mode-hook))
7434 (defun xetla-get-branch-info-at-point ()
7435 "Get archive/category-branch-version information."
7436 (let ((buffer-version (xetla-name-construct
7437 xetla-buffer-archive-name
7438 xetla-buffer-category-name
7439 (xetla-get-archive-info 'xetla-branch-info))))
7440 (list 'branch buffer-version)))
7442 (defun xetla-branch-make-branch (branch)
7443 "Create a new branch named BRANCH."
7444 (interactive "sBranch name: ")
7445 (xetla-make-branch xetla-buffer-archive-name
7446 xetla-buffer-category-name
7449 (defun xetla-branch-refresh ()
7450 "Refresh the current branch list."
7453 xetla-buffer-archive-name
7454 xetla-buffer-category-name))
7456 (defun xetla-branch-list-parent-category ()
7457 "List the parent category of the current branch."
7459 (xetla-categories xetla-buffer-archive-name))
7461 (defun xetla-branch-list-versions ()
7462 "List the versions of the current branch."
7464 (let ((branch (xetla-get-archive-info 'xetla-branch-info)))
7466 (xetla-versions xetla-buffer-archive-name
7467 xetla-buffer-category-name
7469 (error "No branch under the point"))))
7471 (xetla-make-bymouse-function xetla-branch-list-versions)
7473 (defun xetla-branch-mirror-archive ()
7474 "Mirror the current branch."
7476 (let ((branch (xetla-get-archive-info 'xetla-branch-info)))
7478 (error "No branch under the point"))
7479 (xetla-archive-mirror xetla-buffer-archive-name
7480 xetla-buffer-category-name
7483 (defun xetla-branch-get-branch (directory)
7484 "Get the current branch and place it in DIRECTORY."
7485 (interactive (list (expand-file-name
7486 (read-directory-name
7487 (format "Restore \"%s\" to: "
7489 (xetla-get-archive-info 'xetla-branch-info)))
7491 (error "No branch under the point"))
7492 (xetla-name-construct
7493 xetla-buffer-archive-name
7494 xetla-buffer-category-name
7496 (let ((branch (xetla-get-archive-info 'xetla-branch-info)))
7498 (xetla-get directory
7500 xetla-buffer-archive-name
7501 xetla-buffer-category-name
7503 (error "No branch under the point"))))
7505 (defun xetla-branch-bookmarks-add-here (name)
7506 "Add a bookmark named NAME for the current branch."
7507 (interactive "sBookmark name: ")
7508 (xetla-bookmarks-add name
7509 (list xetla-buffer-archive-name
7510 xetla-buffer-category-name
7511 (xetla-get-archive-info 'xetla-branch-info)
7513 (message "bookmark %s added." name))
7515 (defun xetla-branch-bookmarks-add (name)
7516 "Add a bookmark named NAME for the current branch."
7517 (interactive "sBookmark name: ")
7518 (xetla-bookmarks-add name
7519 (list xetla-buffer-archive-name
7520 xetla-buffer-category-name
7522 (message "bookmark %s added." name))
7527 ;; --------------------------------------
7528 ;; xetla-version-list-mode
7529 ;; --------------------------------------
7530 (defun xetla-version-list-mode ()
7531 "Major Mode to show arch versions:
7532 \\{xetla-version-list-mode-map}"
7534 (kill-all-local-variables)
7535 (use-local-map xetla-version-list-mode-map)
7536 (easy-menu-add xetla-version-list-mode-menu)
7537 (setq major-mode 'xetla-version-list-mode)
7538 (setq mode-name "xetla-version")
7539 (add-hook 'xetla-make-version-hook 'xetla-version-refresh)
7541 (toggle-read-only 1)
7542 (set-buffer-modified-p nil)
7543 (set (make-local-variable 'xetla-get-revision-info-at-point-function)
7544 'xetla-get-version-info-at-point)
7545 (run-hooks 'xetla-version-list-mode-hook))
7547 (defun xetla-get-version-info-at-point ()
7548 "Get archive/category-branch-version-revision information."
7549 (let ((buffer-version (xetla-name-construct
7550 xetla-buffer-archive-name
7551 xetla-buffer-category-name
7552 xetla-buffer-branch-name
7553 (xetla-get-archive-info 'xetla-version-info))))
7554 (list 'version buffer-version)))
7556 (defun xetla-version-refresh ()
7557 "Refresh the current version list."
7560 xetla-buffer-archive-name
7561 xetla-buffer-category-name
7562 xetla-buffer-branch-name))
7564 (defun xetla-version-list-parent-branch ()
7565 "List the parent branch of this version."
7567 (xetla-branches xetla-buffer-archive-name
7568 xetla-buffer-category-name))
7570 (defun xetla-version-list-revisions ()
7571 "List the revisions of this version."
7573 (let ((version (xetla-get-archive-info 'xetla-version-info)))
7575 (xetla-revisions xetla-buffer-archive-name
7576 xetla-buffer-category-name
7577 xetla-buffer-branch-name
7579 (error "No version under the point"))))
7581 (xetla-make-bymouse-function xetla-version-list-revisions)
7583 (defun xetla-version-make-version (version)
7584 "Create a new version named VERSION."
7585 (interactive "sVersion name: ")
7586 (xetla-make-version xetla-buffer-archive-name
7587 xetla-buffer-category-name
7588 xetla-buffer-branch-name
7591 (defun xetla-version-bookmarks-add-here (name)
7592 "Add a bookmark named NAME for the current version."
7593 (interactive "sBookmark name: ")
7594 (xetla-bookmarks-add name
7595 (list xetla-buffer-archive-name
7596 xetla-buffer-category-name
7597 xetla-buffer-branch-name
7598 (xetla-get-archive-info 'xetla-version-info)
7600 (message "bookmark %s added." name))
7602 (defun xetla-version-bookmarks-add (name)
7603 "Add a bookmark named NAME for the current version."
7604 (interactive "sBookmark name: ")
7605 (xetla-bookmarks-add name
7606 (list xetla-buffer-archive-name
7607 xetla-buffer-category-name
7608 xetla-buffer-branch-name
7610 (message "bookmark %s added." name))
7612 (defun xetla-version-get-version (directory)
7613 "Get a version and place it in DIRECTORY."
7614 (interactive (list (expand-file-name
7615 (read-directory-name
7616 (format "Restore \"%s\" to: "
7618 (xetla-get-archive-info 'xetla-version-info)))
7620 (error "No version under the point"))
7621 (xetla-name-construct
7622 xetla-buffer-archive-name
7623 xetla-buffer-category-name
7624 xetla-buffer-branch-name
7626 (let ((version (xetla-get-archive-info 'xetla-version-info)))
7628 (xetla-get directory
7630 xetla-buffer-archive-name
7631 xetla-buffer-category-name
7632 xetla-buffer-branch-name
7634 (error "No version under the point"))))
7637 (defun xetla-version-mirror-archive ()
7638 "Mirror the current version."
7640 (let ((version (xetla-get-archive-info 'xetla-version-info)))
7642 (xetla-archive-mirror xetla-buffer-archive-name
7643 xetla-buffer-category-name
7644 xetla-buffer-branch-name
7647 (defun xetla-version-tag (to-archive to-category to-branch to-version)
7648 "Run tla tag from the current location in version buffer.
7649 The tag is created in TO-ARCHIVE/TO-CATEGORY-TO-BRANCH-TO-VERSION."
7651 (let ((l (xetla-name-read "Tag to: " 'prompt 'prompt 'prompt 'prompt)))
7653 (xetla-name-archive l)
7654 (xetla-name-category l)
7655 (xetla-name-branch l)
7656 (xetla-name-version l))))
7657 (let ((to-fq (xetla-name-construct to-archive
7662 (from-version (xetla-get-archive-info 'xetla-version-info)))
7663 (unless from-version
7664 (error "No version under the point"))
7665 (setq from-fq (xetla-name-construct
7666 xetla-buffer-archive-name
7667 xetla-buffer-category-name
7668 xetla-buffer-branch-name
7670 (xetla-version-tag-internal from-fq to-fq)))
7673 (defun xetla-version-tag-internal (from-fq to-fq &optional synchronously)
7674 "Create a tag from FROM-FQ to TO-FQ.
7675 If SYNCHRONOUSLY is non-nil, internal `xetla-get' runs synchronously.
7676 Else it runs asynchronously."
7677 (when (yes-or-no-p (format "Create a tag from `%s' to `%s'? " from-fq to-fq))
7678 (unless (xetla-tag from-fq to-fq)
7679 (error "Fail to create a tag"))
7680 (when (y-or-n-p "Tag created. Get a copy of this revision? ")
7681 (let* ((prompt "Get a copy in: ")
7685 (setq dir (read-directory-name prompt dir)
7686 parent (expand-file-name
7687 (concat (file-name-as-directory dir) "..")))
7689 ;; Parent directoy must be.
7690 ((not (file-directory-p parent))
7691 (message "`%s' is not directory" parent)
7694 ;; dir itself must not be.
7695 ((file-exists-p dir)
7696 (message "`%s' exists already" dir)
7699 (setq to-fq-split (xetla-name-split to-fq))
7708 ;; --------------------------------------
7709 ;; xetla-revision-list-mode
7710 ;; --------------------------------------
7711 (defun xetla-revision-list-mode ()
7712 "Major Mode to show arch revisions:
7713 \\{xetla-revision-list-mode-map}"
7715 (kill-all-local-variables)
7716 (toggle-read-only -1)
7717 (use-local-map xetla-revision-list-mode-map)
7718 (easy-menu-add xetla-revision-list-mode-menu)
7719 (setq major-mode 'xetla-revision-list-mode)
7720 (setq mode-name "xetla-revision")
7721 (add-hook 'xetla-make-revision-hook 'xetla-revision-refresh)
7723 (set (make-local-variable 'xetla-revision-list-cookie)
7724 (ewoc-create 'xetla-revision-list-printer))
7725 (toggle-read-only 1)
7726 (set-buffer-modified-p nil)
7727 (set (make-local-variable 'xetla-get-revision-info-at-point-function)
7728 'xetla-get-revision-info-at-point)
7729 (setq mode-line-process 'xetla-mode-line-process)
7730 (run-hooks 'xetla-revision-list-mode-hook))
7732 (defun xetla-get-revision-info-at-point ()
7733 "Get archive/category-branch-version-revision-patch information.
7734 Returns nil if not on a revision list, or not on a revision entry in a
7736 (let ((elem (ewoc-data (ewoc-locate xetla-revision-list-cookie))))
7737 (when (eq (car elem) 'entry-patch)
7738 (let* ((full (xetla-revision-revision (caddr elem)))
7739 (buffer-revision (xetla-name-construct full)))
7740 (list 'revision buffer-revision)))))
7742 (defun xetla-revision-refresh ()
7743 "Refresh the current list of revisions."
7746 xetla-buffer-archive-name
7747 xetla-buffer-category-name
7748 xetla-buffer-branch-name
7749 xetla-buffer-version-name))
7751 (defun xetla-revision-list-parent-version ()
7752 "List the versions of the parent of this revision."
7754 (xetla-versions xetla-buffer-archive-name
7755 xetla-buffer-category-name
7756 xetla-buffer-branch-name))
7758 (defun xetla-revision-get-revision (directory archive category branch
7760 "Get a revision and place it in DIRECTORY.
7761 The revision is named by ARCHIVE/CATEGORY-BRANCH-VERSION-REVISION."
7763 (let* ((elem (ewoc-data (ewoc-locate xetla-revision-list-cookie)))
7764 (full (xetla-revision-revision (caddr elem)))
7765 (revision (xetla-name-revision full))
7766 (archive (xetla-name-archive full))
7767 (category (xetla-name-category full))
7768 (branch (xetla-name-branch full))
7769 (version (xetla-name-version full))
7772 (error "No revision under the point"))
7773 (setq dir (expand-file-name
7774 (read-directory-name
7775 (format "Restore \"%s\" to: "
7776 (xetla-name-construct
7777 archive category branch version revision)))))
7778 (if (file-exists-p dir)
7779 (error "Directory %s already exists" dir))
7780 (list dir archive category branch version revision)))
7782 (xetla-get directory t archive category branch version revision)
7783 (error "No revision under the point")))
7785 (defun xetla-revision-cache-revision (archive category branch version revision)
7786 "Create a cached revision for the revision at point."
7788 (let* ((elem (ewoc-data (ewoc-locate xetla-revision-list-cookie)))
7789 (full (xetla-revision-revision (caddr elem)))
7790 (archive (xetla-name-archive full))
7791 (category (xetla-name-category full))
7792 (branch (xetla-name-branch full))
7793 (version (xetla-name-version full))
7794 (revision (xetla-name-revision full)))
7796 (error "No revision under the point"))
7797 (list archive category branch version revision)))
7799 (xetla-cache-revision archive category branch version revision)
7800 (error "No revision under the point")))
7802 (defun xetla-revision-add-to-library (archive category branch version revision)
7803 "Add the revision at point to library."
7805 (let* ((elem (ewoc-data (ewoc-locate xetla-revision-list-cookie)))
7806 (full (xetla-revision-revision (caddr elem)))
7807 (archive (xetla-name-archive full))
7808 (category (xetla-name-category full))
7809 (branch (xetla-name-branch full))
7810 (version (xetla-name-version full))
7811 (revision (xetla-name-revision full)))
7813 (error "No revision under the point"))
7814 (list archive category branch version revision)))
7816 (xetla-library-add archive category branch version revision)
7817 (error "No revision under the point")))
7819 (defun xetla-revision-maybe-refresh ()
7820 "Refresh the revision list if new information is available.
7821 If the current ewoc doesn't contain creator, date, and summary, and
7822 if these values should now be displayed, run the refresh function."
7823 (when (or xetla-revisions-shows-date
7824 xetla-revisions-shows-creator
7825 xetla-revisions-shows-summary
7826 xetla-revisions-shows-merges
7827 xetla-revisions-shows-merged-by)
7829 (ewoc-elem (ewoc-nth xetla-revision-list-cookie 0)))
7830 (while (and ewoc-elem (not stop))
7831 (let ((elem (ewoc-data ewoc-elem)))
7832 (if (eq (car elem) 'entry-patch)
7834 (setq ewoc-elem (ewoc-next xetla-revision-list-cookie
7836 (when (and ewoc-elem
7837 (null (xetla-revision-summary (caddr (ewoc-data ewoc-elem)))))
7838 (xetla-generic-refresh)))))
7840 (defun xetla-revision-toggle-date ()
7841 "Toggle display of the date in the revision list."
7843 (setq xetla-revisions-shows-date (not xetla-revisions-shows-date))
7844 (xetla-revision-maybe-refresh)
7845 (ewoc-refresh xetla-revision-list-cookie))
7847 (defun xetla-revision-toggle-summary ()
7848 "Toggle display of the summary information in the revision list."
7850 (setq xetla-revisions-shows-summary (not xetla-revisions-shows-summary))
7851 (xetla-revision-maybe-refresh)
7852 (ewoc-refresh xetla-revision-list-cookie))
7854 (defun xetla-revision-toggle-creator ()
7855 "Toggle display of the creator in the revision list."
7857 (setq xetla-revisions-shows-creator (not xetla-revisions-shows-creator))
7858 (xetla-revision-maybe-refresh)
7859 (ewoc-refresh xetla-revision-list-cookie))
7861 (defun xetla-revision-toggle-library ()
7862 "Toggle display of the revision library in the revision list."
7864 (setq xetla-revisions-shows-library (not xetla-revisions-shows-library))
7865 (ewoc-refresh xetla-revision-list-cookie))
7867 (defun xetla-revision-toggle-merges ()
7868 "Toggle display of the merges in the revision list."
7870 (setq xetla-revisions-shows-merges (not xetla-revisions-shows-merges))
7871 (xetla-revision-maybe-refresh)
7872 (ewoc-refresh xetla-revision-list-cookie))
7874 (defun xetla-revision-toggle-merged-by ()
7875 "Toggle display of merged-by in the revision list."
7877 (setq xetla-revisions-shows-merged-by
7878 (not xetla-revisions-shows-merged-by))
7879 (when (and (not xetla-revision-merge-by-computed)
7880 xetla-revisions-shows-merged-by)
7881 (xetla-revision-maybe-refresh)
7882 (xetla-revision-compute-merged-by))
7883 (ewoc-refresh xetla-revision-list-cookie))
7885 (defun xetla-revision-changeset (arg)
7886 "Gets and display the changeset at point in a revision list buffer.
7887 If used with a prefix arg ARG, don't include the diffs from the output."
7889 (let* ((cookie xetla-revision-list-cookie)
7890 (full (xetla-revision-revision
7891 (caddr (ewoc-data (ewoc-locate cookie)))))
7892 (revision (xetla-name-construct full)))
7893 (xetla-get-changeset revision t nil arg)))
7895 (defun xetla-revision-store-delta (across-versions)
7896 "Store a delta between two marked revisions.
7897 If prefix argument ACROSS-VERSIONS is given, read revision details from the
7900 (xetla-revision-delta across-versions t))
7902 (defun xetla-revision-delta (across-versions &optional stored-to-directory)
7903 "Run tla delta from marked revision to revision at point.
7904 If prefix-argument ACROSS-VERSIONS is nil, read a revision
7905 in the current version. If ACROSS-VERSIONS is non-nil, read an archive,
7906 a category, a branch, a version, and a revision to specify the revision.
7907 If STORED-TO-DIRECTORY is nil, ask the user whether the changeset is stored
7908 to or not. If STORED-TO-DIRECTORY is non-nil, don't ask the use and the
7909 changeset is stored."
7912 (xetla-revision-revision
7913 (caddr (ewoc-data (ewoc-locate xetla-revision-list-cookie)))))
7914 (modified-fq (xetla-name-construct modified))
7916 (let ((marked (xetla-revision-marked-revisions)))
7917 (when (< 1 (length marked))
7918 (error "Delta can be run against one marked revision as the base revision"))
7919 (cond ((and marked (null (cdr marked)))
7920 ;; use the marked revision
7921 ;; (xetla-revision-unmark-all)
7922 (xetla-revision-revision (car marked)))
7925 (format "Revision for delta to %s from: "
7928 (xetla-name-revision modified)))
7929 (if across-versions 'prompt (xetla-name-archive modified))
7930 (if across-versions 'prompt (xetla-name-category modified))
7931 (if across-versions 'prompt (xetla-name-branch modified))
7932 (if across-versions 'prompt (xetla-name-version modified))
7935 (unless (xetla-name-archive base)
7936 (error "Archive for the base is not specified"))
7937 (unless (xetla-name-category base)
7938 (error "Cateogory for the base is not specified"))
7939 (unless (xetla-name-branch base)
7940 (error "Branch for the base is not specified"))
7941 (unless (xetla-name-version base)
7942 (error "Version for the base is not specified"))
7943 (unless (xetla-name-revision base)
7944 ;; No revision for modified is specified.
7945 ;; Use HEAD revision.
7946 (setcar (nthcdr 4 base)
7948 (xetla-name-archive base)
7949 (xetla-name-category base)
7950 (xetla-name-branch base)
7951 (xetla-name-version base))))
7953 (when (or stored-to-directory
7954 (and (not stored-to-directory)
7955 (y-or-n-p "Store the delta to a directory? ")))
7956 (setq stored-to-directory 'ask))
7958 (xetla-delta (xetla-name-construct base)
7960 stored-to-directory)))
7962 (defun xetla-revision-bookmarks-add (name)
7963 "Add a bookmark named NAME for the current revision."
7964 (interactive "sBookmark name: ")
7965 (xetla-bookmarks-add name
7966 (list xetla-buffer-archive-name
7967 xetla-buffer-category-name
7968 xetla-buffer-branch-name
7969 xetla-buffer-version-name
7971 (message "bookmark %s added." name))
7973 (defun xetla-revision-sync-tree (arg)
7974 "Unify a tree's patch log with the current revision.
7975 With prefix argument ARG, use the latest version instead."
7977 (let* ((last-inventory (xetla-last-visited-inventory-buffer))
7978 (local-tree (or (if last-inventory
7979 (with-current-buffer last-inventory
7981 default-directory)))
7982 (current (ewoc-locate xetla-revision-list-cookie)))
7984 (not (and (eq (car (ewoc-data current))
7986 (eq (caddr (ewoc-data current))
7988 (setq current (ewoc-prev xetla-revision-list-cookie current)))
7990 (eq (car (ewoc-data current)) 'separator)
7991 (eq (caddr (ewoc-data current)) 'bookmark))
7992 (setq local-tree (cadddr (ewoc-data current))))
7993 (let ((to-tree (read-directory-name "Sync with tree: " local-tree)))
7994 (let* ((elem (ewoc-data (ewoc-locate
7995 xetla-revision-list-cookie)))
7996 (full (xetla-revision-revision (caddr elem))))
7997 (xetla-sync-tree (xetla-name-construct
7998 (if arg (butlast full) full))
8001 (defun xetla-revision-star-merge-version ()
8002 "Run star-merge for the version at point."
8004 (xetla-revision-star-merge t))
8006 (defun xetla-revision-star-merge (arg)
8007 "Run star-merge from the revision at point.
8008 With prefix argument ARG, merge all missing revisions from this version."
8010 (let* ((last-inventory (xetla-last-visited-inventory-buffer))
8011 (local-tree (or (if last-inventory
8012 (with-current-buffer last-inventory
8014 default-directory)))
8015 (current (ewoc-locate xetla-revision-list-cookie)))
8017 (not (and (eq (car (ewoc-data current))
8019 (eq (caddr (ewoc-data current))
8021 (setq current (ewoc-prev xetla-revision-list-cookie current)))
8023 (eq (car (ewoc-data current)) 'separator)
8024 (eq (caddr (ewoc-data current)) 'bookmark))
8025 (setq local-tree (cadddr (ewoc-data current))))
8026 (let ((to-tree (read-directory-name "Merge to tree: "
8028 (let* ((elem (ewoc-data (ewoc-locate
8029 xetla-revision-list-cookie)))
8030 (full (xetla-revision-revision (caddr elem))))
8031 (xetla-star-merge (xetla-name-construct
8032 (if arg (butlast full) full))
8035 (defun xetla-revision-replay-version ()
8036 "Call `xetla-revision-replay' with a prefix arg."
8038 (xetla-revision-replay t))
8040 (defun xetla-revision-replay (arg)
8041 "Run replay from the current location.
8042 If there are marked revisions, these are replayed.
8043 Otherwise, if an argument ARG is given, all missing
8044 revisions from this version are replayed. If there are no marked
8045 revisions and no argument is given, the revision under the point
8048 (let* ((last-inventory (xetla-last-visited-inventory-buffer))
8049 (local-tree (or (if last-inventory
8050 (with-current-buffer last-inventory
8052 default-directory)))
8053 (current (ewoc-locate xetla-revision-list-cookie)))
8055 (not (and (eq (car (ewoc-data current))
8057 (eq (caddr (ewoc-data current))
8059 (setq current (ewoc-prev xetla-revision-list-cookie current)))
8061 (eq (car (ewoc-data current)) 'separator)
8062 (eq (caddr (ewoc-data current)) 'bookmark))
8063 (setq local-tree (cadddr (ewoc-data current))))
8064 (let ((to-tree (read-directory-name "Replay to tree: " local-tree)))
8065 (if (xetla-revision-marked-revisions)
8066 (let ((revisions (mapcar 'xetla-revision-revision
8067 (xetla-revision-marked-revisions))))
8068 (xetla-replay (sort (mapcar (lambda (revision)
8069 (xetla-name-construct
8074 (let* ((elem (ewoc-data (ewoc-locate
8075 xetla-revision-list-cookie)))
8076 (full (xetla-revision-revision (caddr elem))))
8077 (xetla-replay (xetla-name-construct
8078 (if arg (butlast full) full))
8081 (defun xetla-revision-mark-revision ()
8082 "Mark revision at point."
8085 (data (ewoc-data (ewoc-locate
8086 xetla-revision-list-cookie))))
8087 (setcar (cdr data) t)
8088 (ewoc-refresh xetla-revision-list-cookie)
8090 (xetla-revision-next)))
8092 (defun xetla-revision-marked-revisions ()
8093 "Return the revisions that are currently marked."
8095 (ewoc-map #'(lambda (x) (when (and (eq (car x) 'entry-patch)
8097 (push (caddr x) acc)))
8098 xetla-revision-list-cookie)
8101 (defun xetla-revision-unmark-revision ()
8102 "Unmark the revision at point."
8105 (data (ewoc-data (ewoc-locate
8106 xetla-revision-list-cookie))))
8107 (setcar (cdr data) nil)
8108 (ewoc-refresh xetla-revision-list-cookie)
8110 (xetla-revision-next)))
8112 (defun xetla-revision-unmark-all ()
8113 "Unmark all revisions."
8115 (let ((pos (point)))
8116 (ewoc-map #'(lambda (x) (when (and (eq (car x) 'entry-patch)
8118 (setcar (cdr x) nil)))
8119 xetla-revision-list-cookie)
8120 (ewoc-refresh xetla-revision-list-cookie)
8123 (defun xetla-revision-tag-from-head ()
8124 "Run tla tag from the newest revision in revision buffer."
8126 (let* ((from (when xetla-buffer-archive-name
8127 (xetla-name-construct xetla-buffer-archive-name
8128 xetla-buffer-category-name
8129 xetla-buffer-branch-name
8130 xetla-buffer-version-name))))
8131 (unless from (error "No head revision"))
8132 (xetla-revision-tag-internal from)))
8134 (defun xetla-revision-tag-from-here ()
8135 "Run tla tag from the current location in revision buffer."
8137 (let ((from (when xetla-revision-list-cookie
8138 (let* ((elem (ewoc-data (ewoc-locate
8139 xetla-revision-list-cookie))))
8140 (apply 'xetla-name-construct (aref (caddr elem) 1))))))
8141 (unless from (error "No revision here"))
8142 (xetla-revision-tag-internal from)))
8144 (defun xetla-revision-tag-internal (from-fq)
8145 "Tag from FROM-FQ to some destination."
8146 (let* ((to (xetla-name-read "Tag to: "
8147 'prompt 'prompt 'prompt 'prompt))
8148 (to-fq (xetla-name-construct to)))
8149 (xetla-version-tag-internal from-fq to-fq)))
8151 (defun xetla-revision-show-changeset ()
8152 "Show a changeset for the current revision."
8154 (let ((elem (ewoc-data (ewoc-locate
8155 xetla-revision-list-cookie))))
8157 (entry-patch (xetla-revision-cat-log))
8158 (entry-change (let ((default-directory (caddr elem)))
8159 (xetla-changes))))))
8161 (xetla-make-bymouse-function xetla-revision-show-changeset)
8163 (defun xetla-revision-cat-log ()
8164 "Show the log entry for the revision at point."
8166 (let* ((elem (ewoc-data (ewoc-locate
8167 xetla-revision-list-cookie)))
8168 (full (xetla-revision-revision (caddr elem)))
8169 (cur-buf (current-buffer))
8170 (log-buf (xetla-cat-log-any full))
8171 (display-buf (xetla-get-buffer-create 'cat-log
8172 (xetla-name-construct full))))
8173 (xetla-switch-to-buffer display-buf)
8174 (let ((inhibit-read-only t))
8176 (insert (with-current-buffer log-buf
8178 (goto-char (point-min)))
8179 (xetla-cat-log-mode)
8180 (when (eq xetla-switch-to-buffer-mode 'pop-to-buffer)
8181 (pop-to-buffer cur-buf))))
8183 (defun xetla-revision-update ()
8184 "Run tla update for this revision."
8186 (let ((local-tree default-directory) ;; Default value
8187 (current (ewoc-locate xetla-revision-list-cookie)))
8189 (not (and (eq (car (ewoc-data current))
8191 (eq (caddr (ewoc-data current))
8193 (setq current (ewoc-prev xetla-revision-list-cookie current)))
8195 (eq (car (ewoc-data current)) 'separator)
8196 (eq (caddr (ewoc-data current)) 'bookmark))
8197 (setq local-tree (cadddr (ewoc-data current))))
8198 (let ((buffer (current-buffer)))
8199 (xetla-update (read-directory-name "Update tree: "
8202 (pop-to-buffer ,buffer)
8203 (xetla-generic-refresh))))))
8205 (defcustom xetla-send-comments-width 25
8206 "*Max length for the summary line when using %t in `xetla-send-comments-format'.")
8208 (defcustom xetla-send-comments-format "Your patch %c--%b--%v--%r (%t)"
8209 "Format for the Subject line for `xetla-revision-send-comments'.
8211 The following substring will be substituted:
8213 %f: Full revision name
8214 %a: The archive name
8215 %c: The category name
8217 %v: The version name
8218 %r: The revision name
8219 %s: The summary line
8220 %t: The summary line, truncated to `xetla-send-comments-width'
8223 (defun xetla-revision-send-comments (revision)
8224 "Sends comments to the author of REVISION.
8226 The email is extracted from the archive name. A new mail message is
8227 opened with a description of the revision. REVISION must be the same
8228 structure as the elem of `xetla-revision-list-cookie'.
8230 When called interactively, REVISION is the revision at point."
8231 (interactive (list (caddr (ewoc-data (ewoc-locate xetla-revision-list-cookie)))))
8232 (let* ((full-rev (xetla-revision-revision revision))
8233 (archive (xetla-name-archive full-rev))
8234 (email (progn (string-match "\\(.*\\)--\\([^-]\\|-[^-]\\)"
8236 (match-string 1 archive)))
8237 (summary (xetla-revision-summary revision))
8238 (subject xetla-send-comments-format))
8239 (dolist (pair '(("%f" . (xetla-name-construct full-rev))
8241 ("%c" . (xetla-name-category full-rev))
8242 ("%b" . (xetla-name-branch full-rev))
8243 ("%v" . (xetla-name-version full-rev))
8244 ("%r" . (xetla-name-revision full-rev))
8246 ("%t" . (if (> (string-width summary)
8247 xetla-send-comments-width)
8248 (concat (truncate-string summary 25)
8252 (replace-regexp-in-string (car pair) (eval (cdr pair))
8254 (compose-mail email subject)
8256 (insert "\n\n" (xetla-name-construct full-rev) "\n"
8258 " " (xetla-revision-date revision) "\n"
8259 " " (xetla-revision-creator revision) "\n"))))
8261 ;; --------------------------------------
8262 ;; xetla-changes-mode
8263 ;; --------------------------------------
8264 (define-derived-mode xetla-changes-mode fundamental-mode "xetla-changes"
8265 "Major mode to display changesets. Derives from `diff-mode'.
8267 Use '\\<xetla-changes-mode-map>\\[xetla-changes-mark-file]' to mark files, and '\\[xetla-changes-unmark-file]' to unmark.
8268 If you commit from this buffer (with '\\[xetla-changes-edit-log]'), then, the list of selected
8269 files in this buffer at the time you actually commit with
8270 \\<xetla-log-edit-mode-map>\\[xetla-log-edit-done].
8273 \\{xetla-changes-mode-map}
8275 (let ((diff-mode-shared-map (copy-keymap xetla-changes-mode-map))
8276 major-mode mode-name)
8279 (set (make-local-variable 'font-lock-defaults)
8280 (list 'xetla-changes-font-lock-keywords t nil nil))
8282 (set (make-local-variable 'xetla-get-file-info-at-point-function)
8283 'xetla-changes-get-file-at-point)
8284 (set (make-local-variable 'xetla-buffer-refresh-function)
8285 'xetla-changes-generic-refresh)
8286 (set (make-local-variable 'xetla-changes-cookie)
8287 (ewoc-create 'xetla-changes-printer))
8288 (make-local-variable 'xetla-buffer-marked-file-list)
8289 (easy-menu-add xetla-changes-mode-menu)
8290 (toggle-read-only 1)
8291 (set-buffer-modified-p nil))
8293 (defun xetla-changes-generic-refresh ()
8294 "Refresh the changes buffer."
8296 (if (eq (car xetla-changes-modified) 'local-tree)
8297 (xetla-changes xetla-changes-summary xetla-changes-base)))
8299 (defun xetla-changes-jump-to-change (&optional other-file)
8300 "Jump to the corresponding file and location of the change.
8301 The prefix argument OTHER-FILE controls whether the original or new
8304 (let* ((elem (ewoc-locate xetla-changes-cookie))
8305 (data (ewoc-data elem)))
8306 (cond ((< (ewoc-location elem) (point-at-bol))
8307 (xetla-changes-diff-goto-source other-file))
8308 ((eq (car data) 'file)
8309 (find-file (cadr data)))
8310 ((eq (car data) 'subtree)
8311 (xetla-switch-to-buffer (cadr data)))
8312 (t (error "Not on a recognized location")))))
8314 (defun xetla-changes-diff-goto-source (other-file)
8315 "Almost the same as `diff-goto-source'.
8316 But the target file is transformed by `xetla-changes-what-changed-original-file'
8317 to handle files in what-changed directory.
8318 OTHER-FILE controls whether the original or new file is visited."
8319 (let ((xetla-original-file-exists-p (symbol-function
8321 (xetla-original-find-file-noselect (symbol-function
8322 'find-file-noselect)))
8323 (flet ((file-exists-p (file)
8324 (unless (string= "/dev/null" file)
8326 xetla-original-file-exists-p
8327 (xetla-changes-what-changed-original-file file))))
8328 (find-file-noselect (file &optional nowarn rawfile wildcards)
8329 (if (featurep 'xemacs)
8330 (funcall xetla-original-find-file-noselect
8331 (xetla-changes-what-changed-original-file file)
8333 (funcall xetla-original-find-file-noselect
8334 (xetla-changes-what-changed-original-file file)
8335 nowarn rawfile wildcards))))
8336 (diff-goto-source other-file))))
8338 (defun xetla-changes-what-changed-original-file (file)
8339 "Remove what-changed directory part from FILE and return it."
8341 "\\(/,,what-changed[^/]+/new-files-archive\\)"
8343 (concat (substring file 0 (match-beginning 1))
8344 (substring file (match-end 1)))
8347 (defun xetla-changes-diff-or-list ()
8348 "Move around the changes buffer.
8349 When in the list part of the buffer, jump to the corresponding
8350 patch. When on a patch, jump to the corresponding entry in the list of
8353 (let* ((elem (ewoc-locate xetla-changes-cookie))
8354 (data (ewoc-data elem)))
8355 (cond ((< (ewoc-location elem) (point-at-bol))
8356 (let ((file (xetla-changes-get-file-at-point))
8357 (elem (ewoc-nth xetla-changes-cookie 0)))
8359 (or (not (eq (car (ewoc-data elem)) 'file))
8360 (not (string= (expand-file-name
8361 (cadr (ewoc-data elem)))
8363 (setq elem (ewoc-next xetla-changes-cookie elem)))
8364 (if elem (goto-char (ewoc-location elem))
8365 (error (format "Can't find file %s in list" file)))
8367 ((eq (car data) 'file)
8368 (re-search-forward (concat "^--- orig/" (cadr data)))
8370 ((eq (car data) 'subtree)
8371 (xetla-switch-to-buffer (cadr data)))
8372 (t (error "Not on a recognized location")))))
8374 (defun xetla-changes-master-buffer ()
8375 "Jump to the master *xetla-changes* buffer for a nested changes buffer."
8377 (unless xetla-changes-buffer-master-buffer
8378 (error "No master buffer"))
8379 (xetla-switch-to-buffer xetla-changes-buffer-master-buffer))
8381 (defun xetla-flash-line-on ()
8382 "Turn on highline mode or equivalent."
8383 (or (xetla-funcall-if-exists hl-line-mode)
8384 (xetla-funcall-if-exists highline-on)))
8386 (defun xetla-flash-line-off ()
8387 "Turn off highline mode or equivalent."
8388 (or (xetla-funcall-if-exists hl-line-mode)
8389 (xetla-funcall-if-exists highline-off)))
8391 (defun xetla-flash-line ()
8392 "Flash the current line."
8393 (let ((buffer (current-buffer)))
8394 (xetla-flash-line-on)
8396 ;; Avoid to switching buffer by asynchronously running
8398 ;; TODO: This is adhoc solution. Something guard-mechanism to avoid
8399 ;; buffer switching may be needed.
8401 (xetla-flash-line-off)))
8403 (defun xetla-changes-view-source (&optional other-file)
8404 "Show the corresponding file and location of the change.
8405 This function does not switch to the file, but it places the cursor
8406 temporarily at the location of the change and will stay in the changes
8407 buffer. Thus you can quickly see more context on a specific change without
8409 The prefix argument OTHER-FILE controls whether the original or new
8412 (let ((diff-window (selected-window)))
8414 (diff-goto-source other-file)
8417 (select-window diff-window))))
8419 (defun xetla-changes-edit-log (&optional insert-changelog)
8420 "Wrapper around `xetla-edit-log', setting the source buffer to current
8423 (xetla-edit-log insert-changelog (current-buffer)))
8425 (defun xetla-changes-rm ()
8426 "Remove the file under point."
8428 (let ((file (xetla-get-file-info-at-point)))
8430 (error "No file at point"))
8433 (defun xetla-changes-mark-file ()
8434 "Mark the file under point."
8436 (let ((current (ewoc-locate xetla-changes-cookie))
8437 (file (xetla-get-file-info-at-point)))
8438 (add-to-list 'xetla-buffer-marked-file-list file)
8439 (ewoc-refresh xetla-changes-cookie)
8440 (goto-char (ewoc-location (or (ewoc-next xetla-changes-cookie
8444 (defun xetla-changes-unmark-file ()
8445 "Unmark the file under point."
8447 (let ((current (ewoc-locate xetla-changes-cookie))
8448 (file (xetla-get-file-info-at-point)))
8449 (setq xetla-buffer-marked-file-list
8450 (delete file xetla-buffer-marked-file-list))
8451 (ewoc-refresh xetla-changes-cookie)
8452 (goto-char (ewoc-location (or (ewoc-next xetla-changes-cookie
8456 (defun xetla-changes-diff ()
8457 "Run tla file-diff on the file at point in *xetla-changes*."
8459 (let ((on-modified-file (xetla-get-file-info-at-point)))
8460 (if on-modified-file
8461 (xetla-file-diff on-modified-file)
8462 (error "Not on a modified file"))))
8464 (defun xetla-changes-next ()
8465 "Move to the next changes."
8467 (let ((cur-location (ewoc-location (ewoc-locate xetla-changes-cookie)))
8468 (next (ewoc-next xetla-changes-cookie
8469 (ewoc-locate xetla-changes-cookie))))
8471 ((> cur-location (point))
8472 (goto-char cur-location))
8474 (goto-char (ewoc-location next)))
8476 (diff-hunk-next)))))
8478 (defun xetla-changes-prev ()
8479 "Move to the previous changes."
8481 (let* ((current (ewoc-locate xetla-changes-cookie))
8482 (cur-location (ewoc-location current))
8483 (prev (ewoc-prev xetla-changes-cookie current))
8484 (next (ewoc-next xetla-changes-cookie current)))
8486 (if prev (goto-char (ewoc-location prev))
8487 (goto-char cur-location)))
8488 ((condition-case nil (progn (diff-hunk-prev) t) (error nil)))
8489 ((> (point-at-bol) cur-location)
8490 (goto-char cur-location))
8492 (goto-char (ewoc-location prev)))
8494 (goto-char cur-location)))
8497 (defun xetla-changes-in-diff ()
8498 "Return t if cursor is in the diffs section of the changes buffer."
8499 (save-excursion (re-search-backward "^--- orig" nil t)))
8502 (defun xetla-changes-ediff (&optional other-file)
8503 "Run ediff on the current changes.
8504 The prefix argument OTHER-FILE controls whether the original or new
8507 (unless (and (car xetla-changes-base)
8508 (car xetla-changes-base))
8509 (error "No revision information to base ediff on"))
8510 (let ((on-modified-file (xetla-get-file-info-at-point))
8512 (if (and on-modified-file (not (xetla-changes-in-diff)))
8513 (xetla-file-ediff-revisions on-modified-file
8515 xetla-changes-modified)
8516 (re-search-backward "^--- orig/")
8517 (re-search-forward "^--- orig/")
8518 (let ((file (expand-file-name
8519 (concat (file-name-as-directory default-directory)
8520 (buffer-substring-no-properties (point)
8524 (while (<= (re-search-forward "\\(^[\\+-].*\n\\)+" nil t) loc)
8525 (setq hunk (1+ hunk)))
8527 (with-current-buffer
8528 (xetla-file-ediff-revisions file xetla-changes-base
8529 xetla-changes-modified)
8530 (ediff-jump-to-difference hunk))))))
8532 (defun xetla-changes-get-file-at-point ()
8533 "Find file at point in *xetla-changes*.
8534 Throw an error when not on a file."
8535 (let ((elem (ewoc-locate xetla-changes-cookie (point))))
8537 (eq (car (ewoc-data elem)) 'file)
8538 (>= (ewoc-location elem) (point-at-bol)))
8539 (cadr (ewoc-data elem)))
8540 (expand-file-name (concat (file-name-as-directory
8542 (diff-find-file-name))))))
8544 (defun xetla-changes-jump-to-change-by-mouse (event &optional other-file)
8545 "Jump to the changes."
8546 (interactive "e\nP")
8547 (mouse-set-point event)
8548 (xetla-changes-jump-to-change other-file))
8550 (defun xetla-changes-revert ()
8551 "Reverts file at point."
8553 (let* ((file (xetla-get-file-info-at-point))
8554 (absolute (if (file-name-absolute-p file)
8557 (concat (file-name-as-directory
8560 (xetla-file-revert absolute)))
8562 ;; --------------------------------------
8563 ;; xetla-changelog-mode
8564 ;; --------------------------------------
8566 (define-derived-mode xetla-changelog-mode change-log-mode "xetla-changelog"
8567 (set (make-local-variable 'font-lock-defaults)
8568 (list 'xetla-changelog-font-lock-keywords
8569 t nil nil 'backward-paragraph))
8571 (use-local-map xetla-changelog-mode-map)
8572 (toggle-read-only 1)
8573 (set-buffer-modified-p nil))
8575 ;; --------------------------------------
8576 ;; xetla-inventory-file-mode
8577 ;; --------------------------------------
8579 (defun xetla-inventory-file-mode ()
8580 "Major mode to edit xetla inventory files (=tagging-method, .arch-inventory)."
8582 (kill-all-local-variables)
8583 (set (make-local-variable 'font-lock-defaults)
8584 '(xetla-inventory-file-font-lock-keywords t))
8585 (set (make-local-variable 'comment-start) "# ")
8586 (setq major-mode 'xetla-inventory-file-mode
8587 mode-name "xetla-inventory-file")
8588 (run-hooks 'xetla-inventory-file-mode-hook))
8590 (defun xetla-inventory-file-jump-from-head (category)
8591 "Search CATEGORY from the head of the buffer."
8592 (let ((p (save-excursion (goto-char (point-min))
8594 (concat "^" category) nil t))))
8598 (defun xetla-inventory-file-jump-from-tail (category)
8599 "Search CATEGORY from the tail of the buffer.
8600 Return nil if CATEGORY is not found."
8601 (let ((p (save-excursion (goto-char (point-max))
8603 (concat "^" category) nil t))))
8607 (defun xetla-inventory-file-add-file (category file)
8608 "Added FILE to CATEGORY."
8609 (unless (xetla-inventory-file-jump-from-tail category)
8610 (goto-char (point-min)))
8611 (save-excursion (open-line 1))
8612 ;; TODO regexp quote FILE
8613 (insert (format "%s ^(%s)$" category file)))
8615 ;; --------------------------------------
8617 ;; --------------------------------------
8618 ;; just 99% cut&paste from vc-follow-link in vc-hook.el, but this way there is
8619 ;; no need to load it thus avoiding interfering with VC ...
8620 (defun xetla-follow-link ()
8621 "Follow a symbolic link.
8622 If the current buffer visits a symbolic link, this function makes it
8623 visit the real file instead. If the real file is already visited in
8624 another buffer, make that buffer current, and kill the buffer
8625 that visits the link."
8626 (let* ((truename (abbreviate-file-name (file-truename buffer-file-name)))
8627 (true-buffer (find-buffer-visiting truename))
8628 (this-buffer (current-buffer)))
8629 (if (eq true-buffer this-buffer)
8631 (kill-buffer this-buffer)
8632 ;; In principle, we could do something like set-visited-file-name.
8633 ;; However, it can't be exactly the same as set-visited-file-name.
8634 ;; I'm not going to work out the details right now. - rms.
8635 (set-buffer (find-file-noselect truename)))
8636 (set-buffer true-buffer)
8637 (kill-buffer this-buffer))))
8640 (defvar vc-ignore-vc-files)
8643 (defun xetla-find-file-hook ()
8644 "Hook executed when opening a file.
8645 Follow symlinked files/directories to the actual location of a file.
8646 Enter smerge mode if the file has conflicts (detected by the presence
8648 (when (xetla-file-has-conflict-p (buffer-file-name))
8649 (xetla-funcall-if-exists smerge-mode 1))
8650 (let (link file result)
8651 (when (and (not vc-ignore-vc-files)
8652 xetla-follow-symlinks
8653 (setq file buffer-file-name)
8654 (not (string= (setq link (file-truename file)) file)))
8656 result (cond ((equal xetla-follow-symlinks 'tree)
8657 (xetla-tree-root file t))
8658 ((equal xetla-follow-symlinks 'id)
8659 (= 0 (xetla-run-tla-sync
8661 :finished 'xetla-status-handler
8662 :error 'xetla-status-handler)))))
8665 (cond ((eq xetla-follow-symlinks-mode 'warn)
8667 "Warning: symbolic link to arch-controlled source file: %s"
8669 ((or (eq xetla-follow-symlinks-mode 'follow)
8670 (find-buffer-visiting file))
8672 (message "Followed link to arch-controlled %s"
8674 ((eq xetla-follow-symlinks-mode 'ask)
8675 (if (y-or-n-p "Follow symbolic link to arch-controlled source file? ")
8678 (message "Followed link to arch-controlled %s"
8681 "Warning: editing through the link bypasses version control")))
8682 (t (error "Unknown mode for xetla-follow-symlinks-mode=%s"
8683 xetla-follow-symlinks-mode)))
8686 ;; --------------------------------------
8688 ;; --------------------------------------
8689 (defvar xetla-insert-arch-tag-functions
8690 '((autoconf-mode . xetla-insert-arch-tag-for-autoconf-mode)
8691 (makefile-mode . xetla-insert-arch-tag-for-makefile-mode))
8692 "Alist containing per mode specialized functions for inserting arch-tag.
8693 Key stands for a major mode. Value is a function which inserts arch-tag.
8694 The function takes two arguments. The first argument is an uuid string.
8695 The second argument is a boolean showing whether the point is in a comment
8698 (defconst xetla-arch-tag-string (concat "arch-ta" "g: ")
8699 "To avoid having the string a-r-c-h-t-a-g: in this buffer ;-).")
8701 (defun xetla-tag-uuid ()
8702 "Candidate for `xetla-tag-function'.
8703 Returns a unique string using uuidgen"
8704 (xetla-strip-final-newline (shell-command-to-string "uuidgen")))
8706 (defun xetla-tag-name-date-filename ()
8707 "Candidate for `xetla-tag-function'.
8708 Returns a string containing the name of the user, the precise date,
8709 and the name of the current file. This should be unique worldwide,
8710 has the advantage of containing usefull information in addition to
8711 the unique identifier. The inconvenient in comparison to
8712 `xetla-tag-uuid' is that an unfortunate modification of the tag is more
8713 easily made (sed script or manual modification)"
8714 (concat (user-full-name) ", "
8715 (format-time-string "%c")
8716 " (" (file-name-nondirectory (buffer-file-name)) ")"))
8719 (defun xetla-tag-string ()
8720 "Return a suitable string for an arch-tag.
8721 Actually calls `xetla-tag-function', which defaults to `xetla-tag-uuid' to generate
8722 string (and possibly add a comment-end after).
8724 Interactively, you should call `xetla-tag-insert', but this function can
8725 be usefull to write template files."
8726 (funcall xetla-tag-function))
8729 (defun xetla-tag-insert ()
8730 "Insert a unique arch-tag in the current file.
8731 Actually calls `xetla-tag-function', which defaults to `xetla-tag-uuid' to generate
8732 string (and possibly add a comment-end after)"
8734 (let ((the-tag-itself (xetla-tag-string))
8735 (in-comment-p (nth 4 (parse-partial-sexp (point) (point-min))))
8738 (handler (assoc major-mode xetla-insert-arch-tag-functions)))
8740 (funcall (cdr handler) the-tag-itself in-comment-p)
8741 (unless in-comment-p
8742 (setq header (if comment-start
8743 (concat comment-start
8744 (if (string-match " $" comment-start)
8747 footer (if (and comment-end (not (string= "" comment-end)))
8748 (format "\n%s(do not change this comment)%s%s"
8749 (make-string (length header) ?\ )
8751 (if (string-match "^ " comment-end)
8754 (insert (concat header xetla-arch-tag-string the-tag-itself
8758 (defun xetla-tag-regenerate ()
8759 "Find an arch tag in the current buffer and regenerates it.
8760 This means changing the ID of the file, which will usually be done after
8761 copying a file in the same tree to avoid duplicates ID.
8763 Raises an error when multiple tags are found or when no tag is found."
8767 (goto-char (point-min))
8768 (unless (search-forward xetla-arch-tag-string nil t)
8769 (error "No arch tag in this buffer"))
8770 (delete-region (point) (progn (end-of-line) (point)))
8771 (insert (funcall xetla-tag-function))
8772 (if (search-forward xetla-arch-tag-string nil t)
8776 (goto-char second-tag)
8778 (error "Multiple tag in this buffer"))))
8780 (defun xetla-regenerate-id-for-file (file)
8781 "Create a new id for the file FILE.
8787 But also works for the tagline method. When the tagline method is
8788 used, the file is opened in a buffer. If the file had modifications,
8789 the tag is modified in the buffer, and the user is prompted for
8790 saving. If the file had no unsaved modifications, the modification is
8791 done in the buffer and the file is saved without prompting.
8793 FILE must be an absolute filename. It can also be a directory"
8795 (if (file-directory-p file)
8797 (delete-file (concat (file-name-as-directory file)
8799 (xetla-add-id nil file))
8800 (let* ((dir (file-name-directory file))
8801 (basename (file-name-nondirectory file))
8802 (id-file (concat dir
8803 (file-name-as-directory ".arch-ids")
8805 (if (file-exists-p id-file)
8806 (progn (delete-file id-file)
8807 (xetla-add-id nil file))
8808 (with-current-buffer
8809 (find-file-noselect file)
8810 (let ((modif (buffer-modified-p)))
8811 (xetla-tag-regenerate)
8813 (when (y-or-n-p (format "Save buffer %s? " (buffer-name)))
8815 ;; No modif. We can safely save without prompting.
8816 (save-buffer))))))))
8818 (defun xetla-insert-arch-tag-for-autoconf-mode (uuid in-comment-p)
8819 "Insert arch-tag, UUID to the current `autoconf-mode' buffer.
8820 IN-COMMENT-P indicates whether we are currently inside a comment."
8822 ;; In current GNU Emacs's autoconf-mode implementation,
8823 ;; next line is never executed.
8824 (error "Comment prefix \"dnl\" is not suitable for gnuarch"))
8825 (let ((header "m4_if(dnl Do not change this comment\n")
8826 (footer "\n)dnl\n"))
8827 (insert (concat header " " xetla-arch-tag-string uuid footer))))
8829 (defun xetla-insert-arch-tag-for-makefile-mode (uuid in-comment-p)
8830 "Insert arch-tag, UUID to the current `makefile-mode' buffer.
8831 If the file is Makefile.am, input for automake, use `##' as `comment-start'.
8832 Comment started with `##' in Makefile.am is automatically stripped by automake.
8833 IN-COMMENT-P indicates whether we are currently inside a comment."
8834 (let ((xetla-insert-arch-tag-functions
8835 (assq-delete-all 'makefile-mode
8836 (copy-sequence xetla-insert-arch-tag-functions)))
8837 (comment-start (if (and (buffer-file-name)
8838 (string-match "Makefile.am$" (buffer-file-name)))
8841 (xetla-tag-insert)))
8844 (defun xetla-ediff-add-log-entry ()
8847 (pop-to-buffer ediff-buffer-A)
8848 (xetla-add-log-entry))
8853 (defvar xetla-tree-lint-cookie nil
8854 "Ewoc cookie used in tree-lint mode.")
8856 (define-derived-mode xetla-tree-lint-mode fundamental-mode
8858 "Major mode to view tree-lint warnings.
8860 \\{xetla-tree-lint-mode-map}
8862 (let ((inhibit-read-only t))
8864 (set (make-local-variable 'xetla-buffer-refresh-function)
8865 `(lambda () (interactive) (xetla-tree-lint ,default-directory)))
8866 (set (make-local-variable 'xetla-tree-lint-cookie)
8867 (ewoc-create 'xetla-tree-lint-printer))
8868 (set (make-local-variable 'xetla-get-file-info-at-point-function)
8869 'xetla-tree-lint-get-file-at-point)
8870 (set (make-local-variable 'xetla-buffer-marked-file-list)
8872 (set (make-local-variable 'xetla-generic-select-files-function)
8873 'xetla-tree-lint-select-files)
8874 (toggle-read-only t))
8876 (defun xetla-tree-lint-get-file-at-point ()
8877 "Find file at point in *xetla-tree-lint*. Error when not on a file."
8878 (let ((data (ewoc-data (ewoc-locate xetla-tree-lint-cookie))))
8879 (if (eq (car data) 'message)
8883 (defun xetla-tree-lint-prepare-buffer (root)
8884 "Prepare the buffer to display the tree-lint warnings for tree ROOT."
8885 (let ((buffer (xetla-get-buffer-create 'tree-lint root)))
8886 (with-current-buffer buffer
8887 (xetla-tree-lint-mode)
8889 xetla-tree-lint-cookie
8890 (list 'message (format "Running tree-lint in %s ..."
8895 (defun xetla-tree-lint (root)
8896 "Run tla tree-lint in directory ROOT."
8898 (list (xetla-read-project-tree-maybe "Run tla tree-lint in: ")))
8899 (let ((default-directory root)
8900 (buffer (xetla-tree-lint-prepare-buffer root)))
8901 (when xetla-switch-to-buffer-first
8902 (xetla-switch-to-buffer buffer))
8903 (xetla-run-tla-async
8905 :related-buffer buffer
8907 `(lambda (output error status arguments)
8908 (if (> (buffer-size output) 0)
8911 (xetla-tree-lint-parse-buffer output ,buffer))
8912 (with-current-buffer ,buffer
8913 (xetla-tree-lint-cursor-goto
8914 (ewoc-nth xetla-tree-lint-cookie 0))))
8915 (message "No tree-lint warnings for %s." ,default-directory)
8916 (with-current-buffer ,buffer
8917 (let ((inhibit-read-only t))
8920 xetla-tree-lint-cookie
8921 (list 'message (format "No tree-lint warnings for %s."
8922 ,default-directory)))))))
8924 `(lambda (output error status arguments)
8926 (xetla-tree-lint-parse-buffer output ,buffer))
8927 (with-current-buffer ,buffer
8928 (xetla-tree-lint-cursor-goto
8929 (ewoc-nth xetla-tree-lint-cookie 0)))))))
8931 (defconst xetla-tree-lint-message-alist
8932 '(("^These files would be source but lack inventory ids"
8934 ("^These explicit ids have no corresponding file:"
8936 ("^These files violate naming conventions:"
8938 ("^These symlinks point to nonexistent files:"
8940 ("^Duplicated ids among each group of files listed here:"
8944 (defun xetla-tree-lint-message-type (message)
8945 "Return a symbol saying which type of message the string MESSAGE is."
8947 (iterator xetla-tree-lint-message-alist))
8948 (while (and iterator (not result))
8949 (when (string-match (caar iterator) message)
8950 (setq result (cadar iterator)))
8951 (setq iterator (cdr iterator)))
8952 (or result 'unknown)))
8954 (defun xetla-tree-lint-parse-buffer (buffer output-buffer)
8955 "Parse the output of xetla tree-lint in BUFFER.
8956 Show in in the tree-lint-mode buffer OUTPUT-BUFFER."
8957 (with-current-buffer output-buffer
8958 (let ((inhibit-read-only t))
8960 (insert (xetla-face-add (format "Tree lint warnings in %s\n"
8963 (setq xetla-tree-lint-cookie
8964 (ewoc-create 'xetla-tree-lint-printer)))
8965 (with-current-buffer buffer
8966 (goto-char (point-min))
8967 (let ((cookie (with-current-buffer output-buffer
8968 xetla-tree-lint-cookie)))
8969 (while (re-search-forward "^." nil t)
8970 (goto-char (point-at-bol))
8971 (let* ((message (buffer-substring-no-properties
8972 (point) (point-at-eol)))
8973 (type (xetla-tree-lint-message-type message)))
8974 (ewoc-enter-last cookie (list 'message message))
8976 (if (eq type 'duplicate-id)
8978 (while (looking-at "\\([^ \t]*\\)[ \t]+\\(.*\\)")
8979 (let* ((file (match-string 1))
8980 (id (match-string 2)))
8981 ;; Format: (duplicate-id "filename" "id" first? last?)
8983 cookie (list 'duplicate-id (xetla-unescape file) id
8986 (while (not (eq (char-after) ?\n))
8987 (let ((file (buffer-substring-no-properties
8988 (point) (point-at-eol))))
8990 (ewoc-enter-last cookie
8992 (xetla-unescape file)
8994 (eq (char-after) ?\n)))))
8997 (while (not (eq (char-after) ?\n))
8998 (ewoc-enter-last cookie
8999 (list type (xetla-unescape
9000 (buffer-substring-no-properties
9003 (forward-line 1)))))
9004 (let ((inhibit-read-only t))
9005 (ewoc-refresh cookie)))))
9007 (defvar xetla-tree-lint-printer-first-duplicate nil
9009 non-nil when the ewoc printer is printing the first group of duplicate ID's")
9011 (defun xetla-tree-lint-printer (elem)
9012 "Ewoc printer for the tree-lint buffer.
9014 (when (not (eq (car elem) 'message))
9015 (insert (if (member (cadr elem)
9016 xetla-buffer-marked-file-list)
9017 (concat " " xetla-mark " ") " ")))
9019 (message (insert "\n" (xetla-face-add (cadr elem) 'xetla-messages)
9021 (setq xetla-tree-lint-printer-first-duplicate t))
9022 (missing-file (insert
9023 (xetla-face-add (cadr elem) 'xetla-to-add
9024 'xetla-tree-lint-file-map
9025 xetla-tree-lint-file-menu)))
9026 (id-without-file (insert
9027 (xetla-face-add (cadr elem) 'xetla-to-add
9028 'xetla-tree-lint-file-map
9029 xetla-tree-lint-file-menu)))
9030 (unrecognized (insert
9031 (xetla-face-add (cadr elem)
9033 'xetla-tree-lint-file-map
9034 xetla-tree-lint-file-menu)))
9035 (broken-link (insert (xetla-face-add (cadr elem)
9037 'xetla-tree-lint-file-map
9038 xetla-tree-lint-file-menu)))
9039 (unknown (insert (xetla-face-add (cadr elem)
9041 'xetla-tree-lint-file-map
9042 xetla-tree-lint-file-menu)))
9044 (insert (xetla-face-add (cadr elem)
9046 'xetla-tree-lint-file-map
9047 xetla-tree-lint-file-menu))
9048 (when (nth 3 elem) (insert "\t"
9049 (xetla-face-add (caddr elem)
9051 (when (nth 4 elem) (insert "\n")))
9052 (t (error "Unimplemented type of tree-lint error")))
9055 (defun xetla-tree-lint-cursor-goto (ewoc-tree-lint)
9056 "Move cursor to the ewoc location of EWOC-TREE-LINT."
9059 (progn (goto-char (ewoc-location ewoc-tree-lint))
9060 (re-search-forward "." nil t)
9062 (goto-char (point-min))))
9064 (defun xetla-tree-lint-next ()
9065 "Move to the next tree lint item."
9067 (let* ((cookie xetla-tree-lint-cookie)
9068 (elem (ewoc-locate cookie))
9069 (next (or (ewoc-next cookie elem) elem)))
9070 (xetla-tree-lint-cursor-goto next)))
9072 (defun xetla-tree-lint-previous ()
9073 "Move to the previous tree lint item."
9075 (let* ((cookie xetla-tree-lint-cookie)
9076 (elem (ewoc-locate cookie))
9077 (previous (or (ewoc-prev cookie elem) elem)))
9078 (xetla-tree-lint-cursor-goto previous)))
9080 (defun xetla-tree-lint-mark-file ()
9081 "Mark the current tree-lint file."
9083 (let ((current (ewoc-locate xetla-tree-lint-cookie))
9084 (files (xetla-tree-lint-select-files nil nil nil nil nil t t)))
9086 (dolist (file files)
9087 (add-to-list 'xetla-buffer-marked-file-list file))
9088 (ewoc-refresh xetla-tree-lint-cookie))
9089 (xetla-tree-lint-cursor-goto
9090 (if (eq (car (ewoc-data current)) 'message)
9092 (ewoc-next xetla-tree-lint-cookie current)))))
9094 (defun xetla-tree-lint-unmark-file ()
9095 "Unmark the current tree-lint file."
9097 (let ((current (ewoc-locate xetla-tree-lint-cookie))
9098 (files (xetla-tree-lint-select-files nil nil nil nil nil t t)))
9100 (dolist (file files)
9101 (setq xetla-buffer-marked-file-list
9102 (delete file xetla-buffer-marked-file-list)))
9103 (ewoc-refresh xetla-tree-lint-cookie))
9104 (xetla-tree-lint-cursor-goto
9105 (if (eq (car (ewoc-data current)) 'message)
9107 (ewoc-next xetla-tree-lint-cookie current)))))
9109 (defun xetla-tree-lint-unmark-all ()
9110 "Unmark all tree-lint files."
9112 (let ((current (ewoc-locate xetla-tree-lint-cookie)))
9113 (setq xetla-buffer-marked-file-list nil)
9114 (ewoc-refresh xetla-tree-lint-cookie)
9115 (xetla-tree-lint-cursor-goto current)))
9118 (defun xetla-tree-lint-select-files (msg-singular
9122 no-group ignore-marked
9125 "Get the list of files under cursor, and ask confirmation of the user.
9126 Prompt with either MSG-SINGULAR, MSG-PLURAL, MSG-ERR OR MSG-PROMPT.
9127 If NO-GROUP is nil and if the cursor is on a message, all the
9128 files belonging to this message are selected. If some files are marked
9129 (i.e. `xetla-buffer-marked-file-list' is non-nil) and IGNORE-MARKED is
9130 non-nil, the list of marked files is returned. If NO-PROMPT is
9131 non-nil, don't ask for confirmation. If Y-OR-N is non-nil, then this
9132 function is used instead of `y-or-n-p'."
9133 (if (and xetla-buffer-marked-file-list
9135 (not (xetla-mouse-event-p last-input-event)))
9136 (let ((list xetla-buffer-marked-file-list))
9137 (unless (or no-prompt
9138 (funcall (or y-or-n 'y-or-n-p)
9139 (if (eq 1 (length list))
9140 (format msg-singular
9146 (let* ((ewoc-elem (ewoc-locate xetla-tree-lint-cookie))
9147 (elem (ewoc-data ewoc-elem)))
9148 (if (eq (car elem) 'message)
9150 (when no-group (error msg-err))
9153 (ewoc-next xetla-tree-lint-cookie ewoc-elem))
9154 (setq elem (and ewoc-elem (ewoc-data ewoc-elem)))
9155 (while (and ewoc-elem (not (eq (car elem) 'message)))
9156 (add-to-list 'list (cadr elem))
9158 (ewoc-next xetla-tree-lint-cookie ewoc-elem))
9159 (setq elem (and ewoc-elem (ewoc-data ewoc-elem))))
9161 (unless (or no-prompt
9162 (funcall (or y-or-n 'y-or-n-p)
9163 (if (eq 1 (length list))
9164 (format msg-singular
9170 (list (if (or no-prompt
9171 (funcall (or y-or-n 'y-or-n-p)
9172 (format msg-singular
9175 (error msg-err)))))))
9177 (defun xetla-tree-lint-add-files (files)
9178 "Prompts and add FILES.
9179 If on a message field, add all the files below this message."
9182 (xetla-tree-lint-select-files "Add %s? "
9184 "Not adding any file"
9186 (apply 'xetla-add-id nil files)
9187 (xetla-tree-lint default-directory))
9189 (defun xetla-tree-lint-delete-files (files)
9190 "Prompts and delete FILES.
9191 If on a message field, delete all the files below this message."
9194 (xetla-tree-lint-select-files "Delete %s? "
9196 "Not deleting any file"
9200 (mapcar 'delete-file files)
9201 (xetla-tree-lint default-directory))
9203 (defun xetla-tree-lint-regenerate-id (files)
9204 "Prompts and regenerate an ID (either explicit or tagline) for FILES."
9207 (xetla-tree-lint-select-files "Regenerate ID for %s? "
9208 "Regenerate ID for %s files? "
9209 "Not regenerating ID for any file"
9210 "Regenerate ID for file: "
9212 (mapcar 'xetla-regenerate-id-for-file files)
9213 (xetla-tree-lint default-directory))
9215 (defun xetla-tree-lint-make-junk (files)
9216 "Prompts and make the FILES junk.
9217 If marked files are, use them as FIELS.
9218 If not, a file under the point is used as FILES.
9219 If on a message field, make all the files below this message junk."
9222 (xetla-tree-lint-select-files "Make %s junk(prefixing \",,\")? "
9223 "Make %s files junk? "
9224 "Not making any file junk"
9228 (xetla-generic-file-prefix files ",,"))
9230 (defun xetla-tree-lint-make-precious (files)
9231 "Prompts and make the FILES precious.
9232 If marked files are, use them as FIELS.
9233 If not, a file under the point is used as FILES.
9234 If on a message field, make all the files below this message precious."
9237 (xetla-tree-lint-select-files "Make %s precious(prefixing \"++\")? "
9238 "Make %s files precious? "
9239 "Not making any file precious? "
9240 "Make file precious: "
9243 (xetla-generic-file-prefix files "++"))
9245 (defun xetla-generic-file-prefix (files prefix)
9246 "Rename FILES with adding prefix PREFIX.
9247 Visited buffer associations also updated."
9250 (let* ((buf (find-buffer-visiting from))
9252 (file-name-directory from)
9254 (file-name-nondirectory from))))
9255 (rename-file from to)
9257 (with-current-buffer buf
9259 (set-visited-file-name to)))))
9261 (xetla-generic-refresh))
9264 ;; end tree-lint-mode
9266 (defvar xetla-arch-version nil
9267 "Version of the underlying tla binary.")
9269 (defvar xetla-arch-version-number nil
9270 "Version _number_ of the underlying tla binary.
9271 It is stored in an alist in the form
9272 \(\(major . <major>\)
9274 \(minor-minor . <minor-minor>\)
9277 (defun xetla-arch-version ()
9278 "Return the TLA (arch) version."
9280 (setq xetla-arch-version
9281 (xetla-run-tla-sync '("-V")
9283 (lambda (output error status arguments)
9284 (xetla-buffer-content output))))
9286 (message xetla-arch-version))
9289 (defun xetla-arch-version-number ()
9290 "Return the TLA (arch) version number.
9291 This is extremely mandatory since tla 1.3 parameters differ from
9292 those used in tla 1.2 for example."
9294 (unless xetla-arch-version
9295 (xetla-arch-version))
9296 (setq xetla-arch-version-number
9302 ".*\\(?:fix-?\\)?\\([0-9]\\)?.*)")
9305 '(major minor minor-minor fix)
9306 (mapcar #'string-to-number
9307 (list (match-string 1 xetla-arch-version)
9308 (match-string 2 xetla-arch-version)
9309 (or (match-string 3 xetla-arch-version)
9311 (or (match-string 4 xetla-arch-version)
9314 (message "%S" xetla-arch-version-number))
9315 xetla-arch-version-number)
9319 (defun xetla-version ()
9320 "Return the XEtla version."
9323 (or (when (locate-library "xetla-version")
9324 (load-library "xetla-version")
9325 (when (boundp 'xetla-version)
9327 (let ((default-directory
9328 (file-name-directory (locate-library "xetla"))))
9329 (defvar xetla-version nil "Version of xetla")
9330 (xetla-run-tla-sync '("logs" "-f" "-r")
9332 (lambda (output error status arguments)
9334 (goto-char (point-min))
9336 (buffer-substring-no-properties
9340 (lambda (output error status arguments)
9341 (setq xetla-version "unknown")))))))
9344 (message "We did not find xetla-version.el nor the arch-tree containing xetla.el!")
9346 (message "Are you using a developer version of XEtla?")
9349 (message xetla-version))
9354 (defun xetla-prepare-patch-submission (xetla-tree-root tarball-base-name email version-string
9355 &optional description subject)
9356 "Submit a patch to a xetla working copy (at XETLA-TREE-ROOT) via email.
9357 With this feature it is not necessary to tag an xetla archive.
9358 You simply edit your checked out copy from your project and call this function.
9359 The function will create a patch as *.tar.gz file (based on TARBALL-BASE-NAME)
9360 and send it to the given email address EMAIL.
9361 VERSION-STRING should indicate the version of xetla that the patch applies to.
9362 DESCRIPTION is a brief descsription of the patch.
9363 SUBJECT is the subject for the email message.
9364 For an example, how to use this function see: `xetla-submit-patch'."
9368 (let* ((default-directory xetla-tree-root)
9369 (tarball-full-base-name (concat default-directory tarball-base-name))
9370 (tarball-full-name (concat tarball-full-base-name ".tar.gz")))
9371 (xetla-changes-save-as-tgz tarball-full-base-name)
9374 (delete-other-windows)
9375 (reporter-submit-bug-report
9383 (insert "[VERSION] " version-string)
9384 (goto-char (point-max))
9385 (mml-attach-file tarball-full-name "application/octet-stream")
9386 (xetla-show-changeset-from-tgz tarball-full-name)
9389 (goto-char (point-min))
9390 (mail-position-on-field "Subject")
9391 (insert (or subject "[PATCH] "))))
9393 (defvar xetla-package-root-directory nil)
9394 (defun xetla-submit-patch ()
9395 "Submit a patch to the XEtla devel list.
9396 With this feature it is not necessary to tag an xetla.el archive.
9397 You simply edit your checked out copy from xetla.el and call this function.
9398 The function will create a patch as *.tar.gz file and send it to the xetla-el-dev list."
9401 (xetla-arch-version)
9402 (xetla-prepare-patch-submission (xetla-tree-root
9403 (file-name-directory (or xetla-package-root-directory (locate-library "xetla"))))
9404 (concat ",,xetla-patch-" (format-time-string "%Y-%m-%d_%H-%M-%S" (current-time)))
9405 "xetla-devel@youngs.au.com"
9408 "Please change the Subject header to a concise description of your patch.\n"
9409 "Please describe your patch between the LOG-START and LOG-END markers:\n"
9416 ;; Integration into gnus
9417 (defvar gnus-summary-xetla-submap nil
9418 "Key mapping added to gnus summary.")
9421 (defvar gnus-summary-mode-map))
9423 (defun xetla-insinuate-gnus ()
9424 "Integrate xetla to gnus.
9425 The following keybindings are installed for gnus-summary:
9426 K t v `xetla-gnus-article-view-patch'
9427 K t a `xetla-gnus-article-apply-patch'
9428 K t l `xetla-gnus-article-extract-log-message'"
9430 (setq gnus-summary-xetla-submap (make-sparse-keymap))
9431 (define-key gnus-summary-xetla-submap [?v] 'xetla-gnus-article-view-patch)
9432 (define-key gnus-summary-xetla-submap [?a] 'xetla-gnus-article-apply-patch)
9433 (define-key gnus-summary-xetla-submap [?l] 'xetla-gnus-article-extract-log-message)
9434 (define-key gnus-summary-mode-map [?K ?t] gnus-summary-xetla-submap))
9436 (defun xetla-gnus-article-view-patch (n)
9437 "View MIME part N, as xetla patchset.
9438 Note, N is forced to 2 at the moment!"
9441 (gnus-article-part-wrapper n 'xetla-gnus-view-patch))
9443 (defun xetla-gnus-view-patch (handle)
9444 "View a patch within gnus. HANDLE should be the handle of the part."
9445 (let ((archive-name (xetla-make-temp-name "gnus-patch-tgz")))
9446 (mm-save-part-to-file handle archive-name)
9447 (gnus-summary-select-article-buffer)
9448 (split-window-vertically)
9449 (xetla-show-changeset-from-tgz archive-name)
9450 (delete-file archive-name)))
9452 (defun xetla-gnus-article-apply-patch (n)
9453 "Apply MIME part N, as xetla patchset.
9454 Note, N is forced to 2 at the moment!"
9457 (gnus-article-part-wrapper n 'xetla-gnus-apply-patch))
9459 (defun xetla-gnus-apply-patch (handle)
9460 "Apply the patch corresponding to HANDLE."
9461 (let ((archive-name (xetla-make-temp-name "gnus-patch-tgz"))
9463 (xetla-gnus-article-extract-log-message)
9464 (mm-save-part-to-file handle archive-name)
9465 (gnus-summary-select-article-buffer)
9466 (split-window-vertically)
9467 (xetla-show-changeset-from-tgz archive-name)
9468 (setq tree (read-directory-name
9470 (xetla-name-match-from-list
9471 (when xetla-memorized-version
9472 (xetla-name-split xetla-memorized-version))
9473 xetla-apply-patch-mapping)))
9474 (xetla-apply-changeset-from-tgz archive-name tree)
9475 (delete-file archive-name)))
9477 (defun xetla-gnus-article-extract-log-message ()
9478 "Parse the mail and extract the log information.
9479 Save it to `xetla-memorized-log-header', `xetla-memorized-log-message'
9480 and `xetla-memorized-version'."
9482 (gnus-summary-select-article-buffer)
9484 (goto-char (point-min))
9485 (let* ((start-pos (search-forward "[PATCH] "))
9486 (end-pos (point-at-eol))
9487 (log-header (buffer-substring-no-properties start-pos end-pos)))
9488 (setq xetla-memorized-log-header log-header))
9489 (goto-char (point-min))
9490 (let* ((start-pos (search-forward "[VERSION] " nil t))
9491 (end-pos (point-at-eol))
9492 (version (when start-pos (buffer-substring-no-properties start-pos end-pos))))
9493 (setq xetla-memorized-version (and start-pos version)))
9494 (goto-char (point-min))
9495 (let* ((start-pos (+ (search-forward "<<LOG-START>>") 1))
9496 (end-pos (- (progn (search-forward "<LOG-END>>") (point-at-bol)) 1))
9497 (log-message (buffer-substring-no-properties start-pos end-pos)))
9498 (setq xetla-memorized-log-message log-message)
9499 (message "Extracted the xetla log message from '%s'" xetla-memorized-log-header)))
9500 (gnus-article-show-summary))
9503 (defun xetla-submit-bug-report ()
9504 "Submit a bug report, with pertinent information to the XEtla Devel list."
9507 (delete-other-windows)
9509 (xetla-arch-version)
9510 (reporter-submit-bug-report
9511 "xetla-devel@youngs.au.com"
9512 (concat "XEtla " xetla-version)
9514 ;; non user variables
9520 (sort (apropos-internal "^xetla-" 'user-variable-p)
9521 (lambda (v1 v2) (string-lessp (format "%s" v1) (format "%s" v2))))
9522 ;; see what the user had loaded
9528 "Please change the Subject header to a concise bug description or feature request.\n"
9529 "In this report, remember to cover the basics, that is, what you \n"
9530 "expected to happen and what in fact did happen.\n"
9531 "Please remove these instructions from your message."))
9533 ;; insert the backtrace buffer content if present
9534 (let ((backtrace (get-buffer "*Backtrace*")))
9536 (goto-char (point-max))
9538 (insert-buffer-substring backtrace)))
9540 (goto-char (point-min))
9541 (mail-position-on-field "Subject")
9542 (insert "[BUG/FEATURE] "))
9544 ;; For people used to Debian's reportbug
9545 (defalias 'xetla-report-bug 'xetla-submit-bug-report)
9546 ;; For people used to Gnus M-x gnus-bug RET
9547 (defalias 'xetla-bug 'xetla-submit-bug-report)
9548 ;; (reporting bugs should be easy ;-)
9552 ;;; xetla.el ends here