;;; p4-browse.el --- minor mode for browsing revisions of a given file
;; Author: Paul Du Bois <dubois@geoworks.com>
;; Maintainer: dubois@infinite-machine.com
;; $Id: //depot/tools/lisp/p4-browse.el#2 $
;;; Commentary:
;; This is a minor mode for viewing revisions of a file. It binds
;; keys that bring up diffs and changelist descriptions for the currently
;; viewed revision of the file.
;; diffs and changelist buffers automatically update themselves when
;; the user moves to a different revision.
;; The single user entry point is the command `p4-browse-file-revisions'.
;;; Code:
(require 'p4)
(defun p4-browse-file-revisions (&optional file)
"Begin browsing revisions of FILE.
Interactively, browses the current buffer's file."
(interactive)
(or file (setq file buffer-file-name))
(p4b-display-revision file "have"))
;;; ----------------------------------------------------------------------
;;; minor mode
;;; ----------------------------------------------------------------------
(defvar p4-browse-minor-mode nil
"Minor mode for browsing through revisions of a file.")
(make-variable-buffer-local 'p4-browse-minor-mode)
;; just here for documentation string
(defun p4-browse-minor-mode ()
"Minor mode for browsing through revisions of a file.
\\[p4b-revision-previous] \\[p4b-revision-next] go back and forward one revision, respectively.
\\[p4b-revision-goto] will jump to a given revision.
\\[p4b-describe-changelist] displays the changelist that created the current revision.
\\[p4b-diff-with-previous] displays the differences from that changelist.
\\[p4b-quit] quits browse mode.")
(defvar p4-browse-map nil)
(if p4-browse-map nil
(let ((map (make-keymap)))
(suppress-keymap map 'nodigits)
(define-key map "c" 'p4b-describe-changelist)
(define-key map "d" 'p4b-diff-with-previous)
(define-key map "g" 'p4b-revision-goto)
(define-key map "q" 'p4b-quit)
(define-key map "<" 'p4b-revision-previous)
(define-key map ">" 'p4b-revision-next)
(define-key map "?" 'p4b-help)
(setq p4-browse-map map)
(setq minor-mode-map-alist
(cons (cons 'p4-browse-minor-mode p4-browse-map)
(delq 'p4-browse-minor-mode minor-mode-map-alist)))))
(or (assq 'p4-browse-minor-mode minor-mode-alist)
(setq minor-mode-alist
(cons '(p4-browse-minor-mode " P4-Browse") minor-mode-alist)))
;; Name of buffer to use for "p4 describe" output
(defvar p4b-description-buffer "*P4 browse: describe*")
(defvar p4b-diff-buffer "*P4 browse: diff2*")
;;; ----------------------------------------------------------------------
;;; User-level commands
;;; ----------------------------------------------------------------------
(defun p4b-help ()
"Display simple help for p4-browse minor mode.
\\{p4-browse-map}"
(interactive)
(message "< > g: Prev/next/goto revision c d ?: Show changelist/diff/help q: quit"))
(defun p4b-describe-changelist ()
"View the changelist description associated with the current revision."
(interactive)
(let ((inhibit-read-only t)
(change (nth 2 (p4b-parse-header))))
(p4-exec-p4-fast p4b-description-buffer "describe" "-s" change)
(save-excursion
(set-buffer p4b-description-buffer)
(set-buffer-modified-p nil)
(toggle-read-only t)
(view-mode-enter nil 'p4b-kill-buffer))
;; play nicely with auto-update
(if (null (get-buffer-window p4b-description-buffer 'visible))
(p4-display-output p4b-description-buffer))))
(defun p4b-kill-buffer (buffer-name)
(let ((buffer (get-buffer buffer-name)))
(if (null buffer) nil
(delete-windows-on buffer)
(kill-buffer buffer))))
(defun p4b-diff-with-previous ()
"Show diff that lead to current revision."
(interactive)
(let* ((header (p4b-parse-header))
(file (nth 0 header))
(revision (string-to-number (nth 1 header)))
(buf p4b-diff-buffer))
(p4-exec-p4-fast buf "diff2"
(format "%s#%d" file (1- revision))
(format "%s#%d" file revision))
(save-excursion
(set-buffer buf)
(run-hooks 'p4-diff-hook)
(goto-char (point-min)))
;; play nicely with auto-update
(if (null (get-buffer-window buf 'visible))
(p4-display-output buf "Diff"))))
(defun p4b-revision-goto (arg)
"View the passed revision of the file.
Prefix argument is the revision to view."
(interactive
(list (if current-prefix-arg
(prefix-numeric-value current-prefix-arg)
;; read string instead of number so user can ask for head/have
(read-string "Revision: "))))
(let ((file (nth 0 (p4b-parse-header))))
(p4b-display-revision file arg 'reuse-window)))
(defun p4b-revision-previous (arg)
"View the previous revision of the file.
Prefix argument is the number of revisions to go back."
(interactive "p")
(p4b-revision-next (- arg)))
(defun p4b-revision-next (arg)
"View the next revision of the file
Prefix argument is the number of revisions to go forward."
(interactive "p")
(let ((file (nth 0 (p4b-parse-header)))
(revision (+ arg (string-to-number (nth 1 (p4b-parse-header))))))
(p4b-display-revision file revision 'reuse-window)))
(defun p4b-quit ()
"Quit and clean up any spare buffers"
(interactive)
(mapcar 'p4b-kill-buffer
(list (current-buffer)
p4b-description-buffer
p4b-diff-buffer)))
;;; ----------------------------------------------------------------------
;;; Internals
;;; ----------------------------------------------------------------------
(defun p4b-parse-header ()
(save-excursion
(goto-char (point-min))
(if (not (looking-at "\\(.*\\)#\\(\\sw+\\).*change \\(\\sw+\\)"))
(error "Can't find header")
(list (match-string 1) (match-string 2) (match-string 3)))))
(defun p4b-display-revision (file revision &optional reuse-window)
(catch 'abort
(let* ((dir default-directory)
;; FILE might be in //depot syntax
(file-path (concat dir "/" (file-name-nondirectory file)))
(buf (get-buffer-create "*tmp revision*"))
(buffer-name (concat (file-name-nondirectory file) "#" revision))
(context (p4-make-window-context (selected-window))))
(message "Fetching revision %s..." revision)
(p4-exec-p4-fast buf "print" (concat file "#" revision))
(message "Fetching revision %s... done." revision)
(save-excursion
(set-buffer buf)
(goto-char (point-min))
;; If the header is not valid, it's probably an error from Perforce
(condition-case nil
(p4b-parse-header)
(error (goto-char 1) (end-of-line)
(message "%s" (buffer-substring 1 (point)))
(throw 'abort nil)))
;; normal-mode relies on the file name to choose a mode
(set-visited-file-name file-path t)
(normal-mode)
(set-visited-file-name nil)
;; set default-dir so p4 client can find P4CONFIG files
(setq default-directory dir)
(setq p4-browse-minor-mode t)
(if (get-buffer buffer-name)
(kill-buffer buffer-name))
(rename-buffer buffer-name)
(set-buffer-modified-p nil)
(toggle-read-only t))
(if reuse-window
(progn
(kill-buffer (current-buffer))
(switch-to-buffer buf)
(p4-restore-window-context (selected-window) context)
(if (get-buffer-window p4b-description-buffer 'visible)
(p4b-describe-changelist))
(if (get-buffer-window p4b-diff-buffer 'visible)
(p4b-diff-with-previous)))
(p4-display-output buf)
(p4b-help)))))
# |
Change |
User |
Description |
Committed |
|
#1
|
301 |
paul_dubois |
Initial checkpoint of p4.el |
|
|