;;; p4.el --- Simple Perforce-Emacs Integration
;;
;; $Id: //guest/sandy_currier/utils/p4.el#1 $
;;; Commentary:
;;
;; Applied the GNU G.P.L. to this file - rv 3/27/1997
;; Programs for Emacs <-> Perforce Integration.
;; Copyright (C) 1996, 1997 Eric Promislow
;; Copyright (C) 1997, 1998 Rajesh Vaidheeswarran
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this program; if not, write to the Free Software
;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;
;; If you have any problems to report, or suggestions, please send them
;; to rv@fore.com
;; LCD Archive Entry:
;; p4|Rajesh Vaidheeswarran|rv@fore.com|
;; P4 SCM Integration into Emacs/XEmacs|
;; 1998/08/07|3.8|not_assigned_yet|
;;
;; WARNING:
;; --------
;;
;; % p4 edit foo.c
;; ... make changes to foo.c in emacs
;; % p4 submit
;; ... keep the writable copy of foo.c in emacs. Start making changes
;; to it. Discover that you can't save it. If you do M-x:p4-edit,
;; you'll lose your changes. You need to do a 'p4 edit' at the
;; command-line.
;;
;; Original Functions: (Contributed by Eric Promislow)
;; p4-exec-p4 (not exported)
;; p4-buffer-action (not exported)
;; p4-edit
;; p4-revert
;; p4-diff
;; Modified Original Functions
;; p4-buffer-action - modified to support optional args to be passed
;; p4-edit - added help
;; p4-revert - added help
;; p4-diff - added help
;; Added Functions:
;; p4-add
;; p4-delete
;; p4-diff2
;; p4-filelog
;; p4-files
;; p4-refresh
;; p4-noinput-exec-p4 (not exported)
;; p4-noinput-buffer-action (not exported)
;; p4-get
;; p4-have
;; p4-help
;; p4-info
;; p4-integ
;; p4-opened
;; p4-users
;; p4-where
;; p4-async-commands (not exported) (deprecated)
;; p4-change
;; p4-client
;; p4-submit
;; p4-user
;; p4-get-client-name
;; p4-set-client-name
;; p4-find-file-hook (not exported)
;; p4-is-vc (not exported)
;; p4-check-mode (not exported)
;; p4-menu-add
;; NOTES:
;; ------
;;
;; It is best if you take this file and byte compile it. To do that, you
;; need to do the following:
;;
;; % emacs -batch -f batch-byte-compile /full/path/to/file/p4.el
;;
;; This creates a binary file p4.elc in the path. Add the path to your
;; load-path variable in .emacs like this:
;;
;; (setq load-path (cons "/full/path/to/file" load-path))
;;
;; Then add the library like this:
;;
;; (load-library "p4")
;;
;;; Code:
;; We need to remap C-x C-q to p4-toggle-read-only, so, make sure that we
;; load vc first.. or else, when vc gets autoloaded, it will remap C-x C-q
;; to vc-toggle-read-only.
(require 'vc)
(defvar p4-emacs-version "4.1" "The Current P4-Emacs Integration Revision.")
;; Find out what type of emacs we are running in. We will be using this
;; quite a few times in this program.
(defvar p4-running-emacs nil
"If the current Emacs is not XEmacs, then, this is non-nil.")
(defvar p4-running-xemacs nil
"If the current Emacs is XEmacs/Lucid, then, this is non-nil.")
(if (string-match "XEmacs\\|Lucid" emacs-version)
(setq p4-running-xemacs t)
(setq p4-running-emacs t))
;; This can be set to wherever 'p4' lies using p4-set-p4-executable
(defvar p4-executable
"/usr/local/bin/p4"
"This is the p4 executable.
To set this, use the function `p4-set-p4-executable'")
;; Set the null device
(defvar p4-null-device
(if (memq system-type '(ms-dos windows-nt)) "NUL" "/dev/null")
"Filesystem null device.")
(defvar p4-output-buffer-name "*P4 Output*" "P4 Output Buffer.")
(defvar p4-global-clt (getenv "P4CLIENT") "The P4 Client in use.")
;; Set this variable in .emacs if you want p4-set-client-name to complete
;; your client name for you.
(defvar p4-my-clients nil
"This variable holds the alist of p4 clients that the function
`p4-set-client-name' can complete on.
This is a alist, and should be set using the function
`p4-set-my-clients'. For example, in your .emacs:
\(load-library \"p4\"\)
\(p4-set-my-clients \'(client1 client2 client3)\)")
;; Set this variable in .emacs if you want to alter the completion
;; behavior of p4-set-client-name.
(defvar p4-strict-complete t
"Set this variable in .emacs if you want to alter the completion behavior
of `p4-set-client-name'.
This is a boolean and should be set to either t or nil. (default is t).")
(defvar p4-old-p4-port (getenv "P4PORT") "The P4 Server/Port in use.")
(if (eq p4-old-p4-port nil)
(progn
(setq p4-old-p4-port "perforce:1666") ;; Default P4 port.
(setenv "P4PORT" p4-old-p4-port)))
(defvar p4-old-notify-list (getenv "P4NOTIFY") "The P4 Notify List.")
(defvar p4-notify-list (getenv "P4NOTIFY") "The P4 Notify List.")
(defvar p4-sendmail-program (if (boundp 'sendmail-program)
sendmail-program
nil)
"The sendmail program. To set this use `p4-set-sendmail-program'.")
(defvar p4-user-email (if (boundp 'user-mail-address)
user-mail-address
nil)
"The e-mail address of the current user. This is used with the notification
system, and must be set if notification should take place. To set this use
`p4-set-user-email'.")
(defvar p4-notify nil
"If this is t then the users in the notification list set by
`p4-set-notify-list' will get a notification of any P4 change submitted from
within emacs.")
;; This can be set with p4-toggle-vc-mode
(defvar p4-do-find-file t
"If non-nil, the `p4-find-file-hook' will run when opening files.")
;; Now add a hook to find-file-hooks
(add-hook 'find-file-hooks 'p4-find-file-hook)
;; Tell Emacs about this new kind of minor mode
(make-variable-buffer-local 'p4-mode)
(put 'p4-mode 'permanent-local t)
(if (not (assoc 'p4-mode minor-mode-alist))
(setq minor-mode-alist (cons '(p4-mode p4-mode)
minor-mode-alist)))
(defvar p4-minor-mode nil
"The minor mode for editing p4 asynchronous command buffers.")
(make-variable-buffer-local 'p4-minor-mode)
(setq p4-minor-map (make-keymap))
(fset 'p4-minor-map p4-minor-map)
(or (assoc 'p4-minor-mode minor-mode-alist)
(setq minor-mode-alist
(cons '(p4-minor-mode " P4") minor-mode-alist)))
(or (assoc 'p4-minor-mode minor-mode-map-alist)
(setq minor-mode-map-alist
(cons '(p4-minor-mode . p4-minor-map) minor-mode-map-alist)))
(put 'p4-current-command 'permanent-local t)
(set-default 'p4-current-command nil)
;; To check if the current buffer's modeline and menu need to be altered
(make-variable-buffer-local 'p4-vc-check)
(put 'p4-vc-check 'permanent-local t)
(set-default 'p4-vc-check nil)
;;; All functions start here.
;; A generic function that we use to execute p4 commands
(defun p4-exec-p4 (output-buffer args
&optional clear-output-buffer)
"Internal function called by various p4 commands."
(save-excursion
(if clear-output-buffer
(progn
(set-buffer output-buffer)
(delete-region (point-min) (point-max))))
(apply 'call-process p4-executable p4-null-device
output-buffer
nil ; update display?
args)))
;; A generic function to take action of a buffer for interactive p4 commands
(defun p4-buffer-action (cmd do-revert
show-output
&optional argument
&optional argument1)
"Internal function called by various p4 commands."
(save-excursion
(if (not (stringp cmd))
(error "p4-buffer-action: Command not a string."))
(save-excursion
(if (and argument argument1)
(p4-exec-p4 (get-buffer-create p4-output-buffer-name)
(list cmd argument argument1) t)
(p4-exec-p4 (get-buffer-create p4-output-buffer-name)
(list cmd (buffer-file-name)) t)))
(if do-revert (revert-buffer t t))
(if show-output
(progn
(delete-other-windows)
(display-buffer p4-output-buffer-name t)))))
;; We use the noinput version for commands like p4 opened, p4 get etc.
;; which don't take any input file name.
(defun p4-noinput-buffer-action (cmd
do-revert
show-output
&optional argument)
"Internal function called by various p4 commands."
(save-excursion
(if (not (stringp cmd))
(error "p4-noinput-buffer-action: Command not a string."))
(save-excursion
(if argument
(p4-exec-p4 (get-buffer-create p4-output-buffer-name)
(append (list cmd) argument) t)
(p4-exec-p4 (get-buffer-create p4-output-buffer-name)
(list cmd) t)))
(if do-revert (revert-buffer t t))
(if show-output
(progn
(delete-other-windows)
(display-buffer p4-output-buffer-name t)))))
;; The p4 edit command
(defun p4-edit (show-output)
"To open the current depot file for edit, type \\[p4-edit].
Open or re-open an existing file for edit.
If file is already open for edit or delete then it is reopened
for edit and moved into the specified change number (or 'default'
change if no change number is given.)
If -t type is given the file is explicitly opened as the specified
file type, which may be text, ltext, xtext, binary, xbinary, ktext
kxtext, symlink, or resource. Not all types are supported on all
operating systems. See the Users' Guide for a description of file
types. If no type is specified, the type is determined automatically
by examination of the file's contents and execution permission bits.
Argument SHOW-OUTPUT displays the *P4 Output* buffer on executing the
command if t."
(interactive "P")
(if current-prefix-arg
(let ((args ""))
(progn
(setq args (read-string "Optional Args: "))
(p4-buffer-action "edit" t show-output args buffer-file-name)))
(p4-buffer-action "edit" t show-output ))
(p4-check-mode))
;; The p4 revert command
(defun p4-revert (show-output)
"Revert all change in the current file.
Argument SHOW-OUTPUT displays the *P4 Output* buffer on executing the
command if t."
(interactive "P")
(if (yes-or-no-p "Really revert changes? ")
(p4-buffer-action "revert" t show-output))
(p4-check-mode))
;; The p4 diff command
(defun p4-diff ()
"To diff the current file and topmost depot version, type \\[p4-diff].
Run diff (on the client) of a client file against the corresponding
revision in the depot. The file is only compared if the file is
opened for edit or the revision provided with the file argument is
not the same as the revision had by the client.
If no file argument is given, diff all open files.
This can be used to view pending changes.
The -f flag forces a diff for every file, regardless of whether
they are opened or if the client has the named revision.
This can be used to verify the client contents.
The -s flag outputs reduces the output of diff to the names of files
satisfying the following criteria:
-sa Opened files that are different than the revision
in the depot, or missing.
-sd Unopened files that are missing on the client.
-se Unopened files that are different than the revision
in the depot.
-sr Opened files that are the same as the revision in the
depot."
(interactive)
(if current-prefix-arg
(let ((args ""))
(progn
(setq args (read-string "Optional Args: "))
(p4-buffer-action "diff" nil t args buffer-file-name)))
(p4-buffer-action "diff" nil t )))
;; The p4 diff2 command
(defun p4-diff2 (version1 version2)
"Display diff of two depot files.
When visiting a depot file, type \\[p4-diff2] and
enter the versions.
Example: (find-file \"/us/rv/tag/main/Construct\")
\\[p4-diff2] <RET>
First Version to diff: 113
Second Version to diff: 100
Will produce the diff between the two versions in the
output buffer *P4 Output*
Run diff (on the server) of two files in the depot. Both files
may optionally include a revision specification; the default is
to compare the head revision. Wildcards may be used, but they
must match between file1 and file2 and they must be escaped from
the user's shell by quoting or with backslashes (\).
The -d flag allows you to pass flags to the underlying diff
program.
-dc passes the -c (context diff) flag.
-dn passes the the -n (rcs diff) flag.
Other diff flags are not supported.
Argument VERSION1 First Version to use.
Argument VERSION2 Second Version to use."
(interactive
"sFirst Depot File or Version# to diff: \nsSecond Depot File or Version# to diff: ")
(let ((p4-diff-version1 p4-vc-check)
(p4-diff-version2 p4-vc-check))
;; try to find out if this is a revision number, or a depot file
(cond ((string-match "[0-9]+" version1)
;; this is a revision of the current file
(setq p4-diff-version1 (concat (buffer-file-name) "#" version1)))
(t
;; this is default.. any random file or a depot file
(setq p4-diff-version1 version1)))
(cond ((string-match "[0-9]+" version2)
;; this is a revision of the current file
(setq p4-diff-version2 (concat (buffer-file-name) "#" version2)))
(t
;; this is default.. any random file or a depot file
(setq p4-diff-version2 version2)))
(p4-buffer-action "diff2" nil t p4-diff-version1 p4-diff-version2)))
;; p4-ediff for all those who diff using ediff
(defun p4-ediff ()
"Use ediff to compare file with its original client version."
(interactive)
(require 'ediff)
(p4-noinput-buffer-action "print" nil nil
(list "-q"
(concat (buffer-file-name) "#have")))
(let ((local (current-buffer))
(depot (get-buffer-create p4-output-buffer-name)))
(ediff-buffers local
depot
`((lambda ()
(make-local-variable 'ediff-cleanup-hook)
(setq ediff-cleanup-hook
(cons (lambda ()
(kill-buffer ,depot)
(p4-menu-add))
ediff-cleanup-hook)))))))
;; The p4 add command
(defun p4-add ()
"To add the current file to the depot, type \\[p4-add].
Optional arguments like '-tktext' can be passed as prefix arguments.
Open a new file for adding to the depot. If the file exists
on the client it is read to determine if it is text or binary.
If it does not exist it is assumed to be text. The file must
either not exist in the depot, or it must be deleted at the
current head revision. Files may be deleted and re-added arbitrarily.
If the -c flag is given the open files are associated with the
specified pending change number; otherwise the open files are
associated with the current 'default' change.
If file is already open it is moved into the specified pending
change. It is not permissible to reopen a file for add unless
it was already open for add.
If -t type is given the file is explicitly opened as the specified
file type, which may be text, ltext, xtext, binary, xbinary, ktext
kxtext, symlink, or resource. Not all types are supported on all
operating systems. See the Users' Guide for a description of file
types. If no type is specified, the type is determined automatically
by examination of the file's contents and execution permission bits."
(interactive)
(if (not (p4-is-vc))
(progn
(if current-prefix-arg
(let ((args ""))
(progn
(setq args (read-string "Optional Args: "))
(p4-buffer-action "add" nil t args buffer-file-name)))
(p4-buffer-action "add" nil t))
(p4-check-mode "Add"))
(message "%s already in depot client %s!" buffer-file-name
(getenv "P4CLIENT"))))
;; The p4 delete command
(defun p4-delete ()
"To delete the current file from the depot, type \\[p4-delete].
Opens a file that currently exists in the depot for deletion.
If the file is present on the client it is removed. If a pending
change number is given with the -c flag the opened file is associated
with that change, otherwise it is associated with the 'default'
pending change.
If file is already open it is reopened for delete and moved into
the specified pending change (or 'default' change if no change
number is given.)
Files that are deleted generally do not appear on the have list."
(interactive)
(if (yes-or-no-p "Really delete from depot? ")
(if (p4-is-vc)
(p4-buffer-action "delete" nil t))
(p4-check-mode)))
;; The p4 filelog command
(defun p4-filelog ()
"To view a history of the change made to the current file, type
\\[p4-filelog].
List the revision history of the files named, working backwards
from the latest revision to the most recent revision 'added'.
If file is given as a client file, the depot file last gotten is
listed. The -l flag produces long output with the full text of the
change descriptions."
(interactive)
(p4-buffer-action "filelog" nil t))
;; The p4 files command
(defun p4-files ()
"List files in the depot. Type, \\[p4-files].
Optional args [file ...] are passed as prefix arguments.
List files named or matching wild card specification. Display shows depot
file name, revision, file type, change action and change number of the
current head revision. If client file names are given as arguments the view
mapping is used to list the corresponding depot files."
(interactive)
(if current-prefix-arg
(let ((args 'nil))
(progn
(setq args (list (read-string "Optional Args: ")))
(p4-noinput-buffer-action "files" nil t args)))
(p4-buffer-action "files" nil t))
(set-buffer p4-output-buffer-name)
(goto-char (point-min))
(let (args max files p4-client-root p4-opened-buffer p4-cur-file ext)
(while (re-search-forward "^\\(//[^ #]*\\)" nil t)
(setq args (cons
(match-string 1)
args)))
(setq max (point-max))
(goto-char max)
(call-process p4-executable nil t nil "info")
(goto-char max)
(re-search-forward "^Client root: \\(.*\\)$")
(setq p4-client-root (match-string 1))
(if (memq system-type '(ms-dos windows-nt))
;; For Windows, since the client root will be terminated with a \ as
;; in c:\ or drive:\foo\bar\, we need to strip the trailing \ .
(let ((p4-clt-root-len (length p4-client-root)))
(setq p4-clt-root-len (- p4-clt-root-len 1))
(setq p4-client-root (substring p4-client-root 0 p4-clt-root-len))
))
(setq p4-opened-buffer (concat "*P4 Files: " p4-global-clt "*"))
(get-buffer-create p4-opened-buffer) ;; We do these two lines
(kill-buffer p4-opened-buffer) ;; to ensure no duplicates
(delete-region max (point-max))
(apply 'call-process
p4-executable nil t nil "where" (reverse args))
(goto-char max)
(while (re-search-forward
(concat "^\\([^ ]+\\) //" p4-global-clt "\\(.*\\)$") nil t)
(setq files (cons
(cons
(match-string 1)
(concat p4-client-root (match-string 2))) files)))
(delete-region max (point-max))
(goto-char (point-min))
(rename-buffer p4-opened-buffer t)
(while (re-search-forward "^\\(/[^#]+\\)#" nil t)
(setq p4-cur-file (cdr (assoc (match-string 1) files)))
(cond (p4-running-xemacs
(setq ext (make-extent (match-beginning 1) (match-end 1)))
(set-extent-face ext 'bold)
(set-extent-mouse-face ext 'highlight)
(set-extent-keymap ext p4-opened-map)
(set-extent-property ext 'filename p4-cur-file))
(p4-running-emacs
(put-text-property (match-beginning 1) (match-end 1)
'mouse-face 'highlight)
(put-text-property (match-beginning 1) (match-end 1)
'face 'bold)
(put-text-property (match-beginning 1) (match-end 1)
'filename
p4-cur-file)
(use-local-map p4-opened-map))
))
(toggle-read-only)))
;; The p4 refresh command
(defun p4-refresh ()
"Refresh the contents of an unopened file. \\[p4-refresh].
Optional args [file ...] are passed as prefix arguments.
Refresh replaces the files with their contents from the depot. Refresh only
refreshes unopened files; opened files must be reverted. This command
requires naming files explicitly."
(interactive)
(if current-prefix-arg
(let ((args 'nil))
(progn
(setq args (list (read-string "Optional Args: ")))
(p4-noinput-buffer-action "refresh" nil t args)))
(p4-buffer-action "refresh" nil t)))
;; The p4 get/sync command
(defun p4-get ()
"To synchronise the local view with the depot, type \\[p4-get].
Optional args [-n] [file ...] can be passed as prefix arguments.
Synchronize a client with its view for the files named, or for the entire
client if no files named. Get handles the case where files have been
updated in the depot and need to be brought up-to-date on the client as well
as the case when the view itself has changed.
Depot files in the clients view not currently gotten on the client will be
added. Client files that were gotten from the depot but that are no longer
in the clients view (or have been deleted in the depot) will be deleted from
the client. Client files that are still in the client view but which have
been updated in the depot are replaced by the needed revision from the
depot.
If file gives a revision specifier, then retrieve the revision so indicated.
The client view is used to map client file names to depot file names and
vice versa.
If -n is given show what revisions would need to be gotten to synchronize
the client with the view, but do not actually get the files. If no files
are named show the result for the entire client view."
(interactive)
(let ((args 'nil))
(if current-prefix-arg
(setq args (list (read-string "Optional Args: ")))
(setq args (list "")))
(p4-noinput-buffer-action "get" nil t args)))
;; The p4 have command
(defun p4-have ()
"To list revisions last gotten, type \\[p4-have].
Optional args [file ...] are passed as prefix arguments.
List revisions of named files that were last gotten from the depot. If no
file name is given list all files gotten on this client."
(interactive)
(let ((args 'nil))
(if current-prefix-arg
(setq args (list (read-string "Optional Args: ")))
(setq args (list "")))
(p4-noinput-buffer-action "have" nil t args)))
;; The p4 help command
(defun p4-help (arg)
"To print help message , type \\[p4-help].
Print a help message about command. If no command name is given print a
general help message about Perforce and give a list of available client
commands.
Argument ARG command for which help is needed."
(interactive "sHelp on which command: ")
(if (string< "" arg)
(p4-noinput-buffer-action "help" nil t (list arg))
(p4-noinput-buffer-action "help" nil t)))
;; The p4 info command
(defun p4-info ()
"To print out client/server information, type \\[p4-info].
Info dumps out what the server knows about the client (the user
name, the client name, and the client directory) and some server
information (the server's address, version, and license data)."
(interactive)
(p4-noinput-buffer-action "info" nil t))
;; The p4 integrate command
(defun p4-integ (branch)
"To schedule integrations between branches, type \\[p4-integ].
Optional args [-n -r] [-c change#] [file ...] are passed as prefix
arguments.
Integ determines what integrations are necessary between related files,
according to the branch named and its view. These integrations, represented
by a list of revisions of branch source files which need to be merged into
the related branch target file, are scheduled for later action. The actual
merge and any necessary conflict resolution is performed using the resolve
command. The -n flag displays what integrations would be necessary but does
not schedule them. A branch name is required.
If the -r flag is present, the mappings in the branch view are reversed,
with the target files and source files exchanging place.
If no file names are given then the entire branch view is examined for
needed integrations. Files that are not mapped in the client's view are
ignored. Files scheduled for integration are opened for the appropriate
action in the default change. If -c change# is given the files are opened
in the numbered pending change.
Argument BRANCH is the branch to integrate into."
(interactive "sP4 Branch Name: ")
(let ((args 'nil))
(if current-prefix-arg
(setq args (list "-b" branch (read-string "Optional Args: ")))
(setq args (list "-b" branch)))
(p4-noinput-buffer-action "integ" nil t args)))
;; For XEmacs, p4-opened will show the list of files and clicking the middle
;; mouse button will cause the file to be opened.
(defvar p4-opened-map
(let ((map (make-sparse-keymap)))
(cond (p4-running-xemacs
(define-key map [button2] 'p4-opened-mouse-find-file))
(p4-running-emacs
(define-key map [mouse-2] 'p4-opened-mouse-find-file)))
(define-key map [return] 'p4-opened-find-file)
(define-key map "q" 'p4-quit-current-buffer)
map)
"The key map to use for selecting opened files.")
(defun p4-opened-mouse-find-file (event)
(interactive "e")
(cond (p4-running-xemacs
(select-window (event-window event))
(p4-opened-find-file (event-point event)))
(p4-running-emacs
(select-window (posn-window (event-end event)))
(p4-opened-find-file (posn-point (event-start event))))))
(defun p4-opened-find-file (pnt)
(interactive "d")
(cond (p4-running-xemacs
(find-file-other-window
(extent-property (extent-at pnt) 'filename)))
(p4-running-emacs
(let ((p4-this-file (get-text-property pnt 'filename)))
(if (eq p4-this-file nil)
(error "There is no file at that cursor location!")
(find-file-other-window p4-this-file))))))
(defun p4-quit-current-buffer (pnt)
(interactive "d")
(if (not (one-window-p))
(delete-window)
(bury-buffer)))
;; The p4 opened command
(defun p4-opened ()
"To display list of files opened for pending change, type \\[p4-opened].
Optional args [-a] [file ...] are passed as prefix arguments.
Shows files currently opened for pending changes or indicates for the
specified individual files whether they are currently opened.
If no file names are given, all files open on the current client
are listed. The -a flag lists opened files in all clients."
(interactive)
(p4-noinput-buffer-action "opened" nil t)
(set-buffer p4-output-buffer-name)
(goto-char (point-min))
(let (args max files p4-client-root p4-opened-buffer p4-cur-file
p4-server-version ext)
(while (re-search-forward "^\\(//[^ #]*\\)" nil t)
(setq args (cons
(match-string 1)
args)))
(setq max (point-max))
(goto-char max)
(call-process p4-executable nil t nil "info")
(goto-char max)
(re-search-forward "^Client root: \\(.*\\)$")
(setq p4-client-root (match-string 1))
(re-search-forward
"^Server version: .*\/.*\/\\(\\([0-9]+\\)\.[0-9]+\\)\/.*(.*)$")
(setq p4-server-version (string-to-number (match-string 2)))
(if (memq system-type '(ms-dos windows-nt))
;; For Windows, since the client root will be terminated with a \ as
;; in c:\ or drive:\foo\bar\, we need to strip the trailing \ .
(let ((p4-clt-root-len (length p4-client-root)))
(setq p4-clt-root-len (- p4-clt-root-len 1))
(setq p4-client-root (substring p4-client-root 0 p4-clt-root-len))
))
(setq p4-opened-buffer (concat "*Opened Files: " p4-global-clt "*"))
(get-buffer-create p4-opened-buffer) ;; We do these two lines
(kill-buffer p4-opened-buffer) ;; to ensure no duplicates
(delete-region max (point-max))
(apply 'call-process
p4-executable nil t nil "where" (reverse args))
(goto-char max)
(if (< p4-server-version 98)
(progn
(while (re-search-forward
(concat "^\\([^ ]+\\) //" p4-global-clt "\\(.*\\)$") nil t)
(setq files (cons
(cons
(match-string 1)
(concat p4-client-root (match-string 2))) files))))
(progn
(while (re-search-forward
(concat "^\\([^ ]+\\) //\\([^ ]+\\) \\(.*\\)$") nil t)
(setq files (cons
(cons
(match-string 1) (match-string 3)) files)))))
(delete-region max (point-max))
(goto-char (point-min))
(rename-buffer p4-opened-buffer t)
(while (re-search-forward "^\\(/[^#]+\\)#" nil t)
(setq p4-cur-file (cdr (assoc (match-string 1) files)))
(cond (p4-running-xemacs
(setq ext (make-extent (match-beginning 1) (match-end 1)))
(set-extent-face ext 'bold)
(set-extent-mouse-face ext 'highlight)
(set-extent-keymap ext p4-opened-map)
(set-extent-property ext 'filename p4-cur-file))
(p4-running-emacs
(put-text-property (match-beginning 1) (match-end 1)
'mouse-face 'highlight)
(put-text-property (match-beginning 1) (match-end 1)
'face 'bold)
(put-text-property (match-beginning 1) (match-end 1)
'filename
p4-cur-file)
(use-local-map p4-opened-map))
))
(toggle-read-only)))
;; The p4 users command
(defun p4-users ()
"To display list of known users, type \\[p4-users].
Optional args [user ...] are passed as prefix arguments.
Reports the list of all users, or those users matching the argument,
currently known to the system. The report includes the last time
each user accessed the system."
(interactive)
(let ((args 'nil))
(if current-prefix-arg
(setq args (list (read-string "Optional Args: ")))
(setq args (list "")))
(p4-noinput-buffer-action "users" nil t args)))
;; The p4 where command
(defun p4-where ()
"To show how local file names map into depot names, type \\[p4-where].
Optional args [file ...] are passed as prefix arguments.
Where shows how the named files map through the client map
into the depot. If no file is given, the mapping for '...'
\(all files in the current directory and below\) is shown."
(interactive)
(let ((args 'nil))
(if current-prefix-arg
(setq args (list (read-string "Optional Args: ")))
(setq args (list "")))
(p4-noinput-buffer-action "where" nil t args)))
;; The following two commands have replaced the deprecated `p4-async-commands'
;; and are much more elegant since they don't depend on external editor
;; clients.
(defun p4-async-process-command (p4-this-command &optional
p4-regexp
p4-this-buffer
p4-out-command)
"Internal function to call an asynchronous process with a local buffer,
instead of calling an external client editor to run within emacs.
Arguments:
P4-THIS-COMMAND is the command that called this internal function.
P4-REGEXP is the optional regular expression to search for to set the cursor
on.
P4-THIS-BUFFER is the optional buffer to create. (Default is *P4 <command>*).
P4-OUT-COMMAND is the optional command that will be used as the command to
be called when `p4-async-call-process' is called."
(if p4-this-buffer
(set-buffer (get-buffer-create p4-this-buffer))
(set-buffer (get-buffer-create (concat "*P4 " p4-this-command "*"))))
(setq p4-current-command p4-this-command)
(call-process-region (point-min) (point-max) p4-executable
t t nil p4-current-command "-o")
(goto-char (point-min))
(insert (concat "# Created using " (p4-emacs-version) ".\n"
"# Type C-c C-c to submit changes and exit buffer.\n"
"# Type C-x k to kill current changes.\n"
"#\n"))
(if p4-regexp (re-search-forward p4-regexp))
(indented-text-mode)
(setq p4-minor-mode t)
(switch-to-buffer (current-buffer))
(if p4-out-command
(setq p4-current-command p4-out-command))
(define-key p4-minor-map "\C-c\C-c" 'p4-async-call-process)
(message "C-c C-c to finish editing and exit buffer."))
(defun p4-async-call-process ()
"Internal function called by `p4-async-process-command' to process the
buffer after editing is done using the minor mode key mapped to `C-c C-c'."
(interactive)
(message "p4 %s ..." p4-current-command)
(let ((max (point-max)) msg)
(goto-char max)
;; this looks like what actually calls the p4 command
;; looks like it places text in current buffer at end
(call-process-region (point-min) max p4-executable
nil '(t t) nil p4-current-command "-i")
;; move to the end of the buffer
(goto-char max)
;; this looks like it saves the output message
(setq msg (buffer-substring max (point-max)))
;; this deletes the output text from the current buffer
(delete-region max (point-max))
;; kill the current buffer (the p4 form?)
(kill-buffer nil)
;; Note: it seems that this is the point to refresh the
(revert-buffer t t nil)
;; stuff the above message into p4-output-buffer-name, saving current buffer state
(save-excursion
(set-buffer (get-buffer-create p4-output-buffer-name))
(delete-region (point-min) (point-max))
(insert msg))
;; display it
(display-buffer p4-output-buffer-name)
;; print a message
(message "p4 %s done." p4-current-command)
(if (and p4-notify (equal p4-current-command "submit"))
(p4-notify p4-notify-list))))
;; The p4 change command
(defun p4-change ()
"To edit the change specification, type \\[p4-change].
Optional args [-d | -o] [ change# ] passed as prefix arguments.
Creates a new change description with no argument or edit the
text description of an existing change if a change number is
given. To associate or remove files from a pending change use
the open commands (edit, add, delete) or revert.
The -d flag discards a pending change, but only if it has no
opened files and no pending fixes associated with it. Use 'opened -a'
to report on opened files and 'reopen' to move them to another
change. Use 'fixes -c change#' to report on pending fixes and
'fix -d -c change# jobs...' to delete pending fixes. The change
can only be deleted by the user and client who created it.
The -o flag causes the change specification to be written
to the standard output. The user's editor is not invoked.
The -i flag causes a change specification to be read from the
standard input. The user's editor is not invoked."
(interactive)
(p4-async-process-command "change" "Description:\n\t" "*P4 New Change*"))
;; The p4 client command
(defun p4-client ()
"To edit a client specification , type \\[p4-client].
With no argument client creates a new client view specification or
edits an existing client specification. The client name is taken
from the environment variable $P4CLIENT if set, or else from
the current host name. The specification form is put into a
temporary file and the editor (given by the environment variable
$EDITOR) is invoked. If a name is given, the specification of
the named client is displayed read-only.
The specification form contains the following fields:
Client: The client name (read only.)
Date: The date specification was last modified (read only.)
Description: A short description of the client (optional).
Root: The root directory of the client file workspace
(given in local file system syntax), under which all
client files will be placed. If you change this, you
must physically relocate any files as well.
View: What files you want to see from the depot and how
they map to locations on the client. The left hand
side specifies a depot path, which must begin with
//depot/. The right hand side gives the corresponding
client path, given in canonical Perforce file syntax.
On expansion to an actual local client file name the
initial //client/ is replaced by the Root value, given
above. You may use wildcards:
... matches any characters including
* matches any character except /
%1 to %9 like *, used to associate wild cards
Wildcarding must be congruent in both the client and
depot paths. You may have any number of view entries.
A new view takes effect on the next 'get'.
Normally, new clients are created with a default view that maps
all depot files onto the client. The -t flag uses the view from
the named template client as a default instead.
The -d flag causes the named client to be deleted.
The -o flag causes the named client specification to be written
to the standard output. The user's editor is not invoked.
The -i flag causes a client specification to be read from the
standard input. The user's editor is not invoked."
(interactive)
(p4-async-process-command "client"))
;; The p4 submit command
(defun p4-submit ()
"To submit a pending change to the depot, type \\[p4-submit].
Submit commits a pending change with its associated files to the depot.
With no argument submit sends the 'default' change. With the -c flag
the designated pending change is sent. Before committing the change
submit locks all associated files not already locked. If any file
cannot be locked the change is aborted. If submit is sending the
default change it first provides the user with a dialog similar to
'p4 change' so the user can compose a change description. In this
dialog the user is presented with the list of open files in change
'default'. Files may be deleted from this list but they cannot be
added. (Use an open command (open, edit, add, delete) to add
additional files to a change or to move files between changes.)
If the submit fails for any reason the files are left open in
a newly created pending change.
Submit is guaranteed to be atomic. Either all files will be
updated in the depot as a unit or none will be.
The -i flag causes a change specification to be read from the
standard input. The user's editor is not invoked."
(interactive)
(p4-async-process-command "change" "Description:\n\t"
"*P4 Submit*" "submit"))
;; The p4 user command
(defun p4-user ()
"To create or edit a user specification, type \\[p4-user].
Create a new user specification or edit an existing user
specification. The specification form is put into a temporary
file and the editor (given by the environment variable $EDITOR)
is invoked.
Normally, a user specification is created automatically the
first time the user invokes any client command that can update
the depot. The 'user' command is generally used to edit the
user's reviewing subscription list for change review.
The user specification form contains the following fields:
User: The user name (read only).
Email: The user's email address (user@client default).
Update: The date the specification was last modified (read only).
Access: The date the user last issued a client command.
FullName: The user's real name.
Reviews: The subscription list for change review. You may
use wildcards:
... matches any characters including
* matches any character except /
There may be any number of review lines.
The -d flag deletes the named user, but only if the user is not
the owner of any branches, clients, jobs, labels, or opened files.
The -o flag causes the named user specification to be written
to the standard output. The user's editor is not invoked.
The -i flag causes a user specification to be read from the
standard input. The user's editor is not invoked."
(interactive)
(p4-async-process-command "user"))
;; A function to get the current P4 client root to be used by various other
;; macros, if needed.
(defun p4-get-client-root (client-name)
"To get the current value of Client's root type \\[p4-get-client-root].
This can be used by any other macro that requires this value.
"
(interactive (list
(if (not (eq p4-my-clients nil))
(completing-read "Client: " p4-my-clients
nil nil p4-global-clt)
(let ((symbol (read-string "Client: "
p4-global-clt)))
(if (equal symbol "")
p4-global-clt
symbol))
)))
(let ((p4-client-root))
(save-excursion
(get-buffer-create p4-output-buffer-name)
(set-buffer p4-output-buffer-name)
(delete-region (point-min) (point-max))
(call-process p4-executable nil t nil "client" "-o" client-name)
(goto-char (point-min))
(re-search-forward "^Root:[ \t]+\\(.*\\)$")
(setq p4-client-root (match-string 1))
(message "Root of %s is %s" client-name p4-client-root)
p4-client-root)))
;; A function to get the current P4 client name
(defun p4-get-client-name ()
"To get the current value of the environment variable P4CLIENT,
type \\[p4-get-client-name].
This will be the current client that is in use for access through
Emacs P4."
(interactive)
(message "P4CLIENT is %s" (getenv "P4CLIENT")))
;; A function to set the current P4 client name
(defun p4-set-client-name (p4-new-client-name)
"To set the current value of P4CLIENT, type \\[p4-set-client-name].
This will change the current client from the previous client to the new
given value.
Setting this value to nil would disable P4 Version Checking.
`p4-set-client-name' will complete any client names set using the function
`p4-set-my-clients'. The strictness of completion will depend on the
variable `p4-strict-complete' (default is t).
Argument P4-NEW-CLIENT-NAME The new client to set to. The default value is
the current client."
(interactive (list
(if (not (eq p4-my-clients nil))
(completing-read "Change Client to: " p4-my-clients
nil p4-strict-complete p4-global-clt)
(let ((symbol (read-string "Change Client to: "
p4-global-clt)))
(if (equal symbol "")
p4-global-clt
symbol))
)))
(if (equal p4-new-client-name "nil")
(progn
(setenv "P4CLIENT" nil)
(message
"P4 Version check disabled. Set a valid client name to enable."))
(progn
(let ((foo (getenv "P4CLIENT")))
(setenv "P4CLIENT" p4-new-client-name)
(setq p4-global-clt p4-new-client-name)
(message "P4CLIENT changed from %s to %s" foo p4-new-client-name)))))
(defun p4-set-my-clients (client-list)
"To set the client completion list used by `p4-set-client-name', use
this function in your .emacs (or any lisp interaction buffer).
This will change the current client list from the previous list to the new
given value.
Setting this value to nil would disable client completion by
`p4-set-client-name'.
The strictness of completion will depend on the variable
`p4-strict-complete' (default is t).
Argument CLIENT-LIST is the 'list' of clients.
To set your clients using your .emacs, use the following:
\(load-library \"p4\"\)
\(p4-set-my-clients \'(client1 client2 client3)\)"
(setq p4-my-clients nil)
(let ((p4-tmp-client-var nil))
(while client-list
(setq p4-tmp-client-var (format "%s" (car client-list)))
(setq client-list (cdr client-list))
(setq p4-my-clients (append p4-my-clients
(list (list p4-tmp-client-var)))))))
;; A function to get the current P4PORT
(defun p4-get-p4-port ()
"To get the current value of the environment variable P4PORT, type \\[p4-get-p4-port].
This will be the current server/port that is in use for access through
Emacs P4."
(interactive)
(message "P4PORT is %s" (getenv "P4PORT")))
;; A function to set the current P4PORT
(defun p4-set-p4-port (p4-new-p4-port)
"To set the current value of P4PORT, type \\[p4-set-p4-port].
This will change the current server from the previous server to the new
given value.
Argument P4-NEW-P4-PORT The new server:port to set to. The default value is
the current value of P4PORT."
(interactive (list (let
((symbol (read-string "Change server:port to: "
p4-old-p4-port)))
(if (equal symbol "")
p4-old-p4-port
symbol))))
(if (equal p4-new-p4-port "nil")
(progn
(setenv "P4PORT" nil)
(message
"P4 Version check disabled. Set a valid P4PORT to enable."))
(progn
(let ((foo p4-old-p4-port))
(setenv "P4PORT" p4-new-p4-port)
(setq p4-old-p4-port p4-new-p4-port)
(message "P4PORT changed from %s to %s" foo p4-new-p4-port)))))
;; The find-file hook for p4.
(defun p4-find-file-hook ()
"To check while loading the file, if it is a P4 version controlled file."
(if (and (getenv "P4CLIENT") (getenv "P4PORT"))
(p4-check-mode)))
;; A function to check if the file being opened is version controlled by p4.
(defun p4-is-vc ()
"If a file is controlled by P4 then return version else return nil."
(save-excursion
(get-buffer-create p4-output-buffer-name)
(set-buffer p4-output-buffer-name)
(delete-region (point-min) (point-max)))
(if (zerop (call-process
p4-executable
nil
p4-output-buffer-name
nil
"files" buffer-file-name))
(progn
(save-excursion
(set-buffer p4-output-buffer-name)
(goto-char (point-min))
(if (re-search-forward "#[0-9]+" (point-max) t)
(substring (match-string 0) 1)
nil)))
nil))
;; set keymap. We use the C-x p Keymap for all perforce commands
(defvar p4-prefix-map (lookup-key global-map "\C-xp")
"The Prefix for P4 Library Commands.")
(if (not (keymapp p4-prefix-map))
(progn
(setq p4-prefix-map (make-sparse-keymap))
(define-key global-map "\C-xp" p4-prefix-map)
(define-key p4-prefix-map "a" 'p4-add)
(define-key p4-prefix-map "c" 'p4-client)
(define-key p4-prefix-map "d" 'p4-diff2)
(define-key p4-prefix-map "e" 'p4-edit)
(define-key p4-prefix-map "f" 'p4-filelog)
(define-key p4-prefix-map "g" 'p4-get-client-name)
(define-key p4-prefix-map "G" 'p4-get)
(define-key p4-prefix-map "h" 'p4-help)
(define-key p4-prefix-map "i" 'p4-info)
(define-key p4-prefix-map "n" 'p4-notify)
(define-key p4-prefix-map "N" 'p4-release-notes)
(define-key p4-prefix-map "o" 'p4-opened)
(define-key p4-prefix-map "P" 'p4-set-p4-port)
(define-key p4-prefix-map "r" 'p4-revert)
(define-key p4-prefix-map "R" 'p4-refresh)
(define-key p4-prefix-map "s" 'p4-set-client-name)
(define-key p4-prefix-map "S" 'p4-submit)
(define-key p4-prefix-map "t" 'p4-toggle-vc-mode)
(define-key p4-prefix-map "u" 'p4-user)
(define-key p4-prefix-map "v" 'p4-emacs-version)
(define-key p4-prefix-map "x" 'p4-delete)
(define-key p4-prefix-map "=" 'p4-diff)
(define-key p4-prefix-map "-" 'p4-ediff)
(define-key p4-prefix-map "?" 'p4-describe-bindings)))
;; For users interested in notifying a change, a notification list can be
;; set up using this function.
(defun p4-set-notify-list (p4-new-notify-list &optional p4-supress-stat)
"To set the current value of P4NOTIFY, type \\[p4-set-notify-list].
This will change the current notify list from the existing list to the new
given value.
An empty string will disable notification.
Argument P4-NEW-NOTIFY-LIST is new value of the notification list.
Optional argument P4-SUPRESS-STAT when t will suppress display of the status
message. "
(interactive (list (let
((symbol (read-string
"Change Notification List to: "
p4-notify-list)))
(if (equal symbol "")
nil
symbol))))
(setq p4-old-notify-list p4-notify-list)
(if p4-new-notify-list
(progn
(setenv "P4NOTIFY" p4-new-notify-list)
(setq p4-notify-list p4-new-notify-list)
(setq p4-notify t))
(progn
(setenv "P4NOTIFY" nil)
(setq p4-notify-list nil)
(setq p4-notify nil)))
(if (not p4-supress-stat)
(message "Notification list changed from '%s' to '%s'"
p4-old-notify-list p4-notify-list)))
;; To get the current notification list.
(defun p4-get-notify-list ()
"To get the current value of the environment variable P4NOTIFY, type \\[p4-get-notify-list].
This will be the current notification list that is in use for mailing
change notifications through Emacs P4."
(interactive)
(message "P4NOTIFY is %s" p4-notify-list))
(defun p4-notify (users)
"To notify a list of users of a change submission manually, type
\\[p4-notify].
To do auto-notification, set the notification list with `p4-set-notify-list'
and on each submission, the users in the list will be notified of the
change.
Since this uses the sendmail program, it is mandatory to set the correct
path to the sendmail program in the variable `p4-sendmail-program'.
Also, it is mandatory to set the user's email address in the variable
`p4-user-email'.
Argument USERS The users to notify to. The default value is the notification
list."
(interactive (list (let
((symbol (read-string "Notify whom? "
p4-notify-list)))
(if (equal symbol "")
nil
symbol))))
(p4-set-notify-list users t)
(if (not (and (eq p4-sendmail-program nil)
(eq p4-user-email nil)))
(p4-do-notify)
(message "%s"
"Please set p4-sendmail-program and p4-user-email variables.")))
(defun p4-do-notify ()
"This is the internal notification function called by `p4-notify'."
(save-excursion
(if (and p4-notify-list (not (equal p4-notify-list "")))
(progn
(save-excursion
(set-buffer (get-buffer-create p4-output-buffer-name))
(goto-char (point-min))
(if (re-search-forward "[0-9]+.*submitted" (point-max) t)
(progn
(let ((p4-matched-change 'nil))
(setq p4-matched-change (substring (match-string 0) 0 -10))
(set-buffer (get-buffer-create "*P4 Notify*"))
(kill-region (point-min) (point-max))
(call-process-region (point-min) (point-max)
p4-executable
t t nil "describe" "-s"
p4-matched-change)
(switch-to-buffer "*P4 Notify*")
(goto-char (point-min))
(let ((p4-chg-desc 'nil))
(if (re-search-forward "^Change.*$" (point-max) t)
(setq p4-chg-desc (match-string 0))
(setq p4-chg-desc (concat
"Notification of Change "
p4-matched-change)))
(goto-char (point-min))
(insert
"From: " p4-user-email "\n"
"To: P4 Notification Recipients:;\n"
"Subject: " p4-chg-desc "\n")
(call-process-region (point-min) (point-max)
p4-sendmail-program t t nil
"-odi" "-oi" p4-notify-list)
(kill-buffer nil))))
(progn
(save-excursion
(set-buffer (get-buffer-create p4-output-buffer-name))
(goto-char (point-max))
(insert "\np4-do-notify: No Change Submissions found."))))))
(progn
(save-excursion
(set-buffer (get-buffer-create p4-output-buffer-name))
(goto-char (point-max))
(insert "\np4-do-notify: Notification list not set."))))))
;; Function to return the current version.
(defun p4-emacs-version ()
"Return the current Emacs-P4 Integration version."
(interactive)
(cond (p4-running-xemacs
(message "XEmacs-P4 Integration v%s" p4-emacs-version))
(p4-running-emacs
(message "Emacs-P4 Integration v%s" p4-emacs-version))))
;; To set the path to the p4 executable
(defun p4-set-p4-executable (p4-exe-name)
"Set the path to the correct P4 Executable.
To set this as a part of the .emacs, add the following to your .emacs:
\(load-library \"p4\"\)
\(p4-set-p4-executable \"/my/path/to/p4\"\)
Argument P4-EXE-NAME The new value of the p4 executable, with full path."
(interactive "fFull path to your P4 executable: " )
(setq p4-executable p4-exe-name))
(defun p4-set-sendmail-program (p4-program)
"Set the path to the correct sendmail.
To set this as a part of the .emacs, add the following to your .emacs:
\(load-library \"p4\"\)
\(p4-set-sendmail-program \"/my/path/to/sendmail\"\)
Argument P4-PROGRAM The full path to sendmail."
(interactive "fFull path to the sendmail program: " )
(setq p4-sendmail-program p4-program))
(defun p4-set-user-email (p4-email-address)
"Set the correct user e-mail address to be used with the notification
system. This must be set for the notification to take place.
The default value is taken from the variable `user-mail-address', if it
exists. Otherwise, the value defaults to nil.
To set this as a part of the .emacs, add the following to your .emacs:
\(load-library \"p4\"\)
\(p4-set-user-email \"joe_user@somewhere.com\"\)
Argument P4-EMAIL-ADDRESS is the complete email address of the current
user."
(interactive "sEnter your e-mail address: ")
(setq p4-user-email p4-email-address))
(defun p4-check-mode (&optional args)
"Check to see whether we should export the menu map to this buffer.
Optional argument ARGS Used only by `p4-add', the `p4-mode' variable is set
to this instead of the value returned from `p4-is-vc'."
;; interactive because when switching perforce servers, or toggling
;; disconnected state, may need to re-check a file
(interactive)
(if p4-do-find-file
(progn
(if args
(setq p4-vc-check args)
(setq p4-vc-check (p4-is-vc)))
(if p4-vc-check
(progn
(p4-menu-add)
(setq p4-mode (concat " P4:" p4-vc-check)))
(setq p4-mode nil))
(p4-force-mode-line-update))))
;; Force mode line updation for different Emacs versions
(defun p4-force-mode-line-update ()
"To Force the mode line update for different flavors of Emacs."
(cond (p4-running-xemacs
(redraw-modeline))
(p4-running-emacs
(force-mode-line-update))))
;; In case, the P4 server is not available, or when operating off-line, the
;; p4-find-file-hook becomes a pain... this functions toggles the use of the
;; hook when opening files.
(defun p4-toggle-vc-mode ()
"In case, the P4 server is not available, or when working off-line, toggle
the VC check on/off when opening files."
(interactive)
(setq p4-do-find-file (not p4-do-find-file))
(if p4-do-find-file
(message "P4 mode check enabled.")
(message "P4 mode check disabled.")))
;; Wrap C-x C-q to allow p4-edit/revert and also to ensure that
;; we don't stomp on vc-toggle-read-only.
(defun p4-toggle-read-only (&optional verbose)
"If p4-mode is non-nil, \\[p4-toggle-read-only] toggles between `p4-edit'
and `p4-revert'.
If the current buffer's file is not under p4, then this function passes on
all the parameters to `vc-toggle-read-only'."
(interactive "P")
(if (and (boundp 'p4-mode) (not (eq p4-mode nil)))
(if buffer-read-only
(p4-edit verbose)
(p4-revert verbose))
(vc-toggle-read-only verbose)))
;; I separated the menu support for Emacs and XEmacs since they seemed
;; to differ considerably.
(cond (p4-running-xemacs
;; Menu Support for XEmacs
(require 'easymenu)
(defun p4-mode-menu (modestr)
(let ((m
'(["Add Current to P4" p4-add
(and buffer-file-name (not p4-mode))]
["Check out/Edit" p4-edit (and buffer-file-name
(and buffer-read-only p4-mode))]
["Show Opened Files" p4-opened p4-do-find-file]
["------------------" nil nil]
["Revert File" p4-revert (and buffer-file-name
(and (not buffer-read-only)
p4-mode))]
["Filelog" p4-filelog (and buffer-file-name p4-mode)]
["------------------" nil nil]
["Diff 2 Versions" p4-diff2 (and buffer-file-name
p4-mode)]
["Diff Current" p4-diff (and buffer-file-name
(and (not buffer-read-only)
p4-mode))]
["Diff Current with Ediff" p4-ediff
(and buffer-file-name (and (not buffer-read-only)
p4-mode))]
["------------------" nil nil]
["Delete File from Depot" p4-delete
(and buffer-file-name
(and buffer-read-only p4-mode))]
["------------------" nil nil]
["Submit Changes" p4-submit (and
buffer-file-name
p4-mode)]
["------------------" nil nil]
["Show Version" p4-emacs-version (and
buffer-file-name
p4-mode)]
["Disable P4 VC Check" p4-toggle-vc-mode
p4-do-find-file]
["Enable P4 VC Check" p4-toggle-vc-mode
(not p4-do-find-file)]
["------------------" nil nil]
["Set P4 Client" p4-set-client-name p4-do-find-file]
["Get Current P4 Client" p4-get-client-name
p4-do-find-file]
["------------------" nil nil]
["Set P4 Server/Port" p4-set-p4-port p4-do-find-file]
["Get Current P4 Server/Port" p4-get-p4-port
p4-do-find-file]
["------------------" nil nil]
["Set P4 Notification List" p4-set-notify-list
p4-mode]
["Get P4 Notification List" p4-get-notify-list p4-notify]
["------------------" nil nil]
["Release Notes for p4.el" p4-release-notes t]
["Describe Key Bindings" p4-describe-bindings t]
)))
(cons modestr m))))
(p4-running-emacs
;; Menu support for Emacs
(or (lookup-key global-map [menu-bar])
(define-key global-map [menu-bar] (make-sparse-keymap "menu-bar")))
(defvar menu-bar-p4-menu (make-sparse-keymap "P4"))
(setq menu-bar-final-items (cons 'p4-menu menu-bar-final-items))
(define-key global-map [menu-bar p4-menu]
(cons "P4" menu-bar-p4-menu))
(define-key menu-bar-p4-menu [p4-describe-bindings]
'("Describe Key Bindings" . p4-describe-bindings))
(define-key menu-bar-p4-menu [p4-release-notes]
'("Release Notes for p4.el" . p4-release-notes))
(define-key menu-bar-p4-menu [separator-notify-list]
'("--"))
(define-key menu-bar-p4-menu [p4-get-notify-list]
'("Get P4 Notification List" . p4-get-notify-list))
(define-key menu-bar-p4-menu [p4-set-notify-list]
'("Set P4 Notification List" . p4-set-notify-list))
(define-key menu-bar-p4-menu [separator-p4-port]
'("--"))
(define-key menu-bar-p4-menu [p4-get-p4-port]
'("Get Current P4 Server/Port" . p4-get-p4-port))
(define-key menu-bar-p4-menu [p4-set-p4-port]
'("Set P4 Server/Port" . p4-set-p4-port))
(define-key menu-bar-p4-menu [separator-server-name]
'("--"))
(define-key menu-bar-p4-menu [p4-get-client-name]
'("Get Current P4 Client" . p4-get-client-name))
(define-key menu-bar-p4-menu [p4-set-client-name]
'("Set P4 Client" . p4-set-client-name))
(define-key menu-bar-p4-menu [separator-client-name]
'("--"))
(define-key menu-bar-p4-menu [p4-toggle-vc-mode]
'("Toggle P4 VC Check" . p4-toggle-vc-mode))
(define-key menu-bar-p4-menu [p4-emacs-version]
'("Show Version" . p4-emacs-version))
(define-key menu-bar-p4-menu [separator-vc-check]
'("--"))
(define-key menu-bar-p4-menu [p4-submit]
'("Submit Changes" . p4-submit))
(define-key menu-bar-p4-menu [separator-submit]
'("--"))
(define-key menu-bar-p4-menu [p4-delete]
'("Delete File from Depot" . p4-delete))
(define-key menu-bar-p4-menu [separator-delete]
'("--"))
(define-key menu-bar-p4-menu [p4-ediff]
'("Diff Current with Ediff" . p4-ediff))
(define-key menu-bar-p4-menu [p4-diff]
'("Diff Current" . p4-diff))
(define-key menu-bar-p4-menu [p4-diff2]
'("Diff 2 Versions" . p4-diff2))
(define-key menu-bar-p4-menu [separator-diff]
'("--"))
(define-key menu-bar-p4-menu [p4-filelog]
'("Filelog" . p4-filelog))
(define-key menu-bar-p4-menu [p4-revert]
'("Revert File" . p4-revert))
(define-key menu-bar-p4-menu [separator-revert]
'("--"))
(define-key menu-bar-p4-menu [p4-opened]
'("Show Opened Files" . p4-opened))
(define-key menu-bar-p4-menu [p4-edit]
'("Checkout/Edit" . p4-edit))
(define-key menu-bar-p4-menu [p4-add]
'("Add Current to P4" . p4-add))
(put 'p4-add 'menu-enable '(and buffer-file-name (not p4-mode)))
(put 'p4-edit 'menu-enable '(and buffer-file-name
(and p4-mode buffer-read-only)))
(put 'p4-opened 'menu-enable '(and t p4-do-find-file))
(put 'p4-delete 'menu-enable '(and buffer-file-name
(and p4-mode buffer-read-only)))
(put 'p4-revert 'menu-enable '(and buffer-file-name
(and p4-mode
(not buffer-read-only))))
(put 'p4-ediff 'menu-enable '(and buffer-file-name
(and p4-mode
(not buffer-read-only))))
(put 'p4-diff 'menu-enable '(and buffer-file-name
(and p4-mode
(not buffer-read-only))))
(put 'p4-diff2 'menu-enable '(and buffer-file-name p4-mode))
(put 'p4-filelog 'menu-enable '(and buffer-file-name p4-mode))
(put 'p4-get-client-name 'menu-enable '(and t p4-do-find-file))
(put 'p4-set-client-name 'menu-enable '(and t p4-do-find-file))
(put 'p4-get-p4-port 'menu-enable '(and t p4-do-find-file))
(put 'p4-set-p4-port 'menu-enable '(and t p4-do-find-file))
(put 'p4-get-notify-list 'menu-enable '(and t p4-notify))
))
(defun p4-menu-add ()
"To add the P4 menu bar button for files that are already not in
the P4 depot or in the current client view.."
(interactive)
(cond (p4-running-xemacs
(if (not (boundp 'p4-mode))
(setq p4-mode nil))
(easy-menu-add (p4-mode-menu "P4"))))
t)
;; issue a message for users trying to use obsolete binding.
(if (not (lookup-key global-map "\C-xP"))
(define-key global-map "\C-xP"
`(lambda ()
(interactive)
(message
"Obsolete key binding for P4 commands. use C-x p instead."))))
(defun p4-describe-bindings ()
"A function to list the key bindings for the p4 prefix map"
(interactive)
(save-excursion
(let ((map (make-sparse-keymap)))
(get-buffer-create p4-output-buffer-name)
(cond
(p4-running-xemacs
(set-buffer p4-output-buffer-name)
(delete-region (point-min) (point-max))
(insert "Key Bindings for P4 Mode\n------------------------\n")
(describe-bindings-internal p4-prefix-map))
(p4-running-emacs
(kill-buffer p4-output-buffer-name)
(describe-bindings "\C-xp")
(set-buffer "*Help*")
(rename-buffer p4-output-buffer-name)))
(define-key map "q" 'p4-quit-current-buffer)
(use-local-map map)
(display-buffer p4-output-buffer-name))))
(defvar p4-release-notes
'(
("0.1" .
"
Created by Eric Promislow.
")
("1.0" .
"
1. Formally maintained by Rajesh Vaidheeswarran.
2. Applied GNU GPL to the file.
")
("1.1" .
"
Cleanup and minor fixes.
")
("1.2" .
"
Added Functions:
----------------
p4-set-notify-list
p4-get-notify-list
p4-notify
p4-emacs-version
Modifications:
--------------
The following functions now accept prefix argument (C-u) to specify
optional argument like -n or ..., etc.
p4-files, p4-refresh, p4-get, p4-have, p4-integ, p4-opened, p4-users,
p4-where.
Now supports RCS/CVS type vc revision level display on mode line, and
menu bar support.
")
("1.3" .
"
1. '/dev/null' replaced with grep-null-device for NT compatibility
2. p4-is-vc now returns the version number if p4 owns the file
3. added p4-ediff which uses ediff to allow more interactive diffing
4. removed p4-noinput-exec-p4 because it was the same as p4-exec-p4.
p4-noinput-buffer-action and p4-buffer-action should probably be one
routine but I left them alone for now.
5. changed kill-region to delete-region to avoid kill ring clutter
6. replaced 'nil with nil and 't with t
7. removed unused p4-output-buffer
")
("1.4" .
"
Split p4.el for Emacs and XEmacs
")
("1.5" .
"
1. Make p4.el work with both Emacs and XEmacs. The problem is that
this might cause some compilation warnings since some functions are
mutually exclusive and don't exist on both. But, testing has proved no
conflicts or problems that arise due to this.
Functions added:
----------------
p4-set-p4-executable p4-set-gnu-client p4-set-notifier
p4-emacs-server-start p4-mode-menu (For XEmacs)
2. Cleanup comments and a document some more functions.
")
("2.0" .
"
Technically, since 1.5 supports Emacs and XEmacs in the same
file, it should have been a major release, but all the cleanup
occurred only after I saw tested that thoroughly.. Hence, I'm calling
this 2.0
1. Remove dependency on grep-null-device and use the system-type variable
to check this instead.
2. BUGFIX: Make sure to use p4-running-gnuemacs instead of
running-gnuemacs and p4-running-xemacs instead of running-xemacs
3. Added p4-force-mode-line-update to wrap the modeline updation
for different flavors of Emacs.
4. Quash all compiler warnings that are independant of Emacs flavor.
The only warnings that exist are:
Emacs (20.2)
================
While compiling p4-add:
** assignment to free variable p4-mode
While compiling p4-emacs-server-start:
** reference to free variable server-process
While compiling p4-check-mode:
** assignment to free variable p4-mode
While compiling the end of the data:
** The following functions are not known to be defined: gnuserv-start,
gnuserv-running-p, p4-menu-add, redraw-modeline, easy-menu-add,
p4-mode-menu
XEmacs (20.4)
=============
While compiling p4-emacs-server-start:
** reference to free variable server-process
While compiling the end of the data:
** the function server-start is not known to be defined.
")
("2.1" .
"
1. Added p4-toggle-vc-mode to administratively enable/disable the VC
check (mainly for offline working, ro when a P4 server is
unavailable).
2. Emacs Menu works now.
3. Standardized the Menus in XEmacs and Emacs.
4. Add a lot of documentation to the variables, and cleanup indentation
in function documentation.
")
("2.2" .
"
1. p4-add now takes prefix argument to specify optional parameters.
2. p4-check-mode modified to accept optional argument so that it can
update menu of a buffer whose file has been added to p4.
3. Add p4-get-client-name and p4-set-client-name to the menu.
")
("2.3" .
"
1. Change all references to 'GNU Emacs' and 'XEmacs' to 'Emacs' and
'XEmacs' respectively, since the former may mistakenly portray XEmacs
as a program that has nothing to do with GNU (It did originate from a
GNU program), which is neither fair nor accurate. The only place where
the explicit check is done for 'GNU Emacs' is in the string-match.
2. Rename p4-running-gnuemacs to p4-running-emacs per the above
sentiment.
3. Add p4-set-p4-port and p4-get-p4-port to set and get the current P4
server/port that we are attached to.
")
("2.4" .
"
1. Used `checkdoc' to correct all the documentation issues that it found.
2. Start a server, only if one is not already in place.
")
("2.5" .
"
1. Support C-x C-q to p4-edit/p4-revert a file under P4 VC. Revert to the
normal behavior otherwise. This is done by `p4-toggle-read-only'.
")
("2.6" .
"
1. Added globally settable variables `p4-my-clients' and
`p4-strict-complete' to enable completion on p4 client names with
`p4-set-client-name'.
2. Added `p4-set-my-clients' to set `p4-my-clients' since it is a pain
for users to set an alist.
3. Bugfix in p4-menu-add for XEmacs to ensure that it didn't complain
about p4-mode being void when called manually in non-p4 buffers.
")
("2.7" .
"
1. p4-user, p4-change and p4-client now use the simpler and more elegant
p4-async-process-command and p4-async-call-process pair to do their
jobs. This leaves only p4-submit to be dealt with, after which the
need for an external editor client (gnuclient/emacsclient) can be done
away with. Thanks to Dima Barsky for the suggestion and sample code.
")
("3.0" .
"
1. Major release number change since a lot of code has been ripped out
and this fundamentally changes the way all external interaction is
done. The previous release techically didnot qualify for this since
p4-submit still used the client server mechanism.
a. All references to `server-start' and `gnuserv-start' removed.
b. `p4-emacs-server-start' function removed.
c. `p4-gnuclient' variable and `p4-set-gnu-client' function removed.
d. `p4-async-commands' function removed.
2. `p4-async-process-command' modified to take optional parameter and
`p4-submit' also now works within that framework.
")
("3.1" .
"
1. Stylistic and other coding changes pointed out by Hrvoje Niksic.
2. Added helpful comments to p4-change/user/client/submit buffers.
3. Send output of p4-async-process-command to p4-output-buffer-name
instead of sending it to the messages log.
")
("3.2" .
"
1. Major overhaul of the notification process. Since there is no async
process anymore, we can do autonotification elegantly within emacs
now. For this the following changes were done:
a. Ripped out function `p4-set-notifier' and variable `p4-notifier'.
b. Added functions `p4-do-notify', `p4-set-sendmail-program' and
`p4-set-user-email'.
c. Modified functions `p4-notify', `p4-set-notify-list' and
`p4-async-process-command'.
This is how notification works:
* set your email address with `p4-set-user-email' (or var `p4-user-email')
* set your sendmail program (variable `p4-sendmail-program')
* set the list of users you need to notify using `p4-set-notify-list'
* Go ahead and submit your change!
* To disable notification, (p4-set-notify-list nil)
2. Added menu commands for setting/getting notification list.
")
("3.3" .
"
1. p4-opened now present selectable list of opened files that will be
opened in the editor.
2. Added menu support for p4-opened.
")
("3.4" .
"
1. p4-diff2 now diff's between any two depot files, not just revisions of
the current file.
2. p4-menu-add now for both flavors.. and added to ediff hooks.
3. Make the *Opened Files: <>* buffer read-only.
4. Use substitute-key-definition to remap the vc-toggle-read-only binding
instead of assuming that it is bound to C-x C-q since that may not be
true for all cases.
5. Bugfix for p4-opened on NT Emacs. Now opens files from listing
produced by p4-opened.
")
("3.5" .
"
1. check keymap definition of `vc-toggle-read-only' and then substitute it
with `p4-toggle-read-only'.
2. Use C-x p instead of C-x P as the p4-prefix-map prefix. And issue a
warning when users try to use the obsolete binding.
3. Print helpful message while submitting changes in async processes.
")
("3.6" .
"
1. Check for Emacs flavor using the `emacs-version' variable instead of
similarly named defun.
2. Move the growing history to the end of the file.
3. Added variable `p4-release-notes' , a cons list of all the versions, and
the modifications in that version, and a function `p4-release-notes' to
print out the modification history and release notes, and bound to C-x p N.
4. Added function `p4-quit-current-buffer' and added a key definition 'q' to
the `p4-opened-map' to allow users to 'quit' the buffer without having to
resort to C-x 0. Thanks to Jason Dillon for the suggestion.
5. Modified `p4-opened-find-file' to work correctly in Emacs.
")
("3.7" .
"
1. Function `p4-files' now lists files that are selectable.
2. Variable `p4-vc-check' is now buffer-local and contains the current version
number of the file.
3. Clean up `p4-diff2' and remove buffer local definitions, and use the
cleaner `let' instead (This was coded when I had no idea that `let'
existed).
4. Add function `p4-describe-bindings' bound to key `C-x p ?' that lists all
the key bindings in the p4 mode.
"
)
("3.8" .
"
1. Added LCD (Lisp Code Directory) Archive Entry Strings to enable
archiving at the ohio-state site ftp.cis.ohio-state.edu:/pub/emacs-lisp/
"
)
("3.9" .
"
1. Added prefix-argument passing for `p4-edit' and `p4-diff'
"
)
("4.0" .
"
1. Added `p4-get-client-root' to be callable from other macros intent on
setting local variables based on the current client's root.
"
)
("4.1" .
"
1. With P4 server 98.2 `p4-where' returns three values (//depot/path
//client/path //fs/path) instead of (//depot/path //client/path). So, check
for server version in `p4-opened' and set the current client file name
property.
"
))
"A cons list of the modification history of p4.el upto the current
release.")
(defun p4-release-notes ()
"Open a buffer and print the Release Notes and Modification History of all
p4.el releases uptil the current release. Bound to \\[p4-release-notes].
A specific version supplied as prefix argument will cause only the changes in the given version to be displayed."
(interactive)
(save-excursion
(let ((p4-this-buffer "*P4 Release Notes*")
(map (make-sparse-keymap))
(p4-version-numbers nil)
(p4-tmp-r-notes p4-release-notes)
(p4-cur-rel nil)
(p4-show-rel nil)
)
(define-key map "q" 'p4-quit-current-buffer)
(get-buffer-create p4-this-buffer) ;; To make sure we kill this buffer.
(kill-buffer p4-this-buffer)
(set-buffer (get-buffer-create p4-this-buffer))
(use-local-map map)
(if current-prefix-arg
(progn
(while p4-tmp-r-notes
(setq p4-version-numbers
(append
p4-version-numbers
(list (list
(format "%s" (car (car p4-tmp-r-notes)))))))
(setq p4-tmp-r-notes (cdr p4-tmp-r-notes)))
(setq p4-show-rel
(completing-read
"Release Notes for Version: "
p4-version-numbers nil
p4-strict-complete p4-emacs-version))))
(insert "Release Notes and Modification History of p4.el\n\n")
(setq p4-tmp-r-notes p4-release-notes)
(while p4-tmp-r-notes
(setq p4-cur-rel (car p4-tmp-r-notes))
(setq p4-tmp-r-notes (cdr p4-tmp-r-notes))
(if p4-show-rel
(if (string-match p4-show-rel (car p4-cur-rel))
(progn
(insert (format "\n%s\nRelease Notes for version %s:\n%s\n"
"******************************"
(car p4-cur-rel)
"******************************"))
(insert (cdr p4-cur-rel))))
(progn
(insert (format "\n%s\nRelease Notes for version %s:\n%s\n"
"******************************"
(car p4-cur-rel)
"******************************"))
(insert (cdr p4-cur-rel)))))
(goto-char (point-min))
(setq buffer-read-only t)
(display-buffer p4-this-buffer))))
;;;###autoload
(if (where-is-internal 'vc-toggle-read-only)
(substitute-key-definition 'vc-toggle-read-only 'p4-toggle-read-only
global-map))
(provide 'p4)
;;; p4.el ends here