Show tree diffs

= How Do I Show Changes to the Whole Tree for a Single Commit? =

CVS
This is where the divide between CVS and Everybody Else really starts to become clear: because CVS tracks individual files and not the whole tree, this task is not so much impossible with CVS as it is meaningless. In particular, the very idea of a single commit applying to the whole tree is meaningless, since CVS revision history is per-file. Even when you run, say cvs ci -m"..." and checkin N files with a common log message, that's still N separate revisions.

Recent CVS versions (1.12.x) have added commitid, which identifies a group of revisions created together (e.g. with one invocation of cvs ci). This sounds promising, but I don't see any way to use commitid to compare two points of a tree.

The only way I know of to compare two points of a tree is with tagging. In particular, if you adopt the clearly insane strategy of tagging the tree before and after every significant checkin -- say, every bug in your bug database -- then you can perform this task.

For example, say you're working in project, a top-level subdirectory of your repository. When you are ready to checkin the fix for bug #123, you could run this sequence of commands:

cvs rtag start-bug-123 project cvs ci -m"Fix bug #123 by ..." cvs rtag end-bug-123 project

Later, you can view this checkin by comparing the tags:

cvs diff -r start-bug-123 -r end-bug-123

There are couple of caveats here:


 * I have used the rtag command rather than tag in case your change adds or removes files. Tagging a working directory with locally-added or removed files does not work very well.  rtag ignores your working directory and applies to the repository, dodging this problem.
 * This is not an atomic operation: if another developer is doing the same thing concurrently, you will have a garbled mess. CVS supports repository-side locks, so you could always cook up a complicated system that locks the tree, tags, commits, tags, and unlocks the tree.  Or you could use a VC system that tracks whole trees and supports atomic commit.

Incidentally, I called this strategy "clearly insane". My justification for this is the way CVS stores tags: because the ,v files in the repository are plain text with tags near the top, CVS has to rewrite a file to tag it. When you tag your whole tree, then, CVS has to rewrite every file in the repository, and each file grows by the size of the tag. If you tag every significant checkin, then eventually CVS' performance will be dominated by the cost of reading and writing all those tags. Each tag you add incrementally increases the cost of every future operation, including adding more tags. Tag-heavy CVS workflows are thus not sustainable in the long run.

git
Assuming the SHA1 identifier of the commit in question is nnnn:

git diff nnnn~1 nnnn

If you don't happen to have the 40-byte commit ID on the tip of your tongue, you'll probably use git-log</tt> to find it. And if you're doing that, you might as well see the diffs along with the rest of the history:

git log -p

This, of course, makes it unnecessary to run git-diff</tt>.

If you happened to tag the commit in question, you only have to remember the tag name:

git diff ~1

Mercurial
Assuming rev identifies the changeset in question (it could be a revision number or changeset ID): hg diff -c rev

Subversion
Assuming the commit in question created revision rev</tt>, you have two ways of showing the diff. The easiest way is to use the -c</tt> (--change</tt>) argument:

svn diff -c rev

The -c</tt> flag was originally added during the Subversion 1.5 timeframe to allow the cherry-picking of individual changes during svn merge</tt>, so svn diff</tt> and svn log</tt> inherited this "friendly" cherry-picking syntax. (However, unlike svn merge</tt> and svn log</tt>, svn diff</tt> can only handle one range at a time.)

The older way of doing things, guaranteed to work even on the most ancient and outdated Subversion clients, is to specify the revision range as a half-open interval:

svn diff -r prev:rev

Where prev</tt> is 1 less than rev</tt>. This will give you precisely the change that turned prev</tt> into rev</tt>&mdash;in other words, the change in revision <tt>rev</tt>.