Index: src/finfo.c
==================================================================
--- src/finfo.c
+++ src/finfo.c
@@ -345,11 +345,12 @@
   blob_appendf(&title, "History of ");
   hyperlinked_path(zFilename, &title, 0);
   @ <h2>%b(&title)</h2>
   blob_reset(&title);
   pGraph = graph_init();
-  @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
+  @ <div id="canvas" style="position:relative;width:1px;height:1px;"
+  @  onclick="clickOnGraph(event)"></div>
   @ <table id="timelineTable" class="timelineTable">
   while( db_step(&q)==SQLITE_ROW ){
     const char *zDate = db_column_text(&q, 0);
     const char *zCom = db_column_text(&q, 1);
     const char *zUser = db_column_text(&q, 2);
@@ -370,11 +371,12 @@
     if( uBg ){
       zBgClr = hash_color(zUser);
     }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
       zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
     }
-    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr, 0);
+    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr,
+                         zUuid, 0);
     if( memcmp(zDate, zPrevDate, 10) ){
       sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
       @ <tr><td>
       @   <div class="divider">%s(zPrevDate)</div>
       @ </td><td></td><td></td></tr>
@@ -446,8 +448,8 @@
       @ <div id="grbtm" style="width:%d(pGraph->mxRail*20+30)px;"></div>
       @     </td><td></td></tr>
     }
   }
   @ </table>
-  timeline_output_graph_javascript(pGraph, 0);
+  timeline_output_graph_javascript(pGraph, 0, 1);
   style_footer();
 }

Index: src/graph.c
==================================================================
--- src/graph.c
+++ src/graph.c
@@ -34,10 +34,11 @@
   int rid;                    /* The rid for the check-in */
   i8 nParent;                 /* Number of parents */
   int *aParent;               /* Array of parents.  0 element is primary .*/
   char *zBranch;              /* Branch name */
   char *zBgClr;               /* Background Color */
+  char zUuid[17];             /* Check-in for file ID */
 
   GraphRow *pNext;            /* Next row down in the list of all rows */
   GraphRow *pPrev;            /* Previous row */
   
   int idx;                    /* Row index.  First is 1.  0 used for "none" */
@@ -175,10 +176,11 @@
   int rid,             /* RID for the check-in */
   int nParent,         /* Number of parents */
   int *aParent,        /* Array of parents */
   const char *zBranch, /* Branch for this check-in */
   const char *zBgClr,  /* Background color. NULL or "" for white. */
+  const char *zUuid,   /* SHA1 hash of the object being graphed */
   int isLeaf           /* True if this row is a leaf */
 ){
   GraphRow *pRow;
   int nByte;
 
@@ -188,10 +190,12 @@
   pRow = (GraphRow*)safeMalloc( nByte );
   pRow->aParent = (int*)&pRow[1];
   pRow->rid = rid;
   pRow->nParent = nParent;
   pRow->zBranch = persistBranchName(p, zBranch);
+  if( zUuid==0 ) zUuid = "";
+  sqlite3_snprintf(sizeof(pRow->zUuid), pRow->zUuid, "%s", zUuid);
   pRow->isLeaf = isLeaf;
   memset(pRow->aiRiser, -1, sizeof(pRow->aiRiser));
   if( zBgClr==0 || zBgClr[0]==0 ) zBgClr = "white";
   pRow->zBgClr = persistBranchName(p, zBgClr);
   memcpy(pRow->aParent, aParent, sizeof(aParent[0])*nParent);

Index: src/timeline.c
==================================================================
--- src/timeline.c
+++ src/timeline.c
@@ -211,11 +211,12 @@
   if( tmFlags & TIMELINE_GRAPH ){
     pGraph = graph_init();
     /* style is not moved to css, because this is
     ** a technical div for the timeline graph
     */
-    @ <div id="canvas" style="position:relative;width:1px;height:1px;"></div>
+    @ <div id="canvas" style="position:relative;width:1px;height:1px;"
+    @  onclick="clickOnGraph(event)"></div>
   }
   db_static_prepare(&qbranch,
     "SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0 AND rid=:rid",
     TAG_BRANCH
   );
@@ -311,11 +312,12 @@
       db_bind_int(&qparent, ":rid", rid);
       while( db_step(&qparent)==SQLITE_ROW && nParent<32 ){
         aParent[nParent++] = db_column_int(&qparent, 0);
       }
       db_reset(&qparent);
-      gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr, zBgClr, isLeaf);
+      gidx = graph_add_row(pGraph, rid, nParent, aParent, zBr, zBgClr,
+                           zUuid, isLeaf);
       db_reset(&qbranch);
       @ <div id="m%d(gidx)"></div>
     }
     @</td>
     if( zBgClr && zBgClr[0] ){
@@ -491,18 +493,22 @@
       @ </td><td></td></tr>
     }
   }
   @ </table>
   if( fchngQueryInit ) db_finalize(&fchngQuery);
