Index: src/info.c
==================================================================
--- src/info.c
+++ src/info.c
@@ -675,16 +675,18 @@
         fossil_free(zUrl);
       }
       @ </td></tr>
       @ <tr><th>Other&nbsp;Links:</th>
       @   <td>
-      @     %z(href("%R/tree?ci=%!S",zUuid))files</a>
-      @   | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
-      @   | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
-      @   | %z(href("%R/artifact/%!S",zUuid))manifest</a>
+      @     %z(href("%R/tree?ci=%S",zUuid))files</a>
+      @   | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
+      @   | %z(href("%R/tree?nofiles&type=tree&ci=%S",zUuid))folders</a>
+      @   | %z(href("%R/artifact/%S",zUuid))manifest</a>
+      @   | <a href="%s(g.zTop)/vdiff?from=pbranch:%S(zUuid)&to=%S(zUuid)">
+      @           changes over parent branch</a>
       if( g.anon.Write ){
-        @   | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
+        @   | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
       }
       @   </td>
       @ </tr>
       blob_reset(&projName);
     }
@@ -919,29 +921,34 @@
 static void checkin_description(int rid){
   Stmt q;
   db_prepare(&q,
     "SELECT datetime(mtime), coalesce(euser,user),"
     "       coalesce(ecomment,comment), uuid,"
+    "       coalesce((SELECT value FROM tagxref"
+    "        WHERE tagid=%d AND tagtype>0 AND rid=blob.rid),'trunk'),"
     "      (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
     "        WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
     "          AND tagxref.rid=blob.rid AND tagxref.tagtype>0)"
     "  FROM event, blob"
     " WHERE event.objid=%d AND type='ci'"
     "   AND blob.rid=%d",
-    rid, rid
+    TAG_BRANCH, rid, rid
   );
   while( db_step(&q)==SQLITE_ROW ){
     const char *zDate = db_column_text(&q, 0);
     const char *zUser = db_column_text(&q, 1);
     const char *zUuid = db_column_text(&q, 3);
+    const char *zBranch = db_column_text(&q, 4);
     const char *zTagList = db_column_text(&q, 4);
     Blob comment;
     int wikiFlags = WIKI_INLINE|WIKI_NOBADLINKS;
     if( db_get_boolean("timeline-block-markup", 0)==0 ){
       wikiFlags |= WIKI_NOBLOCK;
     }
     hyperlink_to_uuid(zUuid);
+    @ on branch <a href="%R/timeline?r=%s(zBranch)&nd&c=%T(zDate)">
+    @   %s(zBranch)</a> - 
     blob_zero(&comment);
     db_column_blob(&q, 2, &comment);
     wiki_convert(&comment, 0, wikiFlags);
     blob_reset(&comment);
     @ (user:

Index: src/name.c
==================================================================
--- src/name.c
+++ src/name.c
@@ -186,10 +186,19 @@
       " WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
       " ORDER BY mtime DESC LIMIT 1",
       &zTag[4], zType);
     return rid;
   }
+
+  /* "parent:", as for parent branch. It returns the checkin of
+     the last checkin of the parent branch that has been merged in. */
+  if( memcmp(zTag, "pbranch:", 8)==0 ){
+    int branchRid = symbolic_name_to_rid(&zTag[8], zType);
+    if (branchRid == 0) return 0;
+    rid = get_parent_branch_rid(branchRid);
+    return rid;
+  }
 
   /* "tag:" + symbolic-name */
   if( memcmp(zTag, "tag:", 4)==0 ){
     rid = db_int(0,
        "SELECT event.objid, max(event.mtime)"
@@ -1138,10 +1147,108 @@
 void test_phatoms_cmd(void){
   db_find_and_open_repository(0,0);
   describe_artifacts_to_stdout("IN (SELECT rid FROM blob WHERE size<0)", 0);
 }
 
+
+
+/*
+** It returns the checkin of the last checkin of the parent branch that has
+** been merged in.
+*/
+int get_parent_branch_rid(int ridRequested){
+  Stmt s;
+  const char *branchName;    /* Name of the branch requested at rid */
+  const char *parentBranchName; /* Name of the parent branch */
+  int rid;
+
+  /* Get the name of the current branch */
+  branchName = db_text(0,
+    "SELECT value FROM tagxref"
+    " WHERE tagid=%d"
+    "   AND tagxref.tagtype>0"
+    "   AND rid=%d",
+    TAG_BRANCH, ridRequested);
+
+  if ( !branchName )
+    return 0;
+
+  /* Find the name of the branch this was forked from */
+  db_prepare(&s,
+    "SELECT pid, tagxref.value FROM plink JOIN tagxref"
+    " WHERE cid=:rid"
+    "   AND isprim=1"
+    "   AND tagxref.tagid=%d"
+    "   AND tagxref.tagtype>0"
+    "   AND tagxref.rid=pid",
+    TAG_BRANCH);
+
+  rid = ridRequested;
+  while( rid > 0 ) {
+    db_bind_int(&s, ":rid", rid);
+    if ( db_step(&s) == SQLITE_ROW ) {
+      rid = db_column_int(&s, 0);
+      parentBranchName = db_column_text(&s, 1);
+      if ( !parentBranchName ) {
+        rid = 0;
+        break;
+      }
+
+      if ( fossil_strcmp(parentBranchName, branchName) ) {
+        parentBranchName = fossil_strdup(parentBranchName);
+        break;
+      }
+    }else{
+      rid = 0;
+      break;
+    }
+    db_reset(&s);
+  }
+  db_finalize(&s);
+
+  if (rid == 0)
+      return 0;
+
+  /* Find the last checkin coming from the parent branch */
+  db_prepare(&s,
+    "SELECT pid, tagxref.value FROM plink JOIN tagxref"
+    " WHERE cid=:rid"
+    "   AND tagxref.tagid=%d"
+    "   AND tagxref.tagtype>0"
+    "   AND tagxref.rid=pid ORDER BY isprim ASC",
+    TAG_BRANCH);
+
+  rid = ridRequested;
+  while( rid > 0 ) {
+    db_bind_int(&s, ":rid", rid);
+    int found = 0;
+    while ( db_step(&s) == SQLITE_ROW ) {
+      const char *branchNamePid; /* Branch name of the pid */
+
+      ++found;
+      rid = db_column_int(&s, 0);
+      branchNamePid = db_column_text(&s, 1);
+      if ( !branchNamePid ) {
+        break;
+      }
+      if ( fossil_strcmp(parentBranchName, branchNamePid)==0 ) {
+        /* Found the last merge from the parent branch */
+        db_finalize(&s);
+        return rid;
+      }
+    }
+    
+    if (found == 0) {
+      break;
+    }
+    db_reset(&s);
+  }
+  db_finalize(&s);
+
+  return 0;
+}
+
 /* Maximum number of collision examples to remember */
 #define MAX_COLLIDE 25
 
 /*
 ** Generate a report on the number of collisions in SHA1 hashes

Index: src/timeline.c
==================================================================
--- src/timeline.c
+++ src/timeline.c
@@ -103,40 +103,36 @@
 */
 char *hash_color(const char *z){
   int i;                       /* Loop counter */
   unsigned int h = 0;          /* Hash on the branch name */
   int r, g, b;                 /* Values for red, green, and blue */
-  int h1, h2, h3, h4;          /* Elements of the hash value */
-  int mx, mn;                  /* Components of HSV */
   static char zColor[10];      /* The resulting color */
-  static int ix[2] = {0,0};    /* Color chooser parameters */
-
-  if( ix[0]==0 ){
-    if( skin_detail_boolean("white-foreground") ){
-      ix[0] = 140;
-      ix[1] = 40;
-    }else{
-      ix[0] = 216;
-      ix[1] = 16;
-    }
-  }
-  for(i=0; z[i]; i++ ){
+  static int whitefg = -1;
+  int cpc = 4;                 /* colours per component */
+  int cfactor = 128/cpc;       /* Factor so n*cpc < 128 */
+  int cmin = cfactor - 1;      /* Factor so the max component is 127
+                                  and the min is different than the bg */
+
+  if(whitefg = -1) 
+    whitefg = skin_detail_boolean("white-foreground");
+
+  /* Calculate the hash based on the branch name */
+  for(i=0; z[i]; i++){
     h = (h<<11) ^ (h<<1) ^ (h>>3) ^ z[i];
   }
-  h1 = h % 6;  h /= 6;
-  h3 = h % 30; h /= 30;
-  h4 = h % 40; h /= 40;
-  mx = ix[0] - h3;
-  mn = mx - h4 - ix[1];
-  h2 = (h%(mx - mn)) + mn;
-  switch( h1 ){
-    case 0:  r = mx; g = h2, b = mn;  break;
-    case 1:  r = h2; g = mx, b = mn;  break;
-    case 2:  r = mn; g = mx, b = h2;  break;
-    case 3:  r = mn; g = h2, b = mx;  break;
-    case 4:  r = h2; g = mn, b = mx;  break;
-    default: r = mx; g = mn, b = h2;  break;
+
+  /* 'cpc' different random values per component, between 'cmin' and 127 */
+  r = cmin + (h % cpc) * cfactor;  h /= cpc;
+  g = cmin + (h % cpc) * cfactor;  h /= cpc;
+  b = cmin + (h % cpc) * cfactor;  h /= cpc;
+
+  /* In case of blackfg, get the inverse effect */
+  if(!whitefg)
+  {
+      r = 255 - r;
+      g = 255 - g;
+      b = 255 - b;
   }
   sqlite3_snprintf(8, zColor, "#%02x%02x%02x", r,g,b);
   return zColor;
 }