1 ;;; p4.el --- Simple Perforce-Emacs Integration
3 ;; $Id: p4.el,v 1.37 2002/07/31 23:24:44 petero2 Exp $
7 ;; Applied the GNU G.P.L. to this file - rv 3/27/1997
9 ;; Programs for Emacs <-> Perforce Integration.
10 ;; Copyright (C) 1996, 1997 Eric Promislow
11 ;; Copyright (C) 1997-2003 Rajesh Vaidheeswarran
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation; either version 2 of the License, or
16 ;; (at your option) any later version.
18 ;; This program is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program; if not, write to the Free Software
25 ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 ;; If you have any problems to report, or suggestions, please send them
28 ;; to p4el-bugs@lists.sourceforge.net
31 ;; p4|Rajesh Vaidheeswarran|rv@NoSpAm.lOsEtHiS.dsmit.com|
32 ;; P4 SCM Integration into Emacs/XEmacs|
33 ;; 2003/09/12|10.3|not_assigned_yet|
39 ;; ... make changes to foo.c in emacs
41 ;; ... keep the writable copy of foo.c in emacs. Start making changes
42 ;; to it. Discover that you can't save it. If you do M-x:p4-edit,
43 ;; you'll lose your changes. You need to do a 'p4 edit' at the
50 ;; It is best if you take this file and byte compile it. To do that, you
51 ;; need to do the following:
53 ;; % emacs -batch -f batch-byte-compile /full/path/to/file/p4.el
55 ;; This creates a binary file p4.elc in the path. Add the path to your
56 ;; load-path variable in .emacs like this:
58 ;; (setq load-path (cons "/full/path/to/file" load-path))
60 ;; Then add the library like this:
62 ;; (load-library "p4")
67 (defvar p4-emacs-version "10.3" "The Current P4-Emacs Integration Revision.")
69 ;; Find out what type of emacs we are running in. We will be using this
70 ;; quite a few times in this program.
72 (defvar p4-running-emacs nil
73 "If the current Emacs is not XEmacs, then, this is non-nil.")
74 (defvar p4-running-xemacs nil
75 "If the current Emacs is XEmacs/Lucid, then, this is non-nil.")
76 (if (string-match "XEmacs\\|Lucid" emacs-version)
77 (setq p4-running-xemacs t)
78 (setq p4-running-emacs t)))
80 ;; Pick up a couple of missing function defs
86 (defvar p4-emacs-maintainer
87 "p4.el maintainers <p4el-bugs@lists.sourceforge.net>"
88 "The maintainer(s) of the emacs-p4 integration. Used for bug reports.")
90 (defvar p4-web-page "http://p4el.sourceforge.net/" "The home of p4.el.")
92 ;; For flavors of Emacs which don't define `defgroup' and `defcustom'.
94 (if (not (fboundp 'defgroup))
95 (defmacro defgroup (sym memb doc &rest args)
96 "Null macro for defgroup in all versions of Emacs that don't define
99 (if (not (fboundp 'defcustom))
100 (defmacro defcustom (sym val doc &rest args)
101 "Macro to alias defcustom to defvar in all versions of Emacs that
102 don't define defcustom"
103 `(defvar ,sym ,val ,doc))))
105 (defgroup p4 nil "Perforce VC System." :group 'tools)
107 ;; This can be set to wherever 'p4' lies using p4-set-p4-executable
109 (defun p4-windows-os ()
110 (memq system-type '(ms-dos windows-nt)))
112 (defcustom p4-executable
115 (list "/usr/local/bin/p4"
116 (concat (getenv "HOME") "/bin/p4")
118 (p4-progname (if (p4-windows-os) "p4.exe" "p4"))
120 (while (and lst (not p4ex))
121 (let ((tmp (concat (file-name-as-directory (car lst))
123 (if (and (file-executable-p tmp)
124 (not (file-directory-p tmp)))
126 (setq lst (cdr lst))))
128 "This is the p4 executable.
129 To set this, use the function `p4-set-p4-executable' or `customize'"
133 ;; This is a string with default arguments to pass to "p4 diff",
134 ;; "p4 diff2", "p4 describe", etc.
135 (defcustom p4-default-diff-options "-du"
136 "Type of p4 diff output to be displayed. \(regular or context or
141 (defcustom p4-default-depot-completion-prefix "//depot/"
142 "Prefix to be used for completion prompt when prompting user for a depot
147 ;; Set this variable to nil to turn off colorized diff buffers.
148 (defcustom p4-colorized-diffs t
149 "Set this to nil to disable colorized diffs."
153 ;; Set whether P4CONFIG should be used exclusively for VC checking
154 (defcustom p4-use-p4config-exclusively nil
155 "Whether P4 mode should use P4CONFIG exclusively to check whether a file
156 is under P4 version control. If set to nil, `p4-check-mode' is always
157 called; otherwise, it checks to see if the file named by P4CONFIG exists in
158 this or a parent directory, and if so, only then runs p4-check-mode.
160 This provides for a much faster `p4-find-file-hook'."
165 (defcustom p4-auto-refresh t
166 "Set this to automatically refresh p4 submitted files in buffers."
170 ;; Check for empty diffs at submit time
171 (defcustom p4-check-empty-diffs t
172 "Set this to check for files with empty diffs before submitting."
176 (defcustom p4-verbose t
177 "When set, p4 will pop up the output buffer with the result of the
183 (defcustom p4-follow-symlinks nil
184 "When set, p4 will call `file-truename' on all opened files."
188 (defcustom p4-mode-hook nil
189 "Hook run by `p4-mode'."
194 (defvar p4-output-buffer-name "*P4 Output*" "P4 Output Buffer."))
196 ;; Set this variable in .emacs if you want p4-set-client-name to complete
197 ;; your client name for you.
198 (defvar p4-my-clients nil
199 "This variable holds the alist of p4 clients that the function
200 `p4-set-client-name' can complete on.
202 Set this variable *only* if you don't want P4 to complete on all the clients
205 This is a alist, and should be set using the function
206 `p4-set-my-clients'. For example, in your .emacs:
208 \(load-library \"p4\"\)
209 \(p4-set-my-clients \'(client1 client2 client3)\)")
211 ;; Set this variable in .emacs if you want to alter the completion
212 ;; behavior of p4-set-client-name.
214 (defcustom p4-strict-complete t
215 "Set this variable in .emacs \(or using `customize'\) if you want to alter
216 the completion behavior of `p4-set-client-name'.
221 (if (not (getenv "P4PORT"))
222 (setenv "P4PORT" "perforce:1666"))
224 (defvar p4-notify-list (getenv "P4NOTIFY") "The P4 Notify List.")
226 (defcustom p4-sendmail-program (if (boundp 'sendmail-program)
229 "The sendmail program. To set this use `customize'."
233 (defcustom p4-user-email (if (boundp 'user-mail-address)
234 user-mail-address nil)
235 "The e-mail address of the current user. This is used with the
236 notification system, and must be set if notification should take place. To
237 set this, use `customize'."
241 (defcustom p4-notify nil
242 "If this is t then the users in the notification list set by
243 `p4-set-notify-list' will get a notification of any P4 change submitted from
248 ;; This can be set with p4-toggle-vc-mode
249 (defcustom p4-do-find-file t
250 "If non-nil, the `p4-find-file-hook' will run when opening files."
254 ;; Now add a hook to find-file-hooks
255 (add-hook 'find-file-hooks 'p4-find-file-hook)
256 ;; .. and one to kill-buffer-hook
257 (add-hook 'kill-buffer-hook 'p4-kill-buffer-hook)
259 ;; Tell Emacs about this new kind of minor mode
260 (defvar p4-mode nil "Is this file under p4?")
261 (make-variable-buffer-local 'p4-mode)
262 (put 'p4-mode 'permanent-local t)
264 (defvar p4-offline-mode nil "Is this file under p4 but handled in offline mode?")
265 (make-variable-buffer-local 'p4-offline-mode)
266 (put 'p4-offline-mode 'permanent-local t)
269 (let ((map (make-sparse-keymap)))
270 (define-key map "\C-x\C-q" 'p4-toggle-read-only)
272 "Keymap for p4 minor mode")
273 (fset 'p4-minor-map p4-minor-map)
274 (or (assoc 'p4-mode minor-mode-alist)
275 (setq minor-mode-alist (cons '(p4-mode p4-mode)
277 (or (assoc 'p4-mode minor-mode-map-alist)
278 (setq minor-mode-map-alist
279 (cons '(p4-mode . p4-minor-map) minor-mode-map-alist)))
280 (or (assoc 'p4-offline-mode minor-mode-alist)
281 (setq minor-mode-alist (cons '(p4-offline-mode p4-offline-mode)
283 (or (assoc 'p4-offline-mode minor-mode-map-alist)
284 (setq minor-mode-map-alist
285 (cons '(p4-offline-mode . p4-minor-map) minor-mode-map-alist)))
287 (defvar p4-async-minor-mode nil
288 "The minor mode for editing p4 asynchronous command buffers.")
289 (make-variable-buffer-local 'p4-async-minor-mode)
290 (defvar p4-async-minor-map (make-sparse-keymap) "Keymap for p4 async minor mode")
291 (fset 'p4-async-minor-map p4-async-minor-map)
293 (or (assoc 'p4-async-minor-mode minor-mode-alist)
294 (setq minor-mode-alist
295 (cons '(p4-async-minor-mode " P4") minor-mode-alist)))
297 (or (assoc 'p4-async-minor-mode minor-mode-map-alist)
298 (setq minor-mode-map-alist
299 (cons '(p4-async-minor-mode . p4-async-minor-map) minor-mode-map-alist)))
301 (defvar p4-current-command nil)
302 (make-variable-buffer-local 'p4-current-command)
303 (put 'p4-current-command 'permanent-local t)
304 (set-default 'p4-current-command nil)
306 (defvar p4-current-args nil)
307 (make-variable-buffer-local 'p4-current-args)
308 (put 'p4-current-args 'permanent-local t)
309 (set-default 'p4-current-args nil)
311 ;; To check if the current buffer's modeline and menu need to be altered
312 (defvar p4-vc-check nil)
313 (make-variable-buffer-local 'p4-vc-check)
314 (put 'p4-vc-check 'permanent-local t)
315 (set-default 'p4-vc-check nil)
317 (defvar p4-set-client-hooks nil
318 "List of functions to be called after a p4 client is changed.
319 The buffer's local variables (if any) will have been processed before the
320 functions are called.")
322 (if p4-running-emacs (require 'timer))
324 (defvar p4-timer nil "Timer object that will be set to cleanup the caches
327 (defcustom p4-cleanup-time 600 "seconds after which `p4-cache-cleanup' will
328 check for dirty caches."
332 (defcustom p4-cleanup-cache t "`p4-cache-cleanup' will cleanup the
333 branches/clients/dirs/labels caches once in a while if this is non-nil."
337 (defvar p4-all-buffer-files nil "An associated list of all buffers and
338 their files under p4 version control. This is to enable autorefreshing of
339 p4 submitted files being visited by the buffer.")
341 (defvar p4-file-refresh-timer nil "Timer object that will be set to refresh
342 the files in Emacs buffers that have been modified by a `p4-submit'.")
344 (defcustom p4-file-refresh-timer-time 60 "seconds after which
345 `p4-file-refresh' will check for modified files in Emacs buffers. Set this
346 variable to 0 to disable periodic refreshing."
350 (defvar p4-async-command-hook nil
351 "This hook is run after an async buffer has been set up by
352 `p4-async-process-command'")
354 (defvar p4-window-config-stack nil
355 "Stack of saved window configurations.")
357 (defcustom p4-window-config-stack-size 20 "Maximum stack size
358 for saved window configurations."
362 (defcustom p4-exec-arg-len-max 20000 "Maximum total length of all
363 arguments to p4 commands."
368 (let ((map (make-sparse-keymap)))
369 (cond (p4-running-xemacs
370 (define-key map [button2] 'p4-buffer-mouse-clicked)
371 (define-key map [button3] 'p4-buffer-mouse-clicked-3))
373 (define-key map [mouse-2] 'p4-buffer-mouse-clicked)
374 (define-key map [mouse-3] 'p4-buffer-mouse-clicked-3)))
375 (define-key map [return] 'p4-buffer-commands)
376 (define-key map "\r" 'p4-buffer-commands)
377 (define-key map "q" 'p4-quit-current-buffer)
378 (define-key map "k" 'p4-scroll-down-1-line)
379 (define-key map "j" 'p4-scroll-up-1-line)
380 (define-key map "b" 'p4-scroll-down-1-window)
381 (define-key map [backspace] 'p4-scroll-down-1-window)
382 (define-key map " " 'p4-scroll-up-1-window)
383 (define-key map "<" 'p4-top-of-buffer)
384 (define-key map ">" 'p4-bottom-of-buffer)
385 (define-key map "=" 'p4-delete-other-windows)
388 (defun p4-make-derived-map (base-map)
390 (cond (p4-running-xemacs
391 (setq map (make-sparse-keymap))
392 (set-keymap-parents map (list base-map)))
394 (setq map (cons 'keymap base-map))))
397 (defvar p4-filelog-map
398 (let ((map (p4-make-derived-map p4-basic-map)))
399 (define-key map "d" 'p4-diff2)
400 (define-key map "f" 'p4-find-file-other-window)
401 (define-key map "s" 'p4-filelog-short-format)
402 (define-key map "l" 'p4-filelog-long-format)
403 (define-key map "k" 'p4-scroll-down-1-line-other-w)
404 (define-key map "j" 'p4-scroll-up-1-line-other-w)
405 (define-key map "b" 'p4-scroll-down-1-window-other-w)
406 (define-key map [backspace] 'p4-scroll-down-1-window-other-w)
407 (define-key map " " 'p4-scroll-up-1-window-other-w)
408 (define-key map "<" 'p4-top-of-buffer-other-w)
409 (define-key map ">" 'p4-bottom-of-buffer-other-w)
410 (define-key map "=" 'p4-delete-other-windows)
411 (define-key map "n" 'p4-goto-next-change)
412 (define-key map "p" 'p4-goto-prev-change)
413 (define-key map "N" (lookup-key map "p"))
415 "The key map to use for selecting filelog properties.")
417 (defvar p4-opened-map
418 (let ((map (p4-make-derived-map p4-basic-map)))
419 (define-key map "n" 'p4-next-depot-file)
420 (define-key map "p" 'p4-prev-depot-file)
421 (define-key map "N" (lookup-key map "p"))
423 "The key map to use for selecting opened files.")
426 (let ((map (p4-make-derived-map p4-basic-map)))
427 (define-key map "n" 'p4-goto-next-diff)
428 (define-key map "p" 'p4-goto-prev-diff)
429 (define-key map "N" (lookup-key map "p"))
430 (define-key map "d" 'p4-next-depot-diff)
431 (define-key map "u" 'p4-prev-depot-diff)
434 (defvar p4-print-rev-map
435 (let ((map (p4-make-derived-map p4-basic-map)))
436 (define-key map "n" 'p4-next-change-rev-line)
437 (define-key map "p" 'p4-prev-change-rev-line)
438 (define-key map "N" (lookup-key map "p"))
439 (define-key map "l" 'p4-toggle-line-wrap)
441 "The key map to use for browsing print-revs buffers.")
443 ;;; All functions start here.
445 ;; A generic function that we use to execute p4 commands
447 (defun p4-exec-p4 (output-buffer args &optional clear-output-buffer)
448 "Internal function called by various p4 commands.
449 Executes p4 in the current buffer's current directory
450 with output to a dedicated output buffer.
451 If successful, adds the P4 menu to the current buffer.
452 Does auto re-highlight management (whatever that is)."
454 (if (eq major-mode 'dired-mode)
455 (let ((dir (dired-current-directory)))
456 (set-buffer output-buffer)
457 (setq default-directory dir)))
458 (if clear-output-buffer
460 (set-buffer output-buffer)
461 (delete-region (point-min) (point-max))))
463 (apply 'call-process (p4-check-p4-executable) nil
465 nil ; update display?
466 "-d" default-directory ;override "PWD" env var
469 (if (and p4-running-emacs
470 (boundp 'hilit-auto-rehighlight))
471 (setq hilit-auto-rehighlight nil))
473 (defun p4-call-p4-here (&rest args)
474 "Internal function called by various p4 commands.
475 Executes p4 in the current buffer (generally a temp)."
476 (apply 'call-process (p4-check-p4-executable) nil
478 nil ; update display?
479 "-d" default-directory ;override "PWD" env var
482 (defun p4-push-window-config ()
483 "Push the current window configuration on the `p4-window-config-stack'
486 (setq p4-window-config-stack
487 (cons (current-window-configuration)
488 p4-window-config-stack))
489 (while (> (length p4-window-config-stack) p4-window-config-stack-size)
490 (setq p4-window-config-stack
491 (reverse (cdr (reverse p4-window-config-stack))))))
493 (defun p4-pop-window-config (num)
494 "Pop `num' elements from the `p4-window-config-stack' stack and use
495 the last popped element to restore the window configuration."
498 (if (eq p4-window-config-stack nil)
499 (error "window config stack empty"))
500 (set-window-configuration (car p4-window-config-stack))
501 (setq p4-window-config-stack (cdr p4-window-config-stack))
503 (message "window config popped (stack size %d)"
504 (length p4-window-config-stack)))
507 ;; The menu definition is in the XEmacs format. Emacs parses and converts
508 ;; this definition to its own menu creation commands.
510 (defalias 'p4-toggle-vc-mode-off 'p4-toggle-vc-mode)
511 (defalias 'p4-toggle-vc-mode-on 'p4-toggle-vc-mode)
515 '(["Specify Arguments..." universal-argument t]
517 ["Add Current to P4" p4-add
518 (and (p4-buffer-file-name) (not p4-mode))]
519 ["Check out/Edit" p4-edit
520 (and (p4-buffer-file-name-2) (or (not p4-mode) buffer-read-only))]
522 (and (p4-buffer-file-name-2) (or (not p4-mode) (not buffer-read-only)))]
523 ["Revert File" p4-revert
524 (and (p4-buffer-file-name-2) (or (not p4-mode) (not buffer-read-only)))]
525 ["Delete File from Depot" p4-delete
526 (and (p4-buffer-file-name-2) (or (not p4-mode) buffer-read-only))]
527 ["Rename Depot File" p4-rename
528 (and (p4-buffer-file-name-2) (or (not p4-mode) buffer-read-only))]
529 ["Submit Changes" p4-submit t]
531 ["Sync/Get Files from Depot" p4-get t]
533 ["Show Opened Files" p4-opened t]
534 ["Filelog" p4-filelog (p4-buffer-file-name-2)]
535 ["Changes" p4-changes t]
536 ["Describe Change" p4-describe t]
538 ["Diff 2 Versions" p4-diff2 (p4-buffer-file-name-2)]
539 ["Diff Current" p4-diff t]
540 ["Diff All Opened Files" p4-diff-all-opened t]
541 ["Diff Current with Ediff" p4-ediff
542 (and (p4-buffer-file-name) (not buffer-read-only) p4-mode)]
543 ["Diff 2 Versions with Ediff" p4-ediff2 (p4-buffer-file-name-2)]
545 ["Schedule Integrations" p4-integ t]
546 ["Resolve Conflicts" p4-resolve t]
548 ["Print" p4-print (p4-buffer-file-name-2)]
549 ["Print with Revision History" p4-blame
550 (p4-buffer-file-name-2)]
551 ["Find File using Depot Spec" p4-depot-find-file
554 ["Edit a Branch Specification" p4-branch t]
555 ["Edit a Label Specification" p4-label t]
556 ["Edit a Client Specification" p4-client t]
557 ["Edit a User Specification" p4-user t]
559 ["Show Version" p4-emacs-version t]
560 ["Disable P4 VC Check" p4-toggle-vc-mode-off
562 ["Enable P4 VC Check" p4-toggle-vc-mode-on
563 (not p4-do-find-file)]
565 ["Set P4 Config" p4-set-client-config p4-do-find-file]
566 ["Get Current P4 Config" p4-get-client-config
569 ["Set P4 Client" p4-set-client-name p4-do-find-file]
570 ["Get Current P4 Client" p4-get-client-name
573 ["Set P4 Server/Port" p4-set-p4-port p4-do-find-file]
574 ["Get Current P4 Server/Port" p4-get-p4-port
577 ["Set P4 Notification List" p4-set-notify-list
579 ["Get P4 Notification List" p4-get-notify-list p4-notify]
581 ["Describe Key Bindings" p4-describe-bindings t]
582 ["Check for later versions of p4.el" p4-browse-web-page t]
584 ["Report Bug in p4.el" p4-bug-report t])
585 "The P4 menu definition")
587 (cond (p4-running-xemacs
588 ;; Menu Support for XEmacs
590 (defun p4-mode-menu (modestr)
591 (cons modestr p4-menu-def)))
594 ;; Menu support for Emacs
595 (or (lookup-key global-map [menu-bar])
596 (define-key global-map [menu-bar] (make-sparse-keymap "menu-bar")))
597 (defvar menu-bar-p4-menu (make-sparse-keymap "P4"))
598 (setq menu-bar-final-items (cons 'p4-menu menu-bar-final-items))
599 (define-key global-map [menu-bar p4-menu]
600 (cons "P4" menu-bar-p4-menu))
601 (let ((m (reverse p4-menu-def))
602 (separator-number 0))
604 (let ((menu-text (elt (car m) 0))
605 (menu-action (elt (car m) 1))
606 (menu-pred (elt (car m) 2)))
609 (define-key menu-bar-p4-menu (vector menu-action)
610 (cons menu-text menu-action))
611 (put menu-action 'menu-enable menu-pred))
612 (define-key menu-bar-p4-menu
615 (int-to-string separator-number))))
617 (setq separator-number (1+ separator-number))))
620 (defun p4-depot-output (command &optional args)
621 "Executes p4 command inside a buffer.
623 (let ((buffer (generate-new-buffer p4-output-buffer-name)))
624 (p4-exec-p4 buffer (cons command args) t)
627 (defun p4-check-p4-executable ()
628 "Check if the `p4-executable' is nil, and if so, prompt the user for a
629 valid `p4-executable'."
631 (if (not p4-executable)
632 (call-interactively 'p4-set-p4-executable)
635 (defun p4-menu-add ()
636 "To add the P4 menu bar button for files that are already not in
637 the P4 depot or in the current client view.."
639 (cond (p4-running-xemacs
640 (if (not (boundp 'p4-mode))
642 (easy-menu-add (p4-mode-menu "P4"))))
645 (defun p4-help-text (cmd text)
648 (let ((buf (generate-new-buffer p4-output-buffer-name))
650 (if (= (p4-exec-p4 buf (list "help" cmd) t) 0)
651 (setq help-text (save-excursion
655 (concat text help-text))
658 ;; To set the path to the p4 executable
659 (defun p4-set-p4-executable (p4-exe-name)
660 "Set the path to the correct P4 Executable.
662 To set this as a part of the .emacs, add the following to your .emacs:
664 \(load-library \"p4\"\)
665 \(p4-set-p4-executable \"/my/path/to/p4\"\)
667 Argument P4-EXE-NAME The new value of the p4 executable, with full path."
668 (interactive "fFull path to your P4 executable: " )
669 (setq p4-executable p4-exe-name)
672 ;; The kill-buffer hook for p4.
673 (defun p4-kill-buffer-hook ()
674 "To Remove a file and its associated buffer from our global list of P4
677 (p4-refresh-refresh-list (p4-buffer-file-name)
680 (defmacro defp4cmd (fkn &rest all-args)
681 (let ((args (car all-args))
682 (help-cmd (cadr all-args))
683 (help-txt (eval (cadr (cdr all-args))))
684 (body (cdr (cddr all-args))))
686 ,(append (list 'lambda args
687 (p4-help-text help-cmd help-txt))
690 (defun p4-noinput-buffer-action (cmd
693 &optional arguments preserve-buffer)
694 "Internal function called by various p4 commands."
697 (if (not preserve-buffer)
699 (get-buffer-create p4-output-buffer-name);; We do these two lines
700 (kill-buffer p4-output-buffer-name))) ;; to ensure no duplicates
701 (p4-exec-p4 (get-buffer-create p4-output-buffer-name)
702 (append (list cmd) arguments)
704 (p4-partial-cache-cleanup cmd)
709 (set-buffer p4-output-buffer-name)
710 (count-lines (point-min) (point-max)))
713 (set-buffer p4-output-buffer-name)
714 (goto-char (point-min))
715 (looking-at "==== "))))
717 (set-buffer p4-output-buffer-name)
718 (message (buffer-substring (point-min)
720 (goto-char (point-min))
723 (p4-push-window-config)
724 (if (not (one-window-p))
725 (delete-other-windows))
726 (display-buffer p4-output-buffer-name t))))
727 (if (and do-revert (p4-buffer-file-name))
728 (revert-buffer t t)))
730 ;; The p4 edit command
731 (defp4cmd p4-edit (show-output)
732 "edit" "To open the current depot file for edit, type \\[p4-edit].\n"
733 (interactive (list p4-verbose))
734 (let ((args (p4-buffer-file-name))
736 (if (or current-prefix-arg (not args))
738 (setq args (if (p4-buffer-file-name-2)
739 (p4-buffer-file-name-2)
741 (setq args (p4-make-list-from-string
742 (p4-read-arg-string "p4 edit: " (cons args 0))))
743 (setq refresh-after t))
744 (setq args (list args)))
745 (p4-noinput-buffer-action "edit" t (and show-output 's) args)
747 (p4-refresh-files-in-buffers)))
749 (p4-update-opened-list))
751 ;; The p4 reopen command
752 (defp4cmd p4-reopen (show-output)
754 "To change the type or changelist number of an opened file, type \\[p4-reopen].
756 Argument SHOW-OUTPUT displays the *P4 Output* buffer on executing the
759 (interactive (list p4-verbose))
760 (let ((args (if (p4-buffer-file-name-2)
761 (p4-buffer-file-name-2)
763 (setq args (p4-make-list-from-string
764 (p4-read-arg-string "p4 reopen: " (cons args 0))))
765 (p4-noinput-buffer-action "reopen" t (and show-output 's) args))
767 (p4-update-opened-list))
769 ;; The p4 revert command
770 (defp4cmd p4-revert (show-output)
771 "revert" "To revert all change in the current file, type \\[p4-revert].\n"
772 (interactive (list p4-verbose))
773 (let ((args (p4-buffer-file-name))
775 (if (or current-prefix-arg (not args))
777 (setq args (if (p4-buffer-file-name-2)
778 (p4-buffer-file-name-2)
780 (setq args (p4-make-list-from-string
781 (p4-read-arg-string "p4 revert: " args)))
782 (setq refresh-after t))
783 (setq args (list args)))
784 (if (yes-or-no-p "Really revert changes? ")
786 (p4-noinput-buffer-action "revert" t (and show-output 's) args)
789 (p4-refresh-files-in-buffers)
790 (p4-check-mode-all-buffers))
792 (p4-update-opened-list)))))
794 ;; The p4 lock command
796 "lock" "To lock an opened file against changelist submission, type \\[p4-lock].\n"
798 (let ((args (list (p4-buffer-file-name-2))))
799 (if (or current-prefix-arg (not (p4-buffer-file-name-2)))
800 (setq args (p4-make-list-from-string
801 (p4-read-arg-string "p4 lock: "
802 (p4-buffer-file-name-2)))))
803 (p4-noinput-buffer-action "lock" t 's args)
804 (p4-update-opened-list)))
806 ;; The p4 unlock command
807 (defp4cmd p4-unlock ()
808 "unlock" "To release a locked file but leave open, type \\[p4-unlock].\n"
810 (let ((args (list (p4-buffer-file-name-2))))
811 (if (or current-prefix-arg (not (p4-buffer-file-name-2)))
812 (setq args (p4-make-list-from-string
813 (p4-read-arg-string "p4 unlock: "
814 (p4-buffer-file-name-2)))))
815 (p4-noinput-buffer-action "unlock" t 's args)
816 (p4-update-opened-list)))
818 ;; The p4 diff command
820 "diff" "To diff the current file and topmost depot version, type \\[p4-diff].\n"
822 (let ((args (p4-make-list-from-string p4-default-diff-options)))
823 (if (p4-buffer-file-name-2)
824 (setq args (append args
825 (list (p4-buffer-file-name-2)))))
826 (if current-prefix-arg
827 (setq args (p4-make-list-from-string
828 (p4-read-arg-string "p4 diff: " p4-default-diff-options))))
829 (p4-noinput-buffer-action "diff" nil 's args)
830 (p4-activate-diff-buffer "*P4 diff*")))
832 (defun p4-diff-all-opened ()
834 (p4-noinput-buffer-action "diff" nil 's
835 (p4-make-list-from-string p4-default-diff-options))
836 (p4-activate-diff-buffer "*P4 diff*"))
839 (defun p4-get-file-rev (default-name rev)
840 (if (string-match "^\\([0-9]+\\|none\\|head\\|have\\)$" rev)
841 (setq rev (concat "#" rev)))
842 (cond ((string-match "^[#@]" rev)
843 (concat default-name rev))
849 ;; The p4 diff2 command
850 (defp4cmd p4-diff2 (version1 version2)
851 "diff2" "Display diff of two depot files.
853 When visiting a depot file, type \\[p4-diff2] and enter the versions.\n"
855 (let ((rev (get-char-property (point) 'rev)))
856 (if (and (not rev) (p4-buffer-file-name-2))
858 (setq rev (p4-is-vc nil (p4-buffer-file-name-2)))
860 (setq rev-num (string-to-number rev)))
862 (setq rev (number-to-string (1- rev-num)))
864 (list (p4-read-arg-string "First Depot File or Version# to diff: " rev)
865 (p4-read-arg-string "Second Depot File or Version# to diff: "))))
868 (diff-options (p4-make-list-from-string p4-default-diff-options)))
869 (if current-prefix-arg
870 (setq diff-options (p4-make-list-from-string
871 (p4-read-arg-string "Optional Args: "
872 p4-default-diff-options))))
873 ;; try to find out if this is a revision number, or a depot file
874 (setq diff-version1 (p4-get-file-rev (p4-buffer-file-name-2) version1))
875 (setq diff-version2 (p4-get-file-rev (p4-buffer-file-name-2) version2))
877 (p4-noinput-buffer-action "diff2" nil t
881 (p4-activate-diff-buffer "*P4 diff2*")))
883 ;; p4-ediff for all those who diff using ediff
885 "Use ediff to compare file with its original client version."
888 (if current-prefix-arg
889 (call-interactively 'p4-ediff2)
891 (p4-noinput-buffer-action "print" nil nil
893 (concat (p4-buffer-file-name) "#have")))
894 (let ((local (current-buffer))
895 (depot (get-buffer-create p4-output-buffer-name)))
899 (make-local-variable 'ediff-cleanup-hook)
900 (setq ediff-cleanup-hook
904 ediff-cleanup-hook)))))))))
906 (defp4cmd p4-ediff2 (version1 version2)
907 "ediff2" "Use ediff to display two versions of a depot file.
909 When visiting a depot file, type \\[p4-ediff2] and enter the versions.\n"
911 (let ((rev (get-char-property (point) 'rev)))
912 (if (and (not rev) (p4-buffer-file-name-2))
914 (setq rev (p4-is-vc nil (p4-buffer-file-name-2)))
916 (setq rev-num (string-to-number rev)))
918 (setq rev (number-to-string (1- rev-num)))
920 (list (p4-read-arg-string "First Depot File or Version# to diff: " rev)
921 (p4-read-arg-string "Second Depot File or Version# to diff: "))))
922 (let* ((file-name (p4-buffer-file-name-2))
923 (basename (file-name-nondirectory file-name))
924 (bufname1 (concat "*P4 ediff " basename "#" version1 "*"))
925 (bufname2 (concat "*P4 ediff " basename "#" version2 "*"))
926 (diff-version1 (p4-get-file-rev file-name version1))
927 (diff-version2 (p4-get-file-rev file-name version2)))
928 (p4-noinput-buffer-action "print" nil nil (list "-q" diff-version1))
929 (set-buffer p4-output-buffer-name)
930 (rename-buffer bufname1 t)
931 (p4-noinput-buffer-action "print" nil nil (list "-q" diff-version2))
932 (set-buffer p4-output-buffer-name)
933 (rename-buffer bufname2 t)
934 (let ((buffer-version-1 (get-buffer-create bufname1))
935 (buffer-version-2 (get-buffer-create bufname2)))
936 (ediff-buffers buffer-version-1
939 (make-local-variable 'ediff-cleanup-hook)
940 (setq ediff-cleanup-hook
942 (kill-buffer ,buffer-version-1)
943 (kill-buffer ,buffer-version-2)
945 ediff-cleanup-hook))))))))
946 ;; The p4 add command
948 "add" "To add the current file to the depot, type \\[p4-add].\n"
950 (let ((args (p4-buffer-file-name))
952 (if (or current-prefix-arg (not args))
954 (setq args (if (p4-buffer-file-name-2)
955 (p4-buffer-file-name-2)
957 (setq args (p4-make-list-from-string
958 (p4-read-arg-string "p4 add: " (cons args 0))))
959 (setq refresh-after t))
960 (setq args (list args)))
961 (p4-noinput-buffer-action "add" nil 's args)
963 (p4-check-mode-all-buffers)
965 (p4-update-opened-list))
968 ;; The p4 delete command
969 (defp4cmd p4-delete ()
970 "delete" "To delete the current file from the depot, type \\[p4-delete].\n"
972 (let ((args (p4-buffer-file-name)))
973 (if (or current-prefix-arg (not args))
974 (setq args (p4-make-list-from-string
975 (p4-read-arg-string "p4 delete: "
976 (p4-buffer-file-name-2))))
977 (setq args (list args)))
978 (if (yes-or-no-p "Really delete from depot? ")
979 (p4-noinput-buffer-action "delete" nil 's args)))
981 (p4-update-opened-list))
983 ;; The p4 filelog command
984 (defp4cmd p4-filelog ()
986 "To view a history of the change made to the current file, type \\[p4-filelog].\n"
988 (let ((file-name (p4-buffer-file-name-2)))
989 (if (or current-prefix-arg (not file-name))
990 (setq file-name (p4-make-list-from-string
991 (p4-read-arg-string "p4 filelog: " file-name)))
992 (setq file-name (list file-name)))
993 (p4-file-change-log "filelog" file-name)))
995 (defun p4-set-extent-properties (start end prop-list)
996 (cond (p4-running-xemacs
997 (let ((ext (make-extent start end)))
999 (set-extent-property ext (caar prop-list) (cdar prop-list))
1000 (setq prop-list (cdr prop-list)))))
1002 (let ((ext (make-overlay start end)))
1004 (overlay-put ext (caar prop-list) (cdar prop-list))
1005 (setq prop-list (cdr prop-list)))))))
1007 (defun p4-create-active-link (start end prop-list)
1008 (p4-set-extent-properties start end
1009 (append (list (cons 'face 'bold)
1010 (cons 'mouse-face 'highlight))
1013 (defun p4-move-buffer-point-to-top (buf-name)
1014 (if (get-buffer-window buf-name)
1015 (save-selected-window
1016 (select-window (get-buffer-window buf-name))
1017 (goto-char (point-min)))))
1019 (defun p4-file-change-log (cmd file-list-spec)
1020 (let ((p4-filelog-buffer
1021 (concat "*P4 " cmd ": "
1022 (p4-list-to-string file-list-spec) "*")))
1023 (p4-noinput-buffer-action cmd nil t (cons "-l" file-list-spec))
1024 (p4-activate-file-change-log-buffer p4-filelog-buffer)))
1026 (defun p4-activate-file-change-log-buffer (bufname)
1027 (let (p4-cur-rev p4-cur-change p4-cur-action
1028 p4-cur-user p4-cur-client)
1029 (p4-activate-print-buffer bufname nil)
1030 (set-buffer bufname)
1031 (setq buffer-read-only nil)
1032 (goto-char (point-min))
1033 (while (re-search-forward (concat
1034 "^\\(\\.\\.\\. #\\([0-9]+\\) \\)?[Cc]hange "
1035 "\\([0-9]+\\) \\([a-z]+\\)?.*on.*by "
1036 "\\([^ @]+\\)@\\([^ \n]+\\).*\n"
1037 "\\(\\(\\([ \t].*\\)?\n\\)*\\)") nil t)
1044 (setq p4-cur-rev (match-string rev-match))
1045 (setq p4-cur-change (match-string ch-match))
1046 (setq p4-cur-action (match-string act-match))
1047 (setq p4-cur-user (match-string user-match))
1048 (setq p4-cur-client (match-string cl-match))
1050 (if (match-beginning rev-match)
1051 (p4-create-active-link (match-beginning rev-match)
1052 (match-end rev-match)
1053 (list (cons 'rev p4-cur-rev))))
1054 (p4-create-active-link (match-beginning ch-match)
1055 (match-end ch-match)
1056 (list (cons 'change p4-cur-change)))
1057 (if (match-beginning act-match)
1058 (p4-create-active-link (match-beginning act-match)
1059 (match-end act-match)
1060 (list (cons 'action p4-cur-action)
1061 (cons 'rev p4-cur-rev))))
1062 (p4-create-active-link (match-beginning user-match)
1063 (match-end user-match)
1064 (list (cons 'user p4-cur-user)))
1065 (p4-create-active-link (match-beginning cl-match)
1066 (match-end cl-match)
1067 (list (cons 'client p4-cur-client)))
1068 (p4-set-extent-properties (match-beginning desc-match)
1069 (match-end desc-match)
1070 (list (cons 'invisible t)
1071 (cons 'isearch-open-invisible t)))))
1072 (p4-find-change-numbers bufname (point-min) (point-max))
1073 (use-local-map p4-filelog-map)
1074 (setq buffer-invisibility-spec (list))
1075 (setq buffer-read-only t)
1076 (p4-move-buffer-point-to-top bufname)))
1078 ;; Scan specified region for references to change numbers
1079 ;; and make the change numbers clickable.
1080 (defun p4-find-change-numbers (buffer start end)
1084 (while (re-search-forward "\\(changes?\\|submit\\|p4\\)[:#]?[ \t\n]+" end t)
1086 (concat "\\([#@]\\|number\\|no\\.\\|\\)[ \t\n]*"
1087 "\\([0-9]+\\)[-, \t\n]*"
1088 "\\(and/or\\|and\\|&\\|or\\|\\)[ \t\n]*"))
1089 (let ((ch-start (match-beginning 2))
1090 (ch-end (match-end 2))
1091 (ch-str (match-string 2))
1092 (next (match-end 0)))
1093 (set-text-properties 0 (length ch-str) nil ch-str)
1094 (p4-create-active-link ch-start ch-end (list (cons 'change ch-str)))
1095 (goto-char next))))))
1097 ;; The p4 files command
1098 (defp4cmd p4-files ()
1099 "files" "List files in the depot. Type, \\[p4-files].\n"
1101 (let ((args (p4-buffer-file-name-2)))
1102 (if (or current-prefix-arg (not args))
1103 (setq args (p4-make-list-from-string
1104 (p4-read-arg-string "p4 files: " (p4-buffer-file-name-2))))
1105 (setq args (list args)))
1106 (p4-noinput-buffer-action "files" nil t args)
1108 (set-buffer p4-output-buffer-name)
1109 (p4-find-change-numbers p4-output-buffer-name (point-min) (point-max)))
1110 (p4-make-depot-list-buffer
1111 (concat "*P4 Files: (" (p4-current-client) ") " (car args) "*"))))
1114 (defvar p4-server-version-cache nil)
1116 (defun p4-get-server-version ()
1117 "To get the version number of the p4 server."
1118 (let ((p4-port (p4-current-server-port))
1120 (setq ser-ver (cdr (assoc p4-port p4-server-version-cache)))
1123 (get-buffer-create p4-output-buffer-name)
1124 (set-buffer p4-output-buffer-name)
1125 (goto-char (point-max))
1127 (if (zerop (p4-call-p4-here "info"))
1131 "^Server version: .*\/.*\/\\(\\([0-9]+\\)\.[0-9]+\\)\/.*(.*)$")
1132 (setq ser-ver (string-to-number (match-string 2)))
1133 (setq p4-server-version-cache (cons (cons p4-port ser-ver)
1134 p4-server-version-cache))
1135 (delete-region pmin (point-max))))))
1138 (defun p4-get-client-root (client-name)
1139 "To get the current value of Client's root type \\[p4-get-client-root].
1140 This can be used by any other macro that requires this value."
1141 (let (p4-client-root pmin)
1143 (get-buffer-create p4-output-buffer-name)
1144 (set-buffer p4-output-buffer-name)
1145 (goto-char (point-max))
1147 (if (zerop (p4-call-p4-here "client" "-o" client-name))
1150 (re-search-forward "^Root:[ \t]+\\(.*\\)$")
1151 (setq p4-client-root (p4-canonize-client-root (match-string 1)))
1152 (delete-region pmin (point-max)))))
1155 (defun p4-canonize-client-root (p4-client-root)
1156 "Canonizes client root"
1157 (let ((len (length p4-client-root)))
1158 ;; For Windows, since the client root may be terminated with
1159 ;; a \ as in c:\ or drive:\foo\bar\, we need to strip the
1161 (if (and (p4-windows-os)
1163 (equal (substring p4-client-root (1- len) len) "\\"))
1164 (setq p4-client-root (substring p4-client-root 0 (1- len))))
1167 (defun p4-map-depot-files (file-list)
1168 "Map a list of files in the depot on the current client.
1169 Return a list of pairs, where each pair consists of a depot
1170 name and a client name."
1173 (let (sub-list (arg-len 0) elt)
1174 (while (and file-list (< arg-len p4-exec-arg-len-max))
1175 (setq elt (car file-list))
1176 (setq file-list (cdr file-list))
1177 (setq sub-list (cons elt sub-list))
1178 (setq arg-len (+ arg-len (length elt) 1)))
1179 (setq file-map (append file-map
1180 (p4-map-depot-files-int sub-list)))))
1183 (defun p4-map-depot-files-int (file-list)
1184 (let* ((current-client (p4-current-client))
1185 (client-root (p4-get-client-root current-client))
1186 (re-current-client (regexp-quote current-client))
1187 (re-client-root (regexp-quote client-root))
1190 (get-buffer-create p4-output-buffer-name)
1191 (set-buffer p4-output-buffer-name)
1192 (goto-char (point-max))
1195 (apply 'p4-call-p4-here "where" file-list)
1197 (if (< (p4-get-server-version) 98)
1198 (while (re-search-forward
1199 (concat "^\\([^\n]+\\) //" re-current-client
1204 (concat client-root (match-string 2)))
1206 (while (re-search-forward
1207 (concat "^\\([^\n]+\\) //" re-current-client
1208 "\\([^\n]+\\) \\(" re-client-root ".*\\)$") nil t)
1211 (match-string 1) (match-string 3)) files))))
1212 (delete-region pmin (point-max)))
1215 (make-face 'p4-depot-unmapped-face)
1216 (set-face-foreground 'p4-depot-unmapped-face "grey30")
1218 (make-face 'p4-depot-deleted-face)
1219 (set-face-foreground 'p4-depot-deleted-face "red")
1221 (make-face 'p4-depot-added-face)
1222 (set-face-foreground 'p4-depot-added-face "blue")
1224 (make-face 'p4-depot-branch-op-face)
1225 (set-face-foreground 'p4-depot-branch-op-face "blue4")
1227 (defun p4-make-depot-list-buffer (bufname &optional print-buffer)
1228 "Take the p4-output-buffer-name buffer, rename it to bufname, and
1229 make all depot file names active, so that clicking them opens
1230 the corresponding client file."
1231 (let (args files depot-regexp)
1232 (set-buffer p4-output-buffer-name)
1233 (goto-char (point-min))
1236 "\\(^\\)\\(//[^/@# ][^/@#]*/[^@#]+\\)#[0-9]+ - "
1237 "^\\(\\.\\.\\. [^/\n]*\\|==== \\)?\\(//[^/@# ][^/@#]*/[^#\n]*\\)"))
1238 (while (re-search-forward depot-regexp nil t)
1239 (setq args (cons (match-string 2) args)))
1240 (setq files (p4-map-depot-files args))
1241 (get-buffer-create bufname);; We do these two lines
1242 (kill-buffer bufname);; to ensure no duplicates
1243 (set-buffer p4-output-buffer-name)
1244 (rename-buffer bufname t)
1245 (goto-char (point-min))
1246 (while (re-search-forward depot-regexp nil t)
1247 (let ((p4-client-file (cdr (assoc (match-string 2) files)))
1248 (p4-depot-file (match-string 2))
1249 (start (match-beginning 2))
1251 (branching-op-p (and (match-string 1)
1252 (string-match "\\.\\.\\. \\.\\.\\..*"
1255 (if (and p4-client-file
1256 (file-readable-p p4-client-file))
1257 (setq prop-list (list (cons 'link-client-name
1259 (setq prop-list (list (cons 'link-depot-name
1261 ;; some kind of operation related to branching/integration
1263 (setq prop-list (append (list
1264 (cons 'history-for p4-depot-file)
1266 'p4-depot-branch-op-face))
1269 ((not p4-client-file)
1270 (p4-set-extent-properties
1272 (append (list (cons 'face 'p4-depot-unmapped-face))
1276 (looking-at ".* deleted?[ \n]"))
1277 (p4-set-extent-properties
1279 (append (list (cons 'face 'p4-depot-deleted-face))
1283 (looking-at ".* \\(add\\|branch\\)\\(ed\\)?[ \n]"))
1284 (p4-create-active-link
1286 (append (list (cons 'face 'p4-depot-added-face))
1289 (p4-create-active-link start end prop-list)))))
1290 (use-local-map p4-opened-map)
1291 (setq buffer-read-only t)
1292 (p4-move-buffer-point-to-top bufname)))
1294 ;; The p4 print command
1295 (defp4cmd p4-print ()
1296 "print" "To print a depot file to a buffer, type \\[p4-print].\n"
1298 (let ((arg-string (p4-buffer-file-name-2))
1299 (rev (get-char-property (point) 'rev))
1300 (change (get-char-property (point) 'change)))
1302 (setq arg-string (concat arg-string "#" rev)))
1304 (setq arg-string (concat arg-string "@" change))))
1305 (if (or current-prefix-arg (not arg-string))
1306 (setq arg-string (p4-make-list-from-string
1307 (p4-read-arg-string "p4 print: " arg-string)))
1308 (setq arg-string (list arg-string)))
1309 (p4-noinput-buffer-action "print" nil t arg-string)
1310 (p4-activate-print-buffer "*P4 print*" t)))
1312 ;; Insert text in a buffer, but make sure that the inserted text doesn't
1313 ;; inherit any properties from surrounding text. This is needed for xemacs
1314 ;; because the insert function makes the inserted text inherit properties.
1315 (defun p4-insert-no-properties (str)
1316 (let ((start (point))
1320 (set-text-properties start end nil)))
1322 (defun p4-font-lock-buffer (buf-name)
1324 (let (file-name (first-line ""))
1325 (set-buffer buf-name)
1326 (goto-char (point-min))
1327 (if (looking-at "^//[^#@]+/\\([^/#@]+\\)")
1329 (setq file-name (match-string 1))
1331 (setq first-line (buffer-substring (point-min) (point)))
1332 (delete-region (point-min) (point))))
1333 (setq buffer-file-name file-name)
1335 (setq buffer-file-name nil)
1337 (font-lock-fontify-buffer)
1340 (if (and p4-running-emacs
1341 (boundp 'hilit-auto-rehighlight))
1342 (setq hilit-auto-rehighlight nil))
1343 (goto-char (point-min))
1344 (p4-insert-no-properties first-line))))
1346 (defun p4-activate-print-buffer (buffer-name print-buffer)
1348 (p4-font-lock-buffer p4-output-buffer-name))
1349 (p4-make-depot-list-buffer buffer-name print-buffer)
1352 "^\\(//[^/@# ][^/@#]*/\\)[^@#]+#[0-9]+ - "
1353 "^\\(//[^/@# ][^/@#]*/\\)")))
1355 (set-buffer buffer-name)
1356 (setq buffer-read-only nil)
1357 (goto-char (point-min))
1358 (while (re-search-forward depot-regexp nil t)
1359 (let ((link-client-name (get-char-property (match-end 1)
1361 (link-depot-name (get-char-property (match-end 1)
1363 (start (match-beginning 1))
1366 (if (re-search-forward depot-regexp nil t)
1367 (setq end (match-beginning 1))))
1368 (if link-client-name
1369 (p4-set-extent-properties start end
1370 (list (cons 'block-client-name
1371 link-client-name))))
1373 (p4-set-extent-properties start end
1374 (list (cons 'block-depot-name
1375 link-depot-name))))))
1376 (setq buffer-read-only t))))
1378 (defconst p4-blame-change-regex
1379 (concat "^\\.\\.\\. #" "\\([0-9]+\\)" ;; revision
1380 "\\s-+change\\s-+" "\\([0-9]+\\)" ;; change
1381 "\\s-+" "\\([^ \t]+\\)" ;; type
1382 "\\s-+on\\s-+" "\\([^ \t]+\\)" ;; date
1383 "\\s-+by\\s-+" "\\([^ \t]+\\)" ;; author
1386 (defconst p4-blame-branch-regex
1387 "^\\.\\.\\. \\.\\.\\. branch from \\(//[^#]*\\)#")
1389 (defconst p4-blame-revision-regex
1390 (concat "^\\([0-9]+\\),?"
1396 (defconst p4-blame-index-regex
1397 (concat " *\\([0-9]+\\)" ;; change
1398 " *\\([0-9]+\\)" ;; revision
1399 " *\\([0-9]+/[0-9]+/[0-9]+\\)" ;; date
1400 "\\s-+\\([^:]*\\)" ;; author
1404 (defconst P4-DATE 1)
1405 (defconst P4-AUTH 2)
1406 (defconst P4-FILE 3)
1409 "To Print a depot file with revision history to a buffer,
1412 (let ((arg-string (p4-buffer-file-name-2))
1413 (rev (get-char-property (point) 'rev))
1414 (change (get-char-property (point) 'change)))
1416 (setq arg-string (concat arg-string "#" rev)))
1418 (setq arg-string (concat arg-string "@" change))))
1419 (if (or current-prefix-arg (not arg-string))
1420 (setq arg-string (p4-read-arg-string "p4 print-revs: " arg-string)))
1421 (p4-blame-int arg-string)))
1423 (defalias 'p4-print-with-rev-history 'p4-blame)
1425 (defun p4-blame-int (file-spec)
1426 (get-buffer-create p4-output-buffer-name);; We do these two lines
1427 (kill-buffer p4-output-buffer-name) ;; to ensure no duplicates
1428 (let ((file-name file-spec)
1429 (buffer (get-buffer-create p4-output-buffer-name))
1430 head-name ;; file spec of the head revision for this blame assignment
1431 branch-p ;; have we tracked into a branch?
1432 cur-file ;; file name of the current branch during blame assignment
1433 change ch-alist fullname head-rev headseen)
1435 ;; we asked for blame constrained by a change number
1436 (if (string-match "\\(.*\\)@\\([0-9]+\\)" file-spec)
1438 (setq file-name (match-string 1 file-spec))
1439 (setq change (string-to-int (match-string 2 file-spec)))))
1441 ;; we asked for blame constrained by a revision
1442 (if (string-match "\\(.*\\)#\\([0-9]+\\)" file-spec)
1444 (setq file-name (match-string 1 file-spec))
1445 (setq head-rev (string-to-int (match-string 2 file-spec)))))
1447 ;; make sure the filespec is unambiguous
1448 (p4-exec-p4 buffer (list "files" file-name) t)
1451 (if (> (count-lines (point-min) (point-max)) 1)
1452 (error "File pattern maps to more than one file.")))
1454 ;; get the file change history:
1455 (p4-exec-p4 buffer (list "filelog" "-i" file-spec) t)
1456 (setq fullname (p4-read-depot-output buffer)
1460 ;; parse the history:
1463 (goto-char (point-min))
1464 (while (< (point) (point-max))
1466 ;; record the current file name (and the head file name,
1467 ;; if we have not yet seen one):
1468 (if (looking-at "^\\(//.*\\)$")
1469 (setq cur-file (match-string 1)))
1471 ;; a non-branch change:
1472 (if (looking-at p4-blame-change-regex)
1473 (let ((rev (string-to-int (match-string 1)))
1474 (ch (string-to-int (match-string 2)))
1475 (op (match-string 3))
1476 (date (match-string 4))
1477 (author (match-string 5)))
1479 ;; after the change constraint, OR
1480 ;; after the revision constraint _for this file_
1481 ;; [remember, branches complicate this]:
1482 ((or (and change (< change ch))
1483 (and head-rev (< head-rev rev)
1484 (string= head-name cur-file))) nil)
1486 ;; file has been deleted, can't assign blame:
1487 ((string= op "delete")
1488 (if (not headseen) (goto-char (point-max))))
1490 ;; OK, we actually want to look at this one:
1494 (cons ch (list rev date author cur-file)) ch-alist))
1495 (if (not head-rev) (setq head-rev rev))
1496 (setq headseen t)) ))
1498 ;; not if we have entered a branch (this used to be used, isn't
1499 ;; right now - maybe again later:
1500 (if (and headseen (looking-at p4-blame-branch-regex))
1501 (setq branch-p t)) )
1504 (if (< (length ch-alist) 1)
1505 (error "Head revision not available"))
1507 (let ((base-ch (int-to-string (caar ch-alist)))
1508 (ch-buffer (get-buffer-create "p4-ch-buf"))
1509 (tmp-alst (copy-alist ch-alist)))
1510 (p4-exec-p4 ch-buffer
1511 (list "print" "-q" (concat cur-file "@" base-ch)) t)
1513 (set-buffer ch-buffer)
1514 (goto-char (point-min))
1515 (while (re-search-forward ".*\n" nil t)
1516 (replace-match (concat base-ch "\n"))))
1517 (while (> (length tmp-alst) 1)
1518 (let ((ch-1 (car (car tmp-alst)))
1519 (ch-2 (car (cadr tmp-alst)))
1520 (file1 (nth P4-FILE (cdr (car tmp-alst))))
1521 (file2 (nth P4-FILE (cdr (cadr tmp-alst))))
1523 (setq ins-string (format "%d\n" ch-2))
1524 (p4-exec-p4 buffer (list "diff2"
1525 (format "%s@%d" file1 ch-1)
1526 (format "%s@%d" file2 ch-2)) t)
1529 (goto-char (point-max))
1530 (while (re-search-backward p4-blame-revision-regex nil t)
1531 (let ((la (string-to-int (match-string 1)))
1532 (lb (string-to-int (match-string 2)))
1533 (op (match-string 3))
1534 (ra (string-to-int (match-string 4)))
1535 (rb (string-to-int (match-string 5))))
1540 (cond ((string= op "a")
1545 (set-buffer ch-buffer)
1547 (let ((beg (point)))
1548 (forward-line (1+ (- lb la)))
1549 (delete-region beg (point)))
1552 (setq ra (1+ ra)))))))
1553 (setq tmp-alst (cdr tmp-alst))))
1554 (p4-noinput-buffer-action "print" nil t
1555 (list (format "%s#%d" fullname head-rev))
1557 (p4-font-lock-buffer p4-output-buffer-name)
1558 (let (line cnum (old-cnum 0) change-data
1559 xth-rev xth-date xth-auth xth-file)
1564 (p4-insert-no-properties "Change Rev Date Author\n")
1565 (while (setq line (p4-read-depot-output ch-buffer))
1566 (setq cnum (string-to-int line))
1567 (if (= cnum old-cnum)
1568 (p4-insert-no-properties (format "%29s : " ""))
1570 ;; extract the change data from our alist: remember,
1571 ;; `eq' works for integers so we can use assq here:
1572 (setq change-data (cdr (assq cnum ch-alist))
1573 xth-rev (nth P4-REV change-data)
1574 xth-date (nth P4-DATE change-data)
1575 xth-auth (nth P4-AUTH change-data)
1576 xth-file (nth P4-FILE change-data))
1578 (p4-insert-no-properties
1579 (format "%6d %4d %10s %7s: " cnum xth-rev xth-date xth-auth))
1581 (if (looking-at p4-blame-index-regex)
1582 (let ((nth-cnum (match-string 1))
1583 (nth-revn (match-string 2))
1584 (nth-user (match-string 4)))
1585 (p4-create-active-link (match-beginning 1)
1587 (list (cons 'change nth-cnum)))
1588 ;; revision needs to be linked to a file now that we
1589 ;; follow integrations (branches):
1590 (p4-create-active-link (match-beginning 2)
1592 (list (cons 'rev nth-revn)
1593 (cons 'link-depot-name xth-file)))
1594 (p4-create-active-link (match-beginning 4)
1596 (list (cons 'user nth-user)))
1597 ;; truncate the user name:
1598 (let ((start (+ (match-beginning 4) 7))
1599 (end (match-end 4)))
1601 (delete-region start end))))))
1602 (setq old-cnum cnum)
1605 (kill-buffer ch-buffer))
1606 (let ((buffer-name (concat "*P4 print-revs " file-name "*")))
1607 (p4-activate-print-buffer buffer-name nil)
1609 (set-buffer buffer-name)
1610 (setq truncate-lines t)
1611 (use-local-map p4-print-rev-map)))))
1613 ;; The p4 refresh command
1614 (defp4cmd p4-refresh ()
1615 "sync" "Refresh the contents of an unopened file. \\[p4-refresh].
1617 This is equivalent to \"sync -f\"
1620 (let ((args (p4-buffer-file-name)))
1621 (if (or current-prefix-arg (not args))
1622 (setq args (p4-make-list-from-string
1623 (p4-read-arg-string "p4 refresh: ")))
1624 (setq args (list args)))
1625 (p4-noinput-buffer-action "refresh" nil t args)
1626 (p4-refresh-files-in-buffers)
1627 (p4-make-depot-list-buffer
1628 (concat "*P4 Refresh: (" (p4-current-client) ") " (car args) "*"))))
1630 ;; The p4 get/sync command
1631 (defp4cmd p4-sync ()
1633 "To synchronise the local view with the depot, type \\[p4-get].\n"
1639 "To synchronise the local view with the depot, type \\[p4-get].\n"
1642 (if current-prefix-arg
1643 (setq args (p4-make-list-from-string (p4-read-arg-string "p4 get: "))))
1644 (p4-noinput-buffer-action "get" nil t args)
1645 (p4-refresh-files-in-buffers)
1646 (p4-make-depot-list-buffer
1647 (concat "*P4 Get: (" (p4-current-client) ") " (car args) "*"))))
1649 ;; The p4 have command
1650 (defp4cmd p4-have ()
1651 "have" "To list revisions last gotten, type \\[p4-have].\n"
1653 (let ((args (list "...")))
1654 (if current-prefix-arg
1655 (setq args (p4-make-list-from-string
1656 (p4-read-arg-string "p4 have: " (p4-buffer-file-name-2)))))
1657 (p4-noinput-buffer-action "have" nil t args)
1658 (p4-make-depot-list-buffer
1659 (concat "*P4 Have: (" (p4-current-client) ") " (car args) "*"))))
1661 ;; The p4 changes command
1662 (defp4cmd p4-changes ()
1663 "changes" "To list changes, type \\[p4-changes].\n"
1665 (let ((arg-list (list "-m" "200" "...")))
1666 (if current-prefix-arg
1667 (setq arg-list (p4-make-list-from-string
1668 (p4-read-arg-string "p4 changes: " "-m 200"))))
1669 (p4-file-change-log "changes" arg-list)))
1671 ;; The p4 help command
1672 (defp4cmd p4-help (arg)
1673 "help" "To print help message, type \\[p4-help].
1675 Argument ARG command for which help is needed.
1677 (interactive (list (p4-make-list-from-string
1678 (p4-read-arg-string "Help on which command: "
1680 (p4-noinput-buffer-action "help" nil t arg)
1681 (p4-make-basic-buffer "*P4 help*"))
1683 (defun p4-make-basic-buffer (buf-name &optional map)
1684 "rename `p4-output-buffer-name' to buf-name \(which will be killed first if
1685 it already exists\), set its local map to map, if specified, or
1686 `p4-basic-map' otherwise. Makes the buffer read only."
1687 (get-buffer-create buf-name)
1688 (kill-buffer buf-name)
1689 (set-buffer p4-output-buffer-name)
1690 (goto-char (point-min))
1691 (rename-buffer buf-name t)
1692 (use-local-map (if (keymapp map) map p4-basic-map))
1693 (setq buffer-read-only t)
1694 (p4-move-buffer-point-to-top buf-name))
1696 ;; The p4 info command
1697 (defp4cmd p4-info ()
1698 "info" "To print out client/server information, type \\[p4-info].\n"
1700 (p4-noinput-buffer-action "info" nil t)
1701 (p4-make-basic-buffer "*P4 info*"))
1703 ;; The p4 integrate command
1704 (defp4cmd p4-integ ()
1705 "integ" "To schedule integrations between branches, type \\[p4-integ].\n"
1707 (let ((args (p4-make-list-from-string
1708 (p4-read-arg-string "p4 integ: " "-b "))))
1709 (p4-noinput-buffer-action "integ" nil t args)
1710 (p4-make-depot-list-buffer "*P4 integ*")))
1712 (defp4cmd p4-resolve ()
1714 "To merge open files with other revisions or files, type \\[p4-resolve].\n"
1716 (let (buffer args (buf-name "*p4 resolve*"))
1717 (if current-prefix-arg
1718 (setq args (p4-make-list-from-string
1719 (p4-read-arg-string "p4 resolve: " nil))))
1720 (setq buffer (get-buffer buf-name))
1721 (if (and (buffer-live-p buffer)
1722 (not (comint-check-proc buffer)))
1724 (let ((cur-dir default-directory))
1727 (goto-char (point-max))
1728 (insert "\n--------\n\n"))))
1729 (setq args (cons "resolve" args))
1730 (setq buffer (apply 'make-comint "p4 resolve" p4-executable nil "-d" default-directory args))
1733 (display-buffer buffer)
1734 (select-window (get-buffer-window buffer))
1735 (goto-char (point-max))))
1737 (defp4cmd p4-rename ()
1738 "rename" "To rename a file in the depot, type \\[p4-rename].
1740 This command will execute the integrate/delete commands automatically.
1743 (let (from-file to-file)
1744 (setq from-file (p4-read-arg-string "rename from: " (p4-buffer-file-name-2)))
1745 (setq to-file (p4-read-arg-string "rename to: " (p4-buffer-file-name-2)))
1746 (p4-noinput-buffer-action "integ" nil t (list from-file to-file))
1747 (p4-exec-p4 (get-buffer-create p4-output-buffer-name)
1748 (list "delete" from-file)
1751 (defun p4-scroll-down-1-line ()
1752 "Scroll down one line"
1756 (defun p4-scroll-up-1-line ()
1757 "Scroll up one line"
1761 (defun p4-scroll-down-1-window ()
1762 "Scroll down one window"
1765 (- (window-height) next-screen-context-lines)))
1767 (defun p4-scroll-up-1-window ()
1768 "Scroll up one window"
1771 (- (window-height) next-screen-context-lines)))
1773 (defun p4-top-of-buffer ()
1776 (goto-char (point-min)))
1778 (defun p4-bottom-of-buffer ()
1781 (goto-char (point-max)))
1783 (defun p4-delete-other-windows ()
1784 "Make buffer full height"
1786 (delete-other-windows))
1788 (defun p4-goto-next-diff ()
1791 (goto-char (window-start))
1792 (if (= (point) (point-max))
1793 (error "At bottom"))
1795 (re-search-forward "^====" nil "")
1797 (set-window-start (selected-window) (point)))
1799 (defun p4-goto-prev-diff ()
1802 (if (= (point) (point-min))
1804 (goto-char (window-start))
1805 (re-search-backward "^====" nil "")
1806 (set-window-start (selected-window) (point)))
1808 (defun p4-next-depot-file ()
1811 (goto-char (window-start))
1812 (if (= (point) (point-max))
1813 (error "At bottom"))
1815 (re-search-forward "^//[^/@# ][^/@#]*/[^@#]+#[0-9]+ - " nil "")
1817 (set-window-start (selected-window) (point)))
1819 (defun p4-prev-depot-file ()
1822 (if (= (point) (point-min))
1824 (goto-char (window-start))
1825 (re-search-backward "^//[^/@# ][^/@#]*/[^@#]+#[0-9]+ - " nil "")
1826 (set-window-start (selected-window) (point)))
1829 (defun p4-next-depot-diff ()
1832 (goto-char (window-start))
1833 (if (= (point) (point-max))
1834 (error "At bottom"))
1836 (re-search-forward "^\\(@@\\|\\*\\*\\* \\|[0-9]+[,acd]\\)" nil "")
1838 (set-window-start (selected-window) (point)))
1840 (defun p4-prev-depot-diff ()
1843 (if (= (point) (point-min))
1845 (goto-char (window-start))
1846 (re-search-backward "^\\(@@\\|\\*\\*\\* \\|[0-9]+[,acd]\\)" nil "")
1847 (set-window-start (selected-window) (point)))
1849 (defun p4-moveto-print-rev-column (old-column)
1850 (let ((colon (save-excursion
1852 (if (looking-at "[^:\n]*:")
1854 (goto-char (match-end 0))
1857 (move-to-column old-column)
1858 (if (and (< (current-column) colon)
1859 (re-search-forward "[^ ][ :]" nil t))
1860 (goto-char (match-beginning 0)))))
1862 (defun p4-next-change-rev-line ()
1863 "Next change/revision line"
1865 (let ((c (current-column)))
1867 (re-search-forward "^ *[0-9]+ +[0-9]+[^:]+:" nil "")
1868 (p4-moveto-print-rev-column c)))
1870 (defun p4-prev-change-rev-line ()
1871 "Previous change/revision line"
1873 (let ((c (current-column)))
1876 (re-search-backward "^ *[0-9]+ +[0-9]+[^:]*:" nil "")
1877 (p4-moveto-print-rev-column c)))
1879 (defun p4-toggle-line-wrap ()
1880 "Toggle line wrap mode"
1882 (setq truncate-lines (not truncate-lines))
1883 (save-window-excursion
1886 (defun p4-quit-current-buffer (pnt)
1889 (if (not (one-window-p))
1893 (defun p4-buffer-mouse-clicked (event)
1894 "Function to translate the mouse clicks in a P4 filelog buffer to
1898 (cond (p4-running-xemacs
1899 (setq win (event-window event))
1900 (setq pnt (event-point event)))
1902 (setq win (posn-window (event-end event)))
1903 (setq pnt (posn-point (event-start event)))))
1906 (p4-buffer-commands pnt)))
1908 (defun p4-buffer-mouse-clicked-3 (event)
1909 "Function to translate the mouse clicks in a P4 filelog buffer to
1913 (cond (p4-running-xemacs
1914 (setq win (event-window event))
1915 (setq pnt (event-point event)))
1917 (setq win (posn-window (event-end event)))
1918 (setq pnt (posn-point (event-start event)))))
1921 (let ((link-name (or (get-char-property pnt 'link-client-name)
1922 (get-char-property pnt 'link-depot-name)))
1923 (rev (get-char-property pnt 'rev)))
1927 (p4-diff2 rev "#head"))
1929 (error "No file to diff!"))))))
1931 (defun p4-buffer-commands (pnt)
1932 "Function to get a given property and do the appropriate command on it"
1934 (let ((rev (get-char-property pnt 'rev))
1935 (change (get-char-property pnt 'change))
1936 (action (get-char-property pnt 'action))
1937 (user (get-char-property pnt 'user))
1938 (client (get-char-property pnt 'client))
1939 (label (get-char-property pnt 'label))
1940 (branch (get-char-property pnt 'branch))
1941 (filename (p4-buffer-file-name-2)))
1942 (cond ((and (not action) rev)
1943 (let ((fn1 (concat filename "#" rev)))
1944 (p4-noinput-buffer-action "print" nil t (list fn1))
1945 (p4-activate-print-buffer "*P4 print*" t)))
1947 (let* ((rev2 (int-to-string (1- (string-to-int rev))))
1948 (fn1 (concat filename "#" rev))
1949 (fn2 (concat filename "#" rev2)))
1950 (if (> (string-to-int rev2) 0)
1952 (p4-noinput-buffer-action
1954 (append (p4-make-list-from-string
1955 p4-default-diff-options)
1957 (p4-activate-diff-buffer "*P4 diff*"))
1958 (error "There is no earlier revision to diff."))))
1959 (change (p4-describe-internal
1960 (append (p4-make-list-from-string p4-default-diff-options)
1962 (user (p4-async-process-command "user" nil
1964 "*P4 User: " user "*")
1965 "user" (list user)))
1966 (client (p4-async-process-command
1967 "client" "Description:\n\t"
1968 (concat "*P4 Client: " client "*") "client" (list client)))
1969 (label (p4-label (list label)))
1970 (branch (p4-branch (list branch)))
1972 ;; Check if a "filename link" or an active "diff buffer area" was
1975 (let ((link-client-name (get-char-property pnt 'link-client-name))
1976 (link-depot-name (get-char-property pnt 'link-depot-name))
1977 (block-client-name (get-char-property pnt 'block-client-name))
1978 (block-depot-name (get-char-property pnt 'block-depot-name))
1979 (p4-history-for (get-char-property pnt 'history-for))
1980 (first-line (get-char-property pnt 'first-line))
1981 (start (get-char-property pnt 'start)))
1984 (p4-file-change-log "filelog" (list p4-history-for)))
1985 ((or link-client-name link-depot-name)
1986 (p4-find-file-or-print-other-window
1987 link-client-name link-depot-name))
1988 ((or block-client-name block-depot-name)
1990 (let ((c (max 0 (- pnt
1999 (while (re-search-forward "^[ +>].*\n" pnt t)
2001 (p4-find-file-or-print-other-window
2002 block-client-name block-depot-name)
2004 (if (not block-client-name)
2007 (goto-char (+ (point) c)))
2008 (p4-find-file-or-print-other-window
2009 block-client-name block-depot-name)))
2011 (error "There is no file at that cursor location!"))))))))
2013 (defun p4-find-file-or-print-other-window (client-name depot-name)
2015 (find-file-other-window client-name)
2016 (p4-noinput-buffer-action "print" nil t
2018 (p4-activate-print-buffer depot-name t)
2021 (defun p4-find-file-other-window ()
2024 (let ((link-client-name (get-char-property (point) 'link-client-name))
2025 (link-depot-name (get-char-property (point) 'link-depot-name))
2026 (block-client-name (get-char-property (point) 'block-client-name))
2027 (block-depot-name (get-char-property (point) 'block-depot-name)))
2028 (cond ((or link-client-name link-depot-name)
2029 (p4-find-file-or-print-other-window
2030 link-client-name link-depot-name)
2032 ((or block-client-name block-depot-name)
2033 (p4-find-file-or-print-other-window
2034 block-client-name block-depot-name)
2035 (other-window 1)))))
2037 (defun p4-filelog-short-format ()
2040 (setq buffer-invisibility-spec t)
2043 (defun p4-filelog-long-format ()
2046 (setq buffer-invisibility-spec (list))
2049 (defun p4-scroll-down-1-line-other-w ()
2050 "Scroll other window down one line"
2052 (scroll-other-window -1))
2054 (defun p4-scroll-up-1-line-other-w ()
2055 "Scroll other window up one line"
2057 (scroll-other-window 1))
2059 (defun p4-scroll-down-1-window-other-w ()
2060 "Scroll other window down one window"
2062 (scroll-other-window
2063 (- next-screen-context-lines (window-height))))
2065 (defun p4-scroll-up-1-window-other-w ()
2066 "Scroll other window up one window"
2068 (scroll-other-window
2069 (- (window-height) next-screen-context-lines)))
2071 (defun p4-top-of-buffer-other-w ()
2072 "Top of buffer, other window"
2075 (goto-char (point-min))
2078 (defun p4-bottom-of-buffer-other-w ()
2079 "Bottom of buffer, other window"
2082 (goto-char (point-max))
2085 (defun p4-goto-next-change ()
2088 (let ((c (current-column)))
2090 (while (get-char-property (point) 'invisible)
2092 (move-to-column c)))
2094 (defun p4-goto-prev-change ()
2097 (let ((c (current-column)))
2099 (while (get-char-property (point) 'invisible)
2101 (move-to-column c)))
2104 ;; Activate special handling for a buffer generated with a diff-like command
2105 (make-face 'p4-diff-file-face)
2106 (set-face-background 'p4-diff-file-face "gray90")
2108 (make-face 'p4-diff-head-face)
2109 (set-face-background 'p4-diff-head-face "gray95")
2111 (make-face 'p4-diff-ins-face)
2112 (set-face-foreground 'p4-diff-ins-face "blue")
2114 (make-face 'p4-diff-del-face)
2115 (set-face-foreground 'p4-diff-del-face "red")
2117 (make-face 'p4-diff-change-face)
2118 (set-face-foreground 'p4-diff-change-face "dark green")
2120 (defun p4-buffer-set-face-property (regexp face-property)
2122 (goto-char (point-min))
2123 (while (re-search-forward regexp nil t)
2124 (let ((start (match-beginning 0))
2125 (end (match-end 0)))
2126 (p4-set-extent-properties start end
2127 (list (cons 'face face-property)))))))
2129 (defun p4-activate-diff-buffer (buffer-name)
2130 (p4-make-depot-list-buffer buffer-name)
2132 (set-buffer buffer-name)
2133 (setq buffer-read-only nil)
2134 (if p4-colorized-diffs
2136 (p4-buffer-set-face-property "^=.*\n" 'p4-diff-file-face)
2137 (p4-buffer-set-face-property "^[@*].*" 'p4-diff-head-face)
2138 (p4-buffer-set-face-property "^\\([+>].*\n\\)+" 'p4-diff-ins-face)
2139 (p4-buffer-set-face-property "^\\([-<].*\n\\)+" 'p4-diff-del-face)
2140 (p4-buffer-set-face-property "^\\(!.*\n\\)+" 'p4-diff-change-face)))
2142 (goto-char (point-min))
2143 (while (re-search-forward "^\\(==== //\\).*\n"
2145 (let* ((link-client-name (get-char-property (match-end 1) 'link-client-name))
2146 (link-depot-name (get-char-property (match-end 1) 'link-depot-name))
2147 (start (match-beginning 0))
2148 (end (save-excursion
2149 (if (re-search-forward "^==== " nil t)
2152 (if link-client-name
2153 (p4-set-extent-properties start end
2154 (list (cons 'block-client-name
2155 link-client-name))))
2157 (p4-set-extent-properties start end
2158 (list (cons 'block-depot-name
2159 link-depot-name))))))
2161 (goto-char (point-min))
2162 (while (re-search-forward
2163 (concat "^[@0-9].*\\([cad+]\\)\\([0-9]*\\).*\n"
2164 "\\(\\(\n\\|[^@0-9\n].*\n\\)*\\)") nil t)
2165 (let ((first-line (string-to-int (match-string 2)))
2166 (start (match-beginning 3))
2167 (end (match-end 3)))
2168 (p4-set-extent-properties start end
2169 (list (cons 'first-line first-line)
2170 (cons 'start start)))))
2172 (goto-char (point-min))
2174 (if (re-search-forward "^\\(\\.\\.\\.\\|====\\)" nil t)
2177 (p4-find-change-numbers buffer-name (point-min) stop))
2179 (goto-char (point-min))
2180 (if (looking-at "^Change [0-9]+ by \\([^ @]+\\)@\\([^ \n]+\\)")
2181 (let ((user-match 1)
2183 cur-user cur-client)
2184 (setq cur-user (match-string user-match))
2185 (setq cur-client (match-string cl-match))
2186 (p4-create-active-link (match-beginning user-match)
2187 (match-end user-match)
2188 (list (cons 'user cur-user)))
2189 (p4-create-active-link (match-beginning cl-match)
2190 (match-end cl-match)
2191 (list (cons 'client cur-client)))))
2193 (use-local-map p4-diff-map)
2194 (setq buffer-read-only t)))
2197 ;; The p4 describe command
2198 (defp4cmd p4-describe ()
2199 "describe" "To get a description for a change number, type \\[p4-describe].\n"
2201 (let ((arg-string (p4-make-list-from-string
2202 (read-string "p4 describe: "
2203 (concat p4-default-diff-options " ")))))
2204 (p4-describe-internal arg-string)))
2206 ;; Internal version of the p4 describe command
2207 (defun p4-describe-internal (arg-string)
2208 (p4-noinput-buffer-action
2209 "describe" nil t arg-string)
2210 (p4-activate-diff-buffer
2211 (concat "*P4 describe: " (p4-list-to-string arg-string) "*")))
2213 ;; The p4 opened command
2214 (defp4cmd p4-opened ()
2216 "To display list of files opened for pending change, type \\[p4-opened].\n"
2219 (if current-prefix-arg
2220 (setq args (p4-make-list-from-string
2221 (p4-read-arg-string "p4 opened: "
2222 (p4-buffer-file-name-2)))))
2223 (p4-opened-internal args)))
2225 (defun p4-opened-internal (args)
2226 (let ((p4-client (p4-current-client)))
2227 (p4-noinput-buffer-action "opened" nil t args)
2228 (p4-make-depot-list-buffer (concat "*Opened Files: " p4-client "*"))))
2230 (defun p4-update-opened-list ()
2231 (if (get-buffer-window (concat "*Opened Files: " (p4-current-client) "*"))
2233 (setq current-prefix-arg nil)
2234 (p4-opened-internal nil))))
2236 (defun p4-regexp-create-links (buffer-name regexp property)
2238 (set-buffer buffer-name)
2239 (setq buffer-read-only nil)
2240 (goto-char (point-min))
2241 (while (re-search-forward regexp nil t)
2242 (let ((start (match-beginning 1))
2244 (str (match-string 1)))
2245 (p4-create-active-link start end (list (cons property str)))))
2246 (setq buffer-read-only t)))
2248 ;; The p4 users command
2249 (defp4cmd p4-users ()
2250 "users" "To display list of known users, type \\[p4-users].\n"
2253 (if current-prefix-arg
2254 (setq args (p4-make-list-from-string
2255 (p4-read-arg-string "p4 users: " nil "user"))))
2256 (p4-noinput-buffer-action "users" nil t args))
2257 (p4-make-basic-buffer "*P4 users*")
2258 (p4-regexp-create-links "*P4 users*" "^\\([^ ]+\\).*\n" 'user))
2260 ;; The p4 jobs command
2261 (defp4cmd p4-jobs ()
2262 "jobs" "To display list of jobs, type \\[p4-jobs].\n"
2265 (if current-prefix-arg
2266 (setq args (p4-make-list-from-string (p4-read-arg-string "p4 jobs: "))))
2267 (p4-noinput-buffer-action "jobs" nil t args))
2268 (p4-make-basic-buffer "*P4 jobs*"))
2270 ;; The p4 fix command
2272 "fix" "To mark jobs as being fixed by a changelist number, type \\[p4-fix].\n"
2274 (let ((args (p4-make-list-from-string (p4-read-arg-string "p4 fix: "
2276 (p4-noinput-buffer-action "fix" nil t args)))
2278 ;; The p4 fixes command
2279 (defp4cmd p4-fixes ()
2280 "fixes" "To list what changelists fix what jobs, type \\[p4-fixes].\n"
2283 (if current-prefix-arg
2284 (setq args (p4-make-list-from-string (p4-read-arg-string "p4 fixes: "))))
2285 (p4-noinput-buffer-action "fixes" nil t args)
2286 (p4-make-basic-buffer "*P4 fixes*")))
2288 ;; The p4 where command
2289 (defp4cmd p4-where ()
2291 "To show how local file names map into depot names, type \\[p4-where].\n"
2294 (if current-prefix-arg
2295 (setq args (p4-make-list-from-string
2296 (p4-read-arg-string "p4 where: "
2297 (p4-buffer-file-name-2)))))
2298 (p4-noinput-buffer-action "where" nil 's args)))
2301 (defun p4-async-process-command (p4-this-command &optional
2307 "Internal function to call an asynchronous process with a local buffer,
2308 instead of calling an external client editor to run within emacs.
2311 P4-THIS-COMMAND is the command that called this internal function.
2313 P4-REGEXP is the optional regular expression to search for to set the cursor
2316 P4-THIS-BUFFER is the optional buffer to create. (Default is *P4 <command>*).
2318 P4-OUT-COMMAND is the optional command that will be used as the command to
2319 be called when `p4-async-call-process' is called.
2321 P4-IN-ARGS is the optional argument passed that will be used as the list of
2322 arguments to the P4-THIS-COMMAND.
2324 P4-OUT-ARGS is the optional argument passed that will be used as the list of
2325 arguments to P4-OUT-COMMAND."
2326 (let ((dir default-directory))
2328 (set-buffer (get-buffer-create p4-this-buffer))
2329 (set-buffer (get-buffer-create (concat "*P4 " p4-this-command "*"))))
2330 (setq p4-current-command p4-this-command)
2332 (if (zerop (apply 'call-process-region (point-min) (point-max)
2333 (p4-check-p4-executable) t t nil
2334 "-d" default-directory
2335 p4-current-command "-o"
2338 (goto-char (point-min))
2339 (insert (concat "# Created using " (p4-emacs-version) ".\n"
2340 "# Type C-c C-c to submit changes and exit buffer.\n"
2341 "# Type C-x k to kill current changes.\n"
2343 (if p4-regexp (re-search-forward p4-regexp))
2344 (indented-text-mode)
2345 (setq p4-async-minor-mode t)
2346 (setq fill-column 79)
2347 (p4-push-window-config)
2348 (switch-to-buffer-other-window (current-buffer))
2350 (setq p4-current-command p4-out-command))
2351 (setq p4-current-args p4-out-args)
2352 (setq buffer-offer-save t)
2354 (define-key p4-async-minor-map "\C-c\C-c" 'p4-async-call-process)
2355 (run-hooks 'p4-async-command-hook)
2356 (set-buffer-modified-p nil)
2357 (message "C-c C-c to finish editing and exit buffer."))
2358 (error "%s %s -o failed to complete successfully."
2359 (p4-check-p4-executable) p4-current-command)))
2361 (defun p4-async-call-process ()
2362 "Internal function called by `p4-async-process-command' to process the
2363 buffer after editing is done using the minor mode key mapped to `C-c C-c'."
2365 (message "p4 %s ..." p4-current-command)
2366 (let ((max (point-max)) msg
2367 (current-command p4-current-command)
2368 (current-args p4-current-args))
2370 (if (zerop (apply 'call-process-region (point-min)
2371 max (p4-check-p4-executable)
2373 "-d" default-directory
2374 current-command "-i"
2378 (setq msg (buffer-substring max (point-max)))
2379 (delete-region max (point-max))
2381 (set-buffer (get-buffer-create p4-output-buffer-name))
2382 (delete-region (point-min) (point-max))
2385 (display-buffer p4-output-buffer-name)
2386 (p4-partial-cache-cleanup current-command)
2387 (message "p4 %s done." current-command)
2388 (if (equal current-command "submit")
2390 (p4-refresh-files-in-buffers)
2391 (p4-check-mode-all-buffers)
2393 (p4-notify p4-notify-list)))))
2394 (error "%s %s -i failed to complete successfully."
2395 (p4-check-p4-executable)
2398 (defun p4-cmd-line-flags (args)
2399 (memq t (mapcar (lambda (x) (not (not (string-match "^-" x))))
2402 ;; The p4 change command
2403 (defp4cmd p4-change ()
2404 "change" "To edit the change specification, type \\[p4-change].\n"
2407 (change-buf-name "*P4 New Change*"))
2408 (if (buffer-live-p (get-buffer change-buf-name))
2409 (switch-to-buffer-other-window (get-buffer change-buf-name))
2410 (if current-prefix-arg
2411 (setq args (p4-make-list-from-string
2412 (p4-read-arg-string "p4 change: " nil))))
2413 (if (p4-cmd-line-flags args)
2414 (p4-noinput-buffer-action "change" nil t args)
2415 (p4-async-process-command "change" "Description:\n\t"
2416 change-buf-name nil args)))))
2418 ;; The p4 client command
2419 (defp4cmd p4-client ()
2420 "client" "To edit a client specification, type \\[p4-client].\n"
2423 (client-buf-name "*P4 client*"))
2424 (if (buffer-live-p (get-buffer client-buf-name))
2425 (switch-to-buffer-other-window (get-buffer client-buf-name))
2426 (if current-prefix-arg
2427 (setq args (p4-make-list-from-string
2428 (p4-read-arg-string "p4 client: " nil "client"))))
2429 (if (p4-cmd-line-flags args)
2430 (p4-noinput-buffer-action "client" nil t args)
2431 (p4-async-process-command "client" "\\(Description\\|View\\):\n\t"
2432 client-buf-name nil args)))))
2434 (defp4cmd p4-clients ()
2435 "clients" "To list all clients, type \\[p4-clients].\n"
2437 (p4-noinput-buffer-action "clients" nil t nil)
2438 (p4-make-basic-buffer "*P4 clients*")
2439 (p4-regexp-create-links "*P4 clients*" "^Client \\([^ ]+\\).*\n" 'client))
2441 (defp4cmd p4-branch (args)
2442 "branch" "Edit a P4-BRANCH specification using \\[p4-branch]."
2444 (p4-make-list-from-string
2445 (p4-read-arg-string "p4 branch: " nil "branch"))))
2446 (if (or (null args) (equal args (list "")))
2447 (error "Branch must be specified!")
2448 (if (p4-cmd-line-flags args)
2449 (p4-noinput-buffer-action "branch" nil t args)
2450 (p4-async-process-command "branch" "Description:\n\t"
2451 (concat "*P4 Branch: "
2452 (car (reverse args)) "*")
2455 (defp4cmd p4-branches ()
2456 "branches" "To list all branches, type \\[p4-branches].\n"
2458 (p4-noinput-buffer-action "branches" nil t nil)
2459 (p4-make-basic-buffer "*P4 branches*")
2460 (p4-regexp-create-links "*P4 branches*" "^Branch \\([^ ]+\\).*\n" 'branch))
2462 (defp4cmd p4-label (args)
2463 "label" "Edit a P4-label specification using \\[p4-label].\n"
2465 (p4-make-list-from-string
2466 (p4-read-arg-string "p4 label: " nil "label"))))
2467 (if (or (null args) (equal args (list "")))
2468 (error "label must be specified!")
2469 (if (p4-cmd-line-flags args)
2470 (p4-noinput-buffer-action "label" nil t args)
2471 (p4-async-process-command "label" "Description:\n\t"
2472 (concat "*P4 label: "
2473 (car (reverse args)) "*")
2476 (defp4cmd p4-labels ()
2477 "labels" "To display list of defined labels, type \\[p4-labels].\n"
2479 (p4-noinput-buffer-action "labels" nil t nil)
2480 (p4-make-basic-buffer "*P4 labels*")
2481 (p4-regexp-create-links "*P4 labels*" "^Label \\([^ ]+\\).*\n" 'label))
2483 ;; The p4 labelsync command
2484 (defp4cmd p4-labelsync ()
2486 "To synchronize a label with the current client contents, type \\[p4-labelsync].\n"
2488 (let ((args (p4-make-list-from-string
2489 (p4-read-arg-string "p4 labelsync: "))))
2490 (p4-noinput-buffer-action "labelsync" nil t args))
2491 (p4-make-depot-list-buffer "*P4 labelsync*"))
2493 (defun p4-filter-out (pred lst)
2496 (if (not (funcall pred (car lst)))
2497 (setq res (cons (car lst) res)))
2498 (setq lst (cdr lst)))
2501 ;; The p4 submit command
2502 (defp4cmd p4-submit (&optional arg)
2503 "submit" "To submit a pending change to the depot, type \\[p4-submit].\n"
2506 (submit-buf-name "*P4 Submit*")
2507 (change-list (if (integerp arg) arg)))
2508 (if (buffer-live-p (get-buffer submit-buf-name))
2509 (switch-to-buffer-other-window (get-buffer submit-buf-name))
2511 (setq args (list "-c" (int-to-string change-list)))
2512 (if current-prefix-arg
2513 (setq args (p4-make-list-from-string
2514 (p4-read-arg-string "p4 submit: " nil)))))
2515 (setq args (p4-filter-out (lambda (x) (string= x "-c")) args))
2516 (p4-save-opened-files)
2517 (if (or (not (and p4-check-empty-diffs (p4-empty-diff-p)))
2521 "File with empty diff opened for edit. Submit anyway? ")))
2522 (p4-async-process-command "change" "Description:\n\t"
2523 submit-buf-name "submit" args)))))
2525 ;; The p4 user command
2526 (defp4cmd p4-user ()
2527 "user" "To create or edit a user specification, type \\[p4-user].\n"
2530 (if current-prefix-arg
2531 (setq args (p4-make-list-from-string
2532 (p4-read-arg-string "p4 user: " nil "user"))))
2533 (if (p4-cmd-line-flags args)
2534 (p4-noinput-buffer-action "user" nil t args)
2535 (p4-async-process-command "user" nil nil nil args))))
2537 ;; The p4 job command
2539 "job" "To create or edit a job, type \\[p4-job].\n"
2542 (if current-prefix-arg
2543 (setq args (p4-make-list-from-string
2544 (p4-read-arg-string "p4 job: " nil "job"))))
2545 (if (p4-cmd-line-flags args)
2546 (p4-noinput-buffer-action "job" nil t args)
2547 (p4-async-process-command "job" "Description:\n\t" nil nil args))))
2549 ;; The p4 jobspec command
2550 (defp4cmd p4-jobspec ()
2551 "jobspec" "To edit the job template, type \\[p4-jobspec].\n"
2553 (p4-async-process-command "jobspec"))
2555 ;; A function to set the current P4 client name
2556 (defun p4-set-client-name (p4-new-client-name)
2557 "To set the current value of P4CLIENT, type \\[p4-set-client-name].
2559 This will change the current client from the previous client to the new
2562 Setting this value to nil would disable P4 Version Checking.
2564 `p4-set-client-name' will complete any client names set using the function
2565 `p4-set-my-clients'. The strictness of completion will depend on the
2566 variable `p4-strict-complete' (default is t).
2568 Argument P4-NEW-CLIENT-NAME The new client to set to. The default value is
2569 the current client."
2571 (completing-read "Change Client to: "
2574 'p4-clients-completion)
2575 nil p4-strict-complete (p4-current-client))
2577 (if (or (null p4-new-client-name) (equal p4-new-client-name "nil"))
2579 (setenv "P4CLIENT" nil)
2580 (if (not (getenv "P4CONFIG"))
2582 "P4 Version check disabled. Set a valid client name to enable."
2584 (setenv "P4CLIENT" p4-new-client-name)
2585 (message "P4CLIENT changed to %s" p4-new-client-name)
2586 (run-hooks 'p4-set-client-hooks)))
2588 (defun p4-get-client-config ()
2589 "To get the current value of the environment variable P4CONFIG,
2590 type \\[p4-get-client-config].
2592 This will be the current configuration that is in use for access through
2596 (message "P4CONFIG is %s" (getenv "P4CONFIG")))
2598 (defun p4-set-client-config (p4config)
2599 "To set the P4CONFIG variable, for use with the current versions of the p4
2602 P4CONFIG is a more flexible mechanism wherein p4 will find the current
2603 client automatically by checking the config file found at the root of a
2604 directory \(recursing all the way to the top\).
2606 In this scenario, a P4CLIENT variable need not be explicitly set.
2608 (interactive "sP4 Config: ")
2609 (if (or (null p4config) (equal p4config ""))
2610 (message "P4CONFIG not changed.")
2611 (setenv "P4CONFIG" p4config)
2612 (message "P4CONFIG changed to %s" p4config)))
2614 (defun p4-set-my-clients (client-list)
2615 "To set the client completion list used by `p4-set-client-name', use
2616 this function in your .emacs (or any lisp interaction buffer).
2618 This will change the current client list from the previous list to the new
2621 Setting this value to nil would disable client completion by
2622 `p4-set-client-name'.
2624 The strictness of completion will depend on the variable
2625 `p4-strict-complete' (default is t).
2627 Argument CLIENT-LIST is the 'list' of clients.
2629 To set your clients using your .emacs, use the following:
2631 \(load-library \"p4\"\)
2632 \(p4-set-my-clients \'(client1 client2 client3)\)"
2633 (setq p4-my-clients nil)
2634 (let (p4-tmp-client-var)
2636 (setq p4-tmp-client-var (format "%s" (car client-list)))
2637 (setq client-list (cdr client-list))
2638 (setq p4-my-clients (append p4-my-clients
2639 (list (list p4-tmp-client-var)))))))
2641 ;; A function to get the current P4PORT
2642 (defun p4-get-p4-port ()
2643 "To get the current value of the environment variable P4PORT, type \
2646 This will be the current server/port that is in use for access through Emacs
2649 (let ((port (p4-current-server-port)))
2650 (message "P4PORT is [local: %s], [global: %s]" port (getenv "P4PORT"))
2653 ;; A function to set the current P4PORT
2654 (defun p4-set-p4-port (p4-new-p4-port)
2655 "To set the current value of P4PORT, type \\[p4-set-p4-port].
2657 This will change the current server from the previous server to the new
2660 Argument P4-NEW-P4-PORT The new server:port to set to. The default value is
2661 the current value of P4PORT."
2662 (interactive (list (let
2663 ((symbol (read-string "Change server:port to: "
2664 (getenv "P4PORT"))))
2665 (if (equal symbol "")
2668 (if (or (null p4-new-p4-port) (equal p4-new-p4-port "nil"))
2670 (setenv "P4PORT" nil)
2671 (if (not (getenv "P4CONFIG"))
2673 "P4 Version check disabled. Set a valid server:port to enable.")))
2674 (setenv "P4PORT" p4-new-p4-port)
2675 (message "P4PORT changed to %s" p4-new-p4-port)))
2677 ;; The find-file hook for p4.
2678 (defun p4-find-file-hook ()
2679 "To check while loading the file, if it is a P4 version controlled file."
2680 (if (or (getenv "P4CONFIG") (getenv "P4CLIENT"))
2683 (defun p4-refresh-refresh-list (buffile bufname)
2684 "Refresh the list of files to be refreshed."
2685 (setq p4-all-buffer-files (delete (list buffile bufname)
2686 p4-all-buffer-files))
2687 (if (not p4-all-buffer-files)
2689 (if (and p4-running-emacs (timerp p4-file-refresh-timer))
2690 (cancel-timer p4-file-refresh-timer))
2691 (if (and p4-running-xemacs p4-file-refresh-timer)
2692 (disable-timeout p4-file-refresh-timer))
2693 (setq p4-file-refresh-timer nil))))
2695 ;; Set keymap. We use the C-x p Keymap for all perforce commands
2696 (defvar p4-prefix-map
2697 (let ((map (make-sparse-keymap)))
2698 (define-key map "a" 'p4-add)
2699 (define-key map "b" 'p4-bug-report)
2700 (define-key map "B" 'p4-branch)
2701 (define-key map "c" 'p4-client)
2702 (define-key map "C" 'p4-changes)
2703 (define-key map "d" 'p4-diff2)
2704 (define-key map "D" 'p4-describe)
2705 (define-key map "e" 'p4-edit)
2706 (define-key map "E" 'p4-reopen)
2707 (define-key map "\C-f" 'p4-depot-find-file)
2708 (define-key map "f" 'p4-filelog)
2709 (define-key map "F" 'p4-files)
2710 (define-key map "g" 'p4-get-client-name)
2711 (define-key map "G" 'p4-get)
2712 (define-key map "h" 'p4-help)
2713 (define-key map "H" 'p4-have)
2714 (define-key map "i" 'p4-info)
2715 (define-key map "I" 'p4-integ)
2716 (define-key map "j" 'p4-job)
2717 (define-key map "J" 'p4-jobs)
2718 (define-key map "l" 'p4-label)
2719 (define-key map "L" 'p4-labels)
2720 (define-key map "\C-l" 'p4-labelsync)
2721 (define-key map "m" 'p4-rename)
2722 (define-key map "n" 'p4-notify)
2723 (define-key map "o" 'p4-opened)
2724 (define-key map "p" 'p4-print)
2725 (define-key map "P" 'p4-set-p4-port)
2726 (define-key map "q" 'p4-pop-window-config)
2727 (define-key map "r" 'p4-revert)
2728 (define-key map "R" 'p4-refresh)
2729 (define-key map "\C-r" 'p4-resolve)
2730 (define-key map "s" 'p4-set-client-name)
2731 (define-key map "S" 'p4-submit)
2732 (define-key map "t" 'p4-toggle-vc-mode)
2733 (define-key map "u" 'p4-user)
2734 (define-key map "U" 'p4-users)
2735 (define-key map "v" 'p4-emacs-version)
2736 (define-key map "V" 'p4-blame)
2737 (define-key map "w" 'p4-where)
2738 (define-key map "x" 'p4-delete)
2739 (define-key map "X" 'p4-fix)
2740 (define-key map "=" 'p4-diff)
2741 (define-key map "-" 'p4-ediff)
2742 (define-key map "?" 'p4-describe-bindings)
2744 "The Prefix for P4 Library Commands.")
2746 (if (not (keymapp (lookup-key global-map "\C-xp")))
2747 (define-key global-map "\C-xp" p4-prefix-map))
2749 ;; For users interested in notifying a change, a notification list can be
2750 ;; set up using this function.
2751 (defun p4-set-notify-list (p4-new-notify-list &optional p4-supress-stat)
2752 "To set the current value of P4NOTIFY, type \\[p4-set-notify-list].
2754 This will change the current notify list from the existing list to the new
2757 An empty string will disable notification.
2759 Argument P4-NEW-NOTIFY-LIST is new value of the notification list.
2760 Optional argument P4-SUPRESS-STAT when t will suppress display of the status
2763 (interactive (list (let
2764 ((symbol (read-string
2765 "Change Notification List to: "
2767 (if (equal symbol "")
2770 (let ((p4-old-notify-list p4-notify-list))
2771 (setenv "P4NOTIFY" p4-new-notify-list)
2772 (setq p4-notify-list p4-new-notify-list)
2773 (setq p4-notify (not (null p4-new-notify-list)))
2774 (if (not p4-supress-stat)
2775 (message "Notification list changed from '%s' to '%s'"
2776 p4-old-notify-list p4-notify-list))))
2778 ;; To get the current notification list.
2779 (defun p4-get-notify-list ()
2780 "To get the current value of the environment variable P4NOTIFY,
2781 type \\[p4-get-notify-list].
2783 This will be the current notification list that is in use for mailing
2784 change notifications through Emacs P4."
2787 (message "P4NOTIFY is %s" p4-notify-list))
2789 (defun p4-notify (users)
2790 "To notify a list of users of a change submission manually, type
2793 To do auto-notification, set the notification list with `p4-set-notify-list'
2794 and on each submission, the users in the list will be notified of the
2797 Since this uses the sendmail program, it is mandatory to set the correct
2798 path to the sendmail program in the variable `p4-sendmail-program'.
2800 Also, it is mandatory to set the user's email address in the variable
2803 Argument USERS The users to notify to. The default value is the notification
2805 (interactive (list (let
2806 ((symbol (read-string "Notify whom? "
2808 (if (equal symbol "")
2811 (p4-set-notify-list users t)
2812 (if (and p4-sendmail-program p4-user-email)
2814 (message "Please set p4-sendmail-program and p4-user-email variables.")))
2816 (defun p4-do-notify ()
2817 "This is the internal notification function called by `p4-notify'."
2819 (if (and p4-notify-list (not (equal p4-notify-list "")))
2821 (set-buffer (get-buffer-create p4-output-buffer-name))
2822 (goto-char (point-min))
2823 (if (re-search-forward "[0-9]+.*submitted" (point-max) t)
2824 (let (p4-matched-change)
2825 (setq p4-matched-change (substring (match-string 0) 0 -10))
2826 (set-buffer (get-buffer-create "*P4 Notify*"))
2827 (delete-region (point-min) (point-max))
2828 (call-process-region (point-min) (point-max)
2829 (p4-check-p4-executable)
2831 "-d" default-directory
2834 (switch-to-buffer "*P4 Notify*")
2835 (goto-char (point-min))
2837 (if (re-search-forward "^Change.*$" (point-max) t)
2838 (setq p4-chg-desc (match-string 0))
2839 (setq p4-chg-desc (concat
2840 "Notification of Change "
2841 p4-matched-change)))
2842 (goto-char (point-min))
2844 "From: " p4-user-email "\n"
2845 "To: P4 Notification Recipients:;\n"
2846 "Subject: " p4-chg-desc "\n")
2847 (call-process-region (point-min) (point-max)
2848 p4-sendmail-program t t nil
2849 "-odi" "-oi" p4-notify-list)
2853 (set-buffer (get-buffer-create p4-output-buffer-name))
2854 (goto-char (point-max))
2855 (insert "\np4-do-notify: No Change Submissions found."))))
2857 (set-buffer (get-buffer-create p4-output-buffer-name))
2858 (goto-char (point-max))
2859 (insert "\np4-do-notify: Notification list not set.")))))
2861 ;; Function to return the current version.
2862 (defun p4-emacs-version ()
2863 "Return the current Emacs-P4 Integration version."
2865 (message (concat (cond (p4-running-xemacs "X")) "Emacs-P4 Integration v%s")
2868 (defun p4-find-p4-config-file ()
2869 (let ((p4config (getenv "P4CONFIG"))
2870 (p4-cfg-dir (cond ((p4-buffer-file-name)
2871 (file-name-directory
2872 (file-truename (p4-buffer-file-name))))
2873 (t (file-truename default-directory)))))
2876 (let (found at-root)
2877 (while (not (or found at-root))
2878 (let ((parent-dir (file-name-directory
2879 (directory-file-name
2881 (if (file-exists-p (concat p4-cfg-dir p4config))
2882 (setq found (concat p4-cfg-dir p4config)))
2883 (setq at-root (string-equal parent-dir p4-cfg-dir))
2884 (setq p4-cfg-dir parent-dir)))
2887 (defun p4-detect-p4 ()
2888 (if (or (not p4-use-p4config-exclusively)
2889 (p4-find-p4-config-file))
2892 (defun p4-get-add-branch-files (&optional name-list)
2893 (let ((output-buffer (p4-depot-output "opened" name-list))
2896 (set-buffer output-buffer)
2897 (goto-char (point-min))
2898 (while (re-search-forward "^\\(//[^/@#]+/[^#\n]*\\)#[0-9]+ - add " nil t)
2899 (setq files (cons (cons (match-string 1) "Add")
2901 (goto-char (point-min))
2902 (while (re-search-forward "^\\(//[^/@#]+/[^#\n]*\\)#[0-9]+ - branch " nil t)
2903 (setq files (cons (cons (match-string 1) "Branch")
2905 (kill-buffer output-buffer)
2906 (setq depot-map (p4-map-depot-files (mapcar 'car files)))
2907 (mapcar (lambda (x) (cons (cdr (assoc (car x) depot-map))
2910 (defun p4-get-have-files (file-list)
2911 (let ((output-buffer (p4-depot-output "have" file-list))
2912 line files depot-map elt)
2913 (while (setq line (p4-read-depot-output output-buffer))
2914 (if (string-match "^\\(//[^/@#]+/[^#\n]*\\)#\\([0-9]+\\) - " line)
2915 (setq files (cons (cons (match-string 1 line)
2916 (match-string 2 line))
2918 (kill-buffer output-buffer)
2919 (setq depot-map (p4-map-depot-files (mapcar 'car files)))
2920 (setq files (mapcar (lambda (x) (cons (cdr (assoc (car x) depot-map))
2923 (setq elt (car file-list))
2924 (setq file-list (cdr file-list))
2925 (if (not (assoc elt files))
2926 (setq files (cons (cons elt nil) files))))
2929 ;; A function to check if the file being opened is version controlled by p4.
2930 (defun p4-is-vc (&optional file-mode-cache filename)
2931 "If a file is controlled by P4 then return version else return nil."
2933 (setq filename (p4-buffer-file-name)))
2935 (let ((el (assoc filename file-mode-cache)))
2937 (setq version (cdr el)))
2938 (if (and (not done) filename)
2939 (let ((output-buffer (p4-depot-output "have" (list filename)))
2941 (setq line (p4-read-depot-output output-buffer))
2942 (kill-buffer output-buffer)
2943 (if (string-match "^//[^/@#]+/[^#\n]*#\\([0-9]+\\) - " line)
2944 (setq version (match-string 1 line)))
2945 (setq done version)))
2946 (if (and (not done) (not file-mode-cache))
2948 (setq file-mode-cache
2949 (p4-get-add-branch-files (and filename (list filename))))
2950 (setq version (cdr (assoc filename file-mode-cache)))))
2953 (defun p4-check-mode (&optional file-mode-cache)
2954 "Check to see whether we should export the menu map to this buffer.
2956 Turning on P4 mode calls the hooks in the variable `p4-mode-hook' with
2961 (setq p4-vc-check (p4-is-vc file-mode-cache))
2965 (setq p4-mode (concat " P4:" p4-vc-check))))
2966 (p4-force-mode-line-update)
2967 (let ((buffile (p4-buffer-file-name))
2968 (bufname (buffer-name)))
2969 (if (and p4-vc-check (not (member (list buffile bufname)
2970 p4-all-buffer-files)))
2971 (add-to-list 'p4-all-buffer-files (list buffile bufname))))
2972 (if (and (not p4-file-refresh-timer) (not (= p4-file-refresh-timer-time 0)))
2973 (setq p4-file-refresh-timer
2974 (cond (p4-running-emacs
2975 (run-at-time nil p4-file-refresh-timer-time
2976 'p4-refresh-files-in-buffers))
2978 (add-timeout p4-file-refresh-timer-time
2979 'p4-refresh-files-in-buffers nil
2980 p4-file-refresh-timer-time)))))
2982 (and p4-vc-check (run-hooks 'p4-mode-hook))
2985 (defun p4-refresh-files-in-buffers (&optional arg)
2986 "Check to see if all the files that are under P4 version control are
2987 actually up-to-date, if in buffers, or need refreshing."
2989 (let ((p4-all-my-files p4-all-buffer-files) buffile bufname thiselt)
2990 (while p4-all-my-files
2991 (setq thiselt (car p4-all-my-files))
2992 (setq p4-all-my-files (cdr p4-all-my-files))
2993 (setq buffile (car thiselt))
2994 (setq bufname (cadr thiselt))
2995 (if (buffer-live-p (get-buffer bufname))
2997 (let ((buf (get-buffer bufname)))
3000 (if (not (buffer-modified-p buf))
3001 (if (not (verify-visited-file-modtime buf))
3002 (if (file-readable-p buffile)
3005 (if (file-readable-p buffile)
3006 (find-file-noselect buffile t)
3008 (setq buffer-read-only (not (file-writable-p
3009 (p4-buffer-file-name))))))
3010 (p4-refresh-refresh-list buffile bufname)))))
3012 (defun p4-check-mode-all-buffers ()
3013 "Call p4-check-mode for all buffers under P4 version control"
3014 (let ((p4-all-my-files p4-all-buffer-files) buffile bufname thiselt
3016 (if (and p4-all-my-files p4-do-find-file)
3017 (setq file-mode-cache
3018 (append (p4-get-add-branch-files)
3019 (p4-get-have-files (mapcar 'car p4-all-my-files)))))
3020 (while p4-all-my-files
3021 (setq thiselt (car p4-all-my-files))
3022 (setq p4-all-my-files (cdr p4-all-my-files))
3023 (setq buffile (car thiselt))
3024 (setq bufname (cadr thiselt))
3025 (if (buffer-live-p (get-buffer bufname))
3027 (set-buffer (get-buffer bufname))
3028 (p4-check-mode file-mode-cache))
3029 (p4-refresh-refresh-list buffile bufname)))))
3031 ;; Force mode line updation for different Emacs versions
3032 (defun p4-force-mode-line-update ()
3033 "To Force the mode line update for different flavors of Emacs."
3034 (cond (p4-running-xemacs
3037 (force-mode-line-update))))
3039 ;; In case, the P4 server is not available, or when operating off-line, the
3040 ;; p4-find-file-hook becomes a pain... this functions toggles the use of the
3041 ;; hook when opening files.
3043 (defun p4-toggle-vc-mode ()
3044 "In case, the P4 server is not available, or when working off-line, toggle
3045 the VC check on/off when opening files."
3047 (setq p4-do-find-file (not p4-do-find-file))
3048 (message (concat "P4 mode check " (if p4-do-find-file
3052 ;; Wrap C-x C-q to allow p4-edit/revert and also to ensure that
3053 ;; we don't stomp on vc-toggle-read-only.
3055 (defun p4-toggle-read-only (&optional arg)
3056 "If p4-mode is non-nil, \\[p4-toggle-read-only] toggles between `p4-edit'
3057 and `p4-revert'. If ARG is non-nil, p4-offline-mode will be enabled for this
3058 buffer before the toggling takes place. In p4-offline-mode, toggle between
3059 making the file writable and write protected."
3061 (if (and arg p4-mode)
3066 (if buffer-read-only
3067 (p4-edit p4-verbose)
3068 (p4-revert p4-verbose)))
3071 (if buffer-file-name
3072 (let ((mode (file-modes buffer-file-name)))
3073 (if buffer-read-only
3074 (setq mode (logand mode (lognot 128)))
3075 (setq mode (logior mode 128)))
3076 (set-file-modes buffer-file-name mode))))))
3078 (defun p4-browse-web-page ()
3079 "Browse the p4.el web page."
3081 (require 'browse-url)
3082 (browse-url p4-web-page))
3084 (defun p4-bug-report ()
3086 (if (string-match " 19\\." (emacs-version))
3087 ;; unfortunately GNU Emacs 19.x doesn't have compose-mail
3088 (mail nil p4-emacs-maintainer (concat "BUG REPORT: "
3089 (p4-emacs-version)))
3090 (compose-mail p4-emacs-maintainer (concat "BUG REPORT: "
3091 (p4-emacs-version))))
3092 (goto-char (point-min))
3093 (re-search-forward (concat "^" (regexp-quote mail-header-separator) "\n"))
3094 ;; Insert warnings for novice users.
3096 "This bug report will be sent to the P4-Emacs Integration Maintainer,\n"
3097 p4-emacs-maintainer "\n\n")
3098 (insert (concat (emacs-version) "\n\n"))
3099 (insert "A brief description of the problem and how to reproduce it:\n")
3101 (let ((message-buf (get-buffer
3102 (cond (p4-running-xemacs " *Message-Log*")
3103 (p4-running-emacs "*Messages*")))))
3106 (end-pos (point-max)))
3108 (set-buffer message-buf)
3111 (setq beg-pos (point)))
3112 (insert "\n\nRecent messages:\n")
3113 (insert-buffer-substring message-buf beg-pos end-pos))))))
3115 (defun p4-describe-bindings ()
3116 "A function to list the key bindings for the p4 prefix map"
3119 (p4-push-window-config)
3120 (let ((map (make-sparse-keymap))
3121 (p4-bindings-buffer "*P4 key bindings*"))
3122 (get-buffer-create p4-bindings-buffer)
3125 (set-buffer p4-bindings-buffer)
3126 (delete-region (point-min) (point-max))
3127 (insert "Key Bindings for P4 Mode\n------------------------\n")
3128 (describe-bindings-internal p4-prefix-map))
3130 (kill-buffer p4-bindings-buffer)
3131 (describe-bindings "\C-xp")
3132 (set-buffer "*Help*")
3133 (rename-buffer p4-bindings-buffer)))
3134 (define-key map "q" 'p4-quit-current-buffer)
3136 (display-buffer p4-bindings-buffer))))
3138 ;; Break up a string into a list of words
3139 ;; (p4-make-list-from-string "ab c de f") -> ("ab" "c" "de" "f")
3140 (defun p4-make-list-from-string (str)
3142 (while (or (string-match "^ *\"\\([^\"]*\\)\"" str)
3143 (string-match "^ *\'\\([^\']*\\)\'" str)
3144 (string-match "^ *\\([^ ]+\\)" str))
3145 (setq lst (append lst (list (match-string 1 str))))
3146 (setq str (substring str (match-end 0))))
3149 (defun p4-list-to-string (lst)
3150 (mapconcat (lambda (x) x) lst " "))
3152 ;; Return the file name associated with a buffer. If the real buffer file
3153 ;; name doesn't exist, try special filename tags set in some of the p4
3155 (defun p4-buffer-file-name-2 ()
3156 (cond ((p4-buffer-file-name))
3157 ((get-char-property (point) 'link-client-name))
3158 ((get-char-property (point) 'link-depot-name))
3159 ((get-char-property (point) 'block-client-name))
3160 ((get-char-property (point) 'block-depot-name))
3161 ((if (and (fboundp 'dired-get-filename)
3162 (dired-get-filename nil t))
3163 (p4-follow-link-name (dired-get-filename nil t))))))
3165 (defun p4-buffer-file-name ()
3166 (cond (buffer-file-name
3167 (p4-follow-link-name buffer-file-name))
3170 (defun p4-follow-link-name (name)
3171 (if p4-follow-symlinks
3172 (file-truename name)
3175 (defvar p4-depot-filespec-history nil
3176 "History for p4-depot filespecs.")
3178 (defvar p4-depot-completion-cache nil
3179 "Cache for `p4-depot-completion'.
3180 It is a list of lists whose car is a filespec and
3181 cdr is the list of anwers")
3183 (defvar p4-branches-history nil
3184 "History for p4 clients.")
3186 (defvar p4-branches-completion-cache nil
3187 "Cache for `p4-depot-completion'.
3188 It is a list of lists whose car is a client and
3189 cdr is the list of answers??")
3191 (defvar p4-clients-history nil
3192 "History for p4 clients.")
3194 (defvar p4-clients-completion-cache nil
3195 "Cache for `p4-depot-completion'.
3196 It is a list of lists whose car is a client and
3197 cdr is the list of answers??")
3199 (defvar p4-jobs-completion-cache nil
3200 "Cache for `p4-depot-completion'.
3201 It is a list of lists whose car is a client and
3202 cdr is the list of answers??")
3204 (defvar p4-labels-history nil
3205 "History for p4 clients.")
3207 (defvar p4-labels-completion-cache nil
3208 "Cache for `p4-depot-completion'.
3209 It is a list of lists whose car is a client and
3210 cdr is the list of answers??")
3212 (defvar p4-users-completion-cache nil
3213 "Cache for `p4-depot-completion'.
3214 It is a list of lists whose car is a client and
3215 cdr is the list of answers??")
3217 (defvar p4-arg-string-history nil
3218 "History for p4 command arguments")
3220 (defun p4-depot-completion-search (filespec cmd)
3221 "Look into `p4-depot-completion-cache' for filespec.
3222 Filespec is the candidate for completion, so the
3223 exact file specification is \"filespec*\".
3225 If found in cache, return a list whose car is FILESPEC and cdr is the list
3227 If not found in cache, return nil.
3228 So the 'no match' answer is different from 'not in cache'."
3230 ((equal cmd "branches") p4-branches-completion-cache)
3231 ((equal cmd "clients") p4-clients-completion-cache)
3232 ((equal cmd "dirs") p4-depot-completion-cache)
3233 ((equal cmd "jobs") p4-jobs-completion-cache)
3234 ((equal cmd "labels") p4-labels-completion-cache)
3235 ((equal cmd "users") p4-users-completion-cache)))
3238 (if (and p4-cleanup-cache (not p4-timer))
3239 (setq p4-timer (cond (p4-running-emacs
3240 (run-at-time p4-cleanup-time nil
3243 (add-timeout p4-cleanup-time 'p4-cache-cleanup
3246 (if (string-match (concat "^" (car (car l)) "[^/]*$") filespec)
3248 ;; filespec is included in cache
3249 (if (string= (car (car l)) filespec)
3250 (setq list (cdr (car l)))
3251 (setq dir (cdr (car l)))
3253 (if (string-match (concat "^" filespec) (car dir))
3254 (setq list (cons (car dir) list)))
3255 (setq dir (cdr dir))))
3257 list (cons filespec list))))
3261 (defun p4-cache-cleanup (&optional arg)
3262 "Cleanup all the completion caches."
3263 (message "Cleaning up the p4 caches ...")
3264 (setq p4-branches-completion-cache nil)
3265 (setq p4-clients-completion-cache nil)
3266 (setq p4-depot-completion-cache nil)
3267 (setq p4-jobs-completion-cache nil)
3268 (setq p4-labels-completion-cache nil)
3269 (setq p4-users-completion-cache nil)
3270 (if (and p4-running-emacs (timerp p4-timer)) (cancel-timer p4-timer))
3271 (if (and p4-running-xemacs p4-timer) (disable-timeout p4-timer))
3273 (message "Cleaning up the p4 caches ... done."))
3275 (defun p4-partial-cache-cleanup (type)
3276 "Cleanup a specific completion cache."
3277 (cond ((string= type "branch")
3278 (setq p4-branches-completion-cache nil))
3279 ((string= type "client")
3280 (setq p4-clients-completion-cache nil))
3281 ((or (string= type "submit") (string= type "change"))
3282 (setq p4-depot-completion-cache nil))
3283 ((string= type "job")
3284 (setq p4-jobs-completion-cache nil))
3285 ((string= type "label")
3286 (setq p4-labels-completion-cache nil))
3287 ((string= type "user")
3288 (setq p4-users-completion-cache nil))))
3290 (defun p4-read-depot-output (buffer &optional regexp)
3291 "Reads first line of BUFFER and returns it.
3292 Read lines are deleted from buffer.
3294 If optional REGEXP is passed in, return the substring of the first line that
3295 matched the REGEXP."
3299 (goto-char (point-min))
3302 (let ((line (buffer-substring (point-min) (point))))
3303 (if (string= line "")
3305 (delete-region (point-min) (point))
3306 (if (and regexp (string-match regexp line))
3307 (setq line (substring line (match-beginning 1) (match-end 1))))
3309 ;; remove trailing newline
3310 (if (equal (substring line (1- (length line)) (length line)) "\n")
3311 (substring line 0 (1- (length line)))
3314 (defun p4-completion-helper (filespec cmd var regexp)
3315 (let (output-buffer line list)
3316 (message "Making %s completion list..." cmd)
3317 (setq output-buffer (p4-depot-output cmd))
3318 (while (setq line (p4-read-depot-output
3319 output-buffer regexp))
3321 (setq list (cons line list))))
3322 (kill-buffer output-buffer)
3324 (cons (cons filespec list) (eval var)))
3327 (defun p4-depot-completion-build (filespec cmd)
3328 "Ask Perforce for a list of files and directories beginning with FILESPEC."
3329 (let (output-buffer line list)
3331 ((equal cmd "branches")
3332 (setq list (p4-completion-helper
3333 filespec cmd 'p4-branches-completion-cache
3334 "^Branch \\([^ \n]*\\) [0-9][0-9][0-9][0-9]/.*$")))
3335 ((equal cmd "clients")
3336 (setq list (p4-completion-helper
3337 filespec cmd 'p4-clients-completion-cache
3338 "^Client \\([^ \n]*\\) [0-9][0-9][0-9][0-9]/.*$")))
3341 (message "Making p4 completion list...")
3342 (setq output-buffer (p4-depot-output cmd
3343 (list (concat filespec "*"))))
3344 (while (setq line (p4-read-depot-output output-buffer))
3345 (if (not (string-match "no such file" line))
3346 (setq list (cons (concat line "/") list))))
3347 (kill-buffer output-buffer)
3348 (setq output-buffer (p4-depot-output "files"
3349 (list (concat filespec "*"))))
3350 (while (setq line (p4-read-depot-output output-buffer))
3351 (if (string-match "^\\(.+\\)#[0-9]+ - " line)
3352 (setq list (cons (match-string 1 line) list))))
3353 (kill-buffer output-buffer)
3354 (setq p4-depot-completion-cache
3355 (cons (cons filespec list) p4-depot-completion-cache)))
3358 (setq list (p4-completion-helper
3359 filespec cmd 'p4-jobs-completion-cache
3360 "\\([^ \n]*\\) on [0-9][0-9][0-9][0-9]/.*$")))
3361 ((equal cmd "labels")
3362 (setq list (p4-completion-helper
3363 filespec cmd 'p4-labels-completion-cache
3364 "^Label \\([^ \n]*\\) [0-9][0-9][0-9][0-9]/.*$")))
3365 ((equal cmd "users")
3366 (setq list (p4-completion-helper
3367 filespec cmd 'p4-users-completion-cache
3368 "^\\([^ ]+\\).*$"))))
3370 (cons filespec list)))
3372 (defun p4-completion-builder (type)
3373 `(lambda (string predicate action)
3374 ,(concat "Completion function for Perforce " type ".
3376 Using the mouse in completion buffer on a client will select it
3377 and exit, unlike standard selection. This is because
3378 `choose-completion-string' (in simple.el) has a special code for
3379 file name selection.")
3381 ,(if (string= type "dirs")
3382 ;; when testing for an exact match, remove trailing /
3383 `(if (and (eq action 'lambda)
3384 (eq (aref string (1- (length string))) ?/))
3385 (setq string (substring string 0 (1- (length string))))))
3387 ;; First, look in cache
3388 (setq list (p4-depot-completion-search string ,type))
3390 ;; If not found in cache, build list.
3392 (setq list (p4-depot-completion-build string ,type)))
3397 (try-completion string (mapcar 'list (cdr list)) predicate))
3401 (all-completions string (mapcar 'list (cdr list)) predicate)))
3402 ,(if (string= type "dirs")
3403 `(setq lst (mapcar (lambda (s)
3404 (if (string-match ".*/\\(.+\\)" s)
3409 ;; Test for an exact match
3411 (and (>= (length list) 2)
3412 (member (car list) (cdr list))))))))
3414 (defalias 'p4-branches-completion (p4-completion-builder "branches"))
3415 (defalias 'p4-clients-completion (p4-completion-builder "clients"))
3416 (defalias 'p4-depot-completion (p4-completion-builder "dirs"))
3417 (defalias 'p4-jobs-completion (p4-completion-builder "jobs"))
3418 (defalias 'p4-labels-completion (p4-completion-builder "labels"))
3419 (defalias 'p4-users-completion (p4-completion-builder "users"))
3422 (defun p4-read-arg-string (prompt &optional initial type)
3423 (let ((minibuffer-local-completion-map
3424 (copy-keymap minibuffer-local-completion-map)))
3425 (define-key minibuffer-local-completion-map " " 'self-insert-command)
3426 (completing-read prompt
3428 'p4-arg-string-completion)
3429 ((string= type "branch")
3430 'p4-branch-string-completion)
3431 ((string= type "client")
3432 'p4-client-string-completion)
3433 ((string= type "label")
3434 'p4-label-string-completion)
3435 ((string= type "job")
3436 'p4-job-string-completion)
3437 ((string= type "user")
3438 'p4-user-string-completion))
3440 initial 'p4-arg-string-history)))
3442 (defun p4-arg-string-completion (string predicate action)
3443 (let ((first-part "") completion)
3444 (if (string-match "^\\(.* +\\)\\(.*\\)" string)
3446 (setq first-part (match-string 1 string))
3447 (setq string (match-string 2 string))))
3448 (cond ((string-match "-b +$" first-part)
3449 (setq completion (p4-branches-completion string predicate action)))
3450 ((string-match "-t +$" first-part)
3451 (setq completion (p4-list-completion
3452 string (list "text " "xtext " "binary "
3453 "xbinary " "symlink ")
3455 ((string-match "-j +$" first-part)
3456 (setq completion (p4-jobs-completion string predicate action)))
3457 ((string-match "-l +$" first-part)
3458 (setq completion (p4-labels-completion string predicate action)))
3459 ((string-match "\\(.*status=\\)\\(.*\\)" string)
3460 (setq first-part (concat first-part (match-string 1 string)))
3461 (setq string (match-string 2 string))
3462 (setq completion (p4-list-completion
3463 string (list "open " "closed " "suspended ")
3465 ((or (string-match "\\(.*@.+,\\)\\(.*\\)" string)
3466 (string-match "\\(.*@\\)\\(.*\\)" string))
3467 (setq first-part (concat first-part (match-string 1 string)))
3468 (setq string (match-string 2 string))
3469 (setq completion (p4-labels-completion string predicate action)))
3470 ((string-match "\\(.*#\\)\\(.*\\)" string)
3471 (setq first-part (concat first-part (match-string 1 string)))
3472 (setq string (match-string 2 string))
3473 (setq completion (p4-list-completion
3474 string (list "none" "head" "have")
3476 ((string-match "^//" string)
3477 (setq completion (p4-depot-completion string predicate action)))
3478 ((string-match "\\(^-\\)\\(.*\\)" string)
3479 (setq first-part (concat first-part (match-string 1 string)))
3480 (setq string (match-string 2 string))
3481 (setq completion (p4-list-completion
3482 string (list "a " "af " "am " "as " "at " "ay "
3483 "b " "c " "d " "dc " "dn "
3484 "ds " "du " "e " "f " "i " "j "
3485 "l " "m " "n " "q " "r " "s " "sa "
3486 "sd " "se " "sr " "t " "v ")
3489 (setq completion (p4-file-name-completion string
3490 predicate action))))
3491 (cond ((null action) ;; try-completion
3492 (if (stringp completion)
3493 (concat first-part completion)
3495 ((eq action t) ;; all-completions
3500 (defun p4-list-completion (string lst predicate action)
3501 (let ((collection (mapcar 'list lst)))
3503 (try-completion string collection predicate))
3505 (all-completions string collection predicate))
3507 (eq (try-completion string collection predicate) t)))))
3509 (defun p4-file-name-completion (string predicate action)
3510 (if (string-match "//\\(.*\\)" string)
3511 (setq string (concat "/" (match-string 1 string))))
3512 (setq string (substitute-in-file-name string))
3513 (setq string (p4-follow-link-name (expand-file-name string)))
3514 (let ((dir-path "") completion)
3515 (if (string-match "^\\(.*[/\\]\\)\\(.*\\)" string)
3517 (setq dir-path (match-string 1 string))
3518 (setq string (match-string 2 string))))
3520 (setq completion (file-name-completion string dir-path))
3521 (if (stringp completion)
3522 (concat dir-path completion)
3525 (file-name-all-completions string dir-path))
3527 (eq (file-name-completion string dir-path) t)))))
3529 (defun p4-string-completion-builder (completion-function)
3530 `(lambda (string predicate action)
3531 (let ((first-part "") completion)
3532 (if (string-match "^\\(.* +\\)\\(.*\\)" string)
3534 (setq first-part (match-string 1 string))
3535 (setq string (match-string 2 string))))
3536 (cond ((string-match "^-" string)
3537 (setq completion nil))
3540 (,completion-function string predicate action))))
3541 (cond ((null action);; try-completion
3542 (if (stringp completion)
3543 (concat first-part completion)
3545 ((eq action t);; all-completions
3550 (defalias 'p4-branch-string-completion (p4-string-completion-builder
3551 'p4-branches-completion))
3553 (defalias 'p4-client-string-completion (p4-string-completion-builder
3554 'p4-clients-completion))
3556 (defalias 'p4-job-string-completion (p4-string-completion-builder
3557 'p4-jobs-completion))
3559 (defalias 'p4-label-string-completion (p4-string-completion-builder
3560 'p4-labels-completion))
3562 (defalias 'p4-user-string-completion (p4-string-completion-builder
3563 'p4-users-completion))
3565 (defun p4-depot-find-file (file)
3566 (interactive (list (completing-read "Enter filespec: "
3567 'p4-depot-completion
3569 p4-default-depot-completion-prefix
3570 'p4-depot-filespec-history)))
3571 (let ((lfile (cdar (p4-map-depot-files (list file)))))
3574 (if (get-file-buffer file)
3575 (switch-to-buffer-other-window file)
3576 (get-buffer-create file)
3578 (p4-noinput-buffer-action "print" nil t (list file))
3579 (p4-activate-print-buffer file t)))))
3582 ;; A function to get the current P4 client name
3583 (defun p4-get-client-name ()
3584 "To get the current value of the environment variable P4CLIENT,
3585 type \\[p4-get-client-name].
3587 This will be the current client that is in use for access through
3590 (let ((client (p4-current-client)))
3591 (message "P4CLIENT [local: %s], [global: %s]" client (getenv "P4CLIENT"))
3594 (defun p4-get-config-info (file-name token)
3595 (let ((output-buffer (generate-new-buffer p4-output-buffer-name))
3596 (data (getenv token)))
3598 (set-buffer output-buffer)
3599 (goto-char (point-min))
3600 (insert-file-contents file-name)
3601 (goto-char (point-min))
3602 (if (re-search-forward (concat "^" (regexp-quote token) "=\\(.*\\)")
3604 (setq data (match-string 1))))
3605 (kill-buffer output-buffer)
3608 (defun p4-current-client ()
3609 "Get the current local client, or the global client, if that."
3610 (let ((p4-config-file (p4-find-p4-config-file))
3612 (if (not p4-config-file)
3613 (setq cur-client (getenv "P4CLIENT"))
3614 (setq cur-client (p4-get-config-info p4-config-file "P4CLIENT")))
3615 (if (not cur-client)
3617 (get-buffer-create p4-output-buffer-name)
3618 (set-buffer p4-output-buffer-name)
3619 (goto-char (point-max))
3621 (if (zerop (p4-call-p4-here "info"))
3624 (if (re-search-forward "^Client name:[ \t]+\\(.*\\)$" nil t)
3625 (setq cur-client (match-string 1)))
3626 (delete-region pmin (point-max))))))
3629 (defun p4-current-server-port ()
3630 "Get the current local server:port address, or the global server:port, if
3632 (let ((p4-config-file (p4-find-p4-config-file)))
3633 (if (not p4-config-file)
3635 (p4-get-config-info p4-config-file "P4PORT"))))
3637 (defun p4-save-opened-files ()
3638 (get-buffer-create p4-output-buffer-name);; We do these two lines
3639 (kill-buffer p4-output-buffer-name) ;; to ensure no duplicates
3640 (let ((output-buffer (p4-depot-output "opened"))
3643 (set-buffer output-buffer)
3644 (goto-char (point-min))
3645 (while (re-search-forward "^\\(.*\\)#[0-9]+ - " nil t)
3646 (setq opened (cons (match-string 1) opened))))
3647 (kill-buffer output-buffer)
3648 (setq opened (mapcar 'cdr (p4-map-depot-files opened)))
3649 (save-window-excursion
3653 (and (buffer-modified-p buffer)
3654 (not (buffer-base-buffer buffer))
3655 (buffer-file-name buffer)
3656 (member (buffer-file-name buffer) opened)
3657 (format "Save file %s? "
3658 (buffer-file-name buffer)))))
3664 '("buffer" "buffers" "save")))))
3666 (defun p4-empty-diff-p ()
3667 "Return t if there exists a file opened for edit with an empty diff"
3668 (let ((buffer (get-buffer-create "p4-edp-buf"))
3670 (p4-exec-p4 buffer (list "opened") t)
3673 (goto-char (point-min))
3674 (while (re-search-forward "^\\(.*\\)#[0-9]* - edit.*" nil t)
3675 (setq opened (cons (match-string 1) opened))))
3678 (p4-exec-p4 buffer (list "diff") t)
3681 (goto-char (point-max))
3683 (goto-char (point-min))
3684 (while (re-search-forward "^==== \\([^#\n]+\\)#.*\n====" nil t)
3685 (if (member (match-string 1) opened)
3688 (goto-char (point-max))))))))
3689 (kill-buffer buffer)