Thursday, January 8, 2009

Who changed the line your working on last?

My team use Perforce for version control, and I recently wrote an implementation of 'blame', also known as 'praise'. When editing a file that is in Perforce, I can run p4-blame, and it will figure out the last change list that this file, and the line that point is on, was changed. It then opens a buffer containing that change list. This is often useful while working, and is so fast when accessed from the editor, it makes it trivial to do.

First, I wanted it to open two buffers, one with the annotated file (the file you're working on with the change list number at the start of each line), and another with the changelist you're interested in. When running the command multiple times I want these buffers to be deleted. So I added a helper function that creates a named buffer, and if it exists already, makes sure it is empty.


(defun get-buffer-create-and-clear(name)
(interactive "sName: ")
(let ((buffer (get-buffer-create name)))
(goto-line 1 buffer)
(erase-buffer)
buffer))


Ok now for the blame code. As you can see, mostly what it's doing is called the P4 command line and passing the output to buffers. Firstly I call annotate to get the change list numbered lines. Then I go to that file and go to the same line that point was at. (number-at-point) returns that number. Finally I call p4 describe to get the changelist information.


(defun p4-blame()
"blame the current line of code... it seeks out the change list of the last person
to change this line. The arguments to annotate, which dumps the source file with the
change list number it was last altered in, are q (no header) i (follow branches) and
c for change numbers rather than revision numbers."
(interactive)
(let* ((line (line-number-at-pos))
(source-file (buffer-file-name))
(annotate-buffer (get-buffer-create-and-clear "*p4-annotate*")))
(shell-command (format "p4.exe annotate -q -i -c %s" source-file) annotate-buffer)
(goto-line line annotate-buffer)
(let ((change-number (number-at-point)))
(if change-number
(let ((blame-buffer (get-buffer-create-and-clear "*p4-blame*")))
(shell-command (format "p4.exe describe %d" change-number) blame-buffer))
(message "error: could not get change list number for line %d" line)))))



The error detection is a bit sloppy here. I don't know how to figure out if the shell-command failed. But it's not a big deal because if you do this in a file that is not in P4 then it will simply print a message saying could not get change list.

Ideally I could send the error output to a buffer, and then parse it to see if it's empty.

1 comment:

anthony said...

this is just what i was looking for, thanks for sharing :)