Function: vc-rcs-annotate-command
vc-rcs-annotate-command is a byte-compiled function defined in
vc-rcs.el.gz.
Signature
(vc-rcs-annotate-command FILE BUFFER &optional REVISION)
Documentation
Annotate FILE, inserting the results in BUFFER.
Optional arg REVISION is a revision to annotate from.
Source Code
;; Defined in /usr/src/emacs/lisp/vc/vc-rcs.el.gz
(defun vc-rcs-annotate-command (file buffer &optional revision)
"Annotate FILE, inserting the results in BUFFER.
Optional arg REVISION is a revision to annotate from."
(vc-setup-buffer buffer)
;; Aside from the "head revision on the trunk", the instructions for
;; each revision on the trunk are an ordered list of kill and insert
;; commands necessary to go from the chronologically-following
;; revision to this one. That is, associated with revision N are
;; edits that applied to revision N+1 would result in revision N.
;;
;; On a branch, however, (some) things are inverted: the commands
;; listed are those necessary to go from the chronologically-preceding
;; revision to this one. That is, associated with revision N are
;; edits that applied to revision N-1 would result in revision N.
;;
;; So, to get per-line history info, we apply reverse-chronological
;; edits, starting with the head revision on the trunk, all the way
;; back through the initial revision (typically "1.1" or similar),
;; then apply forward-chronological edits -- keeping track of which
;; revision is associated with each inserted line -- until we reach
;; the desired revision for display (which may be either on the trunk
;; or on a branch).
(let* ((tree (with-temp-buffer
(insert-file-contents (vc-rcs-registered file))
(vc-rcs-parse)))
(revisions (cdr (assq 'revisions tree)))
;; The revision N whose instructions we currently are processing.
(cur (cdr (assq 'head (cdr (assq 'headers tree)))))
;; Alist from the parse tree for N.
(meta (cdr (assoc cur revisions)))
;; Point and temporary string, respectively.
p s
;; "Next-branch list". Nil means the desired revision to
;; display lives on the trunk. Non-nil means it lives on a
;; branch, in which case the value is a list of revision pairs
;; (PARENT . CHILD), the first PARENT being on the trunk, that
;; links each series of revisions in the path from the initial
;; revision to the desired revision to display.
nbls
;; "Path-accumulate-predicate plus revision/date/author".
;; Until set, forward-chronological edits are not accumulated.
;; Once set, its value (updated every revision) is used for
;; the text property `:vc-rcs-r/d/a' for inserts during
;; processing of forward-chronological instructions for N.
;; See internal func `r/d/a'.
prda
;; List of forward-chronological instructions, each of the
;; form: (POS . ACTION), where POS is a buffer position. If
;; ACTION is a string, it is inserted, otherwise it is taken as
;; the number of characters to be deleted.
path
;; N+1. When `cur' is "", this is the initial revision.
pre)
(unless revision
(setq revision cur))
(unless (assoc revision revisions)
(error "No such revision: %s" revision))
;; Find which branches (if any) must be included in the edits.
(let ((par revision)
bpt kids)
(while (setq bpt (vc-rcs-branch-part par)
par (vc-rcs-branch-part bpt))
(setq kids (cdr (assq 'branches (cdr (assoc par revisions)))))
;; A branchpoint may have multiple children. Find the right one.
(while (not (string= bpt (vc-rcs-branch-part (car kids))))
(setq kids (cdr kids)))
(push (cons par (car kids)) nbls)))
;; Start with the full text.
(set-buffer buffer)
(insert (cdr (assq 'text meta)))
;; Apply reverse-chronological edits on the trunk, computing and
;; accumulating forward-chronological edits after some point, for
;; later.
(cl-flet ((r/d/a () (vector pre
(cdr (assq 'date meta))
(cdr (assq 'author meta)))))
(while (when (setq pre cur cur (cdr (assq 'next meta)))
(not (string= "" cur)))
(setq
;; Start accumulating the forward-chronological edits when N+1
;; on the trunk is either the desired revision to display, or
;; the appropriate branchpoint for it. Do this before
;; updating `meta' since `r/d/a' uses N+1's `meta' value.
prda (when (or prda (string= (if nbls (caar nbls) revision) pre))
(r/d/a))
meta (cdr (assoc cur revisions)))
;; Edits in the parse tree specify a line number (in the buffer
;; *BEFORE* editing occurs) to start from, but line numbers
;; change as a result of edits. To DTRT, we apply edits in
;; order of descending buffer position so that edits further
;; down in the buffer occur first w/o corrupting specified
;; buffer positions of edits occurring towards the beginning of
;; the buffer. In this way we avoid using markers. A pleasant
;; property of this approach is ability to push instructions
;; onto `path' directly, w/o need to maintain rev boundaries.
(dolist (insn (cdr (assq :insn meta)))
(goto-char (point-min))
(forward-line (1- (pop insn)))
(setq p (point))
(pcase (pop insn)
('k (setq s (buffer-substring-no-properties
p (progn (forward-line (car insn))
(point))))
(when prda
(push `(,p . ,(propertize s :vc-rcs-r/d/a prda)) path))
(delete-region p (point)))
('i (setq s (car insn))
(when prda
(push `(,p . ,(length s)) path))
(insert s)))))
;; For the initial revision, setting `:vc-rcs-r/d/a' directly is
;; equivalent to pushing an insert instruction (of the entire buffer
;; contents) onto `path' then erasing the buffer, but less wasteful.
(put-text-property (point-min) (point-max) :vc-rcs-r/d/a (r/d/a))
;; Now apply the forward-chronological edits for the trunk.
(dolist (insn path)
(goto-char (pop insn))
(if (stringp insn)
(insert insn)
(delete-char insn)))
;; Now apply the forward-chronological edits (directly from the
;; parse-tree) for the branch(es), if necessary. We re-use vars
;; `pre' and `meta' for the sake of internal func `r/d/a'.
(while nbls
(setq pre (cdr (pop nbls)))
(while (progn
(setq meta (cdr (assoc pre revisions))
prda nil)
(dolist (insn (cdr (assq :insn meta)))
(goto-char (point-min))
(forward-line (1- (pop insn)))
(pcase (pop insn)
('k (delete-region
(point) (progn (forward-line (car insn))
(point))))
('i (insert (propertize
(car insn)
:vc-rcs-r/d/a
(or prda (setq prda (r/d/a))))))))
(prog1 (not (string= (if nbls (caar nbls) revision) pre))
(setq pre (cdr (assq 'next meta)))))))))
;; Lastly, for each line, insert at bol nicely-formatted history info.
;; We do two passes to collect summary information used to minimize
;; the annotation's usage of screen real-estate: (1) Consider rendered
;; width of revision plus author together as a unit; and (2) Omit
;; author entirely if all authors are the same as the user.
(let ((ht (make-hash-table :test 'eq))
(me (user-login-name))
(maxw 0)
(all-me t)
rda w a)
(goto-char (point-max))
(while (not (bobp))
(forward-line -1)
(setq rda (get-text-property (point) :vc-rcs-r/d/a))
(unless (gethash rda ht)
(setq a (aref rda 2)
all-me (and all-me (string= a me)))
(puthash rda (setq w (+ (length (aref rda 0))
(length a)))
ht)
(setq maxw (max w maxw))))
(let ((padding (make-string maxw 32)))
(cl-flet ((pad (w) (substring-no-properties padding w))
(render (rda &rest ls)
(propertize
(apply #'concat
(format-time-string "%Y-%m-%d" (aref rda 1))
" "
(aref rda 0)
ls)
:vc-annotate-prefix t
:vc-rcs-r/d/a rda)))
(maphash
(if all-me
(lambda (rda w)
(puthash rda (render rda (pad w) ": ") ht))
(lambda (rda w)
(puthash rda (render rda " " (pad w) " " (aref rda 2) ": ") ht)))
ht)))
(while (not (eobp))
(insert (gethash (get-text-property (point) :vc-rcs-r/d/a) ht))
(forward-line 1))))