Examining History

Your Subversion repository is like a time machine. It keeps a record of every change ever committed and allows you to explore this history by examining previous versions of files and directories as well as the metadata that accompanies them. With a single Subversion command, you can check out the repository (or restore an existing working copy) exactly as it was at any date or revision number in the past. However, sometimes you just want to peer into the past instead of going into it.

Several commands can provide you with historical data from the repository:

svn diff

Shows line-level details of a particular change

svn log

Shows you broad information: log messages with date and author information attached to revisions and which paths changed in each revision

svn cat

Retrieves a file as it existed in a particular revision number and displays it on your screen

svn annotate

Retrieves a human-readable file as it existed in a particular revision number, displaying its contents in a tabular form with last-changed information added to each line of the file

svn list

Displays the files in a directory for any given revision

Examining the Details of Historical Changes

We've already seen svn diff before—it displays file differences in unified diff format; we used it to show the local modifications made to our working copy before committing to the repository.

In fact, it turns out that there are three distinct uses of svn diff:

  • Examining local changes

  • Comparing your working copy to the repository

  • Comparing repository revisions

Examining local changes

As we've seen, invoking svn diff with no options will compare your working files to the cached pristine copies in the .svn area:

$ svn diff
Index: rules.txt
===================================================================
--- rules.txt	(revision 3)
+++ rules.txt	(working copy)
@@ -1,4 +1,5 @@
 Be kind to others
 Freedom = Responsibility
 Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$

Comparing working copy to repository

If a single --revision (-r) number is passed, your working copy is compared to the specified revision in the repository:

$ svn diff -r 3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt	(revision 3)
+++ rules.txt	(working copy)
@@ -1,4 +1,5 @@
 Be kind to others
 Freedom = Responsibility
 Everything in moderation
-Chew with your mouth open
+Chew with your mouth closed
+Listen when others are speaking
$

Comparing repository revisions

If two revision numbers, separated by a colon, are passed via --revision (-r), the two revisions are directly compared:

$ svn diff -r 2:3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt	(revision 2)
+++ rules.txt	(revision 3)
@@ -1,4 +1,4 @@
 Be kind to others
-Freedom = Chocolate Ice Cream
+Freedom = Responsibility
 Everything in moderation
 Chew with your mouth open
$

A more convenient way of comparing one revision to the previous revision is to use the --change (-c) option:

$ svn diff -c 3 rules.txt
Index: rules.txt
===================================================================
--- rules.txt	(revision 2)
+++ rules.txt	(revision 3)
@@ -1,4 +1,4 @@
 Be kind to others
-Freedom = Chocolate Ice Cream
+Freedom = Responsibility
 Everything in moderation
 Chew with your mouth open
$

Lastly, you can compare repository revisions even when you don't have a working copy on your local machine, just by including the appropriate URL on the command line:

$ svn diff -c 5 http://svn.example.com/repos/example/trunk/text/rules.txt
…
$

Generating a List of Historical Changes

To find information about the history of a file or directory, use the svn log command. svn log will provide you with a record of who made changes to a file or directory, at what revision it changed, the time and date of that revision, and—if it was provided—the log message that accompanied the commit:

$ svn log
------------------------------------------------------------------------
r3 | sally | 2008-05-15 23:09:28 -0500 (Thu, 15 May 2008) | 1 line

Added include lines and corrected # of cheese slices.
------------------------------------------------------------------------
r2 | harry | 2008-05-14 18:43:15 -0500 (Wed, 14 May 2008) | 1 line

Added main() methods.
------------------------------------------------------------------------
r1 | sally | 2008-05-10 19:50:31 -0500 (Sat, 10 May 2008) | 1 line

Initial import
------------------------------------------------------------------------

Note that the log messages are printed in reverse chronological order by default. If you wish to see a different range of revisions in a particular order or just a single revision, pass the --revision (-r) option:

Table 2.1. Common log requests

