The CVS version control system has a basic operation called
annotate
, which causes it to print out a copy of any given file with each line marked with the revision
number, date, and author of the last change that affected it. Here is a snippet:
1.103 (neil 03-Jan-02): /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is 1.103 (neil 03-Jan-02): unsigned. */ 1.21 (ghazi 25-Feb-99): static HOST_WIDEST_INT 1.1 (law 11-Aug-97): left_shift (pfile, a, unsignedp, b) 1.1 (law 11-Aug-97): cpp_reader *pfile; 1.21 (ghazi 25-Feb-99): HOST_WIDEST_INT a; 1.37 (zack 07-Mar-00): unsigned int unsignedp; 1.21 (ghazi 25-Feb-99): unsigned HOST_WIDEST_INT b;
This is incredibly useful, but could be made even more so in the context of an interactive tool for exploring a file's history. Here are some ways this could be done.
The annotations can be distracting, largely because they take up a great deal of space at the left of the (text) display. With the hypothetical tool, we have a graphical interface, which means they can be dimmed a bit. Also, all of the punctuation can be removed, although a dividing line might be nice.
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
The tool could even hide the version numbers, and offer access to the information they can get you in another way. This would fit well with a different underlying version control system where revision numbers are less important (Subversion, perhaps).
neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
neil | 03-Jan-02 | unsigned. */ |
ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
law | 11-Aug-97 | cpp_reader *pfile; |
ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
zack | 07-Mar-00 | unsigned int unsignedp; |
ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
If we know that a revision, say 1.37, is of particular interest, we can ask the tool to make it stand out, which facilitates scrolling through the document for changes in that revision.
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
To show an overview of the file, the tool could shrink most of the code so that only key lines are readable. For instance, with this code, we could collapse everything but the lines that name functions. Here's a bigger chunk of the same file with this treatment applied, and the same change highlighted as in the last example.
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
1.1 | law | 11-Aug-97 | { |
1.21 | ghazi | 25-Feb-99 | if (b >= HOST_BITS_PER_WIDEST_INT) |
1.1 | law | 11-Aug-97 | { |
1.1 | law | 11-Aug-97 | if (! unsignedp && a != 0) |
1.1 | law | 11-Aug-97 | integer_overflow (pfile); |
1.1 | law | 11-Aug-97 | return 0; |
1.1 | law | 11-Aug-97 | } |
1.1 | law | 11-Aug-97 | else if (unsignedp) |
1.21 | ghazi | 25-Feb-99 | return (unsigned HOST_WIDEST_INT) a << b; |
1.1 | law | 11-Aug-97 | else |
1.1 | law | 11-Aug-97 | { |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT l = a << b; |
1.1 | law | 11-Aug-97 | if (l >> b != a) |
1.1 | law | 11-Aug-97 | integer_overflow (pfile); |
1.1 | law | 11-Aug-97 | return l; |
1.1 | law | 11-Aug-97 | } |
1.1 | law | 11-Aug-97 | } |
1.1 | law | 11-Aug-97 | |
1.103 | neil | 03-Jan-02 | /* Handle shifting A right by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | right_shift (pfile, a, unsignedp, b) |
1.7 | ghazi | 13-May-98 | cpp_reader *pfile ATTRIBUTE_UNUSED; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
1.1 | law | 11-Aug-97 | { |
1.21 | ghazi | 25-Feb-99 | if (b >= HOST_BITS_PER_WIDEST_INT) |
1.21 | ghazi | 25-Feb-99 | return unsignedp ? 0 : a >> (HOST_BITS_PER_WIDEST_INT - 1); |
1.1 | law | 11-Aug-97 | else if (unsignedp) |
1.21 | ghazi | 25-Feb-99 | return (unsigned HOST_WIDEST_INT) a >> b; |
1.1 | law | 11-Aug-97 | else |
1.1 | law | 11-Aug-97 | return a >> b; |
1.1 | law | 11-Aug-97 | } |
If we know that a revision, say 1.21, is of no interest at all (perhaps it only adjusts formatting) we can ask the tool to ignore that revision. This is not the same as displaying only revisions 1.1 through 1.20. The lines modified in rev 1.21 revert to their state before that revision, but the lines modified in later revisions stay the same. Optionally, lines that would be different if something wasn't being hidden could be indicated.
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.1 | law | 11-Aug-97 | static long |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | long a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.1 | law | 11-Aug-97 | unsigned long b; |
or
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.1 | law | 11-Aug-97 | static long |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | long a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.1 | law | 11-Aug-97 | unsigned long b; |
This can of course be combined with highlighting.
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.1 | law | 11-Aug-97 | static long |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | long a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.1 | law | 11-Aug-97 | unsigned long b; |
Often it is useful to see what changed in the most recent revision to the file. The highlighting feature could be used for that, but it would be better for it to be an independent function. There are several different ways to display changes, none of which are perfect for all situations.
This feature is critically associated with the ability to walk forward and backward in time, which would be accessible by way of a scrollbar or similar control. Suppose we are looking at revision 1.20 of this snippet. That would look like this:
1.1 | law | 11-Aug-97 | static long |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | long a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.1 | law | 11-Aug-97 | unsigned long b; |
since the code here had not been changed at all since the file was created. However, stepping forward one revision, to 1.21, reveals a change. The simplest way to show this is to mark all the lines which have changed.
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
We may want to keep the old lines visible for comparison.
1.1 | law | 11-Aug-97 | static long |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | long a; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.1 | law | 11-Aug-97 | unsigned long b; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
We'd also like to see the comment attached to the current revision, probably in a separate pane.
* cppexp.c (left_shift, right_shift, parse_charconst, COMPARE, cpp_parse_expr): Replace uses of long/HOST_BITS_PER_LONG with HOST_WIDEST_INT/HOST_BITS_PER_WIDEST_INT.
For a change like this, it might be helpful to narrow down the changed text a bit more.
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
or
1.1 | law | 11-Aug-97 | |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.1 | law | 11-Aug-97 | |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.1 | law | 11-Aug-97 | |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
Advancing another revision causes all the markings to vanish, since this area was not changed in revision 1.22. Somewhere else in the file, a new set of change markings has appeared.
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.1 | law | 11-Aug-97 | int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
It may be useful to distinguish pure insertions or deletions from changes (as, e.g. diff -c
does).
For instance, when we get up to revision 1.103, some lines are inserted:
1.103 | neil | 03-Jan-02 | /* Handle shifting A left by B bits. UNSIGNEDP is non-zero if A is |
1.103 | neil | 03-Jan-02 | unsigned. */ |
1.21 | ghazi | 25-Feb-99 | static HOST_WIDEST_INT |
1.1 | law | 11-Aug-97 | left_shift (pfile, a, unsignedp, b) |
1.1 | law | 11-Aug-97 | cpp_reader *pfile; |
1.21 | ghazi | 25-Feb-99 | HOST_WIDEST_INT a; |
1.37 | zack | 07-Mar-00 | unsigned int unsignedp; |
1.21 | ghazi | 25-Feb-99 | unsigned HOST_WIDEST_INT b; |
The simple scrollbar arrangement for moving through history works great as long as time remains linear. However, any revision may anchor several branches, forcing the tool to decide which one to follow when the user moves forward in time. Likewise, a revision can be a merge point, with two parents; three-way or higher merges are unlikely, but the user interface should cope. (In CVS, branches are difficult to use and therefore rare; however, they are far more common with systems that make branching and merging easy.)
One possible approach is to display the version graph in another pane, and allow the user to select a path through it. The scrollbar then advances or retreats along that path. The interface for interacting with the graph is a topic in itself, of course.