Check-in [76bc05d739]
Not logged in
Overview

SHA1 Hash:76bc05d739e816553faf49245db4b26a5d9726fa
Date: 2009-12-30 20:33:59
User: btheado
Comment:merge with trunk
Timelines: ancestors | descendants | both | sql-func
Other Links: files | ZIP archive | manifest

Tags And Properties
Changes
[hide diffs]

Modified src/checkin.c from [c1819242e8] to [43b7742fd6].

@@ -31,14 +31,22 @@
 /*
 ** Generate text describing all changes.  Prepend zPrefix to each line
 ** of output.
 **
 ** We assume that vfile_check_signature has been run.
+**
+** If missingIsFatal is true, then any files that are missing or which
+** are not true files results in a fatal error.
 */
-static void status_report(Blob *report, const char *zPrefix){
+static void status_report(
+  Blob *report,          /* Append the status report here */
+  const char *zPrefix,   /* Prefix on each line of the report */
+  int missingIsFatal     /* MISSING and NOT_A_FILE are fatal errors */
+){
   Stmt q;
   int nPrefix = strlen(zPrefix);
+  int nErr = 0;
   db_prepare(&q,
     "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)"
     "  FROM vfile "
     " WHERE file_is_selected(id)"
     "   AND (chnged OR deleted OR rid=0 OR pathname!=origname) ORDER BY 1"
@@ -50,25 +58,37 @@
     int isNew = db_column_int(&q,3)==0;
     int isRenamed = db_column_int(&q,4);
     char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
     blob_append(report, zPrefix, nPrefix);
     if( isDeleted ){
-      blob_appendf(report, "DELETED  %s\n", zPathname);
-    }else if( access(zFullName, 0) ){
-      blob_appendf(report, "MISSING  %s\n", zPathname);
+      blob_appendf(report, "DELETED    %s\n", zPathname);
+    }else if( !file_isfile(zFullName) ){
+      if( access(zFullName, 0)==0 ){
+        blob_appendf(report, "NOT_A_FILE %s\n", zPathname);
+        if( missingIsFatal ){
+          fossil_warning("not a file: %s", zPathname);
+          nErr++;
+        }
+      }else{
+        blob_appendf(report, "MISSING    %s\n", zPathname);
+        if( missingIsFatal ){
+          fossil_warning("missing file: %s", zPathname);
+          nErr++;
+        }
+      }
     }else if( isNew ){
-      blob_appendf(report, "ADDED    %s\n", zPathname);
+      blob_appendf(report, "ADDED      %s\n", zPathname);
     }else if( isDeleted ){
-      blob_appendf(report, "DELETED  %s\n", zPathname);
+      blob_appendf(report, "DELETED    %s\n", zPathname);
     }else if( isChnged==2 ){
       blob_appendf(report, "UPDATED_BY_MERGE %s\n", zPathname);
     }else if( isChnged==3 ){
       blob_appendf(report, "ADDED_BY_MERGE %s\n", zPathname);
     }else if( isChnged==1 ){
-      blob_appendf(report, "EDITED   %s\n", zPathname);
+      blob_appendf(report, "EDITED     %s\n", zPathname);
     }else if( isRenamed ){
-      blob_appendf(report, "RENAMED  %s\n", zPathname);
+      blob_appendf(report, "RENAMED    %s\n", zPathname);
     }
     free(zFullName);
   }
   db_finalize(&q);
   db_prepare(&q, "SELECT uuid FROM vmerge JOIN blob ON merge=rid"
@@ -76,10 +96,13 @@
   while( db_step(&q)==SQLITE_ROW ){
     blob_append(report, zPrefix, nPrefix);
     blob_appendf(report, "MERGED_WITH %s\n", db_column_text(&q, 0));
   }
   db_finalize(&q);
+  if( nErr ){
+    fossil_fatal("aborting due to prior errors");
+  }
 }
 
 /*
 ** COMMAND: changes
 **
@@ -92,12 +115,12 @@
   Blob report;
   int vid;
   db_must_be_within_tree();
   blob_zero(&report);
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid);
-  status_report(&report, "");
+  vfile_check_signature(vid, 0);
+  status_report(&report, "", 0);
   blob_write_to_file(&report, "-");
 }
 
 /*
 ** COMMAND: status
@@ -121,21 +144,24 @@
 }
 
 /*
 ** COMMAND: ls
 **
-** Usage: %fossil ls
-**
-** Show the names of all files in the current checkout
+** Usage: %fossil ls [-l]
+**
+** Show the names of all files in the current checkout.  The -l provides
+** extra information about each file.
 */
 void ls_cmd(void){
   int vid;
   Stmt q;
-
+  int isBrief;
+
+  isBrief = find_option("l","l", 0)==0;
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid);
+  vfile_check_signature(vid, 0);
   db_prepare(&q,
      "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
      "  FROM vfile"
      " ORDER BY 1"
   );
@@ -144,22 +170,28 @@
     int isDeleted = db_column_int(&q, 1);
     int isNew = db_column_int(&q,2)==0;
     int chnged = db_column_int(&q,3);
     int renamed = db_column_int(&q,4);
     char *zFullName = mprintf("%s/%s", g.zLocalRoot, zPathname);
-    if( isNew ){
-      printf("ADDED     %s\n", zPathname);
-    }else if( access(zFullName, 0) ){
-      printf("MISSING   %s\n", zPathname);
+    if( isBrief ){
+      printf("%s\n", zPathname);
+    }else if( isNew ){
+      printf("ADDED      %s\n", zPathname);
+    }else if( !file_isfile(zFullName) ){
+      if( access(zFullName, 0)==0 ){
+        printf("NOT_A_FILE %s\n", zPathname);
+      }else{
+        printf("MISSING    %s\n", zPathname);
+      }
     }else if( isDeleted ){
-      printf("DELETED   %s\n", zPathname);
+      printf("DELETED    %s\n", zPathname);
     }else if( chnged ){
-      printf("EDITED    %s\n", zPathname);
+      printf("EDITED     %s\n", zPathname);
     }else if( renamed ){
-      printf("RENAMED   %s\n", zPathname);
+      printf("RENAMED    %s\n", zPathname);
     }else{
-      printf("UNCHANGED %s\n", zPathname);
+      printf("UNCHANGED  %s\n", zPathname);
     }
     free(zFullName);
   }
   db_finalize(&q);
 }
@@ -256,12 +288,26 @@
 ** editor specified in the global_config table or either
 ** the VISUAL or EDITOR environment variable.
 **
 ** Store the final commit comment in pComment.  pComment is assumed
 ** to be uninitialized - any prior content is overwritten.
+**
+** zInit is the text of the most recent failed attempt to check in
+** this same change.  Use zInit to reinitialize the check-in comment
+** so that the user does not have to retype.
+**
+** zBranch is the name of a new branch that this check-in is forced into.
+** zBranch might be NULL or an empty string if no forcing occurs.
+**
+** parent_rid is the recordid of the parent check-in.
 */
-static void prepare_commit_comment(Blob *pComment, char *zInit){
+static void prepare_commit_comment(
+  Blob *pComment,
+  char *zInit,
+  const char *zBranch,
+  int parent_rid
+){
   const char *zEditor;
   char *zCmd;
   char *zFile;
   Blob text, line;
   char *zComment;
@@ -271,18 +317,24 @@
     "\n"
     "# Enter comments on this check-in.  Lines beginning with # are ignored.\n"
     "# The check-in comment follows wiki formatting rules.\n"
     "#\n", -1
   );
+  if( zBranch && zBranch[0] ){
+    blob_appendf(&text, "# tags: %s\n#\n", zBranch);
+  }else{
+    char *zTags = info_tags_of_checkin(parent_rid);
+    if( zTags )  blob_appendf(&text, "# tags: %z\n#\n", zTags);
+  }
   if( g.markPrivate ){
     blob_append(&text,
       "# PRIVATE BRANCH: This check-in will be private and will not sync to\n"
       "# repositories.\n"
       "#\n", -1
     );
   }
-  status_report(&text, "# ");
+  status_report(&text, "# ", 1);
   zEditor = db_get("editor", 0);
   if( zEditor==0 ){
     zEditor = getenv("VISUAL");
   }
   if( zEditor==0 ){
@@ -553,11 +605,11 @@
   }else if( zCommentFile ){
     blob_zero(&comment);
     blob_read_from_file(&comment, zCommentFile);
   }else{
     char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'");
-    prepare_commit_comment(&comment, zInit);
+    prepare_commit_comment(&comment, zInit, zBranch, vid);
     free(zInit);
   }
   if( blob_size(&comment)==0 ){
     Blob ans;
     blob_zero(&ans);

Modified src/checkout.c from [093dea6b92] to [93e8f5c3cb].

@@ -39,11 +39,11 @@
 int unsaved_changes(void){
   int vid;
   db_must_be_within_tree();
   vid = db_lget_int("checkout",0);
   if( vid==0 ) return 2;
-  vfile_check_signature(vid);
+  vfile_check_signature(vid, 1);
   return db_exists("SELECT 1 FROM vfile WHERE chnged"
                    " OR coalesce(origname!=pathname,0)");
 }
 
 /*

Modified src/db.c from [6a9e0f8839] to [07c27acf80].

@@ -1469,47 +1469,46 @@
 ** With a value argument it changes the property for the current repository.
 **
 ** The "unset" command clears a property setting.
 **
 **
-**    anon-login-enable-captcha-filler
-**                     If enabled, the Login page will provide a button
+**    auto-captcha     If enabled, the Login page will provide a button
 **                     which uses JavaScript to fill out the captcha for
-**                     the user. (Most bots cannot use JavaScript.)
+**                     the "anonymous" user. (Most bots cannot use JavaScript.)
 **
 **    autosync         If enabled, automatically pull prior to
 **                     commit or update and automatically push
 **                     after commit or tag or branch creation.
+**
+**    clearsign        When enabled (the default), fossil will attempt to
+**                     sign all commits with gpg.  When disabled, commits will
+**                     be unsigned.
 **
 **    diff-command     External command to run when performing a diff.
 **                     If undefined, the internal text diff will be used.
 **
 **    dont-push        Prevent this repository from pushing from client to
 **                     server.  Useful when setting up a private branch.
 **
 **    editor           Text editor command used for check-in comments.
 **
+**    gdiff-command    External command to run when performing a graphical
+**                     diff. If undefined, text diff will be used.
+**
 **    http-port        The TCP/IP port number to use by the "server"
 **                     and "ui" commands.  Default: 8080
-**
-**    gdiff-command    External command to run when performing a graphical
-**                     diff. If undefined, text diff will be used.
 **
 **    localauth        If enabled, require that HTTP connections from
 **                     127.0.0.1 be authenticated by password.  If
 **                     false, all HTTP requests from localhost have
 **                     unrestricted access to the repository.
 **
-**    clearsign        When enabled (the default), fossil will attempt to
-**                     sign all commits with gpg.  When disabled, commits will
-**                     be unsigned.
+**    mtime-changes    Use file modification times (mtimes) to detect when
+**                     files have been modified.
 **
 **    pgp-command      Command used to clear-sign manifests at check-in.
 **                     The default is "gpg --clearsign -o ".
-**
-**    mtime-changes    Use file modification times (mtimes) to detect when
-**                     files have been modified.
 **
 **    proxy            URL of the HTTP proxy.  If undefined or "off" then
 **                     the "http_proxy" environment variable is consulted.
 **                     If the http_proxy environment variable is undefined
 **                     then a direct HTTP connection is used.
@@ -1519,21 +1518,21 @@
 **                     Defaults to "start" on windows, "open" on Mac,
 **                     and "firefox" on Unix.
 */
 void setting_cmd(void){
   static const char *azName[] = {
-    "anon-login-enable-captcha-filler",
+    "auto-captcha",
     "autosync",
+    "clearsign",
     "diff-command",
     "dont-push",
     "editor",
     "gdiff-command",
     "http-port",
     "localauth",
-    "clearsign",
-    "pgp-command",
     "mtime-changes",
+    "pgp-command",
     "proxy",
     "web-browser",
   };
   int i;
   int globalFlag = find_option("global","g",0)!=0;

Modified src/diff.c from [423e7bd7db] to [189451b157].

@@ -710,11 +710,11 @@
   while( db_step(&q)==SQLITE_ROW ){
     int pid = db_column_int(&q, 0);
     const char *zUuid = db_column_text(&q, 1);
     const char *zDate = db_column_text(&q, 2);
     const char *zUser = db_column_text(&q, 3);
-    if( g.okHistory ){
+    if( webLabel ){
       zLabel = mprintf("<a href='%s/info/%s'>%.10s</a> %s %9.9s",
                        g.zBaseURL, zUuid, zUuid, zDate, zUser);
     }else{
       zLabel = mprintf("%.10s %s %9.9s", zUuid, zDate, zUser);
     }
@@ -744,14 +744,55 @@
   if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
   if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
     fossil_redirect_home();
   }
   style_header("File Annotation");
-  annotate_file(&ann, fnid, mid, 1);
+  annotate_file(&ann, fnid, mid, g.okHistory);
   @ <pre>
   for(i=0; i<ann.nOrig; i++){
     ((char*)ann.aOrig[i].z)[ann.aOrig[i].n] = 0;
     @ %s(ann.aOrig[i].zSrc): %h(ann.aOrig[i].z)
   }
   @ </pre>
   style_footer();
+}
+
+/*
+** COMMAND: annotate
+**
+** %fossil annotate FILENAME
+**
+** Output the text of a file with markings to show when each line of
+** the file was introduced.
+*/
+void annotate_cmd(void){
+  int fnid;         /* Filename ID */
+  int fid;          /* File instance ID */
+  int mid;          /* Manifest where file was checked in */
+  Blob treename;    /* FILENAME translated to canonical form */
+  char *zFilename;  /* Cannonical filename */
+  Annotator ann;    /* The annotation of the file */
+  int i;            /* Loop counter */
+
+  db_must_be_within_tree();
+  if (g.argc<3) {
+    usage("FILENAME");
+  }
+  file_tree_name(g.argv[2], &treename, 1);
+  zFilename = blob_str(&treename);
+  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
+  if( fnid==0 ){
+    fossil_fatal("no such file: %s", zFilename);
+  }
+  fid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFilename);
+  if( fid==0 ){
+    fossil_fatal("not part of current checkout: %s", zFilename);
+  }
+  mid = db_int(0, "SELECT mid FROM mlink WHERE fid=%d AND fnid=%d", fid, fnid);
+  if( mid==0 ){
+    fossil_panic("unable to find manifest");
+  }
+  annotate_file(&ann, fnid, mid, 0);
+  for(i=0; i<ann.nOrig; i++){
+    printf("%s: %.*s\n", ann.aOrig[i].zSrc, ann.aOrig[i].n, ann.aOrig[i].z);
+  }
 }

Modified src/diffcmd.c from [20363d7b42] to [6b2be81037].

@@ -190,11 +190,11 @@
 */
 static void diff_one_against_disk(const char *zFrom, const char *zDiffCmd){
   Blob fname;
   Blob content;
   file_tree_name(g.argv[2], &fname, 1);
-  historical_version_of_file(zFrom, blob_str(&fname), &content);
+  historical_version_of_file(zFrom, blob_str(&fname), &content, 0);
   diff_file(&content, g.argv[2], g.argv[2], zDiffCmd);
   blob_reset(&content);
   blob_reset(&fname);
 }
 
@@ -207,11 +207,11 @@
   int vid;
   Blob sql;
   Stmt q;
 
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid);
+  vfile_check_signature(vid, 1);
   blob_zero(&sql);
   db_begin_transaction();
   if( zFrom ){
     int rid = name_to_rid(zFrom);
     if( !is_a_version(rid) ){
@@ -295,12 +295,12 @@
   char *zName;
   Blob fname;
   Blob v1, v2;
   file_tree_name(g.argv[2], &fname, 1);
   zName = blob_str(&fname);
-  historical_version_of_file(zFrom, zName, &v1);
-  historical_version_of_file(zTo, zName, &v2);
+  historical_version_of_file(zFrom, zName, &v1, 0);
+  historical_version_of_file(zTo, zName, &v2, 0);
   diff_file_mem(&v1, &v2, zName, zDiffCmd);
   blob_reset(&v1);
   blob_reset(&v2);
   blob_reset(&fname);
 }

Modified src/info.c from [dd6de258e1] to [461c233b78].

@@ -27,10 +27,28 @@
 */
 #include "config.h"
 #include "info.h"
 #include <assert.h>
 
+/*
+** Return a string (in memory obtained from malloc) holding a
+** comma-separated list of tags that apply to check-in with
+** record-id rid.
+**
+** Return NULL if there are no such tags.
+*/
+char *info_tags_of_checkin(int rid){
+  char *zTags;
+  zTags = db_text(0, "SELECT group_concat(substr(tagname, 5), ', ')"
+                     "  FROM tagxref, tag"
+                     " WHERE tagxref.rid=%d AND tagxref.tagtype>0"
+                     "   AND tag.tagid=tagxref.tagid"
+                     "   AND tag.tagname GLOB 'sym-*'",
+                     rid);
+  return zTags;
+}
+
 
 /*
 ** Print common information about a particular record.
 **
 **     *  The UUID
@@ -44,16 +62,16 @@
   char *zTags;
   char *zDate;
   char *zUuid;
   zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   if( zUuid ){
-    zDate = db_text("",
+    zDate = db_text(0,
       "SELECT datetime(mtime) || ' UTC' FROM event WHERE objid=%d",
       rid
     );
          /* 01234567890123 */
-    printf("%-13s %s %s\n", zUuidName, zUuid, zDate);
+    printf("%-13s %s %s\n", zUuidName, zUuid, zDate ? zDate : "");
     free(zUuid);
     free(zDate);
   }
   db_prepare(&q, "SELECT uuid, pid FROM plink JOIN blob ON pid=rid "
                  " WHERE cid=%d", rid);
@@ -77,16 +95,11 @@
     );
     printf("child:        %s %s\n", zUuid, zDate);
     free(zDate);
   }
   db_finalize(&q);
