2021-01-15 08:12:30 +01:00
|
|
|
|
;;; p4-lowlwevel.el --- low-level support for Perforce operations in Emacs -*- lexical-binding: t; -*-
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2009-05-27 13:11:58 +02:00
|
|
|
|
;; Copyright (C) 2009 Magnus Henoch
|
2002-01-06 04:45:11 +01:00
|
|
|
|
;; Copyright (C) 2002 Curl Corporation.
|
|
|
|
|
|
|
|
|
|
;; Author: Jonathan Kamens <jik@kamens.brookline.ma.us>
|
2009-05-27 13:11:58 +02:00
|
|
|
|
;; Maintainer: Magnus Henoch <magnus.henoch@gmail.com>
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
|
|
|
|
;; $Id$
|
2002-01-06 04:55:28 +01:00
|
|
|
|
;; The path above is on the Perforce server public.perforce.com:1666.
|
|
|
|
|
;; You can get this file using a P4 client talking to that depot, or
|
|
|
|
|
;; from the URL
|
2009-05-27 13:11:58 +02:00
|
|
|
|
;; http://public.perforce.com/guest/magnus_henoch/vc-p4/p4-lowlevel.el.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
|
|
|
|
;; 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, 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, you can either send email to this
|
|
|
|
|
;; program's maintainer or write to: The Free Software Foundation,
|
|
|
|
|
;; Inc.; 59 Temple Place, Suite 330; Boston, MA 02111-1307, USA.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;; This file provides an Emacs-lisp interface to the Perforce client
|
|
|
|
|
;; functionality which is necessary to implement support for Perforce
|
|
|
|
|
;; in Emacs VC mode.
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2022-03-03 05:23:04 +01:00
|
|
|
|
(require 'subr-x)
|
|
|
|
|
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(defgroup p4-lowlevel nil
|
|
|
|
|
"Emacs-lisp interface to Perforce operations."
|
|
|
|
|
:group 'tools)
|
|
|
|
|
|
|
|
|
|
(defcustom p4-lowlevel-p4-program "p4"
|
|
|
|
|
"*The `p4' program to run to execute Perforce commands."
|
|
|
|
|
:type 'string
|
|
|
|
|
:group 'p4-lowlevel)
|
|
|
|
|
|
|
|
|
|
(defcustom p4-lowlevel-diff-switches '("-du")
|
|
|
|
|
"*The flags to use when calling `p4 diff' or `p4 diff2'."
|
|
|
|
|
:type '(choice (const :tag "None" nil)
|
|
|
|
|
(repeat :tag "Argument List"
|
|
|
|
|
:value ("")
|
|
|
|
|
string))
|
|
|
|
|
:group 'p4-lowlevel)
|
|
|
|
|
|
|
|
|
|
(defcustom p4-lowlevel-command-messages nil
|
|
|
|
|
"*If non-nil, display run messages from P4 commands.
|
2021-02-27 23:09:44 +01:00
|
|
|
|
If ‘vc-command-messages’ is bound and non-nil, it does the same
|
|
|
|
|
thing."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'p4-lowlevel)
|
|
|
|
|
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(defcustom p4-lowlevel-ignore-error-list '("file(s) up-to-date")
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"*A list of Perforce errors to ignore.
|
|
|
|
|
Note that each item in the list can be a portion of the error
|
|
|
|
|
string you wish to ignore."
|
2015-03-10 23:43:47 +01:00
|
|
|
|
:type '(repeat (string :tag "Error to Ignore"))
|
|
|
|
|
:group 'p4-lowlevel)
|
|
|
|
|
|
2021-01-15 07:39:21 +01:00
|
|
|
|
(define-error 'p4-error
|
|
|
|
|
"A Perforce error occurred")
|
|
|
|
|
|
|
|
|
|
(define-error 'p4-not-logged-in-error
|
|
|
|
|
"You’re not logged in to Perforce"
|
|
|
|
|
'p4-error)
|
|
|
|
|
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(defun p4-lowlevel-locate-p4 ()
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Attempts to locate the p4 command.
|
|
|
|
|
Used by `vc-p4-registered' to avoid an error on systems on which
|
|
|
|
|
the Perforce client is not installed."
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(if (and (boundp 'exec-suffixes) (fboundp 'file-executable-p))
|
|
|
|
|
(locate-file p4-lowlevel-p4-program exec-path exec-suffixes 'file-executable-p) ; GNU Emacs
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(locate-file p4-lowlevel-p4-program exec-path '("" ".btm" ".bat" ".cmd" ".exe" ".com") 'executable))) ; XEmacs
|
2015-03-10 23:43:47 +01:00
|
|
|
|
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(defun p4-lowlevel-command-to-buffer (args &optional input output)
|
|
|
|
|
"Call `p4-lowlevel-p4-command' with specified list of ARGS.
|
|
|
|
|
ARGS can be a list or a single string argument. Do not specify the
|
|
|
|
|
`-s' argument to p4; it is always specified by this function. The
|
|
|
|
|
remaining arguments are optional. If INPUT is non-nil, it is a buffer
|
|
|
|
|
or file to use as input. If OUTPUT is non-nil, it is a buffer in
|
|
|
|
|
which to insert the command's output at point; otherwise, a temporary
|
|
|
|
|
buffer is created.
|
|
|
|
|
|
|
|
|
|
If the p4 invocation is unsuccessful, checks to make sure there's an
|
|
|
|
|
`exit:' line at the end of the output with a non-zero value in it; if
|
|
|
|
|
not, it adds such a line containing either the non-zero exit status or
|
|
|
|
|
the signal information from `call-process', after deleting any
|
|
|
|
|
previous `exit: 0' line (which shouldn't be there since the
|
|
|
|
|
`call-process' failed).
|
|
|
|
|
|
|
|
|
|
Returns the buffer containing the program output."
|
|
|
|
|
(if (stringp args)
|
|
|
|
|
(setq args (list args)))
|
|
|
|
|
(let* (apply-args p4-args call-func string-command exit-status
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(temp-name " *p4-lowlevel-output*")
|
|
|
|
|
(output-buffer (or output (p4-lowlevel-get-buffer-create temp-name)))
|
|
|
|
|
(my-default-directory default-directory))
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(with-current-buffer output-buffer
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(while (and my-default-directory
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(not (file-exists-p my-default-directory))
|
|
|
|
|
(not (string= "" my-default-directory)))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(setq my-default-directory
|
|
|
|
|
(file-name-as-directory
|
|
|
|
|
(file-name-directory (directory-file-name
|
2002-01-06 04:45:11 +01:00
|
|
|
|
my-default-directory)))))
|
|
|
|
|
(if (not (file-exists-p my-default-directory))
|
|
|
|
|
(setq my-default-directory "/"))
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(if (bufferp input)
|
|
|
|
|
(progn (set-buffer input)
|
|
|
|
|
(setq call-func 'call-process-region)
|
|
|
|
|
(setq apply-args (list (point-min) (point-max)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
p4-lowlevel-p4-program nil)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq call-func 'call-process)
|
|
|
|
|
(setq apply-args (list p4-lowlevel-p4-program input)))
|
|
|
|
|
(setq p4-args (append (list "-s") args))
|
|
|
|
|
(if (p4-lowlevel-command-messages)
|
|
|
|
|
(progn (setq string-command
|
|
|
|
|
(mapconcat (lambda (s) s)
|
|
|
|
|
(append (list p4-lowlevel-p4-program)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
p4-args) " "))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(message "Running %s..." string-command)))
|
|
|
|
|
(let ((default-directory my-default-directory))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(setq exit-status (apply call-func
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(append apply-args (list output-buffer nil)
|
|
|
|
|
p4-args))))
|
|
|
|
|
(if (p4-lowlevel-command-messages)
|
|
|
|
|
(message "Running %s... exit %s" string-command exit-status))
|
|
|
|
|
(if (and (numberp exit-status) (zerop exit-status)) t
|
|
|
|
|
(set-buffer output-buffer)
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(forward-line -1)
|
|
|
|
|
(if (looking-at "^exit: [^0]") t
|
|
|
|
|
(if (looking-at "^exit:")
|
|
|
|
|
(kill-line 1)
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(or (looking-at "^") (insert "\n")))
|
|
|
|
|
(insert "exit: ")
|
|
|
|
|
(if (numberp exit-status)
|
|
|
|
|
(insert (format "%d" exit-status))
|
|
|
|
|
(insert exit-status))
|
|
|
|
|
(insert "\n")))
|
|
|
|
|
output-buffer)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-buffer-to-alist (&optional buffer)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Convert the current buffer containing p4 output to an alist.
|
|
|
|
|
If optional argument BUFFER is non-nil, converts that buffer
|
|
|
|
|
instead. The returned alist contains one element for each `unit'
|
|
|
|
|
in the buffer. A `unit' is either a line containing a
|
|
|
|
|
tag (`info:' or `info#:', `error:', `exit:', etc.) or a block of
|
|
|
|
|
text with no tag. The car of each element is the tag for the
|
|
|
|
|
line (i.e., the string to the left of the colon, or `text' for
|
|
|
|
|
text units), and the cdr is the value for the line (i.e.,
|
|
|
|
|
everything after the first `: ' sequence) or the entire block of
|
|
|
|
|
text without a tag (including newlines other than the last one).
|
2002-01-06 04:45:11 +01:00
|
|
|
|
The alist is in the same order as the contents of the buffer."
|
|
|
|
|
(save-excursion
|
|
|
|
|
(if buffer (set-buffer buffer))
|
2009-07-21 01:37:50 +02:00
|
|
|
|
;; Strip CR from end of line
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (search-forward "\r" nil t)
|
|
|
|
|
(delete-char -1))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let (alist tag value
|
|
|
|
|
(last-match-end (point-min)))
|
|
|
|
|
(goto-char last-match-end)
|
|
|
|
|
(while (re-search-forward "^\\([A-Za-z0-9]+\\): \\(.*\\)" nil t)
|
|
|
|
|
(if (not (= (match-beginning 1) last-match-end))
|
|
|
|
|
(progn (setq tag "text"
|
2017-06-07 09:03:49 +02:00
|
|
|
|
value (buffer-substring last-match-end
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(- (match-beginning 1) 1))
|
|
|
|
|
alist (cons (cons tag value) alist))))
|
|
|
|
|
(setq tag (match-string 1)
|
|
|
|
|
value (match-string 2)
|
|
|
|
|
alist (cons (cons tag value) alist)
|
|
|
|
|
last-match-end (+ (match-end 2) 1)))
|
|
|
|
|
(if (not (= last-match-end (point-max)))
|
|
|
|
|
(progn (setq tag "text"
|
|
|
|
|
value (buffer-substring last-match-end
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(- (point-max) 1))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
alist (cons (cons tag value) alist))))
|
|
|
|
|
(nreverse alist))))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-command-to-alist (args &optional input)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Call `p4-lowlevel-command-to-buffer' and then `p4-lowlevel-buffer-to-alist'.
|
|
|
|
|
Passes ARGS and optional INPUT to
|
|
|
|
|
`p4-lowlevel-command-to-buffer'. Hands the resulting buffer to
|
|
|
|
|
`p4-lowlevel-buffer-to-alist' for parsing. Kills the output
|
|
|
|
|
buffer when it has been parsed. Returns the resulting alist on
|
|
|
|
|
success, or the return value of `p4-lowlevel-command-to-buffer'
|
|
|
|
|
on failure."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let ((output-buffer (p4-lowlevel-command-to-buffer args input))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
return-value)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq return-value (p4-lowlevel-buffer-to-alist output-buffer))
|
|
|
|
|
(kill-buffer output-buffer)
|
|
|
|
|
return-value))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-re-assoc (key alist)
|
|
|
|
|
"Get elements matching regexp KEY in ALIST, or nil if none."
|
|
|
|
|
(let (element matching-alist)
|
|
|
|
|
(while alist
|
|
|
|
|
(setq element (car alist)
|
|
|
|
|
alist (cdr alist))
|
|
|
|
|
(if (string-match key (car element))
|
|
|
|
|
(setq matching-alist (cons element matching-alist))))
|
|
|
|
|
(nreverse matching-alist)))
|
|
|
|
|
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(defun p4-lowlevel-should-ignore-error (error-string)
|
|
|
|
|
"Determines whether the error specified by ERROR-STRING should be ignored."
|
|
|
|
|
(if (stringp error-string)
|
|
|
|
|
(let (should-ignore)
|
|
|
|
|
(dolist (item p4-lowlevel-ignore-error-list should-ignore)
|
|
|
|
|
(if (string-match item error-string)
|
|
|
|
|
(progn
|
|
|
|
|
(setq should-ignore t)
|
|
|
|
|
(if p4-lowlevel-command-messages
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(message "Ignoring error: %s" error-string))))))
|
|
|
|
|
nil))
|
2015-03-10 23:43:47 +01:00
|
|
|
|
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(defun p4-lowlevel-successful-alist-p (output)
|
|
|
|
|
"Determines if OUTPUT, a buffer or alist, is from a successful p4 command.
|
|
|
|
|
Does this by confirming that OUTPUT is a buffer or alist, that there
|
|
|
|
|
are no `error' keys in it, and that its `exit' element (if any) has a
|
|
|
|
|
value of 0."
|
|
|
|
|
(if (bufferp output)
|
|
|
|
|
(setq output (p4-lowlevel-buffer-to-alist output)))
|
|
|
|
|
(if (not (listp output))
|
|
|
|
|
nil
|
|
|
|
|
(let ((element (assoc "exit" output)))
|
|
|
|
|
(if (not (or (not element) (equal (cdr element) "0")))
|
|
|
|
|
nil
|
|
|
|
|
(if (p4-lowlevel-re-assoc "^error" output)
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(let ((err (assoc "error" output)))
|
|
|
|
|
(if (and (not (null err)) (p4-lowlevel-should-ignore-error (cdr err)))
|
|
|
|
|
t
|
|
|
|
|
nil))
|
|
|
|
|
t)))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-items-matching-tag (tag output)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return a list of the items matching TAG in p4 OUTPUT, or nil if none.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
OUTPUT may be a buffer or alist."
|
|
|
|
|
(if (bufferp output)
|
|
|
|
|
(setq output (p4-lowlevel-buffer-to-alist output)))
|
|
|
|
|
(mapcar (lambda (pair) (cdr pair)) (p4-lowlevel-re-assoc tag output)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-lines-matching-tag (tag output)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return a string containing the lines matching TAG in p4 OUTPUT.
|
|
|
|
|
Return nil if none of the lines match TAG. OUTPUT may be a buffer
|
|
|
|
|
or alist. The lines are terminated by newlines. The tags are not
|
|
|
|
|
included in the string."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (bufferp output)
|
|
|
|
|
(setq output (p4-lowlevel-buffer-to-alist output)))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(let* ((alist (p4-lowlevel-re-assoc tag output))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(lines (apply 'concat
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(apply 'append
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(mapcar (lambda (pair)
|
|
|
|
|
(list (cdr pair) "\n")) alist)))))
|
|
|
|
|
(if (equal lines "")
|
|
|
|
|
nil
|
|
|
|
|
lines)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-errors (output)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return a string containing the errors in p4 OUTPUT, or nil if none.
|
|
|
|
|
OUTPUT may be a buffer or alist. The error lines are separated by
|
2002-01-06 04:45:11 +01:00
|
|
|
|
newlines, but there is no ending newline on the string."
|
|
|
|
|
(let ((errors (p4-lowlevel-lines-matching-tag "^error" output)))
|
|
|
|
|
(if errors
|
|
|
|
|
(substring errors 0 -1)
|
|
|
|
|
nil)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-info-lines (output)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return a string containing the info in p4 OUTPUT, or nil if none.
|
|
|
|
|
OUTPUT may be a buffer or alist. The info lines are terminated by
|
2002-01-06 04:45:11 +01:00
|
|
|
|
newlines."
|
|
|
|
|
(p4-lowlevel-lines-matching-tag "^info" output))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-text (output)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return a string containing the text in p4 OUTPUT, or nil if none.
|
|
|
|
|
OUTPUT may be a buffer or alist. The text lines are terminated by
|
2002-01-06 04:45:11 +01:00
|
|
|
|
newlines."
|
|
|
|
|
(p4-lowlevel-lines-matching-tag "^text" output))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-command-or-error (args &optional input output-format noerror)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Execute p4 command specified by ARGS and return output or signal error.
|
|
|
|
|
Pass optional argument INPUT to `p4-lowlevel-command-to-buffer'.
|
|
|
|
|
If optional argument OUTPUT-FORMAT is \'string, return a string
|
|
|
|
|
containing the output (including tags). If it is \'buffer, return
|
|
|
|
|
the temporary buffer containing the output. If it is a buffer,
|
|
|
|
|
put output in that buffer and return it. If it is anything else,
|
|
|
|
|
return an alist of the output. If optional fourth argument
|
|
|
|
|
NOERROR is true, then returns nil rather than raising an error."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let* (errors error-buffer return-value
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(output-buffer (p4-lowlevel-command-to-buffer args input
|
|
|
|
|
(if (bufferp output-format)
|
|
|
|
|
output-format)))
|
|
|
|
|
(output-alist (p4-lowlevel-buffer-to-alist output-buffer)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (p4-lowlevel-successful-alist-p output-alist) t
|
|
|
|
|
(setq errors (or (p4-lowlevel-errors output-alist) "Unknown error"))
|
|
|
|
|
(kill-buffer output-buffer)
|
|
|
|
|
(or noerror
|
2021-01-15 07:39:21 +01:00
|
|
|
|
(when (string-match (rx string-start "Perforce password (P4PASSWD) invalid or unset.") errors)
|
|
|
|
|
(signal 'p4-not-logged-in-error nil))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (not (string-match "\n" errors))
|
|
|
|
|
(error "P4 error: %s" errors)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(setq error-buffer (p4-lowlevel-get-buffer-create
|
|
|
|
|
" *p4-lowlevel-error*"))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(set-buffer error-buffer)
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert errors)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(pop-to-buffer error-buffer nil t)
|
|
|
|
|
(resize-temp-buffer-window)
|
|
|
|
|
(error ""))))
|
|
|
|
|
(cond
|
|
|
|
|
(errors (setq return-value nil))
|
|
|
|
|
((eq output-format 'string)
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(with-current-buffer output-buffer
|
|
|
|
|
(setq return-value (buffer-string)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(kill-buffer output-buffer))
|
|
|
|
|
((or (eq output-format 'buffer) (bufferp output-format))
|
|
|
|
|
(setq return-value output-buffer))
|
|
|
|
|
(t (setq return-value output-alist)
|
|
|
|
|
(kill-buffer output-buffer)))
|
|
|
|
|
return-value))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-command-into-buffer (args buffer)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Execute p4 command specified by ARGS, raising errors when necessary.
|
|
|
|
|
If BUFFER is a string, then puts output in buffer whose name is
|
|
|
|
|
formed by concatenating ` *p4-lowevel-', BUFFER-NAME, and
|
|
|
|
|
`*' (e.g., if BUFFER is `diff', then output goes in buffer `
|
|
|
|
|
*p4-lowevel-diff*'). If BUFFER is a buffer, then puts output in
|
|
|
|
|
that buffer. Returns the buffer."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let* ((output-alist (p4-lowlevel-command-or-error args))
|
|
|
|
|
(output-buffer (if (bufferp buffer) buffer
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(p4-lowlevel-get-buffer-create
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(concat " *p4-lowlevel-" buffer "*")))))
|
|
|
|
|
(with-current-buffer output-buffer
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(erase-buffer)
|
2009-11-16 21:56:58 +01:00
|
|
|
|
(insert (p4-lowlevel-lines-matching-tag "^\\(text\\|info\\)" output-alist))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
output-buffer)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-command-messages ()
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return t if ‘vc-command-messages’ bound and true.
|
|
|
|
|
Fall back to the value of ‘p4-lowlevel-command-messages’."
|
2023-04-04 07:53:02 +02:00
|
|
|
|
(or (bound-and-true-p vc-command-messages)
|
|
|
|
|
p4-lowlevel-command-messages))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-canonicalize-revision (rev)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Turn REV into a form which can be concatenated to file names in P4 commands."
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; There is some ambiguity here, since a number can be either a revision
|
|
|
|
|
;; number (#rev) or a change number (@change). We assume that a bare number is
|
|
|
|
|
;; a revision number.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if rev
|
2015-03-10 23:43:47 +01:00
|
|
|
|
(if (eq rev t)
|
|
|
|
|
nil
|
|
|
|
|
(if (string= rev "")
|
|
|
|
|
nil
|
|
|
|
|
(if (string-match "\\`[0-9]+\\'" rev)
|
|
|
|
|
(concat "#" rev)
|
|
|
|
|
(if (not (string-match "^[#@]" rev))
|
|
|
|
|
(concat "@" rev)
|
|
|
|
|
rev))))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 add" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Do NOT need to support "-c".
|
|
|
|
|
;; Do NOT need to support "-t".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-add (file)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Tell Perforce to add FILE to the repository.
|
2021-09-08 09:12:03 +02:00
|
|
|
|
Returns nil or raises an error on failure."
|
|
|
|
|
;; Note that because "p4 -s add" has bugs, at least as of p4 99.2, this won't
|
|
|
|
|
;; necessarily detect when the add fails, e.g., because of an attempt to add a
|
|
|
|
|
;; file which already exists in the repository.
|
|
|
|
|
(p4-lowlevel-command-or-error (list "add" file)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-delete (file)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Tell Perforce to delete FILE from the repository.
|
2021-09-08 09:12:03 +02:00
|
|
|
|
Returns nil or raises an error on failure."
|
|
|
|
|
(p4-lowlevel-command-or-error (list "delete" file)))
|
2020-05-13 20:07:38 +02:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 change" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Do NOT need to support "-f".
|
|
|
|
|
;; Do NOT need to support "-d".
|
|
|
|
|
;; DO need to support "-o".
|
|
|
|
|
;; DO need to support "-i".
|
|
|
|
|
;; DO need to support specified changelist #'s.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-change (&key buffer op)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Create or edit a P4 changelist from/to BUFFER.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
If optional OP is a number, then the corresponding changelist is
|
2021-02-27 23:09:44 +01:00
|
|
|
|
retrieved into BUFFER, or into a new buffer if BUFFER is nil. If
|
|
|
|
|
OP is non-nil and not a number, then then BUFFER should contain
|
|
|
|
|
an existing changelist which is saved to the database; the number
|
|
|
|
|
of the new or updated changelist is returned. If OP is nil then a
|
|
|
|
|
new changelist is retrieved into BUFFER (or a new buffer). The
|
2021-09-08 09:12:03 +02:00
|
|
|
|
output buffer is returned."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let* ((input-buffer (if (and op (not (numberp op))) buffer nil))
|
|
|
|
|
(flag-arg (if (or (not op) (numberp op)) "-o" "-i"))
|
|
|
|
|
(number-arg (if (numberp op) (list (number-to-string op))))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (append (list "change" flag-arg) number-arg))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
alist info)
|
|
|
|
|
(setq alist (p4-lowlevel-command-or-error args input-buffer nil))
|
|
|
|
|
(setq info (p4-lowlevel-info-lines alist))
|
|
|
|
|
(if (and op (not (numberp op)))
|
|
|
|
|
(if (string-match "\\([0-9]+\\)" info)
|
|
|
|
|
(string-to-number (match-string 1 info))
|
|
|
|
|
(error "P4 error: could not parse info \"%s\"" info))
|
|
|
|
|
(if (not buffer)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(setq buffer (p4-lowlevel-get-buffer-create
|
|
|
|
|
" *p4-lowlevel-change*")))
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(with-current-buffer buffer
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert (p4-lowlevel-info-lines alist))
|
|
|
|
|
buffer))))
|
|
|
|
|
|
2021-01-23 19:30:34 +01:00
|
|
|
|
(cl-defun p4-lowlevel-changes
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(file-pattern &key output-format rev1 rev2 i-flag l-flag m-val s-val)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Call “p4 changes” command on FILE-PATTERN.
|
|
|
|
|
Optional OUTPUT-FORMAT is as described in
|
|
|
|
|
`p4-lowlevel-command-or-error'. Optionally, limit output to the
|
|
|
|
|
revisions between REV1 and REV2. If I-FLAG is non-nil, pass `-i';
|
|
|
|
|
if L-FLAG is non-nil, pass `-l'; if M-VAL is non-nil, pass that
|
2021-09-08 09:12:03 +02:00
|
|
|
|
value with `-m'; if S-VAL is non-nil, pass that value with `-s'."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev1 (p4-lowlevel-canonicalize-revision rev1)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
rev2 (p4-lowlevel-canonicalize-revision rev2))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let ((full-file
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(if (or rev1 rev2)
|
|
|
|
|
(format "%s%s,%s" file-pattern (or rev1 "") (or rev2 ""))
|
|
|
|
|
file-pattern))
|
|
|
|
|
(i-list (if i-flag (list "-i")))
|
|
|
|
|
(l-list (if l-flag (list "-l")))
|
|
|
|
|
(m-list (if m-val (list "-m" (if (numberp m-val)
|
|
|
|
|
(number-to-string m-val)
|
|
|
|
|
m-val))))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(s-list (if s-val (list "-s" s-val))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(p4-lowlevel-command-or-error (append
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(list "changes")
|
|
|
|
|
i-list l-list m-list s-list
|
|
|
|
|
(list full-file))
|
|
|
|
|
nil output-format)))
|
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 diff" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-d<flag>".
|
|
|
|
|
;; DO need to support "-f" (in fact, need to specify it all the time).
|
|
|
|
|
;; Do NOT need to support "-s<flag>".
|
|
|
|
|
;; DO need to support "-t" (in fact, need to specify it all the time).
|
|
|
|
|
;; DO need to support diffing a single file.
|
|
|
|
|
;; Do NOT need to support diffing multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-diff (files &key rev buffer)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Run `p4 diff' on FILES at revision REV.
|
|
|
|
|
Return a buffer containing the results. REV is in the syntax
|
|
|
|
|
described by `p4 help revisions'. If REV is nil, compare the
|
|
|
|
|
client's synced revision to the file on disk. Uses
|
|
|
|
|
`p4-lowlevel-diff-switches' to determine flags to pass to `p4
|
2021-09-08 09:12:03 +02:00
|
|
|
|
diff'. If optional BUFFER is non-nil, put output in that buffer."
|
2009-11-16 21:56:58 +01:00
|
|
|
|
(unless (listp files)
|
|
|
|
|
(setq files (list files)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev (p4-lowlevel-canonicalize-revision rev))
|
2009-11-16 21:56:58 +01:00
|
|
|
|
(setq files
|
|
|
|
|
(mapcar (lambda (file)
|
|
|
|
|
(if (file-directory-p file)
|
|
|
|
|
(concat (directory-file-name file) "/...")
|
|
|
|
|
file))
|
|
|
|
|
files))
|
|
|
|
|
(let* ((file-specs (if rev
|
|
|
|
|
(mapcar (lambda (file) (concat file rev)) files)
|
|
|
|
|
files))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(diff-args (append (list "diff") p4-lowlevel-diff-switches
|
2009-11-16 21:56:58 +01:00
|
|
|
|
(list "-f" "-t") file-specs))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(buffer (p4-lowlevel-command-into-buffer diff-args
|
|
|
|
|
(or buffer "diff"))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
buffer))
|
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-diff-s (file flag)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Run `p4 diff -s' on FILE, using FLAG as the argument to `-s'.
|
2021-09-08 09:12:03 +02:00
|
|
|
|
Return a list of the matching files."
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(p4-lowlevel-items-matching-tag
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"^info"
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(p4-lowlevel-command-or-error (list "diff" (format "-s%s" flag) file))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 diff2" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-d<flag>".
|
|
|
|
|
;; Do NOT need to support "-q".
|
|
|
|
|
;; DO need to support "-t" (in fact, need to specify it all the time).
|
|
|
|
|
;; Do NOT need to support "-b".
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-diff2 (file1 file2 &key rev1 rev2 buffer)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Run `p4 diff2' on FILE1 and FILE2 and return a buffer containing the results.
|
|
|
|
|
If optional REV1 and/or REV2 are non-nil, they specify the
|
|
|
|
|
revisions to diff in the syntax described by `p4 help revisions'.
|
|
|
|
|
If optional BUFFER is non-nil, output goes in that buffer. Uses
|
|
|
|
|
`p4-lowlevel-diff-switches' to determine flags to pass to `p4
|
2021-09-08 09:12:03 +02:00
|
|
|
|
diff2'."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev1 (p4-lowlevel-canonicalize-revision rev1)
|
|
|
|
|
rev2 (p4-lowlevel-canonicalize-revision rev2))
|
|
|
|
|
(let* ((file1-spec (if rev1 (concat file1 rev1) file1))
|
|
|
|
|
(file2-spec (if rev2 (concat file2 rev2) file2))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(diff-args (append (list "diff2") p4-lowlevel-diff-switches
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(list "-t" file1-spec file2-spec)))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(buffer (p4-lowlevel-command-into-buffer diff-args
|
|
|
|
|
(or buffer "diff"))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
buffer))
|
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 edit" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Do NOT need to support "-c".
|
|
|
|
|
;; Do NOT need to support "-t".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-edit (file)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Tell Perforce we want to edit FILE.
|
2021-02-27 23:09:44 +01:00
|
|
|
|
Returns non-nil on success or nil on failure (or raises an
|
2021-09-08 09:12:03 +02:00
|
|
|
|
error)."
|
|
|
|
|
(p4-lowlevel-command-or-error (list "edit" file)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 filelog" command, at least for
|
|
|
|
|
;; the time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-i".
|
|
|
|
|
;; DO need to support "-l".
|
|
|
|
|
;; Do NOT need to support "-m".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-filelog (file &key buffer long follow-branches limit)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Fetch the p4 log of FILE and return a buffer containing it.
|
2021-02-27 23:09:44 +01:00
|
|
|
|
If optional BUFFER is non-nil, put output in that buffer. If
|
|
|
|
|
optional LONG is non-nil, return long output (i.e., pass the `-l'
|
|
|
|
|
flag). If optional FOLLOW-BRANCHES is non-nil, include pre-branch
|
|
|
|
|
log entries in output (i.e., pass the `-i' flag). If LIMIT is
|
2021-09-08 09:12:03 +02:00
|
|
|
|
non-nil, get only the last LIMIT log entries."
|
2021-01-23 19:25:34 +01:00
|
|
|
|
(let* ((long-flag (if long (list "-l")))
|
|
|
|
|
(branch-flag (if follow-branches (list "-i")))
|
|
|
|
|
(limit-flag (if limit (list "-m" (number-to-string limit))))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (append (list "filelog") long-flag branch-flag limit-flag (list file))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(p4-lowlevel-command-into-buffer args (or buffer "log"))))
|
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-opened (file)
|
|
|
|
|
"Fetch the string returned by running `p4 opened' on FILE."
|
|
|
|
|
(p4-lowlevel-command-or-error (list "opened" file) nil 'string))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 fstat" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Do NOT need to support any command-line switches.
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-fstat (file &key rev noerror)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Fetch p4 information about FILE (optionally, at REV).
|
2021-02-27 23:09:44 +01:00
|
|
|
|
REV should be in the syntax described by `p4 help revisions'.
|
|
|
|
|
Returns a list of field-name/value elements on success, or raises
|
|
|
|
|
an error on failure. If optional third argument NOERROR is true,
|
|
|
|
|
then returns nil rather than raising an error on failure. If FILE
|
|
|
|
|
matches multiple files, then returns a list of lists of
|
2021-09-08 09:12:03 +02:00
|
|
|
|
field-name/value elements."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev (p4-lowlevel-canonicalize-revision rev))
|
|
|
|
|
(let* ((file-spec (if rev (concat file rev) file))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (list "fstat" file-spec))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(alist (p4-lowlevel-re-assoc
|
|
|
|
|
"^info" (p4-lowlevel-command-or-error args nil nil noerror)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
element line field value values lists)
|
|
|
|
|
(while alist
|
|
|
|
|
(setq element (car alist)
|
|
|
|
|
alist (cdr alist)
|
|
|
|
|
line (cdr element))
|
|
|
|
|
(if (not (string-match " " line))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
t
|
|
|
|
|
(setq field (substring line 0 (match-beginning 0))
|
|
|
|
|
value (substring line (match-end 0)))
|
|
|
|
|
(if (string= field "depotFile") ; we assume depotFile is
|
|
|
|
|
; always first
|
|
|
|
|
(if (not values)
|
|
|
|
|
t
|
|
|
|
|
(setq lists (cons (nreverse values) lists))
|
|
|
|
|
(setq values nil)))
|
|
|
|
|
(setq values (cons (cons field value) values))))
|
2007-08-02 04:27:55 +02:00
|
|
|
|
(when values
|
|
|
|
|
(setq lists (cons (nreverse values) lists)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (= (length lists) 1)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(car lists)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
lists)))
|
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-info ()
|
|
|
|
|
"Return an alist representing the output of `p4 info'."
|
|
|
|
|
(let* ((base-alist (p4-lowlevel-command-or-error (list "info") nil nil t))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(info-elements (p4-lowlevel-re-assoc "^info" base-alist))
|
|
|
|
|
line tag value info-alist element)
|
|
|
|
|
(while info-elements
|
|
|
|
|
(setq element (car info-elements)
|
|
|
|
|
info-elements (cdr info-elements)
|
|
|
|
|
line (cdr element))
|
|
|
|
|
(if (string-match ": " line)
|
|
|
|
|
(setq tag (substring line 0 (match-beginning 0))
|
|
|
|
|
value (substring line (match-end 0))
|
|
|
|
|
info-alist (cons (cons tag value) info-alist))))
|
|
|
|
|
(nreverse info-alist)))
|
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-print (file &key rev output-format quiet)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Retrieve the contents of FILE using `p4 print'.
|
2021-02-27 23:09:44 +01:00
|
|
|
|
If optional REV is non-nil, retrieve that revision, which should
|
|
|
|
|
be in the syntax described by `p4 help revisions'. Optional
|
|
|
|
|
OUTPUT-FORMAT is interpreted as described for
|
|
|
|
|
`p4-lowlevel-command-or-error'. If optional QUIET is non-nil,
|
2021-09-08 09:12:03 +02:00
|
|
|
|
then the `-q' flag is passed to `p4 print'."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev (p4-lowlevel-canonicalize-revision rev))
|
|
|
|
|
(let* ((fullfile (if rev (concat file rev) file))
|
|
|
|
|
(quiet-args (if quiet (list "-q")))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (append (list "print") quiet-args (list fullfile))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(p4-lowlevel-command-or-error args nil output-format)))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 reopen" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-c changelist#", so that we can reopen a file in
|
|
|
|
|
;; the default changelist before submitting it.
|
|
|
|
|
;; Do NOT need to support "-t".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-reopen (file &key changelist)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Call `p4 reopen' on FILE.
|
2021-09-08 09:12:03 +02:00
|
|
|
|
Optional CHANGELIST specifies the changelist to which to move it."
|
|
|
|
|
(let* ((changelist-args (if changelist (list "-c" changelist)))
|
|
|
|
|
(args (append (list "reopen") changelist-args (list file))))
|
2021-01-23 19:19:13 +01:00
|
|
|
|
(p4-lowlevel-command-or-error args)))
|
2017-06-07 09:03:49 +02:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 resolve" command, at least for
|
|
|
|
|
;; the time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-af" (in fact, need to specify it all the time).
|
|
|
|
|
;; Do NOT need to support "-am", "-as", "-at", "-ay".
|
|
|
|
|
;; Do NOT need to support "-f".
|
|
|
|
|
;; Do NOT need to support "-n".
|
|
|
|
|
;; DO need to support "-t" (in fact, need to specify it all the time).
|
|
|
|
|
;; Do NOT need to support "-v".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-resolve (file)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Call `p4 resolve' on FILE.
|
|
|
|
|
Specifies the `-af' and `-t' options to ensure a non-interactive
|
2021-09-08 09:12:03 +02:00
|
|
|
|
resolve. Raises an error if the command fails."
|
|
|
|
|
(p4-lowlevel-command-or-error (list "resolve" "-af" "-t" file)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 revert" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Do NOT need to support "-a".
|
|
|
|
|
;; Do NOT need to support "-c".
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-revert (file)
|
|
|
|
|
"Tell Perforce to revert FILE."
|
|
|
|
|
(p4-lowlevel-command-or-error (list "revert" file)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 submit" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; Only need to support non-interactive use; therefore, only need to
|
|
|
|
|
;; support "p4 submit -i".
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-submit (change-spec)
|
|
|
|
|
"Call `p4 submit' on CHANGE-SPEC, which should be a string or buffer."
|
|
|
|
|
(let (buffer)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (bufferp change-spec)
|
|
|
|
|
(setq buffer change-spec)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
(setq buffer (p4-lowlevel-get-buffer-create
|
|
|
|
|
" *p4-lowlevel-submit-input*"))
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(with-current-buffer buffer
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert change-spec)))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(p4-lowlevel-command-or-error (list "submit" "-i") buffer)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2020-05-13 20:29:35 +02:00
|
|
|
|
;; Here's what we need to support from the "p4 sync" command, at least for the
|
|
|
|
|
;; time being:
|
|
|
|
|
;;
|
|
|
|
|
;; DO need to support "-f".
|
|
|
|
|
;; Do NOT need to support "-n".
|
|
|
|
|
;; DO need to support the specification of a file revision.
|
|
|
|
|
;; Do NOT need to support the specification of multiple files.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-sync (file &key rev force)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
"Call `p4 sync' for FILE.
|
2021-02-27 23:09:44 +01:00
|
|
|
|
If optional REV is specified, use that revision specifier. If
|
2021-09-08 09:12:03 +02:00
|
|
|
|
optional FORCE is non-nil, pass the `-f' flag. "
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev (p4-lowlevel-canonicalize-revision rev))
|
|
|
|
|
(let* ((fullfile (if rev (concat file rev) file))
|
|
|
|
|
(force-args (if force (list "-f")))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (append (list "sync") force-args (list fullfile))))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(p4-lowlevel-command-or-error args)))
|
|
|
|
|
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(cl-defun p4-lowlevel-integrate (from-file to-file &key rev1 rev2 force)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Call `p4 integrate' from FROM-FILE to TO-FILE.
|
|
|
|
|
Optionally a revision range can be specified by REV1 and REV2,
|
|
|
|
|
forcing the integration (i.e., specifying `-f' to `p4 integrate'
|
2021-09-08 09:12:03 +02:00
|
|
|
|
if FORCE is non-nil."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq rev1 (p4-lowlevel-canonicalize-revision rev1)
|
2017-06-07 09:03:49 +02:00
|
|
|
|
rev2 (p4-lowlevel-canonicalize-revision rev2))
|
2021-01-23 19:01:21 +01:00
|
|
|
|
(let* ((force-arg (if force (list "-f")))
|
|
|
|
|
(from-full (if (or rev1 rev2)
|
|
|
|
|
(format "%s%s,%s" from-file (or rev1 "") (or rev2 ""))
|
|
|
|
|
from-file))
|
2021-09-08 09:12:03 +02:00
|
|
|
|
(args (append (list "integrate") force-arg (list from-full to-file))))
|
2021-01-23 19:01:21 +01:00
|
|
|
|
(p4-lowlevel-command-or-error args)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-client-version (&optional noerror)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Return the Perforce client version string from `p4 -V'.
|
2002-01-06 04:45:11 +01:00
|
|
|
|
Returns the third field of the last line of output from `p4 -V', or
|
|
|
|
|
signals an error if the invocation failed. if optional NOERROR is
|
2021-02-27 23:09:44 +01:00
|
|
|
|
non-nil, returns nil instead of signaling an error."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let ((version-string (p4-lowlevel-command-or-error "-V" nil 'string
|
2017-06-07 09:03:49 +02:00
|
|
|
|
noerror)))
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(if (string-match "\n[^/\n]+/[^/\n]+/\\([^\n/]+\\)/.*\n?\\'"
|
2017-06-07 09:03:49 +02:00
|
|
|
|
version-string)
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq version-string (match-string 1 version-string)))
|
|
|
|
|
version-string))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-get-buffer-create (name)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"A specialization of ‘get-buffer-create’.
|
|
|
|
|
Always change ‘default-directory’ of the returned buffer to the
|
|
|
|
|
current ‘default-directory’, even if the buffer already exists.
|
|
|
|
|
Argument NAME is passed directly to `get-buffer-create', so check
|
|
|
|
|
the documentation for that function to see its purpose."
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(let ((buf (get-buffer-create name))
|
|
|
|
|
(caller-default-directory default-directory))
|
2021-02-28 00:11:22 +01:00
|
|
|
|
(with-current-buffer buf
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(setq default-directory caller-default-directory))
|
|
|
|
|
buf))
|
|
|
|
|
|
2020-03-03 06:08:23 +01:00
|
|
|
|
(defun p4-lowlevel-local-clients ()
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Find the clients that are available on the current host."
|
2020-03-03 06:08:23 +01:00
|
|
|
|
(mapcar (lambda (client) (alist-get "client" client nil nil #'string=))
|
|
|
|
|
(seq-filter (lambda (client) (string= (alist-get "Host" client nil nil #'string=)
|
|
|
|
|
(system-name)))
|
|
|
|
|
(p4-lowlevel-clients))))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-clients ()
|
|
|
|
|
"Get the clients available."
|
|
|
|
|
(let ((lines (p4-lowlevel-command-to-alist `("-z" "tag" "clients" "-u" ,(user-login-name)))))
|
|
|
|
|
(seq-reduce (lambda (acc itm)
|
|
|
|
|
(p4-lowlevel-clients-parse-line acc itm))
|
|
|
|
|
lines nil)))
|
|
|
|
|
|
|
|
|
|
(defun p4-lowlevel-clients-parse-line (acc line)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
"Parse a given line for information and store it in ACC.
|
|
|
|
|
ACC is an alist of found keys and their values from the output in
|
|
|
|
|
LINE."
|
2020-03-03 06:08:23 +01:00
|
|
|
|
(pcase (car line)
|
|
|
|
|
((pred (string= "info1"))
|
|
|
|
|
(let ((values (split-string (cdr line) " ")))
|
|
|
|
|
(pcase (car values)
|
|
|
|
|
((pred (string= "client"))
|
|
|
|
|
(cons (list (cons (car values) (cadr values))) acc))
|
|
|
|
|
((or (pred (string= "Update"))
|
|
|
|
|
(pred (string= "Access"))
|
|
|
|
|
(pred (string= "Owner"))
|
|
|
|
|
(pred (string= "Options"))
|
|
|
|
|
(pred (string= "SubmitOptions"))
|
|
|
|
|
(pred (string= "LineEnd"))
|
|
|
|
|
(pred (string= "Root"))
|
|
|
|
|
(pred (string= "Host"))
|
|
|
|
|
(pred (string= "Stream"))
|
|
|
|
|
(pred (string= "Type"))
|
|
|
|
|
(pred (string= "Description")))
|
|
|
|
|
(cons (cons (cons (car values)
|
|
|
|
|
(cadr values))
|
|
|
|
|
(car acc))
|
|
|
|
|
(cdr acc)))
|
|
|
|
|
(_ acc))))
|
|
|
|
|
(`("exit" . ,result)
|
|
|
|
|
(if (not (= result 0))
|
2021-02-27 23:09:44 +01:00
|
|
|
|
(error "Clients returned non-zero exit code")))
|
2020-03-03 06:08:23 +01:00
|
|
|
|
(_ acc)))
|
|
|
|
|
|
2021-01-15 07:39:21 +01:00
|
|
|
|
(cl-defun p4-lowlevel-login (&key password status)
|
|
|
|
|
"Call ‘p4 login’ with arguments.
|
|
|
|
|
PASSWORD should be a string representing the password to use to
|
|
|
|
|
log in to Perforce. If STATUS is specified the status of the
|
|
|
|
|
current ticket is displayed (if there is one) instead."
|
|
|
|
|
(let* ((status-args (and status '("-s")))
|
|
|
|
|
(args (append '("login") status-args)))
|
|
|
|
|
(condition-case err
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(when password (insert password))
|
|
|
|
|
(p4-lowlevel-command-or-error args (current-buffer)))
|
|
|
|
|
(p4-not-logged-in-error
|
|
|
|
|
(unless status (signal (car err) (cdr err)))))))
|
|
|
|
|
|
2021-02-25 07:17:00 +01:00
|
|
|
|
(cl-defun p4-lowlevel-rename (from-file to-file &key changelist preview server-only)
|
|
|
|
|
"Call ‘p4 move -r’ with arguments.
|
|
|
|
|
FROM-FILE is the source file. TO-FILE is the destination.
|
|
|
|
|
CHANGELIST specifies which changelist the operation should be
|
|
|
|
|
added to. If PREVIEW is specified, don’t actually do the
|
|
|
|
|
operation. If SERVER-ONLY is specified it means to do the move
|
|
|
|
|
only on the server and not touch the local files."
|
|
|
|
|
(let* ((changelist-args (and changelist `("-c" ,changelist)))
|
|
|
|
|
(preview-args (and preview '("-n")))
|
|
|
|
|
(server-only-args (and server-only '("-k")))
|
|
|
|
|
(args (append '("move" "-r")
|
|
|
|
|
changelist-args
|
|
|
|
|
preview-args
|
|
|
|
|
server-only-args
|
|
|
|
|
(list from-file to-file))))
|
2022-03-03 05:23:04 +01:00
|
|
|
|
(p4-lowlevel-command-or-error args)
|
|
|
|
|
(when-let* ((buffer (find-buffer-visiting from-file)))
|
|
|
|
|
(with-current-buffer buffer
|
|
|
|
|
(set-visited-file-name to-file)))))
|
2021-02-25 07:17:00 +01:00
|
|
|
|
|
2002-01-06 04:45:11 +01:00
|
|
|
|
(provide 'p4-lowlevel)
|
2021-02-27 23:09:44 +01:00
|
|
|
|
|
|
|
|
|
(provide 'p4-lowlevel)
|
|
|
|
|
|
|
|
|
|
;;; p4-lowlevel.el ends here
|