-  timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0);
+  timeline_output_graph_javascript(pGraph, (tmFlags & TIMELINE_DISJOINT)!=0, 0);
 }
 
 /*
 ** Generate all of the necessary javascript to generate a timeline
 ** graph.
 */
-void timeline_output_graph_javascript(GraphContext *pGraph, int omitDescenders){
+void timeline_output_graph_javascript(
+  GraphContext *pGraph,     /* The graph to be displayed */
+  int omitDescenders,       /* True to omit descenders */
+  int fileDiff              /* True for file diff.  False for check-in diff */
+){
   if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
     GraphRow *pRow;
     int i;
     char cSep;
     @ <script  type="text/JavaScript">
@@ -535,10 +541,11 @@
     **   mi:  "merge-in".  An array of integer x-coordinates from which
     **        merge arrows should be drawn into this node.  If the value is
     **        negative, then the x-coordinate is the absolute value of mi[]
     **        and a thin merge-arrow descender is drawn to the bottom of
     **        the screen.
+    **    h:  The SHA1 hash of the object being graphed
     */
     cgi_printf("var rowinfo = [\n");
     for(pRow=pGraph->pFirst; pRow; pRow=pRow->pNext){
       int mo = pRow->mergeOut;
       if( mo<0 ){
@@ -576,11 +583,11 @@
           cgi_printf("%c%d", cSep, mi);
           cSep = ',';
         }
       }
       if( cSep=='[' ) cgi_printf("[");
-      cgi_printf("]}%s", pRow->pNext ? ",\n" : "];\n");
+      cgi_printf("],h:\"%s\"}%s", pRow->zUuid, pRow->pNext ? ",\n" : "];\n");
     }
     cgi_printf("var nrail = %d\n", pGraph->mxRail+1);
     graph_free(pGraph);
     @ var canvasDiv = gebi("canvas");
 #if 0
@@ -598,10 +605,11 @@
     @   n.style.top = y0+"px";
     @   n.style.width = w+"px";
     @   n.style.height = h+"px";
     @   n.style.backgroundColor = color;
     @   canvasDiv.appendChild(n);
+    @   return n;
     @ }
     @ function absoluteY(id){
     @   var obj = gebi(id);
     @   if( !obj ) return;
     @   var top = 0;
@@ -699,10 +707,12 @@
     @     }else{
     @       drawThinArrow(y0,mx,p.x-5);
     @     }
     @   }
     @ }
+    @ var selBox = null;
+    @ var selRow = null;
     @ function renderGraph(){
     @   var canvasDiv = gebi("canvas");
     @   while( canvasDiv.hasChildNodes() ){
     @     canvasDiv.removeChild(canvasDiv.firstChild);
     @   }
@@ -736,10 +746,41 @@
     @     };
     @   }
 #endif
     @   for(var i in rowinfo){
     @     drawNode(rowinfo[i], left, btm);
+    @   }
+    @   if( selRow!=null ) clickOnRow(selRow);
+    @ }
+    @ function clickOnGraph(event){
+    @   var x=event.clientX-absoluteX("canvas")+window.pageXOffset;
+    @   var y=event.clientY-absoluteY("canvas")+window.pageYOffset;
+    @   for(var i in rowinfo){
+    @     p = rowinfo[i];
+    @     if( p.y<y-10 ) continue;
+    @     if( p.y>y+10 ) break;
+    @     if( p.x>x-10 && p.x<x+10 ){
+    @       clickOnRow(p);
+    @       break;
+    @     }
+    @   }
+    @ }
+    @ function clickOnRow(p){
+    @   if( selRow==null ){
+    @     selBox = drawBox("red",p.x-2,p.y-2,p.x+3,p.y+3);
+    @     selRow = p;
+    @   }else if( selRow==p ){
+    @     var canvasDiv = gebi("canvas");
+    @     canvasDiv.removeChild(selBox);
+    @     selBox = null;
+    @     selRow = null;
+    @   }else{
+    if( fileDiff ){
+      @     location.href="%R/fdiff?v1="+selRow.h+"&v2="+p.h;
+    }else{
+      @     location.href="%R/vdiff?from="+selRow.h+"&to="+p.h;
+    }
     @   }
     @ }
     @ var lastId = "m"+rowinfo[rowinfo.length-1].id;
     @ var lastY = 0;
     @ function checkHeight(){