-  zTags = db_text(0, "SELECT group_concat(substr(tagname, 5), ', ')"
-                     "  FROM tagxref, tag"
-                     " WHERE tagxref.rid=%d AND tagxref.tagtype>0"
-                     "   AND tag.tagid=tagxref.tagid"
-                     "   AND tag.tagname GLOB 'sym-*'",
-                     rid);
+  zTags = info_tags_of_checkin(rid);
   if( zTags && zTags[0] ){
     printf("tags:         %s\n", zTags);
   }
   free(zTags);
   if( zComment ){
@@ -233,19 +246,30 @@
 ** WEBPAGE: vinfo
 ** WEBPAGE: ci
 ** URL:  /ci?name=RID|ARTIFACTID
 **
 ** Display information about a particular check-in.
+**
+** We also jump here from /info if the name is a version.
+**
+** If the /ci page is used (instead of /vinfo or /info) then the
+** default behavior is to show unified diffs of all file changes.
+** With /vinfo and /info, only a list of the changed files are
+** shown, without diffs.  This behavior is inverted if the
+** "show-version-diffs" setting is turned on.
 */
 void ci_page(void){
   Stmt q;
   int rid;
   int isLeaf;
+  int showDiff;
+  const char *zName;
 
   login_check_credentials();
   if( !g.okRead ){ login_needed(); return; }
-  rid = name_to_rid(PD("name","0"));
+  zName = PD("name","0");
+  rid = name_to_rid(zName);
   if( rid==0 ){
     style_header("Check-in Information Error");
     @ No such object: %h(g.argv[2])
     style_footer();
     return;
@@ -285,25 +309,24 @@
     }
     @ </td></tr>
     @ <tr><th>Date:</th><td>
     hyperlink_to_date(zDate, "</td></tr>");
     if( zEUser ){
-      @ <tr><th>Edited&nbsp;User:</td><td>
+      @ <tr><th>Edited&nbsp;User:</th><td>
       hyperlink_to_user(zEUser,zDate,"</td></tr>");
       @ <tr><th>Original&nbsp;User:</th><td>
       hyperlink_to_user(zUser,zDate,"</td></tr>");
     }else{
-      @ <tr><th>User:</td><td>
+      @ <tr><th>User:</th><td>
       hyperlink_to_user(zUser,zDate,"</td></tr>");
     }
     if( zEComment ){
       @ <tr><th>Edited&nbsp;Comment:</th><td>%w(zEComment)</td></tr>
       @ <tr><th>Original&nbsp;Comment:</th><td>%w(zComment)</td></tr>
     }else{
       @ <tr><th>Comment:</th><td>%w(zComment)</td></tr>
     }
-    @ </td></tr>
     if( g.okAdmin ){
       db_prepare(&q,
          "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)"
          "  FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)"
          " WHERE blob.rid=%d",
@@ -355,10 +378,25 @@
     login_anonymous_available();
   }
   db_finalize(&q);
   showTags(rid, "");
   @ <div class="section">Changes</div>
+  showDiff = g.zPath[0]!='c';
+  if( db_get_boolean("show-version-diffs", 0)==0 ){
+    showDiff = !showDiff;
+    if( showDiff ){
+      @ <a href="%s(g.zBaseURL)/vinfo/%T(zName)">[hide&nbsp;diffs]</a><br/>
+    }else{
+      @ <a href="%s(g.zBaseURL)/ci/%T(zName)">[show&nbsp;diffs]</a><br/>
+    }
+  }else{
+    if( showDiff ){
+      @ <a href="%s(g.zBaseURL)/ci/%T(zName)">[hide&nbsp;diffs]</a><br/>
+    }else{
+      @ <a href="%s(g.zBaseURL)/vinfo/%T(zName)">[show&nbsp;diffs]</a><br/>
+    }
+  }
   db_prepare(&q,
      "SELECT pid, fid, name, substr(a.uuid,1,10), substr(b.uuid,1,10)"
      "  FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
      "         LEFT JOIN blob a ON a.rid=pid"
      "         LEFT JOIN blob b ON b.rid=fid"
@@ -380,22 +418,28 @@
         @ <p>Changes to %h(zName)</p>
       }
     }else if( zOld && zNew ){
       @ <p>Modified <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a>
       @ from <a href="%s(g.zBaseURL)/artifact/%s(zOld)">[%s(zOld)]</a>
-      @ to <a href="%s(g.zBaseURL)/artifact/%s(zNew)">[%s(zNew)]</a></p>
+      @ to <a href="%s(g.zBaseURL)/artifact/%s(zNew)">[%s(zNew)].</a>
+      if( !showDiff ){
+        @ &nbsp;&nbsp;
+        @ <a href="%s(g.zBaseURL)/fdiff?v1=%d(pid)&v2=%d(fid)">[diff]</a>
+      }
     }else if( zOld ){
       @ <p>Deleted <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a>
       @ version <a href="%s(g.zBaseURL)/artifact/%s(zOld)">[%s(zOld)]</a></p>
       continue;
     }else{
       @ <p>Added <a href="%s(g.zBaseURL)/finfo?name=%T(zName)">%h(zName)</a>
       @ version <a href="%s(g.zBaseURL)/artifact/%s(zNew)">[%s(zNew)]</a></p>
     }
-    @ <blockquote><pre>
-    append_diff(pid, fid);
-    @ </pre></blockquote>
+    if( showDiff ){
+      @ <blockquote><pre>
+      append_diff(pid, fid);
+      @ </pre></blockquote>
+    }
   }
   db_finalize(&q);
   style_footer();
 }
 

Modified src/login.c from [646f690c52] to [5c91402605].

