Tuesday, January 1, 2008

For All Your Finger-Pointing Needs

While working with a large codebase, I often want to find the origin of a particular line. Subversion offers a tool, annotate (aka blame, aka praise), which displays the author and revision for every line in a file, indicating who made the last change to a line. However, the last change is often not very useful; it was a minor change as a result of some other change you're not interested in, or the code was moved around due to refactoring, and you need to go back even further.

When I need to do this, I find myself doing a sequence of:

1. svn blame FILE | less; find the revision N where the line was last changed
2. svn log -rN FILE | less; if the change is interesting, read the commit log for the file
3. svn blame FILE@N-1 | less; using Subversion's little-known pinned revision syntax, find the previous time the line was changed
4. Using N-1 as the new N, return to step 2.

: Pretty much any Subversion command that takes a path argument can be given PATH@REVISION instead to use the version of the path at a particular revision. This is great for diff and cat as well as blame. I use it for working with deleted files and branches and diffing a branch against trunk.

I've put together a rough version of a tool to make this easier; it's at /trunk/blamegame in my repository, which is here for browsing with ViewVC, or it can be checked out with svn co http://zevils.com/svn/trunk/blamegame blamegame . It still needs some fine-tuning and documentation, but invoke it like blamegame FILE LINE (where FILE is a URL or the path to a file in a Subversion working copy) to start looking at a particular line of a file. You can navigate and search the file using a less-like interface. To drill down to the previous change to a line, hit r and then enter the line number. l, o, n, and m switch between viewing the commit log, the changed parts of the old file, the changed parts of the new file, and (the default) the diff. If you need to change the path you're looking at (for instance, to jump inside a branch), use the p command. h will show the available commands.

Let me know what you think.