Command Description
svn log -r 5:19 Display logs for revisions 5 through 19 in chronological order
svn log -r 19:5 Display logs for revisions 5 through 19 in reverse chronological order
svn log -r 8 Display logs for revision 8 only

You can also examine the log history of a single file or directory. For example:

$ svn log foo.c
…
$ svn log http://foo.com/svn/trunk/code/foo.c
…

These will display log messages only for those revisions in which the named file (or directory) changed.

If you want even more information about a file or directory, svn log also takes a --verbose (-v) option. Because Subversion allows you to move and copy files and directories, it is important to be able to track path changes in the filesystem. So, in verbose mode, svn log will include a list of changed paths in a revision in its output:

$ svn log -r 8 -v
------------------------------------------------------------------------
r8 | sally | 2008-05-21 13:19:25 -0500 (Wed, 21 May 2008) | 1 line
Changed paths:
   M /trunk/code/foo.c
   M /trunk/code/bar.h
   A /trunk/code/doc/README

Frozzled the sub-space winch.

------------------------------------------------------------------------

svn log also takes a --quiet (-q) option, which suppresses the body of the log message. When combined with --verbose (-v), it gives just the names of the changed files.

As of Subversion 1.7, users of the Subversion command-line can also take advantage of a special output mode for svn log which integrates a difference report such as is generated by the svn diff command we introduced earlier. When you invoke svn log with the --diff option, Subversion will append to each revision log chunk in the log report a diff-style difference report. This is a very convenient way to see both the high-level, semantic changes and the line-based modifications of a revision all at the same time!

Beginning with Subversion 1.8, svn log accepts --search and --search-and options. The options allow you to filter the output of svn log based on the search pattern you supply. When using these options, a log message is shown only if a revision's author, date, log message text, or list of changed paths, matches the search pattern.

Browsing the Repository

Using svn cat and svn list, you can view various revisions of files and directories without changing the working revision of your working copy. In fact, you don't even need a working copy to use either one.

Displaying file contents

If you want to examine an earlier version of a file and not necessarily the differences between two files, you can use svn cat:

$ svn cat -r 2 rules.txt
Be kind to others
Freedom = Chocolate Ice Cream
Everything in moderation
Chew with your mouth open
$

You can also redirect the output directly into a file:

$ svn cat -r 2 rules.txt > rules.txt.v2
$

Displaying line-by-line change attribution

Very similar to the svn cat command we discussed in the previous section is the svn annotate command. This command also displays the contents of a versioned file, but it does so using a tabular format. Each line of output shows not only a line of the file's content but also the username, the revision number and (optionally) the datestamp of the revision in which that line was last modified.

When used with a working copy file target, svn annotate will by default show line-by-line attribution of the file as it currently appears in the working copy.

$ svn annotate rules.txt
     1      harry Be kind to others
     3      sally Freedom = Responsibility
     1      harry Everything in moderation
     -          - Chew with your mouth closed
     -          - Listen when others are speaking

Notice that for some lines, there is no attribution provided. In this case, that's because those lines have been modified in the working copy's version of the file. In this way, svn annotate becomes another way for you to see which lines in the file you have changed. You can use the BASE revision keyword (see the section called “Revision Keywords”) to instead see the unmodified form of the file as it resides in your working copy.

$ svn annotate rules.txt@BASE
     1      harry Be kind to others
     3      sally Freedom = Responsibility
     1      harry Everything in moderation
     1      harry Chew with your mouth open

The --verbose (-v) option causes svn annotate to also include on each line the datestamp associated with that line's reported revision number. (This adds a significant amount of width to each line of ouput, so we'll skip the demonstration here.)

As with svn cat, you can also ask svn annotate to display previous versions of the file. This can be a handy trick when, after finding out who most recently modified a particular line of interest in the file, you then wish to see who modified the same line prior to that.

$ svn blame rules.txt -r 2
     1      harry Be kind to others
     1      harry Freedom = Chocolate Ice Cream
     1      harry Everything in moderation
     1      harry Chew with your mouth open