@@ -79,10 +79,29 @@
     fossil_redirect_home();
   }
 }
 
 /*
+** The IP address of the client is stored as part of the anonymous
+** login cookie for additional security.  But some clients are behind
+** firewalls that shift the IP address with each HTTP request.  To
+** allow such (broken) clients to log in, extract just a prefix of the
+** IP address.
+*/
+static char *ipPrefix(const char *zIP){
+  int i, j;
+  for(i=j=0; zIP[i]; i++){
+    if( zIP[i]=='.' ){
+      j++;
+      if( j==2 ) break;
+    }
+  }
+  return mprintf("%.*s", i, zIP);
+}
+
+
+/*
 ** Check to see if the anonymous login is valid.  If it is valid, return
 ** the userid of the anonymous user.
 */
 static int isValidAnonymousLogin(
   const char *zUsername,  /* The username.  Must be "anonymous" */
@@ -168,11 +187,11 @@
 
     zIpAddr = PD("REMOTE_ADDR","nil");
     zCookieName = login_cookie_name();
     zNow = db_text("0", "SELECT julianday('now')");
     blob_init(&b, zNow, -1);
-    blob_appendf(&b, "/%s/%s", zIpAddr, db_get("captcha-secret",""));
+    blob_appendf(&b, "/%z/%s", ipPrefix(zIpAddr), db_get("captcha-secret",""));
     sha1sum_blob(&b, &b);
     zCookie = sqlite3_mprintf("anon/%s/%s", zNow, blob_buffer(&b));
     blob_reset(&b);
     free(zNow);
     cgi_set_cookie(zCookieName, zCookie, 0, 6*3600);
@@ -250,22 +269,24 @@
   @ "Login" button.  Your user name will be stored in a browser cookie.
   @ You must configure your web browser to accept cookies in order for
   @ the login to take.</p>
   if( zAnonPw ){
     unsigned int uSeed = captcha_seed();
-    char const * zDecoded = captcha_decode(uSeed);
-    int iAllowPasswordFill = db_get_boolean( "anon-login-enable-captcha-filler", 0 );
+    char const *zDecoded = captcha_decode(uSeed);
+    int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
     char *zCaptcha = captcha_render(zDecoded);
 
     @ <input type="hidden" name="cs" value="%u(uSeed)"/>
     @ <p>Visitors may enter <b>anonymous</b> as the user-ID with
     @ the 8-character hexadecimal password shown below:</p>
     @ <center><table border="1" cellpadding="10"><tr><td><pre>
     @ %s(zCaptcha)
     @ </pre></td></tr></table>
-    if( iAllowPasswordFill ) {
-        @ <input type="button" value="Fill out captcha" onclick="document.getElementById('u').value='anonymous'; document.getElementById('p').value='%s(zDecoded)';"/>
+    if( bAutoCaptcha ) {
+        @ <input type="button" value="Fill out captcha"
+        @  onclick="document.getElementById('u').value='anonymous';
+        @           document.getElementById('p').value='%s(zDecoded)';"/>
     }
     @ </center>
     free(zCaptcha);
   }
   if( g.zLogin ){
@@ -356,11 +377,12 @@
       rTime = atof(&zCookie[5]);
       for(i=5; zCookie[i] && zCookie[i]!='/'; i++){}
       blob_init(&b, &zCookie[5], i-5);
       if( zCookie[i]=='/' ){ i++; }
       blob_append(&b, "/", 1);
-      blob_appendf(&b, "%s/%s", zRemoteAddr, db_get("captcha-secret",""));
+      blob_appendf(&b, "%z/%s", ipPrefix(zRemoteAddr),
+                   db_get("captcha-secret",""));
       sha1sum_blob(&b, &b);
       uid = db_int(0,
           "SELECT uid FROM user WHERE login='anonymous'"
           " AND length(cap)>0"
           " AND length(pw)>0"

Modified src/main.c from [0e2e1d04fd] to [f14e3f8203].

@@ -67,10 +67,11 @@
   int localOpen;          /* True if the local database is open */
   char *zLocalRoot;       /* The directory holding the  local database */
   int minPrefix;          /* Number of digits needed for a distinct UUID */
   int fSqlTrace;          /* True if -sqltrace flag is present */
   int fSqlPrint;          /* True if -sqlprint flag is present */
+  int fQuiet;             /* True if -quiet flag is present */
   int fHttpTrace;         /* Trace outbound HTTP requests */
   int fNoSync;            /* Do not do an autosync even.  --nosync */
   char *zPath;            /* Name of webpage being served */
   char *zExtra;           /* Extra path information past the webpage name */
   char *zBaseURL;         /* Full text of the URL being served */
@@ -232,10 +233,11 @@
     zCmdName = "cgi";
   }else if( argc<2 ){
     fprintf(stderr, "Usage: %s COMMAND ...\n", argv[0]);
     exit(1);
   }else{
+    g.fQuiet = find_option("quiet", 0, 0)!=0;
     g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
     g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
     g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
     g.zLogin = find_option("user", "U", 1);
     zCmdName = argv[1];
@@ -733,11 +735,11 @@
 void cmd_test_http(void){
   login_set_capabilities("s");
   cmd_http();
 }
 
-
+#ifndef __MINGW32__
 #if !defined(__DARWIN__) && !defined(__APPLE__)
 /*
 ** Search for an executable on the PATH environment variable.
 ** Return true (1) if found and false (0) if not found.
 */
@@ -755,10 +757,11 @@
     if( bExists==0 ) return 1;
     zPath += i;
   }
   return 0;
 }
+#endif
 #endif
 
 /*
 ** COMMAND: server
 ** COMMAND: ui

Modified src/main.mk from [08be0ee0ca] to [0f9d767d5d].

@@ -60,10 +60,11 @@
   $(SRCDIR)/schema.c \
   $(SRCDIR)/search.c \
   $(SRCDIR)/setup.c \
   $(SRCDIR)/sha1.c \
   $(SRCDIR)/shun.c \
+  $(SRCDIR)/skins.c \
   $(SRCDIR)/stat.c \
   $(SRCDIR)/style.c \
   $(SRCDIR)/sync.c \
   $(SRCDIR)/tag.c \
   $(SRCDIR)/th_main.c \
@@ -130,10 +131,11 @@
   schema_.c \
   search_.c \
   setup_.c \
   sha1_.c \
   shun_.c \
+  skins_.c \
   stat_.c \
   style_.c \
   sync_.c \
   tag_.c \
   th_main_.c \
@@ -200,10 +202,11 @@
   schema.o \
   search.o \
   setup.o \
   sha1.o \
   shun.o \
+  skins.o \
   stat.o \
   style.o \
   sync.o \
   tag.o \
   th_main.o \
@@ -261,16 +264,16 @@
 	# noop
 
 clean:
 	rm -f *.o *_.c $(APPNAME) VERSION.h
 	rm -f translate makeheaders mkindex page_index.h headers
-	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
+	rm -f add.h allrepo.h bag.h blob.h branch.h browse.h captcha.h cgi.h checkin.h checkout.h clearsign.h clone.h comformat.h configure.h construct.h content.h db.h delta.h deltacmd.h descendants.h diff.h diffcmd.h doc.h encode.h file.h finfo.h http.h http_socket.h http_transport.h info.h login.h main.h manifest.h md5.h merge.h merge3.h name.h pivot.h pqueue.h printf.h rebuild.h report.h rss.h rstats.h schema.h search.h setup.h sha1.h shun.h skins.h stat.h style.h sync.h tag.h th_main.h timeline.h tkt.h tktsetup.h undo.h update.h url.h user.h verify.h vfile.h wiki.h wikiformat.h winhttp.h xfer.h zip.h
 
 page_index.h: $(TRANS_SRC) mkindex
 	./mkindex $(TRANS_SRC) >$@
 headers:	page_index.h makeheaders VERSION.h
-	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
+	./makeheaders  add_.c:add.h allrepo_.c:allrepo.h bag_.c:bag.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h construct_.c:construct.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h file_.c:file.h finfo_.c:finfo.h http_.c:http.h http_socket_.c:http_socket.h http_transport_.c:http_transport.h info_.c:info.h login_.c:login.h main_.c:main.h manifest_.c:manifest.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h name_.c:name.h pivot_.c:pivot.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h report_.c:report.h rss_.c:rss.h rstats_.c:rstats.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h update_.c:update.h url_.c:url.h user_.c:user.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winhttp_.c:winhttp.h xfer_.c:xfer.h zip_.c:zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h VERSION.h
 	touch headers
 headers: Makefile
 Makefile:
 add_.c:	$(SRCDIR)/add.c translate
 	./translate $(SRCDIR)/add.c >add_.c
@@ -613,10 +616,17 @@
 
 shun.o:	shun_.c shun.h  $(SRCDIR)/config.h
 	$(XTCC) -o shun.o -c shun_.c
 
 shun.h:	headers
+skins_.c:	$(SRCDIR)/skins.c translate
+	./translate $(SRCDIR)/skins.c >skins_.c
+
+skins.o:	skins_.c skins.h  $(SRCDIR)/config.h
+	$(XTCC) -o skins.o -c skins_.c
+
+skins.h:	headers
 stat_.c:	$(SRCDIR)/stat.c translate
 	./translate $(SRCDIR)/stat.c >stat_.c
 
 stat.o:	stat_.c stat.h  $(SRCDIR)/config.h
 	$(XTCC) -o stat.o -c stat_.c

Modified src/makemake.tcl from [8696139730] to [1776157a16].

@@ -54,10 +54,11 @@
   schema
   search
   setup
   sha1
   shun
+  skins
   stat
   style
   sync
   tag
   th_main

Modified src/merge.c from [5979683d08] to [1bca64b642].

@@ -77,11 +77,11 @@
                  "checkout and %s", g.argv[2]);
   }
   if( pid>1 && !db_exists("SELECT 1 FROM plink WHERE cid=%d", pid) ){
     fossil_panic("not a version: record #%d", mid);
   }
-  vfile_check_signature(vid);
+  vfile_check_signature(vid, 1);
   db_begin_transaction();
   undo_begin();
   load_vfile_from_rid(mid);
   load_vfile_from_rid(pid);
 

Modified src/merge3.c from [61efa8a333] to [bbe357cf65].

@@ -161,10 +161,12 @@
   int nConflict = 0;     /* Number of merge conflicts seen so far */
   static const char zBegin[] = ">>>>>>> BEGIN MERGE CONFLICT\n";
   static const char zMid[]   = "============================\n";
   static const char zEnd[]   = "<<<<<<< END MERGE CONFLICT\n";
 
+  blob_zero(pOut);         /* Merge results stored in pOut */
+
   /* Compute the edits that occur from pPivot => pV1 (into aC1)
   ** and pPivot => pV2 (into aC2).  Each of the aC1 and aC2 arrays is
   ** an array of integer triples.  Within each triple, the first integer
   ** is the number of lines of text to copy directly from the pivot,
   ** the second integer is the number of lines of text to omit from the
@@ -177,11 +179,10 @@
     free(aC1);
     free(aC2);
     return -1;
   }
 
-  blob_zero(pOut);         /* Merge results stored in pOut */
   blob_rewind(pV1);        /* Rewind inputs:  Needed to reconstruct output */
   blob_rewind(pV2);
   blob_rewind(pPivot);
 
   /* Determine the length of the aC1[] and aC2[] change vectors */

Modified src/printf.c from [e955d82165] to [8a4feba8cf].

@@ -153,11 +153,11 @@
 ** Find the length of a string as long as that length does not
 ** exceed N bytes.  If no zero terminator is seen in the first
 ** N bytes then return N.  If N is negative, then this routine
 ** is an alias for strlen().
 */
-static int strnlen_(const char *z, int N){
+static int StrNLen32(const char *z, int N){
   int n = 0;
   while( (N-- != 0) && *(z++)!=0 ){ n++; }
   return n;
 }
 
@@ -563,11 +563,11 @@
       case etPATH: {
         int i;
         int limit = flag_alternateform ? va_arg(ap,int) : -1;
         char *e = va_arg(ap,char*);
         if( e==0 ){e="";}
-        length = strnlen_(e, limit);
+        length = StrNLen32(e, limit);
         zExtra = bufpt = malloc(length+1);
         for( i=0; i<length; i++ ){
           if( e[i]=='\\' ){
             bufpt[i]='/';
           }else{
@@ -584,11 +584,11 @@
         if( bufpt==0 ){
           bufpt = "";
         }else if( xtype==etDYNSTRING ){
           zExtra = bufpt;
         }
-        length = strnlen_(bufpt, limit);
+        length = StrNLen32(bufpt, limit);
         if( precision>=0 && precision<length ) length = precision;
         break;
       }
       case etBLOB: {
         int limit = flag_alternateform ? va_arg(ap, int) : -1;

Modified src/rebuild.c from [ab6ed5de1a] to [0c81bac41f].

@@ -91,12 +91,14 @@
 static void rebuild_step_done(rid){
   /* assert( bag_find(&bagDone, rid)==0 ); */
   bag_insert(&bagDone, rid);
   if( ttyOutput ){
     processCnt++;
-    printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize));
-    fflush(stdout);
+    if (!g.fQuiet) {
+      printf("%d (%d%%)...\r", processCnt, (processCnt*100/totalSize));
+      fflush(stdout);
+    }
   }
 }
 
 /*
 ** Rebuild cross-referencing information for the artifact
@@ -206,11 +208,14 @@
   char *zTable;
 
   bag_init(&bagDone);
   ttyOutput = doOut;
   processCnt = 0;
-  printf("0 (0%%)...\r"); fflush(stdout);
+  if (!g.fQuiet) {
+    printf("0 (0%%)...\r");
+    fflush(stdout);
+  }
   db_multi_exec(zSchemaUpdates);
   for(;;){
     zTable = db_text(0,
        "SELECT name FROM sqlite_master"
        " WHERE type='table'"
@@ -273,11 +278,11 @@
     }
   }
   db_finalize(&s);
   manifest_crosslink_end();
   rebuild_tag_trunk();
-  if( ttyOutput ){
+  if(!g.fQuiet && ttyOutput ){
     printf("\n");
   }
   return errCnt;
 }
 

Modified src/report.c from [54647a292b] to [9fa0e17130].

@@ -483,28 +483,14 @@
   @ subsequent columns are shown on their own rows in the table.  This might
   @ be useful for displaying the description of tickets.  This rule does not
   @ hold if the column is prefixed with "_wiki_".
   @ </p></li>
   @
-  @ <li><p>The <b>aux()</b> SQL function takes a parameter name as an argument
-  @ and returns the value that the user enters in the resulting HTML form. A
-  @ second optional parameter provides a default value for the field.</p></li>
-  @
-  @ <li><p>The <b>option()</b> SQL function takes a parameter name
-  @ and a quoted SELECT statement as parameters. The query results are
-  @ presented as an HTML dropdown menu and the function returns
-  @ the currently selected value. Results may be a single value column or
-  @ two <b>value,description</b> columns. The first row is the default.</p></li>
-  @
   @ <li><p>The <b>cgi()</b> SQL function takes a parameter name as an argument
   @ and returns the value of a corresponding CGI query value. If the CGI
   @ parameter doesn't exist, an optional second argument will be returned
   @ instead.</p></li>
-  @
-  @ <li><p>The <b>search()</b> SQL function takes a keyword pattern and
-  @ a search text. The function returns an integer score which is
-  @ higher depending on how well the search went.</p></li>
   @
   @ <li><p>The query can join other tables in the database besides TICKET.
   @ </p></li>
   @ </ul>
   @

Modified src/setup.c from [800f3104a6] to [e3fd99aa04].

@@ -70,10 +70,12 @@
     "Configure the SCM behavior of the repository");
   setup_menu_entry("Timeline", "setup_timeline",
     "Timeline display preferences");
   setup_menu_entry("Tickets", "tktsetup",
     "Configure the trouble-ticketing system for this repository");
+  setup_menu_entry("Skins", "setup_skin",
+    "Select from a menu of prepackaged \"skins\" for the web interface");
   setup_menu_entry("CSS", "setup_editcss",
     "Edit the Cascading Style Sheet used by all pages of this repository");
   setup_menu_entry("Header", "setup_header",
     "Edit HTML text inserted at the top of every page");
   setup_menu_entry("Footer", "setup_footer",
@@ -269,11 +271,10 @@
   ** modified user record.  After writing the user record, redirect
   ** to the page that displays a list of users.
   */
   doWrite = cgi_all("login","info","pw") && !higherUser;
   if( doWrite ){
-    char const * anonLoginCheckedbox = PD("anonymousEnableAutofill",0);
     char zCap[50];
     int i = 0;
     int aa = P("aa")!=0;
     int ad = P("ad")!=0;
     int ae = P("ae")!=0;
@@ -338,16 +339,10 @@
     db_multi_exec(
        "REPLACE INTO user(uid,login,info,pw,cap) "
        "VALUES(nullif(%d,0),%Q,%Q,%Q,'%s')",
       uid, P("login"), P("info"), zPw, zCap
     );
-    if( anonLoginCheckedbox && (*anonLoginCheckedbox) ){
-      db_set( "anon-login-enable-captcha-filler", "on", 0 );
-    }
-    else{
-      db_set( "anon-login-enable-captcha-filler", "off", 0 );
-    }
     cgi_redirect("setup_ulist");
     return;
   }
 
   /* Load the existing information about the user, if any
@@ -479,16 +474,11 @@
   @   </td>
   @ </tr>
   @ <tr>
   @   <td align="right">Password:</td>
   if( strcmp(zLogin, "anonymous")==0 ){
-    int enabled = db_get_boolean( "anon-login-enable-captcha-filler", 0 );
-    char const * checked = enabled ? "checked=\"checked\"" : "";
-    /* User the password for "anonymous" as cleartext */
-    @   <td><input type="text" name="pw" value="%h(zPw)"/>
-    @   <br/>Enable password-filler button for anonymous login? <input type="checkbox" name="anonymousEnableAutofill" %s(checked)/><br/>
-    @   </td>
+    @   <td><input type="text" name="pw" value="%h(zPw)"></td>
   }else if( zPw[0] ){
     /* Obscure the password for all other users */
     @   <td><input type="password" name="pw" value="**********"></td>
   }else{
     /* Show an empty password as an empty input field */
@@ -735,10 +725,19 @@
   @ from the ~/.fossil database. Password login is always required
   @ for incoming web connections on internet addresses other than
   @ 127.0.0.1.</p></li>
 
   @ <hr>
+  onoff_attribute("Show javascript button to fill in CAPTCHA",
+                  "auto-captcha", "autocaptcha", 0);
+  @ <p>When enabled, a button appears on the login screen for user
+  @ "anonymous" that will automatically fill in the CAPTCHA password.
+  @ This is less secure that forcing the user to do it manually, but is
+  @ probably secure enough and it is certainly more convenient for
+  @ anonymous users.</p>
+
+  @ <hr>
   entry_attribute("Login expiration time", 6, "cookie-expire", "cex", "8766");
   @ <p>The number of hours for which a login is valid.  This must be a
   @ positive number.  The default is 8760 hours which is approximately equal
   @ to a year.</p>
 
@@ -783,10 +782,18 @@
                   "timeline-utc", "utc", 1);
   @ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
   @ Zulu) instead of in local time.</p>
 
   @ <hr>
+  onoff_attribute("Show version differences by default",
+                  "show-version-diffs", "vdiff", 0);
+  @ <p>On the version-information pages linked from the timeline can either
+  @ show complete diffs of all file changes, or can just list the names of
+  @ the files that have changed.  Users can get to either page by
+  @ clicking.  This setting selects the default.</p>
+
+  @ <hr>
   entry_attribute("Max timeline comment length", 6,
                   "timeline-max-comment", "tmc", "0");
   @ <p>The maximum length of a comment to be displayed in a timeline.
   @ "0" there is no length limit.</p>
 
@@ -813,10 +820,19 @@
 
   @ <hr>
   onoff_attribute("Automatically synchronize with repository",
                   "autosync", "autosync", 1);
   @ <p>Automatically keeps your work in sync with a centralized server.</p>
+
+  @ <hr>
+  onoff_attribute("Show javascript button to fill in CAPTCHA",
+                  "auto-captcha", "autocaptcha", 0);
+  @ <p>When enabled, a button appears on the login screen for user
+  @ "anonymous" that will automatically fill in the CAPTCHA password.
+  @ This is less secure that forcing the user to do it manually, but is
+  @ probably secure enough and it is certainly more convenient for
+  @ anonymous users.</p>
 
   @ <hr>
   onoff_attribute("Sign all commits with GPG",
                   "clearsign", "clearsign", 1);
   @ <p>When enabled (the default), fossil will attempt to
@@ -939,18 +955,21 @@
     textarea_attribute(0, 0, 0, "css", "css", zDefaultCSS);
   }
   style_header("Edit CSS");
   @ <form action="%s(g.zBaseURL)/setup_editcss" method="POST">
   login_insert_csrf_secret();
-  @ Edit the CSS:<br />
+  @ Edit the CSS below:<br />
   textarea_attribute("", 40, 80, "css", "css", zDefaultCSS);
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default CSS:
+  @ The default CSS is shown below for reference.  Other examples
+  @ of CSS files can be seen on the <a href="setup_skin">skins page</a>.
+  @ See also the <a href="setup_header">header</a> and
+  @ <a href="setup_footer">footer</a> editing screens.
   @ <blockquote><pre>
   @ %h(zDefaultCSS)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -981,11 +1000,14 @@
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default page header:
+  @ The default header is shown below for reference.  Other examples
+  @ of headers can be seen on the <a href="setup_skin">skins page</a>.
+  @ See also the <a href="setup_editcss">CSS</a> and
+  @ <a href="setup_footer">footer</a> editing screeens.
   @ <blockquote><pre>
   @ %h(zDefaultHeader)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -1015,11 +1037,14 @@
   @ <br />
   @ <input type="submit" name="submit" value="Apply Changes">
   @ <input type="submit" name="clear" value="Revert To Default">
   @ </form>
   @ <hr>
-  @ Here is the default page footer:
+  @ The default footer is shown below for reference.  Other examples
+  @ of footers can be seen on the <a href="setup_skin">skins page</a>.
+  @ See also the <a href="setup_editcss">CSS</a> and
+  @ <a href="setup_header">header</a> editing screens.
   @ <blockquote><pre>
   @ %h(zDefaultFooter)
   @ </pre></blockquote>
   style_footer();
   db_end_transaction(0);
@@ -1063,15 +1088,19 @@
   style_header("Edit Project Logo");
   @ <p>The current project logo has a MIME-Type of <b>%h(zMime)</b> and looks
   @ like this:</p>
   @ <blockquote><img src="%s(g.zTop)/logo" alt="logo"></blockquote>
   @
-  @ <form action="%s(g.zBaseURL)/setup_logo" method="POST"
-  @  enctype="multipart/form-data">
   @ <p>The logo is accessible to all users at this URL:
   @ <a href="%s(g.zBaseURL)/logo">%s(g.zBaseURL)/logo</a>.
-  @ To set a new logo image, select a file to use as the logo using
+  @ The logo may or may not appear on each
+  @ page depending on the <a href="setup_editcss">CSS</a> and
+  @ <a href="setup_header">header setup</a>.</p>
+  @
+  @ <form action="%s(g.zBaseURL)/setup_logo" method="POST"
+  @  enctype="multipart/form-data">
+  @ <p>To set a new logo image, select a file to use as the logo using
   @ the entry box below and then press the "Change Logo" button.</p>
   login_insert_csrf_secret();
   @ Logo Image file:
   @ <input type="file" name="im" size="60" accepts="image/*"><br>
   @ <input type="submit" name="set" value="Change Logo">

Added src/skins.c version [61096f713e]

@@ -1,1 +1,847 @@
+/*
+** Copyright (c) 2009 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the GNU General Public
+** License as published by the Free Software Foundation; either
+** version 2 of the License, or (at your option) any later version.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+** General Public License for more details.
+**
+** You should have received a copy of the GNU General Public
+** License along with this library; if not, write to the
+** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+** Boston, MA  02111-1307, USA.
+**
+** Author contact information:
+**   drh@hwaci.com
+**   http://www.hwaci.com/drh/
+**
+*******************************************************************************
+**
+** Implementation of the Setup page for "skins".
+*/
+#include <assert.h>
+#include "config.h"
+#include "skins.h"
+
+/* @-comment: // */
+/*
+** A black-and-white theme with the project title in a bar across the top
+** and no logo image.
+*/
+static const char zBuiltinSkin1[] =
+@ REPLACE INTO config VALUES('css','/* General settings for the entire page */
+@ body {
+@   margin: 0ex 1ex;
+@   padding: 0px;
+@   background-color: white;
+@   font-family: "sans serif";
+@ }
+@
+@ /* The project logo in the upper left-hand corner of each page */
+@ div.logo {
+@   display: table-row;
+@   text-align: center;
+@   /* vertical-align: bottom;*/
+@   font-size: 2em;
+@   font-weight: bold;
+@   background-color: #707070;
+@   color: #ffffff;
+@ }
+@
+@ /* The page title centered at the top of each page */
+@ div.title {
+@   display: table-cell;
+@   font-size: 1.5em;
+@   font-weight: bold;
+@   text-align: left;
+@   padding: 0 0 0 10px;
+@   color: #404040;
+@   vertical-align: bottom;
+@   width: 100%;
+@ }
+@
+@ /* The login status message in the top right-hand corner */
+@ div.status {
+@   display: table-cell;
+@   text-align: right;
+@   vertical-align: bottom;
+@   color: #404040;
+@   font-size: 0.8em;
+@   font-weight: bold;
+@ }
+@
+@ /* The header across the top of the page */
+@ div.header {
+@   display: table;
+@   width: 100%;
+@ }
+@
+@ /* The main menu bar that appears at the top of the page beneath
+@ ** the header */
+@ div.mainmenu {
+@   padding: 5px 10px 5px 10px;
+@   font-size: 0.9em;
+@   font-weight: bold;
+@   text-align: center;
+@   letter-spacing: 1px;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The submenu bar that *sometimes* appears below the main menu */
+@ div.submenu {
+@   padding: 3px 10px 3px 0px;
+@   font-size: 0.9em;
+@   text-align: center;
+@   background-color: #606060;
+@   color: white;
+@ }
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
+@   padding: 3px 10px 3px 10px;
+@   color: white;
+@   text-decoration: none;
+@ }
+@ div.mainmenu a:hover, div.submenu a:hover {
+@   color: #404040;
+@   background-color: white;
+@ }
+@
+@ /* All page content from the bottom of the menu or submenu down to
+@ ** the footer */
+@ div.content {
+@   padding: 0ex 0ex 0ex 0ex;
+@ }
+@ /* Hyperlink colors */
+@ div.content a { color: #604000; }
+@ div.content a:link { color: #604000;}
+@ div.content a:visited { color: #600000; }
+@
+@ /* Some pages have section dividers */
+@ div.section {
+@   margin-bottom: 0px;
+@   margin-top: 1em;
+@   padding: 1px 1px 1px 1px;
+@   font-size: 1.2em;
+@   font-weight: bold;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The "Date" that occurs on the left hand side of timelines */
+@ div.divider {
+@   background: #a0a0a0;
+@   border: 2px #505050 solid;
+@   font-size: 1em; font-weight: normal;
+@   padding: .25em;
+@   margin: .2em 0 .2em 0;
+@   float: left;
+@   clear: left;
+@ }
+@
+@ /* The footer at the very bottom of the page */
+@ div.footer {
+@   font-size: 0.8em;
+@   margin-top: 12px;
+@   padding: 5px 10px 5px 10px;
+@   text-align: right;
+@   background-color: #404040;
+@   color: white;
+@ }
+@
+@ /* The label/value pairs on (for example) the vinfo page */
+@ table.label-value th {
+@   vertical-align: top;
+@   text-align: right;
+@   padding: 0.2ex 2ex;
+@ }');
+@ REPLACE INTO config VALUES('header','<html>
+@ <head>
+@ <title>$<project_name>: $<title></title>
+@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
+@       href="$baseurl/timeline.rss">
+@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
+@       media="screen">
+@ </head>
+@ <body>
+@ <div class="header">
+@   <div class="logo">
+@     <nobr>$<project_name></nobr>
+@   </div>
+@ </div>
+@ <div class="header">
+@   <div class="title">$<title></div>
+@   <div class="status"><nobr><th1>
+@      if {[info exists login]} {
+@        puts "Logged in as $login"
+@      } else {
+@        puts "Not logged in"
+@      }
+@   </th1></nobr></div>
+@ </div>
+@ <div class="mainmenu"><th1>
+@ html "<a href=''$baseurl$index_page''>Home</a> "
+@ if {[hascap h]} {
+@   html "<a href=''$baseurl/dir''>Files</a> "
+@ }
+@ if {[hascap o]} {
+@   html "<a href=''$baseurl/leaves''>Leaves</a> "
+@   html "<a href=''$baseurl/timeline''>Timeline</a> "
+@   html "<a href=''$baseurl/brlist''>Branches</a> "
+@   html "<a href=''$baseurl/taglist''>Tags</a> "
+@ }
+@ if {[hascap r]} {
+@   html "<a href=''$baseurl/reportlist''>Tickets</a> "
+@ }
+@ if {[hascap j]} {
+@   html "<a href=''$baseurl/wiki''>Wiki</a> "
+@ }
+@ if {[hascap s]} {
+@   html "<a href=''$baseurl/setup''>Admin</a> "
+@ } elseif {[hascap a]} {
+@   html "<a href=''$baseurl/setup_ulist''>Users</a> "
+@ }
+@ if {[info exists login]} {
+@   html "<a href=''$baseurl/login''>Logout</a> "
+@ } else {
+@   html "<a href=''$baseurl/login''>Login</a> "
+@ }
+@ </th1></div>
+@ ');
+@ REPLACE INTO config VALUES('footer','<div class="footer">
+@ Fossil version $manifest_version $manifest_date
+@ </div>
+@ </body></html>
+@ ');
+;
+
+/*
+** A tan theme with the project title above the user identification
+** and no logo image.
+*/
+static const char zBuiltinSkin2[] =
+@ REPLACE INTO config VALUES('css','/* General settings for the entire page */
+@ body {
+@   margin: 0ex 0ex;
+@   padding: 0px;
+@   background-color: #fef3bc;
+@   font-family: sans-serif;
+@ }
+@
+@ /* The project logo in the upper left-hand corner of each page */
+@ div.logo {
+@   display: inline;
+@   text-align: center;
+@   vertical-align: bottom;
+@   font-weight: bold;
+@   font-size: 2.5em;
+@   color: #a09048;
+@ }
+@
+@ /* The page title centered at the top of each page */
+@ div.title {
+@   display: table-cell;
+@   font-size: 2em;
+@   font-weight: bold;
+@   text-align: left;
+@   padding: 0 0 0 5px;
+@   color: #a09048;
+@   vertical-align: bottom;
+@   width: 100%;
+@ }
+@
+@ /* The login status message in the top right-hand corner */
+@ div.status {
+@   display: table-cell;
+@   text-align: right;
+@   vertical-align: bottom;
+@   color: #a09048;
+@   padding: 5px 5px 0 0;
+@   font-size: 0.8em;
+@   font-weight: bold;
+@ }
+@
+@ /* The header across the top of the page */
+@ div.header {
+@   display: table;
+@   width: 100%;
+@ }
+@
+@ /* The main menu bar that appears at the top of the page beneath
+@ ** the header */
+@ div.mainmenu {
+@   padding: 5px 10px 5px 10px;
+@   font-size: 0.9em;
+@   font-weight: bold;
+@   text-align: center;
+@   letter-spacing: 1px;
+@   background-color: #a09048;
+@   color: black;
+@ }
+@
+@ /* The submenu bar that *sometimes* appears below the main menu */
+@ div.submenu {
+@   padding: 3px 10px 3px 0px;
+@   font-size: 0.9em;
+@   text-align: center;
+@   background-color: #c0af58;
+@   color: white;
+@ }
+@ div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited {
+@   padding: 3px 10px 3px 10px;
+@   color: white;
+@   text-decoration: none;
+@ }
+@ div.mainmenu a:hover, div.submenu a:hover {
+@   color: #a09048;
+@   background-color: white;
+@ }
+@
+@ /* All page content from the bottom of the menu or submenu down to
+@ ** the footer */
+@ div.content {
+@   padding: 1ex 5px;
+@ }
+@ div.content a { color: #706532; }
+@ div.content a:link { color: #706532; }
+@ div.content a:visited { color: #704032; }
+@ div.content a:hover { background-color: white; color: #706532; }
+@
+@ /* Some pages have section dividers */
+@ div.section {
+@   margin-bottom: 0px;
+@   margin-top: 1em;
+@   padding: 3px 3px 0 3px;
+@   font-size: 1.2em;
+@   font-weight: bold;
+@   background-color: #a09048;
+@   color: white;
+@ }
+@
+@ /* The "Date" that occurs on the left hand side of timelines */
+@ div.divider {
+@   background: #e1d498;
+@   border: 2px #a09048 solid;
+@   font-size: 1em; font-weight: normal;
+@   padding: .25em;
+@   margin: .2em 0 .2em 0;
+@   float: left;
+@   clear: left;
+@ }
+@
+@ /* The footer at the very bottom of the page */
+@ div.footer {
+@   font-size: 0.8em;
+@   margin-top: 12px;
+@   padding: 5px 10px 5px 10px;
+@   text-align: right;
+@   background-color: #a09048;
+@   color: white;
+@ }
+@
+@ /* Hyperlink colors */
+@ div.footer a { color: white; }
+@ div.footer a:link { color: white; }
+@ div.footer a:visited { color: white; }
+@ div.footer a:hover { background-color: white; color: #558195; }
+@
+@ /* <verbatim> blocks */
+@ pre.verbatim {
+@    background-color: #f5f5f5;
+@    padding: 0.5em;
+@ }
+@
+@ /* The label/value pairs on (for example) the ci page */
+@ table.label-value th {
+@   vertical-align: top;
+@   text-align: right;
+@   padding: 0.2ex 2ex;
+@ }
+@ ');
+@ REPLACE INTO config VALUES('header','<html>
+@ <head>
+@ <title>$<project_name>: $<title></title>
+@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
+@       href="$baseurl/timeline.rss">
+@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
+@       media="screen">
+@ </head>
+@ <body>
+@ <div class="header">
+@   <div class="title">$<title></div>
+@   <div class="status">
+@     <div class="logo"><nobr>$<project_name></nobr></div><br/>
+@     <nobr><th1>
+@      if {[info exists login]} {
+@        puts "Logged in as $login"
+@      } else {
+@        puts "Not logged in"
+@      }
+@   </th1></nobr></div>
+@ </div>
+@ <div class="mainmenu"><th1>
+@ html "<a href=''$baseurl$index_page''>Home</a> "
+@ if {[hascap h]} {
+@   html "<a href=''$baseurl/dir''>Files</a> "
+@ }
+@ if {[hascap o]} {
+@   html "<a href=''$baseurl/leaves''>Leaves</a> "
+@   html "<a href=''$baseurl/timeline''>Timeline</a> "
+@   html "<a href=''$baseurl/brlist''>Branches</a> "
+@   html "<a href=''$baseurl/taglist''>Tags</a> "
+@ }
+@ if {[hascap r]} {
+@   html "<a href=''$baseurl/reportlist''>Tickets</a> "
+@ }
+@ if {[hascap j]} {
+@   html "<a href=''$baseurl/wiki''>Wiki</a> "
+@ }
+@ if {[hascap s]} {
+@   html "<a href=''$baseurl/setup''>Admin</a> "
+@ } elseif {[hascap a]} {
+@   html "<a href=''$baseurl/setup_ulist''>Users</a> "
+@ }
+@ if {[info exists login]} {
+@   html "<a href=''$baseurl/login''>Logout</a> "
+@ } else {
+@   html "<a href=''$baseurl/login''>Login</a> "
+@ }
+@ </th1></div>
+@ ');
+@ REPLACE INTO config VALUES('footer','<div class="footer">
+@ Fossil version $manifest_version $manifest_date
+@ </div>
+@ </body></html>
+@ ');
+;
+
+/*
+** Black letters on a white or cream background with the main menu
+** stuck on the left-hand side.
+*/
+static const char zBuiltinSkin3[] =
+@ REPLACE INTO config VALUES('css','/* General settings for the entire page */
+@ body {
+@     margin:0px 0px 0px 0px;
+@     padding:0px;
+@     font-family:verdana, arial, helvetica, "sans serif";
+@     color:#333;
+@     background-color:white;
+@ }
+@
+@ /* consistent colours */
+@ h2 {
+@   color: #333;
+@ }
+@ h3 {
+@   color: #333;
+@ }
+@
+@ /* The project logo in the upper left-hand corner of each page */
+@ div.logo {
+@   display: table-cell;
+@   text-align: left;
+@   vertical-align: bottom;
+@   font-weight: bold;
+@   color: #333;
+@ }
+@
+@ /* The page title centered at the top of each page */
+@ div.title {
+@   display: table-cell;
+@   font-size: 2em;
+@   font-weight: bold;
+@   text-align: center;
+@   color: #333;
+@   vertical-align: bottom;
+@   width: 100%;
+@ }
+@
+@ /* The login status message in the top right-hand corner */
+@ div.status {
+@   display: table-cell;
+@   padding-right: 10px;
+@   text-align: right;
+@   vertical-align: bottom;
+@   padding-bottom: 5px;
+@   color: #333;
+@   font-size: 0.8em;
+@   font-weight: bold;
+@ }
+@
+@ /* The header across the top of the page */
+@ div.header {
+@     margin:10px 0px 10px 0px;
+@     padding:1px 0px 0px 20px;
+@     border-style:solid;
+@     border-color:black;
+@     border-width:1px 0px;
+@     background-color:#eee;
+@ }
+@
+@ /* The main menu bar that appears at the top left of the page beneath
+@ ** the header. Width must be co-ordinated with the container below */
+@ div.mainmenu {
+@   float: left;
+@   margin-left: 10px;
+@   margin-right: 10px;
+@   font-size: 0.9em;
+@   font-weight: bold;
+@   padding:5px;
+@   background-color:#eee;
+@   border:1px solid #999;
+@   width:8em;
+@ }
+@
+@ /* Main menu is now a list */
+@ div.mainmenu ul {
+@   padding: 0;
+@   list-style:none;
+@ }
+@ div.mainmenu a, div.mainmenu a:visited{
+@   padding: 1px 10px 1px 10px;
+@   color: #333;
+@   text-decoration: none;
+@ }
+@ div.mainmenu a:hover {
+@   color: #eee;
+@   background-color: #333;
+@ }
+@
+@ /* Container for the sub-menu and content so they don''t spread
+@ ** out underneath the main menu */
+@ #container {
+@   padding-left: 9em;
+@ }
+@
+@ /* The submenu bar that *sometimes* appears below the main menu */
+@ div.submenu {
+@   padding: 3px 10px 3px 10px;
+@   font-size: 0.9em;
+@   text-align: center;
+@   border:1px solid #999;
+@   border-width:1px 0px;
+@   background-color: #eee;
+@   color: #333;
+@ }
+@ div.submenu a, div.submenu a:visited {
+@   padding: 3px 10px 3px 10px;
+@   color: #333;
+@   text-decoration: none;
+@ }
+@ div.submenu a:hover {
+@   color: #eee;
+@   background-color: #333;
+@ }
+@
+@ /* All page content from the bottom of the menu or submenu down to
+@ ** the footer */
+@ div.content {
+@   float right;
+@   padding: 2ex 1ex 0ex 2ex;
+@ }
+@
+@ /* Some pages have section dividers */
+@ div.section {
+@   margin-bottom: 0px;
+@   margin-top: 1em;
+@   padding: 1px 1px 1px 1px;
+@   font-size: 1.2em;
+@   font-weight: bold;
+@   border-style:solid;
+@   border-color:#999;
+@   border-width:1px 0px;
+@   background-color: #eee;
+@   color: #333;
+@ }
+@
+@ /* The "Date" that occurs on the left hand side of timelines */
+@ div.divider {
+@   background: #eee;
+@   border: 2px #999 solid;
+@   font-size: 1em; font-weight: normal;
+@   padding: .25em;
+@   margin: .2em 0 .2em 0;
+@   float: left;
+@   clear: left;
+@   color: #333
+@ }
+@
+@ /* The footer at the very bottom of the page */
+@ div.footer {
+@   font-size: 0.8em;
+@   margin-top: 12px;
+@   padding: 5px 10px 5px 10px;
+@   text-align: right;
+@   background-color: #eee;
+@   color: #555;
+@ }
+@
+@ /* <verbatim> blocks */
+@ pre.verbatim {
+@    background-color: #f5f5f5;
+@    padding: 0.5em;
+@ }
+@
+@ /* The label/value pairs on (for example) the ci page */
+@ table.label-value th {
+@   vertical-align: top;
+@   text-align: right;
+@   padding: 0.2ex 2ex;
+@ }');
+@ REPLACE INTO config VALUES('header','<html>
+@ <head>
+@ <title>$<project_name>: $<title></title>
+@ <link rel="alternate" type="application/rss+xml" title="RSS Feed"
+@       href="$baseurl/timeline.rss">
+@ <link rel="stylesheet" href="$baseurl/style.css" type="text/css"
+@       media="screen">
+@ </head>
+@ <body>
+@ <div class="header">
+@   <div class="logo">
+@     <!-- <img src="$baseurl/logo" alt="logo"> -->
+@     <br><nobr>$<project_name></nobr>
+@   </div>
+@   <div class="title">$<title></div>
+@   <div class="status"><nobr><th1>
+@      if {[info exists login]} {
+@        puts "Logged in as $login"
+@      } else {
+@        puts "Not logged in"
+@      }
+@   </th1></nobr></div>
+@ </div>
+@ <div class="mainmenu"><ul><th1>
+@ html "<li><a href=''$baseurl$index_page''>Home</a></li>"
+@ if {[hascap h]} {
+@   html "<li><a href=''$baseurl/dir''>Files</a></li>"
+@ }
+@ if {[hascap o]} {
+@   html "<li><a href=''$baseurl/leaves''>Leaves</a></li>"
+@   html "<li><a href=''$baseurl/timeline''>Timeline</a></li>"
+@   html "<li><a href=''$baseurl/brlist''>Branches</a></li>"
+@   html "<li><a href=''$baseurl/taglist''>Tags</a></li>"
+@ }
+@ if {[hascap r]} {
+@   html "<li><a href=''$baseurl/reportlist''>Tickets</a></li>"
+@ }
+@ if {[hascap j]} {
+@   html "<li><a href=''$baseurl/wiki''>Wiki</a></li>"
+@ }
+@ if {[hascap s]} {
+@   html "<li><a href=''$baseurl/setup''>Admin</a></li>"
+@ } elseif {[hascap a]} {
+@   html "<li><a href=''$baseurl/setup_ulist''>Users</a></li>"
+@ }
+@ if {[info exists login]} {
+@   html "<li><a href=''$baseurl/login''>Logout</a></li>"
+@ } else {
+@   html "<li><a href=''$baseurl/login''>Login</a></li>"
+@ }
+@ </th1></ul></div>
+@ <div id="container">
+@ ');
+@ REPLACE INTO config VALUES('footer','</div>
+@ <div class="footer">
+@ Fossil version $manifest_version $manifest_date
+@ </div>
+@ </body></html>
+@ ');
+;
+/*
+** An array of available built-in skins.
+*/
+static struct BuiltinSkin {
+  const char *zName;
+  const char *zValue;
+} aBuiltinSkin[] = {
+  { "Default",                     0 /* Filled in at runtime */ },
+  { "Plain Gray, No Logo",         zBuiltinSkin1                },
+  { "Khaki, No Logo",              zBuiltinSkin2                },
+  { "Black & White, Menu on Left", zBuiltinSkin3                },
+};
+
+/*
+** For a skin named zSkinName, compute the name of the CONFIG table
+** entry where that skin is stored and return it.
+**
+** Return NULL if zSkinName is NULL or an empty string.
+**
+** If ifExists is true, and the named skin does not exist, return NULL.
+*/
+static char *skinVarName(const char *zSkinName, int ifExists){
+  char *z;
+  if( zSkinName==0 || zSkinName[0]==0 ) return 0;
+  z = mprintf("skin:%s", zSkinName);
+  if( ifExists && !db_exists("SELECT 1 FROM config WHERE name=%Q", z) ){
+    free(z);
+    z = 0;
+  }
+  return z;
+}
+
+/*
+** Construct and return a string that represents the current skin if
+** useDefault==0 or a string for the default skin if useDefault==1.
+**
+** Memory to hold the returned string is obtained from malloc.
+*/
+static char *getSkin(int useDefault){
+  Blob val;
+  blob_zero(&val);
+  blob_appendf(&val, "REPLACE INTO config VALUES('css',%Q);\n",
+     useDefault ? zDefaultCSS : db_get("css", (char*)zDefaultCSS)
+  );
+  blob_appendf(&val, "REPLACE INTO config VALUES('header',%Q);\n",
+     useDefault ? zDefaultHeader : db_get("header", (char*)zDefaultHeader)
+  );
+  blob_appendf(&val, "REPLACE INTO config VALUES('footer',%Q);\n",
+     useDefault ? zDefaultFooter : db_get("footer", (char*)zDefaultFooter)
+  );
+  return blob_str(&val);
+}
+
+/*
+** Construct the default skin string and fill in the corresponding
+** entry in aBuildinSkin[]
+*/
+static void setDefaultSkin(void){
+  aBuiltinSkin[0].zValue = getSkin(1);
+}
+
+/*
+** WEBPAGE: setup_skin
+*/
+void setup_skin(void){
+  const char *z;
+  char *zName;
+  char *zErr = 0;
+  const char *zCurrent;  /* Current skin */
+  int i;                 /* Loop counter */
+  Stmt q;
+
+  login_check_credentials();
+  if( !g.okSetup ){
+    login_needed();
+  }
+  db_begin_transaction();
+
+  /* Process requests to delete a user-defined skin */
+  if( P("del1") && (zName = skinVarName(P("sn"), 1))!=0 ){
+    style_header("Confirm Custom Skin Delete");
+    @ <form action="%s(g.zBaseURL)/setup_skin" method="POST">
+    @ <p>Deletion of a custom skin is a permanent action that cannot
+    @ be undone.  Please confirm that this is what you want to do:</p>
+    @ <input type="hidden" name="sn" value="%h(P("sn"))">
+    @ <input type="submit" name="del2" value="Confirm - Delete The Skin">
+    @ <input type="submit" name="cancel" value="Cancel - Do Not Delete">
+    login_insert_csrf_secret();
+    @ </form>
+    style_footer();
+    return;
+  }
+  if( P("del2")!=0 && (zName = skinVarName(P("sn"), 1))!=0 ){
+    db_multi_exec("DELETE FROM config WHERE name=%Q", zName);
+  }
+
+  setDefaultSkin();
+  zCurrent = getSkin(0);
+
+  if( P("save")!=0 && (zName = skinVarName(P("save"),0))!=0 ){
+    if( db_exists("SELECT 1 FROM config WHERE name=%Q", zName)
+          || strcmp(zName, "Default")==0 ){
+      zErr = mprintf("Skin name \"%h\" already exists. "
+                     "Choose a different name.", P("sn"));
+    }else{
+      db_multi_exec("INSERT INTO config VALUES(%Q,%Q)",
+         zName, zCurrent
+      );
+    }
+  }
+
+  /* The user pressed the "Use This Skin" button. */
+  if( P("load") && (z = P("sn"))!=0 && z[0] ){
+    int seen = 0;
+    for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+      if( strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){
+        seen = 1;
+        break;
+      }
+    }
+    if( !seen ){
+      seen = db_exists("SELECT 1 FROM config WHERE name GLOB 'skin:*'"
+                       " AND value=%Q", zCurrent);
+    }
+    if( !seen ){
+      db_multi_exec(
+        "INSERT INTO config VALUES("
+        "  strftime('skin:Backup On %%Y-%%m-%%d %%H:%%M:%%S'),"
+        "  %Q)", zCurrent
+      );
+    }
+    seen = 0;
+    for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+      if( strcmp(aBuiltinSkin[i].zName, z)==0 ){
+        seen = 1;
+        zCurrent = aBuiltinSkin[i].zValue;
+        db_multi_exec("%s", zCurrent);
+        break;
+      }
+    }
+    if( !seen ){
+      zName = skinVarName(z,0);
+      zCurrent = db_get(zName, 0);
+      db_multi_exec("%s", zCurrent);
+    }
+  }
 
+  style_header("Skins");
+  @ <p>A "skin" is a combination of
+  @ <a href="setup_editcss">CSS</a>,
+  @ <a href="setup_header">Header</a>, and
+  @ <a href="setup_footer">Footer</a> that determines the look and feel
+  @ of the web interface.</p>
+  @
+  @ <h2>Available Skins:</h2>
+  @ <ol>
+  for(i=0; i<sizeof(aBuiltinSkin)/sizeof(aBuiltinSkin[0]); i++){
+    z = aBuiltinSkin[i].zName;
+    if( strcmp(aBuiltinSkin[i].zValue, zCurrent)==0 ){
+      @ <li><p>%h(z).&nbsp;&nbsp; <b>Currently In Use</b></p>
+    }else{
+      @ <li><form action="%s(g.zBaseURL)/setup_skin" method="POST">
+      @ %h(z).&nbsp;&nbsp;
+      @ <input type="hidden" name="sn" value="%h(z)">
+      @ <input type="submit" name="load" value="Use This Skin">
+      @ </form></li>
+    }
+  }
+  db_prepare(&q,
+     "SELECT substr(name, 6), value FROM config"
+     " WHERE name GLOB 'skin:*'"
+     " ORDER BY name"
+  );
+  while( db_step(&q)==SQLITE_ROW ){
+    const char *zN = db_column_text(&q, 0);
+    const char *zV = db_column_text(&q, 1);
+    if( strcmp(zV, zCurrent)==0 ){
+      @ <li><p>%h(zN).&nbsp;&nbsp;  <b>Currently In Use</b></p>
+    }else{
+      @ <li><form action="%s(g.zBaseURL)/setup_skin" method="POST">
+      @ %h(zN).&nbsp;&nbsp;
+      @ <input type="hidden" name="sn" value="%h(zN)">
+      @ <input type="submit" name="load" value="Use This Skin">
+      @ <input type="submit" name="del1" value="Delete This Skin">
+      @ </form></li>
+    }
+  }
+  db_finalize(&q);
+  @ </ol>
+  style_footer();
+  db_end_transaction(0);
+}

Modified src/style.c from [c7fc56d774] to [3cc4be49bc].

@@ -360,11 +360,11 @@
 @   text-align: right;
 @   background-color: #558195;
 @   color: white;
 @ }
 @
-@ /* Make the links in the footer less ugly... */
+@ /* Hyperlink colors in the footer */
 @ div.footer a { color: white; }
 @ div.footer a:link { color: white; }
 @ div.footer a:visited { color: white; }
 @ div.footer a:hover { background-color: white; color: #558195; }
 @
@@ -377,25 +377,10 @@
 @ /* The label/value pairs on (for example) the ci page */
 @ table.label-value th {
 @   vertical-align: top;
 @   text-align: right;
 @   padding: 0.2ex 2ex;
-@ }
-@
-@ /* For marking important UI elements which shouldn't be
-@    lightly dismissed. I mainly use it to mark "not yet
-@    implemented" parts of a page. Whether or not to have
-@    a 'border' attribute set is arguable. */
-@ .achtung {
-@   color: #ff0000;
-@   background: #ffff00;
-@   border: 1px solid #ff0000;
-@ }
-@
-@ div.miniform {
-@     font-size: smaller;
-@     margin: 8px;
 @ }
 ;
 
 /*
 ** WEBPAGE: style.css

Modified src/timeline.c from [f76d6fc0d6] to [ece922611b].

@@ -28,17 +28,37 @@
 #include <time.h>
 #include "config.h"
 #include "timeline.h"
 
 /*
+** Shorten a UUID so that is the minimum length needed to contain
+** at least one digit in the range 'a'..'f'.  The minimum length is 10.
+*/
+static void shorten_uuid(char *zDest, const char *zSrc){
+  int i;
+  for(i=0; i<10 && zSrc[i]<='9'; i++){}
+  memcpy(zDest, zSrc, 10);
+  if( i==10 ){
+    do{
+      zDest[i] = zSrc[i];
+      i++;
+    }while( zSrc[i-1]<='9' );
+  }else{
+    i = 10;
+  }
+  zDest[i] = 0;
+}
+
+
+/*
 ** Generate a hyperlink to a version.
 */
 void hyperlink_to_uuid(const char *zUuid){
   char zShortUuid[UUID_SIZE+1];
-  sprintf(zShortUuid, "%.10s", zUuid);
+  shorten_uuid(zShortUuid, zUuid);
   if( g.okHistory ){
-    @ <a href="%s(g.zBaseURL)/info/%s(zUuid)">[%s(zShortUuid)]</a>
+    @ <a href="%s(g.zBaseURL)/info/%s(zShortUuid)">[%s(zShortUuid)]</a>
   }else{
     @ <b>[%s(zShortUuid)]</b>
   }
 }
 
@@ -51,14 +71,14 @@
   const char *zIn,     /* Javascript proc for mouseover */
   const char *zOut,    /* Javascript proc for mouseout */
   int id               /* Argument to javascript procs */
 ){
   char zShortUuid[UUID_SIZE+1];
-  sprintf(zShortUuid, "%.10s", zUuid);
+  shorten_uuid(zShortUuid, zUuid);
   if( g.okHistory ){
     @ <a onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'
-    @    href="%s(g.zBaseURL)/vinfo/%s(zUuid)">[%s(zShortUuid)]</a>
+    @    href="%s(g.zBaseURL)/vinfo/%s(zShortUuid)">[%s(zShortUuid)]</a>
   }else{
     @ <b onmouseover='%s(zIn)("m%d(id)")' onmouseout='%s(zOut)("m%d(id)")'>
     @ [%s(zShortUuid)]</b>
   }
 }
@@ -397,10 +417,11 @@
 **    p=RID          artifact RID and up to COUNT parents and ancestors
 **    d=RID          artifact RID and up to COUNT descendants
 **    t=TAGID        show only check-ins with the given tagid
 **    u=USER         only if belonging to this user
 **    y=TYPE         'ci', 'w', 't'
+**    s=TEXT         string search (comment and brief)
 **
 ** p= and d= can appear individually or together.  If either p= or d=
 ** appear, then u=, y=, a=, and b= are ignored.
 **
 ** If a= and b= appear, only a= is used.  If neither appear, the most
@@ -419,10 +440,11 @@
   const char *zType = PD("y","all"); /* Type of events.  All if NULL */
   const char *zAfter = P("a");       /* Events after this time */
   const char *zBefore = P("b");      /* Events before this time */
   const char *zCirca = P("c");       /* Events near this time */
   const char *zTagName = P("t");     /* Show events with this tag */
+  const char *zString = P("s");      /* String text search of comment and brief */
   HQuery url;                        /* URL for various branch links */
   int tagid;                         /* Tag ID */
   int tmFlags;                       /* Timeline flags */
 
   /* To view the timeline, must have permission to read project data.
@@ -464,13 +486,13 @@
     blob_appendf(&sql, " AND event.objid IN ok");
     nd = 0;
     if( d_rid ){
       compute_descendants(d_rid, nEntry+1);
       nd = db_int(0, "SELECT count(*)-1 FROM ok");
-      if( nd>0 ){
+      if( nd>=0 ){
         db_multi_exec("%s", blob_str(&sql));
-        blob_appendf(&desc, "%d descendants", nd);
+        blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
       }
       timeline_add_dividers(
         db_text("1","SELECT datetime(mtime,'localtime') FROM event"
                     " WHERE objid=%d", d_rid)
       );
@@ -523,10 +545,15 @@
       }
     }
     if( zUser ){
       blob_appendf(&sql, " AND event.user=%Q", zUser);
       url_add_parameter(&url, "u", zUser);
+    }
+    if ( zString ){
+      blob_appendf(&sql, " AND (event.comment LIKE '%%%q%%' OR event.brief LIKE '%%%q%%')",
+        zString, zString);
+      url_add_parameter(&url, "s", zString);
     }
     if( zAfter ){
       while( isspace(zAfter[0]) ){ zAfter++; }
       if( zAfter[0] ){
         blob_appendf(&sql,
@@ -827,31 +854,10 @@
   ;
   return zBaseSql;
 }
 
 /*
-** Equivalent to timeline_query_for_tty(), except that:
-**
-** a) accepts a the -type=XX flag to set the event type to filter on.
-**    The values of XX are the same as supported by the /timeline page.
-**
-** b) The returned string must be freed using free().
-*/
-char * timeline_query_for_tty_m(void){
-  Blob bl;
-  char const * zType = 0;
-  blob_zero(&bl);
-  blob_append( &bl, timeline_query_for_tty(), -1 );
-  zType = find_option( "type", "t", 1 );
-  if( zType && *zType )
-  {
-      blob_appendf( &bl, " AND event.type=%Q", zType );
-  }
-  return blob_buffer(&bl);
-}
-
-/*
 ** Return true if the input string is a date in the ISO 8601 format:
 ** YYYY-MM-DD.
 */
 static int isIsoDate(const char *z){
   return strlen(z)==10
@@ -894,11 +900,11 @@
   int n, k;
   const char *zCount;
   const char *zType;
   char *zOrigin;
   char *zDate;
-  char *zSQL;
+  Blob sql;
   int objid = 0;
   Blob uuid;
   int mode = 0 ;       /* 0:none  1: before  2:after  3:children  4:parents */
   db_find_and_open_repository(1);
   zCount = find_option("count","n",1);
@@ -961,31 +967,33 @@
       if( isIsoDate(zOrigin) ) zShift = ",'+1 day'";
     }
     zDate = mprintf("(SELECT julianday(%Q%s, 'utc'))", zOrigin, zShift);
   }
   if( mode==0 ) mode = 1;
-  zSQL = mprintf("%z AND event.mtime %s %s",
-     timeline_query_for_tty_m(),
+  blob_zero(&sql);
+  blob_append(&sql, timeline_query_for_tty(), -1);
+  blob_appendf(&sql, "  AND event.mtime %s %s",
      (mode==1 || mode==4) ? "<=" : ">=",
      zDate
   );
+
   if( mode==3 || mode==4 ){
     db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
     if( mode==3 ){
       compute_descendants(objid, n);
     }else{
       compute_ancestors(objid, n);
     }
-    zSQL = mprintf("%z AND blob.rid IN ok", zSQL);
+    blob_appendf(&sql, " AND blob.rid IN ok");
   }
   if( zType && (zType[0]!='a') ){
-      zSQL = mprintf( "%z AND event.type=%Q ", zSQL, zType);
+    blob_appendf(&sql, " AND event.type=%Q ", zType);
   }
 
-  zSQL = mprintf("%z ORDER BY event.mtime DESC", zSQL);
-  db_prepare(&q, zSQL);
-  free( zSQL );
+  blob_appendf(&sql, " ORDER BY event.mtime DESC");
+  db_prepare(&q, blob_str(&sql));
+  blob_reset(&sql);
   print_timeline(&q, n);
   db_finalize(&q);
 }
 
 /*

Modified src/undo.c from [e141d7b88f] to [0d2cc71745].

@@ -189,13 +189,13 @@
 /*
 ** COMMAND: undo
 **
 ** Usage: %fossil undo ?FILENAME...?
 **
-** Undo the most recent update or merge operation.  If FILENAME is
+** Undo the most recent update or merge or revert operation.  If FILENAME is
 ** specified then restore the content of the named file(s) but otherwise
-** leave the update or merge in effect.
+** leave the update or merge or revert in effect.
 **
 ** A single level of undo/redo is supported.  The undo/redo stack
 ** is cleared by the commit and checkout commands.
 */
 void undo_cmd(void){
@@ -228,12 +228,12 @@
 /*
 ** COMMAND: redo
 **
 ** Usage: %fossil redo ?FILENAME...?
 **
-** Redo the an update or merge operation that has been undone by the
-** undo command.  If FILENAME is specified then restore the changes
+** Redo the an update or merge or revert operation that has been undone
+** by the undo command.  If FILENAME is specified then restore the changes
 ** associated with the named file(s) but otherwise leave the update
 ** or merge undone.
 **
 ** A single level of undo/redo is supported.  The undo/redo stack
 ** is cleared by the commit and checkout commands.

Modified src/update.c from [74a2862e7d] to [eb5c093f17].

@@ -36,53 +36,72 @@
 }
 
 /*
 ** COMMAND: update
 **
-** Usage: %fossil update ?VERSION? ?--latest?
+** Usage: %fossil update ?VERSION? ?FILES...?
+**
+** Change the version of the current checkout to VERSION.  Any uncommitted
+** changes are retained and applied to the new checkout.
+**
+** The VERSION argument can be a specific version or tag or branch name.
+** If the VERSION argument is omitted, then the leaf of the the subtree
+** that begins at the current version is used, if there is only a single
+** leaf.  VERSION can also be "current" to select the leaf of the current
+** version or "latest" to select the most recent check-in.
+**
+** If one or more FILES are listed after the VERSION then only the
+** named files are candidates to be updated.  If FILES is omitted, all
+** files in the current checkout are subject to be updated.
 **
-** The optional argument is a version that should become the current
-** version.  If the argument is omitted, then use the leaf of the
-** tree that begins with the current version, if there is only a
-** single leaf.  If there are a multiple leaves, the latest is used
-** if the --latest flag is present.
+** The -n or --nochange option causes this command to do a "dry run".  It
+** prints out what would have happened but does not actually make any
+** changes to the current checkout or the repository.
 **
-** This command is different from the "checkout" in that edits are
-** not overwritten.  Edits are merged into the new version.
+** The -v or --verbose option prints status information about unchanged
+** files in addition to those file that actually do change.
 */
 void update_cmd(void){
   int vid;              /* Current version */
   int tid=0;            /* Target version - version we are changing to */
   Stmt q;
-  int latestFlag;       /* Pick the latest version if true */
-  int forceFlag;        /* True force the update */
+  int latestFlag;       /* --latest.  Pick the latest version if true */
+  int nochangeFlag;     /* -n or --nochange.  Do a dry run */
+  int verboseFlag;      /* -v or --verbose.  Output extra information */
 
   url_proxy_options();
   latestFlag = find_option("latest",0, 0)!=0;
-  forceFlag = find_option("force","f",0)!=0;
-  if( g.argc!=3 && g.argc!=2 ){
-    usage("?VERSION?");
-  }
+  nochangeFlag = find_option("nochange","n",0)!=0;
+  verboseFlag = find_option("verbose","v",0)!=0;
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
   if( vid==0 ){
     fossil_fatal("cannot find current version");
   }
   if( db_exists("SELECT 1 FROM vmerge") ){
     fossil_fatal("cannot update an uncommitted merge");
   }
 
-  if( g.argc==3 ){
-    tid = name_to_rid(g.argv[2]);
-    if( tid==0 ){
-      fossil_fatal("not a version: %s", g.argv[2]);
-    }
-    if( !is_a_version(tid) ){
-      fossil_fatal("not a version: %s", g.argv[2]);
+  if( g.argc>=3 ){
+    if( strcmp(g.argv[2], "current")==0 ){
+      /* If VERSION is "current", then use the same algorithm to find the
+      ** target as if VERSION were omitted. */
+    }else if( strcmp(g.argv[2], "latest")==0 ){
+      /* If VERSION is "latest", then use the same algorithm to find the
+      ** target as if VERSION were omitted and the --latest flag is present.
+      */
+      latestFlag = 1;
+    }else{
+      tid = name_to_rid(g.argv[2]);
+      if( tid==0 ){
+        fossil_fatal("no such version: %s", g.argv[2]);
+      }else if( !is_a_version(tid) ){
+        fossil_fatal("no such version: %s", g.argv[2]);
+      }
     }
   }
-  autosync(AUTOSYNC_PULL);
+  if( !nochangeFlag ) autosync(AUTOSYNC_PULL);
 
   if( tid==0 ){
     compute_leaves(vid, 1);
     if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
       db_prepare(&q,
@@ -99,11 +118,11 @@
                     " WHERE event.objid=leaves.rid"
                     " ORDER BY event.mtime DESC");
   }
 
   db_begin_transaction();
-  vfile_check_signature(vid);
+  vfile_check_signature(vid, 1);
   undo_begin();
   load_vfile_from_rid(tid);
 
   /*
   ** The record.fn field is used to match files against each other.  The
@@ -111,11 +130,11 @@
   ** in the current checkout, the pivot, and the version being merged.
   */
   db_multi_exec(
     "DROP TABLE IF EXISTS fv;"
     "CREATE TEMP TABLE fv("
-    "  fn TEXT PRIMARY KEY,"      /* The filename */
+    "  fn TEXT PRIMARY KEY,"      /* The filename relative to root */
     "  idv INTEGER,"              /* VFILE entry for current version */
     "  idt INTEGER,"              /* VFILE entry for target version */
     "  chnged BOOLEAN,"           /* True if current version has been edited */
     "  ridv INTEGER,"             /* Record ID for current version */
     "  ridt INTEGER "             /* Record ID for target */
@@ -151,99 +170,141 @@
       id, rid, chnged, fn
     );
   }
   db_finalize(&q);
 
+  /* If FILES appear on the command-line, remove from the "fv" table
+  ** every entry that is not named on the command-line.
+  */
+  if( g.argc>=4 ){
+    Blob sql;              /* SQL statement to purge unwanted entries */
+    char *zSep = "(";      /* Separator in the list of filenames */
+    Blob treename;         /* Normalized filename */
+    int i;                 /* Loop counter */
+
+    blob_zero(&sql);
+    blob_append(&sql, "DELETE FROM fv WHERE fn NOT IN ", -1);
+    for(i=3; i<g.argc; i++){
+      file_tree_name(g.argv[i], &treename, 1);
+      blob_appendf(&sql, "%s'%q'", zSep, blob_str(&treename));
+      blob_reset(&treename);
+      zSep = ",";
+    }
+    blob_append(&sql, ")", -1);
+    db_multi_exec(blob_str(&sql));
+    blob_reset(&sql);
+  }
+
   db_prepare(&q,
     "SELECT fn, idv, ridv, idt, ridt, chnged FROM fv ORDER BY 1"
   );
   while( db_step(&q)==SQLITE_ROW ){
-    const char *zName = db_column_text(&q, 0);
-    int idv = db_column_int(&q, 1);
-    int ridv = db_column_int(&q, 2);
-    int idt = db_column_int(&q, 3);
-    int ridt = db_column_int(&q, 4);
-    int chnged = db_column_int(&q, 5);
-
+    const char *zName = db_column_text(&q, 0);  /* The filename from root */
+    int idv = db_column_int(&q, 1);             /* VFILE entry for current */
+    int ridv = db_column_int(&q, 2);            /* RecordID for current */
+    int idt = db_column_int(&q, 3);             /* VFILE entry for target */
+    int ridt = db_column_int(&q, 4);            /* RecordID for target */
+    int chnged = db_column_int(&q, 5);          /* Current is edited */
+    char *zFullPath;                            /* Full pathname of the file */
+
+    zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
     if( idv>0 && ridv==0 && idt>0 ){
       /* Conflict.  This file has been added to the current checkout
       ** but also exists in the target checkout.  Use the current version.
       */
       printf("CONFLICT %s\n", zName);
     }else if( idt>0 && idv==0 ){
       /* File added in the target. */
       printf("ADD %s\n", zName);
       undo_save(zName);
-      vfile_to_disk(0, idt, 0);
+      if( !nochangeFlag ) vfile_to_disk(0, idt, 0);
     }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
       /* The file is unedited.  Change it to the target version */
       printf("UPDATE %s\n", zName);
       undo_save(zName);
-      vfile_to_disk(0, idt, 0);
+      if( !nochangeFlag ) vfile_to_disk(0, idt, 0);
+    }else if( idt>0 && idv>0 && file_size(zFullPath)<0 ){
+      /* The file missing from the local check-out. Restore it to the
+      ** version that appears in the target. */
+      printf("UPDATE %s\n", zName);
+      undo_save(zName);
+      if( !nochangeFlag ) vfile_to_disk(0, idt, 0);
     }else if( idt==0 && idv>0 ){
       if( ridv==0 ){
         /* Added in current checkout.  Continue to hold the file as
         ** as an addition */
         db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
       }else if( chnged ){
+        /* Edited locally but deleted from the target.  Delete it. */
         printf("CONFLICT %s\n", zName);
       }else{
         char *zFullPath;
         printf("REMOVE %s\n", zName);
         undo_save(zName);
         zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
-        unlink(zFullPath);
+        if( !nochangeFlag ) unlink(zFullPath);
         free(zFullPath);
       }
     }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
       /* Merge the changes in the current tree into the target version */
       Blob e, r, t, v;
       int rc;
-      char *zFullPath;
       printf("MERGE %s\n", zName);
       undo_save(zName);
-      zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
       content_get(ridt, &t);
       content_get(ridv, &v);
       blob_zero(&e);
       blob_read_from_file(&e, zFullPath);
       rc = blob_merge(&v, &e, &t, &r);
       if( rc>=0 ){
-        blob_write_to_file(&r, zFullPath);
+        if( !nochangeFlag ) blob_write_to_file(&r, zFullPath);
         if( rc>0 ){
           printf("***** %d merge conflicts in %s\n", rc, zName);
         }
       }else{
         printf("***** Cannot merge binary file %s\n", zName);
       }
-      free(zFullPath);
       blob_reset(&v);
       blob_reset(&e);
       blob_reset(&t);
       blob_reset(&r);
-
+    }else if( verboseFlag ){
+      printf("UNCHANGED %s\n", zName);
     }
+    free(zFullPath);
   }
   db_finalize(&q);
 
   /*
   ** Clean up the mid and pid VFILE entries.  Then commit the changes.
   */
-  db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
-  manifest_to_disk(tid);
-  db_lset_int("checkout", tid);
-  db_end_transaction(0);
+  if( nochangeFlag ){
+    db_end_transaction(1);  /* With --nochange, rollback changes */
+  }else{
+    if( g.argc<=3 ){
+      /* All files updated.  Shift the current checkout to the target. */
+      db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
+      manifest_to_disk(tid);
+      db_lset_int("checkout", tid);
+    }else{
+      /* A subset of files have been checked out.  Keep the current
+      ** checkout unchanged. */
+      db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
+    }
+    db_end_transaction(0);
+  }
 }
 
 
 /*
 ** Get the contents of a file within a given revision.
 */
 int historical_version_of_file(
   const char *revision,    /* The baseline name containing the file */
   const char *file,        /* Full treename of the file */
-  Blob *content            /* Put the content here */
+  Blob *content,           /* Put the content here */
+  int errCode              /* Error code if file not found.  Panic if 0. */
 ){
   Blob mfile;
   Manifest m;
   int i, rid=0;
 
@@ -251,10 +312,11 @@
     rid = name_to_rid(revision);
   }else{
     rid = db_lget_int("checkout", 0);
   }
   if( !is_a_version(rid) ){
+    if( errCode>0 ) return errCode;
     fossil_fatal("no such check-out: %s", revision);
   }
   content_get(rid, &mfile);
 
   if( manifest_parse(&m, &mfile) ){
@@ -262,76 +324,77 @@
       if( strcmp(m.aFile[i].zName, file)==0 ){
         rid = uuid_to_rid(m.aFile[i].zUuid, 0);
         return content_get(rid, content);
       }
     }
-    fossil_fatal("file %s does not exist in baseline: %s", file, revision);
-  }else{
+    if( errCode<=0 ){
+      fossil_fatal("file %s does not exist in baseline: %s", file, revision);
+    }
+  }else if( errCode<=0 ){
     fossil_panic("could not parse manifest for baseline: %s", revision);
   }
-  return 0;
+  return errCode;
 }
 
 
 /*
 ** COMMAND: revert
 **
-** Usage: %fossil revert ?--yes? ?-r REVISION? FILE
+** Usage: %fossil revert ?-r REVISION? FILE ...
 **
 ** Revert to the current repository version of FILE, or to
 ** the version associated with baseline REVISION if the -r flag
-** appears.  This command will confirm your operation unless the
-** file is missing or the --yes option is used.
-**/
+** appears.
+**
+** If a file is reverted accidently, it can be restored using
+** the "fossil undo" command.
+*/
 void revert_cmd(void){
-  const char *zFile;
+  char *zFile;
   const char *zRevision;
   Blob fname;
   Blob record;
-  Blob ans;
-  int rid = 0, yesRevert;
-
-  yesRevert = find_option("yes", "y", 0)!=0;
+  int i;
+  int errCode;
+  int rid = 0;
+
   zRevision = find_option("revision", "r", 1);
   verify_all_options();
 
-  if( g.argc!=3 ){
-    usage("?OPTIONS FILE");
+  if( g.argc<3 ){
+    usage("?OPTIONS? FILE ...");
   }
   db_must_be_within_tree();
-
-  zFile = mprintf("%/", g.argv[g.argc-1]);
+  db_begin_transaction();
+  undo_begin();
 
-  file_tree_name(zFile, &fname, 1);
+  blob_zero(&record);
+  for(i=2; i<g.argc; i++){
+    zFile = mprintf("%/", g.argv[i]);
+    file_tree_name(zFile, &fname, 1);
 
-  if( access(zFile, 0) ) yesRevert = 1;
-  if( yesRevert==0 ){
-    char *prompt = mprintf("revert file %B? this will"
-                           " destroy local changes (y/N)? ",
-                           &fname);
-    blob_zero(&ans);
-    prompt_user(prompt, &ans);
-    free( prompt );
-    if( blob_str(&ans)[0]=='y' ){
-      yesRevert = 1;
+    if( zRevision!=0 ){
+      errCode = historical_version_of_file(zRevision, blob_str(&fname),
+                                           &record, 2);
+    }else{
+      rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
+      if( rid==0 ){
+        errCode = 2;
+      }else{
+        content_get(rid, &record);
+        errCode = 0;
+      }
     }
-  }
 
-  if( yesRevert==1 && zRevision!=0 ){
-    historical_version_of_file(zRevision, zFile, &record);
-  }else if( yesRevert==1 ){
-    rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B", &fname);
-    if( rid==0 ){
-      fossil_panic("no history for file: %b", &fname);
-    }
-    content_get(rid, &record);
-  }
-
-  if( yesRevert==1 ){
-    blob_write_to_file(&record, zFile);
-    printf("%s reverted\n", zFile);
+    if( errCode==2 ){
+      fossil_warning("file not in repository: %s", zFile);
+    }else{
+      undo_save(blob_str(&fname));
+      blob_write_to_file(&record, zFile);
+      printf("%s reverted\n", zFile);
+    }
     blob_reset(&record);
     blob_reset(&fname);
-  }else{
-    printf("revert canceled\n");
+    free(zFile);
   }
+  db_end_transaction(0);
 }

Modified src/vfile.c from [5504b327f7] to [882972137d].

@@ -142,11 +142,12 @@
 ** that has changed.
 **
 ** If VFILE.DELETED is null or if VFILE.RID is zero, then we can assume
 ** the file has changed without having the check the on-disk image.
 */
-void vfile_check_signature(int vid){
+void vfile_check_signature(int vid, int notFileIsFatal){
+  int nErr = 0;
   Stmt q;
   Blob fileCksum, origCksum;
   int checkMtime = db_get_boolean("mtime-changes", 0);
 
   db_begin_transaction();
@@ -166,11 +167,17 @@
     zName = db_column_text(&q, 1);
     rid = db_column_int(&q, 2);
     isDeleted = db_column_int(&q, 3);
     oldChnged = db_column_int(&q, 4);
     oldMtime = db_column_int64(&q, 6);
-    if( oldChnged>=2 ){
+    if( !file_isfile(zName) && file_size(zName)>=0 ){
+      if( notFileIsFatal ){
+        fossil_warning("not a ordinary file: %s", zName);
+        nErr++;
+      }
+      chnged = 1;
+    }else if( oldChnged>=2 ){
       chnged = oldChnged;
     }else if( isDeleted || rid==0 ){
       chnged = 1;
     }
     if( chnged!=1 ){
@@ -193,10 +200,11 @@
     if( chnged!=oldChnged ){
       db_multi_exec("UPDATE vfile SET chnged=%d WHERE id=%d", chnged, id);
     }
   }
   db_finalize(&q);
+  if( nErr ) fossil_fatal("abort due to prior errors");
   db_end_transaction(0);
 }
 
 /*
 ** Write all files from vid to the disk.  Or if vid==0 and id!=0
@@ -396,12 +404,14 @@
 }
 
 /*
 ** Compute an aggregate MD5 checksum over the repository image of every
 ** file in manifest vid.  The file names are part of the checksum.
-**
 ** Return the resulting checksum in blob pOut.
+**
+** If pManOut is not NULL then fill it with the checksum found in the
+** "R" card near the end of the manifest.
 */
 void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
   int i, fid;
   Blob file, mfile;
   Manifest m;

Modified src/winhttp.c from [f308305b21] to [4bf909b551].

@@ -136,11 +136,11 @@
 ** Start a listening socket and process incoming HTTP requests on
 ** that socket.
 */
 void win32_http_server(int mnPort, int mxPort, char *zBrowser){
   WSADATA wd;
-  SOCKET s;
+  SOCKET s = INVALID_SOCKET;
   SOCKADDR_IN addr;
   int idCnt = 0;
   int iPort = mnPort;
 
   if( WSAStartup(MAKEWORD(1,1), &wd) ){

Modified src/xfer.c from [002c451193] to [d3ef278c07].

@@ -1070,12 +1070,14 @@
       if( blob_buffer(&xfer.line)[0]=='#' ){
         continue;
       }
       xfer.nToken = blob_tokenize(&xfer.line, xfer.aToken, count(xfer.aToken));
       nCardRcvd++;
-      printf("\r%d", nCardRcvd);
-      fflush(stdout);
+      if (!g.fQuiet) {
+        printf("\r%d", nCardRcvd);
+        fflush(stdout);
+      }
 
       /*   file UUID SIZE \n CONTENT
       **   file UUID DELTASRC SIZE \n CONTENT
       **
       ** Receive a file transmitted from the server.