Unlike the svn cat command, the functionality of svn annotate is tied heavily to the idea of lines of text in a human-readable file. As such, if you attempt to run the command on a file that Subversion has determined is not human-readable (per the file's svn:mime-type property—see the section called “File Content Type” for details), you'll get an error message.

$ svn annotate images/logo.png
Skipping binary file (use --force to treat as text): 'images/logo.png'
$

As revealed in the error message, you can use the --force option to disable this check and proceed with the annotation as if the file's contents are, in fact, human-readable and line-based. Naturally, if you force Subversion to try to perform line-based annotation on a nontextual file, you'll get what you asked for: a screenful of nonsense.

$ svn annotate images/logo.png --force
     6      harry \211PNG
     6      harry ^Z
     6      harry 
     7      harry \274\361\MI\300\365\353^X\300…
Tip

Depending on your mood at the time you execute this command and your reasons for doing so, you may find yourself typing svn blame … or svn praise … instead of using the canonical svn annotate command form. That's okay—the Subversion developers anticipated as much, so those particular command aliases work, too!

Finally, as with many of Subversion's informational commands, you can also reference files in your svn annotate command invocations by their repository URLs, allowing access to this information even when you don't have ready access to a working copy.

Listing versioned directories

The svn list command shows you what files are in a repository directory without actually downloading the files to your local machine:

$ svn list http://svn.example.com/repo/project
README
branches/
tags/
trunk/

If you want a more detailed listing, pass the --verbose (-v) flag to get output like this:

$ svn list -v http://svn.example.com/repo/project
  23351 sally                 Feb 05 13:26 ./
  20620 harry            1084 Jul 13  2006 README
  23339 harry                 Feb 04 01:40 branches/
  23198 harry                 Jan 23 17:17 tags/
  23351 sally                 Feb 05 13:26 trunk/

The columns tell you the revision at which the file or directory was last modified, the user who modified it, the size if it is a file, the date it was last modified, and the item's name.

Warning

The svn list command with no arguments defaults to the repository URL of the current working directory, not the local working copy directory. After all, if you want a listing of your local directory, you could use just plain ls (or any reasonable non-Unixy equivalent).

Fetching Older Repository Snapshots

In addition to all of the previous commands, you can use the --revision (-r) option with svn update to take an entire working copy back in time:[8]

# Make the current directory look like it did in r1729.
$ svn update -r 1729
Updating '.':
…
$
Tip

Many Subversion newcomers attempt to use the preceding svn update example to undo committed changes, but this won't work as you can't commit changes that you obtain from backdating a working copy if the changed files have newer revisions. See the section called “Resurrecting Deleted Items” for a description of how to undo a commit.

If you'd prefer to create a whole new working copy from an older snapshot, you can do so by modifying the typical svn checkout command. As with svn update, you can provide the --revision (-r) option. But for reasons that we cover in the section called “Peg and Operative Revisions”, you might instead want to specify the target revision as part of Subversion's expanded URL syntax.

# Checkout the trunk from r1729.
$ svn checkout http://svn.example.com/svn/repo/trunk@1729 trunk-1729
…
# Checkout the current trunk as it looked in r1729.
$ svn checkout http://svn.example.com/svn/repo/trunk -r 1729 trunk-1729
…
$

Lastly, if you're building a release and wish to bundle up your versioned files and directories, you can use svn export to create a local copy of all or part of your repository without any .svn administrative directories included. The basic syntax of this subcommand is identical to that of svn checkout:

# Export the trunk from the latest revision.
$ svn export http://svn.example.com/svn/repo/trunk trunk-export
…
# Export the trunk from r1729.
$ svn export http://svn.example.com/svn/repo/trunk@1729 trunk-1729
…
# Export the current trunk as it looked in r1729. 
$ svn export http://svn.example.com/svn/repo/trunk -r 1729 trunk-1729
…
$


[8] See? We told you that Subversion was a time machine.