Fossil

Check-in [38e1ce66]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Merge trunk
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sqlite3-compat
Files: files | file ages | folders
SHA1:38e1ce66c7bfb5924a7bb16bd482f0faf6a91ed4
User & Date: jan.nijtmans 2015-02-26 22:38:00
Context
2015-04-08
13:49
Merge trunk check-in: ff571668 user: jan.nijtmans tags: sqlite3-compat
2015-02-26
22:38
Merge trunk check-in: 38e1ce66 user: jan.nijtmans tags: sqlite3-compat
00:31
Update the built-in SQLite to the latest trunk version, as the 3.8.8.3 version contains an unused local variable and hence will not compile when -Werror is used. Add a missing space to an <input> element on the timeline submenu. check-in: c3d72bd9 user: drh tags: trunk
2015-01-28
03:34
Fix for Cygwin ACL bug check-in: 4043d5ea user: jan.nijtmans tags: sqlite3-compat
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to Dockerfile.

     1      1   ###
     2      2   #   Dockerfile for Fossil
     3      3   ###
     4      4   FROM fedora:21
     5      5   
     6      6   ### Now install some additional parts we will need for the build
     7         -RUN yum update -y && yum install -y gcc make zlib-devel openssl-devel tcl-devel tar && yum clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil
            7  +RUN yum update -y && yum install -y gcc make zlib-devel openssl-devel tcl tar && yum clean all && groupadd -r fossil -g 433 && useradd -u 431 -r -g fossil -d /opt/fossil -s /sbin/nologin -c "Fossil user" fossil
     8      8   
     9         -### If you want to build "trunk", change the next line accordingly.
    10         -ENV FOSSIL_INSTALL_VERSION release
            9  +### If you want to build "release", change the next line accordingly.
           10  +ENV FOSSIL_INSTALL_VERSION trunk
    11     11   
    12     12   RUN curl "http://www.fossil-scm.org/index.html/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx
    13     13   RUN cd fossil-src && ./configure --disable-lineedit --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl --with-tcl-stubs --with-tcl-private-stubs && make;
    14     14   RUN cp fossil-src/fossil /usr/bin && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil
    15     15   
    16     16   ### Build is done, remove modules no longer needed
    17         -RUN yum remove -y gcc make zlib-devel openssl-devel tcl-devel tar && yum clean all
           17  +RUN yum remove -y gcc make zlib-devel openssl-devel tar && yum clean all
    18     18   
    19     19   USER fossil
    20     20   
    21     21   ENV HOME /opt/fossil
    22     22   
    23     23   RUN fossil new --docker -A admin /opt/fossil/repository.fossil && fossil user password -R /opt/fossil/repository.fossil admin admin && fossil cache init -R /opt/fossil/repository.fossil
    24     24   
    25     25   EXPOSE 8080
    26     26   
    27     27   CMD ["/usr/bin/fossil", "server", "/opt/fossil/repository.fossil"]

Changes to VERSION.

     1         -1.30
            1  +1.31

Changes to auto.def.

    66     66       user-error "system sqlite3 not found"
    67     67     }
    68     68   
    69     69     find_internal_sqlite
    70     70   }
    71     71   
    72     72   if {[string match *-solaris* [get-define host]]} {
    73         -    define-append EXTRA_CFLAGS -D_XOPEN_SOURCE=500
           73  +    define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__}
    74     74   }
    75     75   
    76     76   if {[opt-bool fossil-debug]} {
    77     77       define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
    78     78       msg-result "Debugging support enabled"
    79     79   }
    80     80   

Changes to autosetup/jimsh0.c.

    36     36   #else
    37     37   #define TCL_PLATFORM_OS "unknown"
    38     38   #define TCL_PLATFORM_PLATFORM "unix"
    39     39   #define TCL_PLATFORM_PATH_SEPARATOR ":"
    40     40   #define HAVE_VFORK
    41     41   #define HAVE_WAITPID
    42     42   #define HAVE_ISATTY
           43  +#define HAVE_MKSTEMP
           44  +#define HAVE_LINK
    43     45   #define HAVE_SYS_TIME_H
    44     46   #define HAVE_DIRENT_H
    45     47   #define HAVE_UNISTD_H
    46     48   #endif
    47         -#define JIM_VERSION 75
           49  +#define JIM_VERSION 76
    48     50   #ifndef JIM_WIN32COMPAT_H
    49     51   #define JIM_WIN32COMPAT_H
    50     52   
    51     53   
    52     54   
    53     55   #ifdef __cplusplus
    54     56   extern "C" {
................................................................................
   111    113   
   112    114   DIR *opendir(const char *name);
   113    115   int closedir(DIR *dir);
   114    116   struct dirent *readdir(DIR *dir);
   115    117   
   116    118   #elif defined(__MINGW32__)
   117    119   
          120  +#include <stdlib.h>
   118    121   #define strtod __strtod
   119    122   
   120    123   #endif
   121    124   
   122    125   #endif 
   123    126   
   124    127   #ifdef __cplusplus
................................................................................
  1046   1049   
  1047   1050   	return Jim_EvalSource(interp, "initjimsh.tcl", 1,
  1048   1051   "\n"
  1049   1052   "\n"
  1050   1053   "\n"
  1051   1054   "proc _jimsh_init {} {\n"
  1052   1055   "	rename _jimsh_init {}\n"
         1056  +"	global jim::exe jim::argv0 tcl_interactive auto_path tcl_platform\n"
  1053   1057   "\n"
  1054   1058   "\n"
  1055         -"	lappend p {*}[split [env JIMLIB {}] $::tcl_platform(pathSeparator)]\n"
  1056         -"	lappend p {*}$::auto_path\n"
  1057         -"	lappend p [file dirname [info nameofexecutable]]\n"
  1058         -"	set ::auto_path $p\n"
         1059  +"	if {[exists jim::argv0]} {\n"
         1060  +"		if {[string match \"*/*\" $jim::argv0]} {\n"
         1061  +"			set jim::exe [file join [pwd] $jim::argv0]\n"
         1062  +"		} else {\n"
         1063  +"			foreach path [split [env PATH \"\"] $tcl_platform(pathSeparator)] {\n"
         1064  +"				set exec [file join [pwd] [string map {\\\\ /} $path] $jim::argv0]\n"
         1065  +"				if {[file executable $exec]} {\n"
         1066  +"					set jim::exe $exec\n"
         1067  +"					break\n"
         1068  +"				}\n"
         1069  +"			}\n"
         1070  +"		}\n"
         1071  +"	}\n"
  1059   1072   "\n"
  1060         -"	if {$::tcl_interactive && [env HOME {}] ne \"\"} {\n"
         1073  +"\n"
         1074  +"	lappend p {*}[split [env JIMLIB {}] $tcl_platform(pathSeparator)]\n"
         1075  +"	if {[exists jim::exe]} {\n"
         1076  +"		lappend p [file dirname $jim::exe]\n"
         1077  +"	}\n"
         1078  +"	lappend p {*}$auto_path\n"
         1079  +"	set auto_path $p\n"
         1080  +"\n"
         1081  +"	if {$tcl_interactive && [env HOME {}] ne \"\"} {\n"
  1061   1082   "		foreach src {.jimrc jimrc.tcl} {\n"
  1062   1083   "			if {[file exists [env HOME]/$src]} {\n"
  1063   1084   "				uplevel #0 source [env HOME]/$src\n"
  1064   1085   "				break\n"
  1065   1086   "			}\n"
  1066   1087   "		}\n"
  1067   1088   "	}\n"
         1089  +"	return \"\"\n"
  1068   1090   "}\n"
  1069   1091   "\n"
  1070   1092   "if {$tcl_platform(platform) eq \"windows\"} {\n"
  1071         -"	set jim_argv0 [string map {\\\\ /} $jim_argv0]\n"
         1093  +"	set jim::argv0 [string map {\\\\ /} $jim::argv0]\n"
  1072   1094   "}\n"
  1073   1095   "\n"
  1074   1096   "_jimsh_init\n"
  1075   1097   );
  1076   1098   }
  1077   1099   int Jim_globInit(Jim_Interp *interp)
  1078   1100   {
................................................................................
  1089   1111   "\n"
  1090   1112   "package require readdir\n"
  1091   1113   "\n"
  1092   1114   "\n"
  1093   1115   "proc glob.globdir {dir pattern} {\n"
  1094   1116   "	if {[file exists $dir/$pattern]} {\n"
  1095   1117   "\n"
  1096         -"		return $pattern\n"
         1118  +"		return [list $pattern]\n"
  1097   1119   "	}\n"
  1098   1120   "\n"
  1099   1121   "	set result {}\n"
  1100   1122   "	set files [readdir $dir]\n"
  1101   1123   "	lappend files . ..\n"
  1102   1124   "\n"
  1103   1125   "	foreach name $files {\n"
................................................................................
  1151   1173   "			break\n"
  1152   1174   "		}\n"
  1153   1175   "	}\n"
  1154   1176   "\n"
  1155   1177   "	foreach old $oldexp {\n"
  1156   1178   "		lappend newexp $old$suf\n"
  1157   1179   "	}\n"
  1158         -"	linsert $newexp 0 $rest\n"
         1180  +"	list $rest {*}$newexp\n"
  1159   1181   "}\n"
  1160   1182   "\n"
  1161   1183   "\n"
  1162   1184   "\n"
  1163   1185   "proc glob.glob {base pattern} {\n"
  1164   1186   "	set dir [file dirname $pattern]\n"
  1165   1187   "	if {$pattern eq $dir || $pattern eq \"\"} {\n"
................................................................................
  1198   1220   "\n"
  1199   1221   "\n"
  1200   1222   "\n"
  1201   1223   "\n"
  1202   1224   "proc glob {args} {\n"
  1203   1225   "	set nocomplain 0\n"
  1204   1226   "	set base \"\"\n"
         1227  +"	set tails 0\n"
  1205   1228   "\n"
  1206   1229   "	set n 0\n"
  1207   1230   "	foreach arg $args {\n"
  1208   1231   "		if {[info exists param]} {\n"
  1209   1232   "			set $param $arg\n"
  1210   1233   "			unset param\n"
  1211   1234   "			incr n\n"
................................................................................
  1215   1238   "			-d* {\n"
  1216   1239   "				set switch $arg\n"
  1217   1240   "				set param base\n"
  1218   1241   "			}\n"
  1219   1242   "			-n* {\n"
  1220   1243   "				set nocomplain 1\n"
  1221   1244   "			}\n"
  1222         -"			-t* {\n"
  1223         -"\n"
  1224         -"			}\n"
  1225         -"\n"
  1226         -"			-* {\n"
  1227         -"				return -code error \"bad option \\\"$switch\\\": must be -directory, -nocomplain, -tails, or --\"\n"
         1245  +"			-ta* {\n"
         1246  +"				set tails 1\n"
  1228   1247   "			}\n"
  1229   1248   "			-- {\n"
  1230   1249   "				incr n\n"
  1231   1250   "				break\n"
  1232   1251   "			}\n"
         1252  +"			-* {\n"
         1253  +"				return -code error \"bad option \\\"$arg\\\": must be -directory, -nocomplain, -tails, or --\"\n"
         1254  +"			}\n"
  1233   1255   "			*  {\n"
  1234   1256   "				break\n"
  1235   1257   "			}\n"
  1236   1258   "		}\n"
  1237   1259   "		incr n\n"
  1238   1260   "	}\n"
  1239   1261   "	if {[info exists param]} {\n"
................................................................................
  1243   1265   "		return -code error \"wrong # args: should be \\\"glob ?options? pattern ?pattern ...?\\\"\"\n"
  1244   1266   "	}\n"
  1245   1267   "\n"
  1246   1268   "	set args [lrange $args $n end]\n"
  1247   1269   "\n"
  1248   1270   "	set result {}\n"
  1249   1271   "	foreach pattern $args {\n"
  1250         -"		set pattern [string map {\n"
         1272  +"		set escpattern [string map {\n"
  1251   1273   "			\\\\\\\\ \\x01 \\\\\\{ \\x02 \\\\\\} \\x03 \\\\, \\x04\n"
  1252   1274   "		} $pattern]\n"
  1253         -"		set patexps [lassign [glob.explode $pattern] rest]\n"
         1275  +"		set patexps [lassign [glob.explode $escpattern] rest]\n"
  1254   1276   "		if {$rest ne \"\"} {\n"
  1255   1277   "			return -code error \"unmatched close brace in glob pattern\"\n"
  1256   1278   "		}\n"
  1257   1279   "		foreach patexp $patexps {\n"
  1258   1280   "			set patexp [string map {\n"
  1259   1281   "				\\x01 \\\\\\\\ \\x02 \\{ \\x03 \\} \\x04 ,\n"
  1260   1282   "			} $patexp]\n"
  1261   1283   "			foreach {realname name} [glob.glob $base $patexp] {\n"
  1262         -"				lappend result $name\n"
         1284  +"				incr n\n"
         1285  +"				if {$tails} {\n"
         1286  +"					lappend result $name\n"
         1287  +"				} else {\n"
         1288  +"					lappend result [file join $base $name]\n"
         1289  +"				}\n"
  1263   1290   "			}\n"
  1264   1291   "		}\n"
  1265   1292   "	}\n"
  1266   1293   "\n"
  1267   1294   "	if {!$nocomplain && [llength $result] == 0} {\n"
  1268         -"		return -code error \"no files matched glob patterns\"\n"
         1295  +"		set s $(([llength $args] > 1) ? \"s\" : \"\")\n"
         1296  +"		return -code error \"no files matched glob pattern$s \\\"[join $args]\\\"\"\n"
  1269   1297   "	}\n"
  1270   1298   "\n"
  1271   1299   "	return $result\n"
  1272   1300   "}\n"
  1273   1301   );
  1274   1302   }
  1275   1303   int Jim_stdlibInit(Jim_Interp *interp)
................................................................................
  1346   1374   "\n"
  1347   1375   "		set stacktrace [info stacktrace]\n"
  1348   1376   "\n"
  1349   1377   "		lappend stacktrace {*}[stacktrace 1]\n"
  1350   1378   "	}\n"
  1351   1379   "	lassign $stacktrace p f l\n"
  1352   1380   "	if {$f ne \"\"} {\n"
  1353         -"		set result \"Runtime Error: $f:$l: \"\n"
         1381  +"		set result \"$f:$l: Error: \"\n"
  1354   1382   "	}\n"
  1355   1383   "	append result \"$msg\\n\"\n"
  1356   1384   "	append result [stackdump $stacktrace]\n"
  1357   1385   "\n"
  1358   1386   "\n"
  1359   1387   "	string trim $result\n"
  1360   1388   "}\n"
  1361   1389   "\n"
  1362   1390   "\n"
  1363   1391   "\n"
  1364   1392   "proc {info nameofexecutable} {} {\n"
  1365         -"	if {[info exists ::jim_argv0]} {\n"
  1366         -"		if {[string match \"*/*\" $::jim_argv0]} {\n"
  1367         -"			return [file join [pwd] $::jim_argv0]\n"
  1368         -"		}\n"
  1369         -"		foreach path [split [env PATH \"\"] $::tcl_platform(pathSeparator)] {\n"
  1370         -"			set exec [file join [pwd] [string map {\\\\ /} $path] $::jim_argv0]\n"
  1371         -"			if {[file executable $exec]} {\n"
  1372         -"				return $exec\n"
  1373         -"			}\n"
  1374         -"		}\n"
         1393  +"	if {[exists ::jim::exe]} {\n"
         1394  +"		return $::jim::exe\n"
  1375   1395   "	}\n"
  1376         -"	return \"\"\n"
  1377   1396   "}\n"
  1378   1397   "\n"
  1379   1398   "\n"
  1380   1399   "proc {dict with} {&dictVar {args key} script} {\n"
  1381   1400   "	set keys {}\n"
  1382   1401   "	foreach {n v} [dict get $dictVar {*}$key] {\n"
  1383   1402   "		upvar $n var_$n\n"
................................................................................
  1585   1604   "\n"
  1586   1605   "proc {file copy} {{force {}} source target} {\n"
  1587   1606   "	try {\n"
  1588   1607   "		if {$force ni {{} -force}} {\n"
  1589   1608   "			error \"bad option \\\"$force\\\": should be -force\"\n"
  1590   1609   "		}\n"
  1591   1610   "\n"
  1592         -"		set in [open $source]\n"
         1611  +"		set in [open $source rb]\n"
  1593   1612   "\n"
  1594   1613   "		if {[file exists $target]} {\n"
  1595   1614   "			if {$force eq \"\"} {\n"
  1596   1615   "				error \"error copying \\\"$source\\\" to \\\"$target\\\": file already exists\"\n"
  1597   1616   "			}\n"
  1598   1617   "\n"
  1599   1618   "			if {$source eq $target} {\n"
................................................................................
  1603   1622   "\n"
  1604   1623   "			file stat $source ss\n"
  1605   1624   "			file stat $target ts\n"
  1606   1625   "			if {$ss(dev) == $ts(dev) && $ss(ino) == $ts(ino) && $ss(ino)} {\n"
  1607   1626   "				return\n"
  1608   1627   "			}\n"
  1609   1628   "		}\n"
  1610         -"		set out [open $target w]\n"
         1629  +"		set out [open $target wb]\n"
  1611   1630   "		$in copyto $out\n"
  1612   1631   "		$out close\n"
  1613   1632   "	} on error {msg opts} {\n"
  1614   1633   "		incr opts(-level)\n"
  1615   1634   "		return {*}$opts $msg\n"
  1616   1635   "	} finally {\n"
  1617   1636   "		catch {$in close}\n"
................................................................................
  1689   1708   "		}\n"
  1690   1709   "		lappend catchopts $opt\n"
  1691   1710   "	}\n"
  1692   1711   "	if {[llength $args] == 0} {\n"
  1693   1712   "		return -code error {wrong # args: should be \"try ?options? script ?argument ...?\"}\n"
  1694   1713   "	}\n"
  1695   1714   "	set args [lassign $args script]\n"
  1696         -"	set code [catch -eval {*}$catchopts [list uplevel 1 $script] msg opts]\n"
         1715  +"	set code [catch -eval {*}$catchopts {uplevel 1 $script} msg opts]\n"
  1697   1716   "\n"
  1698   1717   "	set handled 0\n"
  1699   1718   "\n"
  1700   1719   "	foreach {on codes vars script} $args {\n"
  1701   1720   "		switch -- $on \\\n"
  1702   1721   "			on {\n"
  1703   1722   "				if {!$handled && ($codes eq \"*\" || [info returncode $code] in $codes)} {\n"
................................................................................
  1707   1726   "						set hmsg $msg\n"
  1708   1727   "					}\n"
  1709   1728   "					if {$optsvar ne \"\"} {\n"
  1710   1729   "						upvar $optsvar hopts\n"
  1711   1730   "						set hopts $opts\n"
  1712   1731   "					}\n"
  1713   1732   "\n"
  1714         -"					set code [catch [list uplevel 1 $script] msg opts]\n"
         1733  +"					set code [catch {uplevel 1 $script} msg opts]\n"
  1715   1734   "					incr handled\n"
  1716   1735   "				}\n"
  1717   1736   "			} \\\n"
  1718   1737   "			finally {\n"
  1719         -"				set finalcode [catch [list uplevel 1 $codes] finalmsg finalopts]\n"
         1738  +"				set finalcode [catch {uplevel 1 $codes} finalmsg finalopts]\n"
  1720   1739   "				if {$finalcode} {\n"
  1721   1740   "\n"
  1722   1741   "					set code $finalcode\n"
  1723   1742   "					set msg $finalmsg\n"
  1724   1743   "					set opts $finalopts\n"
  1725   1744   "				}\n"
  1726   1745   "				break\n"
................................................................................
  2500   2519       Jim_CreateCommand(interp, buf, JimAioSubCmdProc, af, JimAioDelProc);
  2501   2520   
  2502   2521       Jim_SetResult(interp, Jim_MakeGlobalNamespaceName(interp, Jim_NewStringObj(interp, buf, -1)));
  2503   2522   
  2504   2523       return JIM_OK;
  2505   2524   }
  2506   2525   
         2526  +#if defined(HAVE_PIPE) || (defined(HAVE_SOCKETPAIR) && defined(HAVE_SYS_UN_H))
  2507   2527   static int JimMakeChannelPair(Jim_Interp *interp, int p[2], Jim_Obj *filename,
  2508   2528       const char *hdlfmt, int family, const char *mode[2])
  2509   2529   {
  2510   2530       if (JimMakeChannel(interp, NULL, p[0], filename, hdlfmt, family, mode[0]) == JIM_OK) {
  2511   2531           Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
  2512   2532           Jim_ListAppendElement(interp, objPtr, Jim_GetResult(interp));
  2513   2533   
................................................................................
  2520   2540   
  2521   2541       
  2522   2542       close(p[0]);
  2523   2543       close(p[1]);
  2524   2544       JimAioSetError(interp, NULL);
  2525   2545       return JIM_ERR;
  2526   2546   }
         2547  +#endif
  2527   2548   
  2528   2549   
  2529   2550   int Jim_MakeTempFile(Jim_Interp *interp, const char *template)
  2530   2551   {
  2531   2552   #ifdef HAVE_MKSTEMP
  2532   2553       int fd;
  2533   2554       mode_t mask;
................................................................................
  2550   2571   
  2551   2572       mask = umask(S_IXUSR | S_IRWXG | S_IRWXO);
  2552   2573   
  2553   2574       
  2554   2575       fd = mkstemp(filenameObj->bytes);
  2555   2576       umask(mask);
  2556   2577       if (fd < 0) {
  2557         -        Jim_SetResultString(interp, "Failed to create tempfile", -1);
         2578  +        JimAioSetError(interp, filenameObj);
  2558   2579           Jim_FreeNewObj(interp, filenameObj);
  2559   2580           return -1;
  2560   2581       }
  2561   2582   
  2562   2583       Jim_SetResult(interp, filenameObj);
  2563   2584       return fd;
  2564   2585   #else
  2565         -    Jim_SetResultString(interp, "tempfile not supported", -1);
         2586  +    Jim_SetResultString(interp, "platform has no tempfile support", -1);
  2566   2587       return -1;
  2567   2588   #endif
  2568   2589   }
  2569   2590   
  2570   2591   FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *command)
  2571   2592   {
  2572   2593       Jim_Cmd *cmdPtr = Jim_GetCommand(interp, command, JIM_ERRMSG);
................................................................................
  3174   3195   #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
  3175   3196   #endif
  3176   3197   
  3177   3198   # ifndef MAXPATHLEN
  3178   3199   # define MAXPATHLEN JIM_PATH_LEN
  3179   3200   # endif
  3180   3201   
         3202  +#if defined(__MINGW32__) || defined(_MSC_VER)
         3203  +#define ISWINDOWS 1
         3204  +#else
         3205  +#define ISWINDOWS 0
         3206  +#endif
         3207  +
  3181   3208   
  3182   3209   static const char *JimGetFileType(int mode)
  3183   3210   {
  3184   3211       if (S_ISREG(mode)) {
  3185   3212           return "file";
  3186   3213       }
  3187   3214       else if (S_ISDIR(mode)) {
................................................................................
  3279   3306           Jim_SetResultString(interp, "..", -1);
  3280   3307       } else if (!p) {
  3281   3308           Jim_SetResultString(interp, ".", -1);
  3282   3309       }
  3283   3310       else if (p == path) {
  3284   3311           Jim_SetResultString(interp, "/", -1);
  3285   3312       }
  3286         -#if defined(__MINGW32__) || defined(_MSC_VER)
  3287         -    else if (p[-1] == ':') {
         3313  +    else if (ISWINDOWS && p[-1] == ':') {
  3288   3314           
  3289   3315           Jim_SetResultString(interp, path, p - path + 1);
  3290   3316       }
  3291         -#endif
  3292   3317       else {
  3293   3318           Jim_SetResultString(interp, path, p - path);
  3294   3319       }
  3295   3320       return JIM_OK;
  3296   3321   }
  3297   3322   
  3298   3323   static int file_cmd_rootname(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
................................................................................
  3371   3396           int len;
  3372   3397           const char *part = Jim_GetString(argv[i], &len);
  3373   3398   
  3374   3399           if (*part == '/') {
  3375   3400               
  3376   3401               last = newname;
  3377   3402           }
  3378         -#if defined(__MINGW32__) || defined(_MSC_VER)
  3379         -        else if (strchr(part, ':')) {
         3403  +        else if (ISWINDOWS && strchr(part, ':')) {
  3380   3404               
  3381   3405               last = newname;
  3382   3406           }
  3383         -#endif
  3384   3407           else if (part[0] == '.') {
  3385   3408               if (part[1] == '/') {
  3386   3409                   part += 2;
  3387   3410                   len -= 2;
  3388   3411               }
  3389   3412               else if (part[1] == 0 && last != newname) {
  3390   3413                   
................................................................................
  3405   3428               }
  3406   3429               memcpy(last, part, len);
  3407   3430               last += len;
  3408   3431           }
  3409   3432   
  3410   3433           
  3411   3434           if (last > newname + 1 && last[-1] == '/') {
  3412         -            *--last = 0;
         3435  +            
         3436  +            if (!ISWINDOWS || !(last > newname + 2 && last[-2] == ':')) {
         3437  +                *--last = 0;
         3438  +            }
  3413   3439           }
  3414   3440       }
  3415   3441   
  3416   3442       *last = 0;
  3417   3443   
  3418   3444       
  3419   3445   
................................................................................
  3589   3615           Jim_SetResultFormatted(interp, "error renaming \"%#s\" to \"%#s\": %s", argv[0], argv[1],
  3590   3616               strerror(errno));
  3591   3617           return JIM_ERR;
  3592   3618       }
  3593   3619   
  3594   3620       return JIM_OK;
  3595   3621   }
         3622  +
         3623  +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
         3624  +static int file_cmd_link(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
         3625  +{
         3626  +    int ret;
         3627  +    const char *source;
         3628  +    const char *dest;
         3629  +    static const char * const options[] = { "-hard", "-symbolic", NULL };
         3630  +    enum { OPT_HARD, OPT_SYMBOLIC, };
         3631  +    int option = OPT_HARD;
         3632  +
         3633  +    if (argc == 3) {
         3634  +        if (Jim_GetEnum(interp, argv[0], options, &option, NULL, JIM_ENUM_ABBREV | JIM_ERRMSG) != JIM_OK) {
         3635  +            return JIM_ERR;
         3636  +        }
         3637  +        argv++;
         3638  +        argc--;
         3639  +    }
         3640  +
         3641  +    dest = Jim_String(argv[0]);
         3642  +    source = Jim_String(argv[1]);
         3643  +
         3644  +    if (option == OPT_HARD) {
         3645  +        ret = link(source, dest);
         3646  +    }
         3647  +    else {
         3648  +        ret = symlink(source, dest);
         3649  +    }
         3650  +
         3651  +    if (ret != 0) {
         3652  +        Jim_SetResultFormatted(interp, "error linking \"%#s\" to \"%#s\": %s", argv[0], argv[1],
         3653  +            strerror(errno));
         3654  +        return JIM_ERR;
         3655  +    }
         3656  +
         3657  +    return JIM_OK;
         3658  +}
         3659  +#endif
  3596   3660   
  3597   3661   static int file_stat(Jim_Interp *interp, Jim_Obj *filename, struct stat *sb)
  3598   3662   {
  3599   3663       const char *path = Jim_String(filename);
  3600   3664   
  3601   3665       if (stat(path, sb) == -1) {
  3602   3666           Jim_SetResultFormatted(interp, "could not read \"%#s\": %s", filename, strerror(errno));
................................................................................
  3887   3951       {   "rename",
  3888   3952           "?-force? source dest",
  3889   3953           file_cmd_rename,
  3890   3954           2,
  3891   3955           3,
  3892   3956           
  3893   3957       },
         3958  +#if defined(HAVE_LINK) && defined(HAVE_SYMLINK)
         3959  +    {   "link",
         3960  +        "?-symbolic|-hard? newname target",
         3961  +        file_cmd_link,
         3962  +        2,
         3963  +        3,
         3964  +        
         3965  +    },
         3966  +#endif
  3894   3967   #if defined(HAVE_READLINK)
  3895   3968       {   "readlink",
  3896   3969           "name",
  3897   3970           file_cmd_readlink,
  3898   3971           1,
  3899   3972           1,
  3900   3973           
................................................................................
  3980   4053       char *cwd = Jim_Alloc(MAXPATHLEN);
  3981   4054   
  3982   4055       if (getcwd(cwd, MAXPATHLEN) == NULL) {
  3983   4056           Jim_SetResultString(interp, "Failed to get pwd", -1);
  3984   4057           Jim_Free(cwd);
  3985   4058           return JIM_ERR;
  3986   4059       }
  3987         -#if defined(__MINGW32__) || defined(_MSC_VER)
  3988         -    {
         4060  +    else if (ISWINDOWS) {
  3989   4061           
  3990   4062           char *p = cwd;
  3991   4063           while ((p = strchr(p, '\\')) != NULL) {
  3992   4064               *p++ = '/';
  3993   4065           }
  3994   4066       }
  3995         -#endif
  3996   4067   
  3997   4068       Jim_SetResultString(interp, cwd, -1);
  3998   4069   
  3999   4070       Jim_Free(cwd);
  4000   4071       return JIM_OK;
  4001   4072   }
  4002   4073   
................................................................................
  5320   5391       return lseek(fd, 0L, SEEK_SET);
  5321   5392   }
  5322   5393   
  5323   5394   static int JimCreateTemp(Jim_Interp *interp, const char *contents, int len)
  5324   5395   {
  5325   5396       int fd = Jim_MakeTempFile(interp, NULL);
  5326   5397   
  5327         -    if (fd == JIM_BAD_FD) {
  5328         -        Jim_SetResultErrno(interp, "couldn't create temp file");
  5329         -        return -1;
  5330         -    }
  5331         -    unlink(Jim_String(Jim_GetResult(interp)));
  5332         -    if (contents) {
  5333         -        if (write(fd, contents, len) != len) {
  5334         -            Jim_SetResultErrno(interp, "couldn't write temp file");
  5335         -            close(fd);
  5336         -            return -1;
         5398  +    if (fd != JIM_BAD_FD) {
         5399  +        unlink(Jim_String(Jim_GetResult(interp)));
         5400  +        if (contents) {
         5401  +            if (write(fd, contents, len) != len) {
         5402  +                Jim_SetResultErrno(interp, "couldn't write temp file");
         5403  +                close(fd);
         5404  +                return -1;
         5405  +            }
         5406  +            lseek(fd, 0L, SEEK_SET);
  5337   5407           }
  5338         -        lseek(fd, 0L, SEEK_SET);
  5339   5408       }
  5340   5409       return fd;
  5341   5410   }
  5342   5411   
  5343   5412   static char **JimSaveEnv(char **env)
  5344   5413   {
  5345   5414       char **saveenv = Jim_GetEnviron();
................................................................................
  7766   7835   }
  7767   7836   
  7768   7837   
  7769   7838   const char *Jim_String(Jim_Obj *objPtr)
  7770   7839   {
  7771   7840       if (objPtr->bytes == NULL) {
  7772   7841           
         7842  +        JimPanic((objPtr->typePtr == NULL, "UpdateStringProc called against typeless value."));
  7773   7843           JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name));
  7774   7844           objPtr->typePtr->updateStringProc(objPtr);
  7775   7845       }
  7776   7846       return objPtr->bytes;
  7777   7847   }
  7778   7848   
  7779   7849   static void JimSetStringBytes(Jim_Obj *objPtr, const char *str)
................................................................................
  8528   8598       objPtr->internalRep.scriptLineValue.line = line;
  8529   8599   
  8530   8600       return objPtr;
  8531   8601   }
  8532   8602   
  8533   8603   static void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
  8534   8604   static void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
  8535         -static int JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
         8605  +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
  8536   8606   static int JimParseCheckMissing(Jim_Interp *interp, int ch);
  8537   8607   
  8538   8608   static const Jim_ObjType scriptObjType = {
  8539   8609       "script",
  8540   8610       FreeScriptInternalRep,
  8541   8611       DupScriptInternalRep,
  8542   8612       NULL,
................................................................................
  8556   8626       int len;                    
  8557   8627       int substFlags;             
  8558   8628       int inUse;                  /* Used to share a ScriptObj. Currently
  8559   8629                                      only used by Jim_EvalObj() as protection against
  8560   8630                                      shimmering of the currently evaluated object. */
  8561   8631       int firstline;              
  8562   8632       int linenr;                 
         8633  +    int missing;                
  8563   8634   } ScriptObj;
  8564   8635   
  8565   8636   void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
  8566   8637   {
  8567   8638       int i;
  8568   8639       struct ScriptObj *script = (void *)objPtr->internalRep.ptr;
  8569   8640   
................................................................................
  8834   8905           Jim_IncrRefCount(token->objPtr);
  8835   8906           token++;
  8836   8907       }
  8837   8908   
  8838   8909       script->len = i;
  8839   8910   }
  8840   8911   
  8841         -static int JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
         8912  +static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
  8842   8913   {
  8843   8914       int scriptTextLen;
  8844   8915       const char *scriptText = Jim_GetString(objPtr, &scriptTextLen);
  8845   8916       struct JimParserCtx parser;
  8846   8917       struct ScriptObj *script;
  8847   8918       ParseTokenList tokenlist;
  8848   8919       int line = 1;
  8849         -    int retcode = JIM_OK;
  8850   8920   
  8851   8921       
  8852   8922       if (objPtr->typePtr == &sourceObjType) {
  8853   8923           line = objPtr->internalRep.sourceValue.lineNumber;
  8854   8924       }
  8855   8925   
  8856   8926       
................................................................................
  8859   8929       JimParserInit(&parser, scriptText, scriptTextLen, line);
  8860   8930       while (!parser.eof) {
  8861   8931           JimParseScript(&parser);
  8862   8932           ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt,
  8863   8933               parser.tline);
  8864   8934       }
  8865   8935   
  8866         -    retcode = JimParseCheckMissing(interp, parser.missing.ch);
  8867         -
  8868   8936       
  8869   8937       ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0);
  8870   8938   
  8871   8939       
  8872   8940       script = Jim_Alloc(sizeof(*script));
  8873   8941       memset(script, 0, sizeof(*script));
  8874   8942       script->inUse = 1;
  8875   8943       if (objPtr->typePtr == &sourceObjType) {
  8876   8944           script->fileNameObj = objPtr->internalRep.sourceValue.fileNameObj;
  8877   8945       }
  8878   8946       else {
  8879   8947           script->fileNameObj = interp->emptyObj;
  8880   8948       }
  8881         -    script->linenr = parser.missing.line;
  8882   8949       Jim_IncrRefCount(script->fileNameObj);
         8950  +    script->missing = parser.missing.ch;
         8951  +    script->linenr = parser.missing.line;
  8883   8952   
  8884   8953       ScriptObjAddTokens(interp, script, &tokenlist);
  8885   8954   
  8886   8955       
  8887   8956       ScriptTokenListFree(&tokenlist);
  8888   8957   
  8889   8958       
  8890   8959       Jim_FreeIntRep(interp, objPtr);
  8891   8960       Jim_SetIntRepPtr(objPtr, script);
  8892   8961       objPtr->typePtr = &scriptObjType;
  8893         -
  8894         -    return retcode;
  8895   8962   }
  8896   8963   
  8897         -ScriptObj *Jim_GetScript(Jim_Interp *interp, Jim_Obj *objPtr)
         8964  +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script);
         8965  +
         8966  +ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr)
  8898   8967   {
  8899   8968       if (objPtr == interp->emptyObj) {
  8900   8969           
  8901   8970           objPtr = interp->nullScriptObj;
  8902   8971       }
  8903   8972   
  8904   8973       if (objPtr->typePtr != &scriptObjType || ((struct ScriptObj *)Jim_GetIntRepPtr(objPtr))->substFlags) {
  8905         -        if (JimSetScriptFromAny(interp, objPtr) == JIM_ERR) {
  8906         -            return NULL;
  8907         -        }
         8974  +        JimSetScriptFromAny(interp, objPtr);
  8908   8975       }
  8909         -    return (ScriptObj *) Jim_GetIntRepPtr(objPtr);
         8976  +
         8977  +    return (ScriptObj *)Jim_GetIntRepPtr(objPtr);
  8910   8978   }
         8979  +
         8980  +static int JimScriptValid(Jim_Interp *interp, ScriptObj *script)
         8981  +{
         8982  +    if (JimParseCheckMissing(interp, script->missing) == JIM_ERR) {
         8983  +        JimAddErrorToStack(interp, script);
         8984  +        return 0;
         8985  +    }
         8986  +    return 1;
         8987  +}
         8988  +
  8911   8989   
  8912   8990   static void JimIncrCmdRefCount(Jim_Cmd *cmdPtr)
  8913   8991   {
  8914   8992       cmdPtr->inUse++;
  8915   8993   }
  8916   8994   
  8917   8995   static void JimDecrCmdRefCount(Jim_Interp *interp, Jim_Cmd *cmdPtr)
................................................................................
 10899  10977           objPtr->typePtr = &coercedDoubleObjType;
 10900  10978           objPtr->internalRep.wideValue = wideValue;
 10901  10979           return JIM_OK;
 10902  10980       }
 10903  10981       else {
 10904  10982           
 10905  10983           if (Jim_StringToDouble(str, &doubleValue) != JIM_OK) {
 10906         -            Jim_SetResultFormatted(interp, "expected number but got \"%#s\"", objPtr);
        10984  +            Jim_SetResultFormatted(interp, "expected floating-point number but got \"%#s\"", objPtr);
 10907  10985               return JIM_ERR;
 10908  10986           }
 10909  10987           
 10910  10988           Jim_FreeIntRep(interp, objPtr);
 10911  10989       }
 10912  10990       objPtr->typePtr = &doubleObjType;
 10913  10991       objPtr->internalRep.doubleValue = doubleValue;
................................................................................
 12329  12407       JIM_EXPROP_BITNOT,
 12330  12408       JIM_EXPROP_UNARYMINUS,
 12331  12409       JIM_EXPROP_UNARYPLUS,
 12332  12410   
 12333  12411       
 12334  12412       JIM_EXPROP_FUNC_FIRST,      
 12335  12413       JIM_EXPROP_FUNC_INT = JIM_EXPROP_FUNC_FIRST,
        12414  +    JIM_EXPROP_FUNC_WIDE,
 12336  12415       JIM_EXPROP_FUNC_ABS,
 12337  12416       JIM_EXPROP_FUNC_DOUBLE,
 12338  12417       JIM_EXPROP_FUNC_ROUND,
 12339  12418       JIM_EXPROP_FUNC_RAND,
 12340  12419       JIM_EXPROP_FUNC_SRAND,
 12341  12420   
 12342  12421       
................................................................................
 12395  12474       Jim_Obj *A = ExprPop(e);
 12396  12475       double dA, dC = 0;
 12397  12476       jim_wide wA, wC = 0;
 12398  12477   
 12399  12478       if ((A->typePtr != &doubleObjType || A->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK) {
 12400  12479           switch (e->opcode) {
 12401  12480               case JIM_EXPROP_FUNC_INT:
        12481  +            case JIM_EXPROP_FUNC_WIDE:
 12402  12482               case JIM_EXPROP_FUNC_ROUND:
 12403  12483               case JIM_EXPROP_UNARYPLUS:
 12404  12484                   wC = wA;
 12405  12485                   break;
 12406  12486               case JIM_EXPROP_FUNC_DOUBLE:
 12407  12487                   dC = wA;
 12408  12488                   intresult = 0;
................................................................................
 12419  12499               default:
 12420  12500                   abort();
 12421  12501           }
 12422  12502       }
 12423  12503       else if ((rc = Jim_GetDouble(interp, A, &dA)) == JIM_OK) {
 12424  12504           switch (e->opcode) {
 12425  12505               case JIM_EXPROP_FUNC_INT:
        12506  +            case JIM_EXPROP_FUNC_WIDE:
 12426  12507                   wC = dA;
 12427  12508                   break;
 12428  12509               case JIM_EXPROP_FUNC_ROUND:
 12429  12510                   wC = dA < 0 ? (dA - 0.5) : (dA + 0.5);
 12430  12511                   break;
 12431  12512               case JIM_EXPROP_FUNC_DOUBLE:
 12432  12513               case JIM_EXPROP_UNARYPLUS:
................................................................................
 13091  13172       OPRINIT("~", 150, 1, JimExprOpIntUnary),
 13092  13173       OPRINIT(NULL, 150, 1, JimExprOpNumUnary),
 13093  13174       OPRINIT(NULL, 150, 1, JimExprOpNumUnary),
 13094  13175   
 13095  13176   
 13096  13177   
 13097  13178       OPRINIT("int", 200, 1, JimExprOpNumUnary),
        13179  +    OPRINIT("wide", 200, 1, JimExprOpNumUnary),
 13098  13180       OPRINIT("abs", 200, 1, JimExprOpNumUnary),
 13099  13181       OPRINIT("double", 200, 1, JimExprOpNumUnary),
 13100  13182       OPRINIT("round", 200, 1, JimExprOpNumUnary),
 13101  13183       OPRINIT("rand", 200, 0, JimExprOpNone),
 13102  13184       OPRINIT("srand", 200, 1, JimExprOpIntUnary),
 13103  13185   
 13104  13186   #ifdef JIM_MATH_FUNCTIONS
................................................................................
 14748  14830       nargv[0] = prefix;
 14749  14831       memcpy(&nargv[1], &objv[0], sizeof(nargv[0]) * objc);
 14750  14832       ret = Jim_EvalObjVector(interp, objc + 1, nargv);
 14751  14833       Jim_Free(nargv);
 14752  14834       return ret;
 14753  14835   }
 14754  14836   
 14755         -static void JimAddErrorToStack(Jim_Interp *interp, int retcode, ScriptObj *script)
        14837  +static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script)
 14756  14838   {
 14757         -    int rc = retcode;
 14758         -
 14759         -    if (rc == JIM_ERR && !interp->errorFlag) {
        14839  +    if (!interp->errorFlag) {
 14760  14840           
 14761  14841           interp->errorFlag = 1;
 14762  14842           Jim_IncrRefCount(script->fileNameObj);
 14763  14843           Jim_DecrRefCount(interp, interp->errorFileNameObj);
 14764  14844           interp->errorFileNameObj = script->fileNameObj;
 14765  14845           interp->errorLine = script->linenr;
 14766  14846   
 14767  14847           JimResetStackTrace(interp);
 14768  14848           
 14769  14849           interp->addStackTrace++;
 14770  14850       }
 14771  14851   
 14772  14852       
 14773         -    if (rc == JIM_ERR && interp->addStackTrace > 0) {
        14853  +    if (interp->addStackTrace > 0) {
 14774  14854           
 14775  14855   
 14776  14856           JimAppendStackTrace(interp, Jim_String(interp->errorProc), script->fileNameObj, script->linenr);
 14777  14857   
 14778  14858           if (Jim_Length(script->fileNameObj)) {
 14779  14859               interp->addStackTrace = 0;
 14780  14860           }
 14781  14861   
 14782  14862           Jim_DecrRefCount(interp, interp->errorProc);
 14783  14863           interp->errorProc = interp->emptyObj;
 14784  14864           Jim_IncrRefCount(interp->errorProc);
 14785  14865       }
 14786         -    else if (rc == JIM_RETURN && interp->returnCode == JIM_ERR) {
 14787         -        
 14788         -    }
 14789         -    else {
 14790         -        interp->addStackTrace = 0;
 14791         -    }
 14792  14866   }
 14793  14867   
 14794  14868   static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Obj **objPtrPtr)
 14795  14869   {
 14796  14870       Jim_Obj *objPtr;
 14797  14871   
 14798  14872       switch (token->type) {
................................................................................
 14926  15000   }
 14927  15001   
 14928  15002   
 14929  15003   static int JimEvalObjList(Jim_Interp *interp, Jim_Obj *listPtr)
 14930  15004   {
 14931  15005       int retcode = JIM_OK;
 14932  15006   
        15007  +    JimPanic((Jim_IsList(listPtr) == 0, "JimEvalObjList() invoked on non-list."));
        15008  +
 14933  15009       if (listPtr->internalRep.listValue.len) {
 14934  15010           Jim_IncrRefCount(listPtr);
 14935  15011           retcode = JimInvokeCommand(interp,
 14936  15012               listPtr->internalRep.listValue.len,
 14937  15013               listPtr->internalRep.listValue.ele);
 14938  15014           Jim_DecrRefCount(interp, listPtr);
 14939  15015       }
................................................................................
 14956  15032       Jim_Obj *prevScriptObj;
 14957  15033   
 14958  15034       if (Jim_IsList(scriptObjPtr) && scriptObjPtr->bytes == NULL) {
 14959  15035           return JimEvalObjList(interp, scriptObjPtr);
 14960  15036       }
 14961  15037   
 14962  15038       Jim_IncrRefCount(scriptObjPtr);     
 14963         -    script = Jim_GetScript(interp, scriptObjPtr);
 14964         -    if (script == NULL) {
        15039  +    script = JimGetScript(interp, scriptObjPtr);
        15040  +    if (!JimScriptValid(interp, script)) {
 14965  15041           Jim_DecrRefCount(interp, scriptObjPtr);
 14966  15042           return JIM_ERR;
 14967  15043       }
 14968  15044   
 14969  15045       Jim_SetEmptyResult(interp);
 14970  15046   
 14971  15047       token = script->token;
................................................................................
 15123  15199           if (argv != sargv) {
 15124  15200               Jim_Free(argv);
 15125  15201               argv = sargv;
 15126  15202           }
 15127  15203       }
 15128  15204   
 15129  15205       
 15130         -    JimAddErrorToStack(interp, retcode, script);
        15206  +    if (retcode == JIM_ERR) {
        15207  +        JimAddErrorToStack(interp, script);
        15208  +    }
        15209  +    
        15210  +    else if (retcode != JIM_RETURN || interp->returnCode != JIM_ERR) {
        15211  +        
        15212  +        interp->addStackTrace = 0;
        15213  +    }
 15131  15214   
 15132  15215       
 15133  15216       interp->currentScriptObj = prevScriptObj;
 15134  15217   
 15135  15218       Jim_FreeIntRep(interp, scriptObjPtr);
 15136  15219       scriptObjPtr->typePtr = &scriptObjType;
 15137  15220       Jim_SetIntRepPtr(scriptObjPtr, script);
................................................................................
 15274  15357       callFramePtr->argv = argv;
 15275  15358       callFramePtr->argc = argc;
 15276  15359       callFramePtr->procArgsObjPtr = cmd->u.proc.argListObjPtr;
 15277  15360       callFramePtr->procBodyObjPtr = cmd->u.proc.bodyObjPtr;
 15278  15361       callFramePtr->staticVars = cmd->u.proc.staticVars;
 15279  15362   
 15280  15363       
 15281         -    script = Jim_GetScript(interp, interp->currentScriptObj);
        15364  +    script = JimGetScript(interp, interp->currentScriptObj);
 15282  15365       callFramePtr->fileNameObj = script->fileNameObj;
 15283  15366       callFramePtr->line = script->linenr;
 15284  15367   
 15285  15368       Jim_IncrRefCount(cmd->u.proc.argListObjPtr);
 15286  15369       Jim_IncrRefCount(cmd->u.proc.bodyObjPtr);
 15287  15370       interp->framePtr = callFramePtr;
 15288  15371   
................................................................................
 15471  15554       fclose(fp);
 15472  15555       buf[readlen] = 0;
 15473  15556   
 15474  15557       scriptObjPtr = Jim_NewStringObjNoAlloc(interp, buf, readlen);
 15475  15558       JimSetSourceInfo(interp, scriptObjPtr, Jim_NewStringObj(interp, filename, -1), 1);
 15476  15559       Jim_IncrRefCount(scriptObjPtr);
 15477  15560   
 15478         -    
 15479         -    if (Jim_GetScript(interp, scriptObjPtr) == NULL) {
 15480         -        
 15481         -        JimAddErrorToStack(interp, JIM_ERR, (ScriptObj *)Jim_GetIntRepPtr(scriptObjPtr));
 15482         -        Jim_DecrRefCount(interp, scriptObjPtr);
 15483         -        return JIM_ERR;
 15484         -    }
 15485         -
 15486  15561       prevScriptObj = interp->currentScriptObj;
 15487  15562       interp->currentScriptObj = scriptObjPtr;
 15488  15563   
 15489  15564       retcode = Jim_EvalObj(interp, scriptObjPtr);
 15490  15565   
 15491  15566       
 15492  15567       if (retcode == JIM_RETURN) {
................................................................................
 16028  16103           ExprByteCode *expr;
 16029  16104           jim_wide stop, currentVal;
 16030  16105           Jim_Obj *objPtr;
 16031  16106           int cmpOffset;
 16032  16107   
 16033  16108           
 16034  16109           expr = JimGetExpression(interp, argv[2]);
 16035         -        incrScript = Jim_GetScript(interp, argv[3]);
        16110  +        incrScript = JimGetScript(interp, argv[3]);
 16036  16111   
 16037  16112           
 16038  16113           if (incrScript == NULL || incrScript->len != 3 || !expr || expr->len != 3) {
 16039  16114               goto evalstart;
 16040  16115           }
 16041  16116           
 16042  16117           if (incrScript->token[1].type != JIM_TT_ESC ||
................................................................................
 17015  17090   
 17016  17091   static int Jim_AppendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 17017  17092   {
 17018  17093       Jim_Obj *stringObjPtr;
 17019  17094       int i;
 17020  17095   
 17021  17096       if (argc < 2) {
 17022         -        Jim_WrongNumArgs(interp, 1, argv, "varName ?value value ...?");
        17097  +        Jim_WrongNumArgs(interp, 1, argv, "varName ?value ...?");
 17023  17098           return JIM_ERR;
 17024  17099       }
 17025  17100       if (argc == 2) {
 17026  17101           stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG);
 17027  17102           if (!stringObjPtr)
 17028  17103               return JIM_ERR;
 17029  17104       }
................................................................................
 17064  17139   
 17065  17140   
 17066  17141   static int Jim_EvalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 17067  17142   {
 17068  17143       int rc;
 17069  17144   
 17070  17145       if (argc < 2) {
 17071         -        Jim_WrongNumArgs(interp, 1, argv, "script ?...?");
        17146  +        Jim_WrongNumArgs(interp, 1, argv, "arg ?arg ...?");
 17072  17147           return JIM_ERR;
 17073  17148       }
 17074  17149   
 17075  17150       if (argc == 2) {
 17076  17151           rc = Jim_EvalObj(interp, argv[1]);
 17077  17152       }
 17078  17153       else {
................................................................................
 17275  17350   static int JimAliasCmd(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 17276  17351   {
 17277  17352       Jim_Obj *cmdList;
 17278  17353       Jim_Obj *prefixListObj = Jim_CmdPrivData(interp);
 17279  17354   
 17280  17355       
 17281  17356       cmdList = Jim_DuplicateObj(interp, prefixListObj);
 17282         -    ListInsertElements(cmdList, -1, argc - 1, argv + 1);
        17357  +    Jim_ListInsertElements(interp, cmdList, Jim_ListLength(interp, cmdList), argc - 1, argv + 1);
 17283  17358   
 17284  17359       return JimEvalObjList(interp, cmdList);
 17285  17360   }
 17286  17361   
 17287  17362   static void JimAliasCmdDelete(Jim_Interp *interp, void *privData)
 17288  17363   {
 17289  17364       Jim_Obj *prefixListObj = privData;
................................................................................
 17595  17670   static int Jim_StringCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 17596  17671   {
 17597  17672       int len;
 17598  17673       int opt_case = 1;
 17599  17674       int option;
 17600  17675       static const char * const options[] = {
 17601  17676           "bytelength", "length", "compare", "match", "equal", "is", "byterange", "range", "replace",
 17602         -        "map", "repeat", "reverse", "index", "first", "last",
        17677  +        "map", "repeat", "reverse", "index", "first", "last", "cat",
 17603  17678           "trim", "trimleft", "trimright", "tolower", "toupper", "totitle", NULL
 17604  17679       };
 17605  17680       enum
 17606  17681       {
 17607  17682           OPT_BYTELENGTH, OPT_LENGTH, OPT_COMPARE, OPT_MATCH, OPT_EQUAL, OPT_IS, OPT_BYTERANGE, OPT_RANGE, OPT_REPLACE,
 17608         -        OPT_MAP, OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST,
        17683  +        OPT_MAP, OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST, OPT_CAT,
 17609  17684           OPT_TRIM, OPT_TRIMLEFT, OPT_TRIMRIGHT, OPT_TOLOWER, OPT_TOUPPER, OPT_TOTITLE
 17610  17685       };
 17611  17686       static const char * const nocase_options[] = {
 17612  17687           "-nocase", NULL
 17613  17688       };
 17614  17689       static const char * const nocase_length_options[] = {
 17615  17690           "-nocase", "-length", NULL
................................................................................
 17634  17709                   len = Jim_Utf8Length(interp, argv[2]);
 17635  17710               }
 17636  17711               else {
 17637  17712                   len = Jim_Length(argv[2]);
 17638  17713               }
 17639  17714               Jim_SetResultInt(interp, len);
 17640  17715               return JIM_OK;
        17716  +
        17717  +        case OPT_CAT:{
        17718  +                Jim_Obj *objPtr;
        17719  +                if (argc == 3) {
        17720  +                    
        17721  +                    objPtr = argv[2];
        17722  +                }
        17723  +                else {
        17724  +                    int i;
        17725  +
        17726  +                    objPtr = Jim_NewStringObj(interp, "", 0);
        17727  +
        17728  +                    for (i = 2; i < argc; i++) {
        17729  +                        Jim_AppendObj(interp, objPtr, argv[i]);
        17730  +                    }
        17731  +                }
        17732  +                Jim_SetResult(interp, objPtr);
        17733  +                return JIM_OK;
        17734  +            }
 17641  17735   
 17642  17736           case OPT_COMPARE:
 17643  17737           case OPT_EQUAL:
 17644  17738               {
 17645  17739                   
 17646  17740                   long opt_length = -1;
 17647  17741                   int n = argc - 4;
................................................................................
 18617  18711               break;
 18618  18712   
 18619  18713           case INFO_SCRIPT:
 18620  18714               if (argc != 2) {
 18621  18715                   Jim_WrongNumArgs(interp, 2, argv, "");
 18622  18716                   return JIM_ERR;
 18623  18717               }
 18624         -            Jim_SetResult(interp, Jim_GetScript(interp, interp->currentScriptObj)->fileNameObj);
        18718  +            Jim_SetResult(interp, JimGetScript(interp, interp->currentScriptObj)->fileNameObj);
 18625  18719               break;
 18626  18720   
 18627  18721           case INFO_SOURCE:{
 18628         -                int line;
        18722  +                jim_wide line;
 18629  18723                   Jim_Obj *resObjPtr;
 18630  18724                   Jim_Obj *fileNameObj;
 18631  18725   
 18632         -                if (argc != 3) {
 18633         -                    Jim_WrongNumArgs(interp, 2, argv, "source");
        18726  +                if (argc != 3 && argc != 5) {
        18727  +                    Jim_WrongNumArgs(interp, 2, argv, "source ?filename line?");
 18634  18728                       return JIM_ERR;
 18635  18729                   }
 18636         -                if (argv[2]->typePtr == &sourceObjType) {
 18637         -                    fileNameObj = argv[2]->internalRep.sourceValue.fileNameObj;
 18638         -                    line = argv[2]->internalRep.sourceValue.lineNumber;
 18639         -                }
 18640         -                else if (argv[2]->typePtr == &scriptObjType) {
 18641         -                    ScriptObj *script = Jim_GetScript(interp, argv[2]);
 18642         -                    fileNameObj = script->fileNameObj;
 18643         -                    line = script->firstline;
        18730  +                if (argc == 5) {
        18731  +                    if (Jim_GetWide(interp, argv[4], &line) != JIM_OK) {
        18732  +                        return JIM_ERR;
        18733  +                    }
        18734  +                    resObjPtr = Jim_NewStringObj(interp, Jim_String(argv[2]), Jim_Length(argv[2]));
        18735  +                    JimSetSourceInfo(interp, resObjPtr, argv[3], line);
 18644  18736                   }
 18645  18737                   else {
 18646         -                    fileNameObj = interp->emptyObj;
 18647         -                    line = 1;
        18738  +                    if (argv[2]->typePtr == &sourceObjType) {
        18739  +                        fileNameObj = argv[2]->internalRep.sourceValue.fileNameObj;
        18740  +                        line = argv[2]->internalRep.sourceValue.lineNumber;
        18741  +                    }
        18742  +                    else if (argv[2]->typePtr == &scriptObjType) {
        18743  +                        ScriptObj *script = JimGetScript(interp, argv[2]);
        18744  +                        fileNameObj = script->fileNameObj;
        18745  +                        line = script->firstline;
        18746  +                    }
        18747  +                    else {
        18748  +                        fileNameObj = interp->emptyObj;
        18749  +                        line = 1;
        18750  +                    }
        18751  +                    resObjPtr = Jim_NewListObj(interp, NULL, 0);
        18752  +                    Jim_ListAppendElement(interp, resObjPtr, fileNameObj);
        18753  +                    Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line));
 18648  18754                   }
 18649         -                resObjPtr = Jim_NewListObj(interp, NULL, 0);
 18650         -                Jim_ListAppendElement(interp, resObjPtr, fileNameObj);
 18651         -                Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line));
 18652  18755                   Jim_SetResult(interp, resObjPtr);
 18653  18756                   break;
 18654  18757               }
 18655  18758   
 18656  18759           case INFO_STACKTRACE:
 18657  18760               Jim_SetResult(interp, interp->stackTrace);
 18658  18761               break;
................................................................................
 21841  21944       Jim_RegisterCoreCommands(interp);
 21842  21945   
 21843  21946       
 21844  21947       if (Jim_InitStaticExtensions(interp) != JIM_OK) {
 21845  21948           JimPrintErrorMessage(interp);
 21846  21949       }
 21847  21950   
 21848         -    Jim_SetVariableStrWithStr(interp, "jim_argv0", argv[0]);
        21951  +    Jim_SetVariableStrWithStr(interp, "jim::argv0", argv[0]);
 21849  21952       Jim_SetVariableStrWithStr(interp, JIM_INTERACTIVE, argc == 1 ? "1" : "0");
 21850  21953       retcode = Jim_initjimshInit(interp);
 21851  21954   
 21852  21955       if (argc == 1) {
 21853  21956           if (retcode == JIM_ERR) {
 21854  21957               JimPrintErrorMessage(interp);
 21855  21958           }

Added fossil.1.

            1  +.TH FOSSIL "1" "February 2015" "http://fossil-scm.org" "User Commands"
            2  +.SH NAME
            3  +fossil \- Distributed Version Control System
            4  +.SH SYNOPSIS
            5  +.B fossil
            6  +\fIhelp\fR
            7  +.br
            8  +.B fossil
            9  +\fIhelp COMMAND\fR
           10  +.br
           11  +.B fossil
           12  +\fICOMMAND [OPTIONS]\fR
           13  +.SH DESCRIPTION
           14  +Fossil is a distributed version control system (DVCS) with built-in
           15  +wiki, ticket tracker, CGI/http interface, and http server.
           16  +
           17  +.SH Common COMMANDs:
           18  +
           19  +add            clean          import         pull           stash 
           20  +.br
           21  +addremove      clone          info           purge          status
           22  +.br
           23  +all            commit         init           push           sync
           24  +.br
           25  +annotate       diff           json           rebuild        tag
           26  +.br
           27  +bisect         export         ls             remote-url     timeline
           28  +.br
           29  +blame          extras         merge          revert         ui
           30  +.br
           31  +branch         finfo          mv             rm             undo
           32  +.br
           33  +bundle         fusefs         open           rss            unpublish
           34  +.br
           35  +cat            gdiff          praise         settings       update
           36  +.br
           37  +changes        help           publish        sqlite3        version
           38  +
           39  +.SH FEATURES
           40  +
           41  +Features as described on the fossil home page.
           42  +
           43  +.HP
           44  +1.
           45  +.B Integrated Bug Tracking, Wiki, & Technotes
           46  +- In addition to doing distributed version control like Git and
           47  +Mercurial, Fossil also supports bug tracking, wiki, and technotes.
           48  +
           49  +.HP
           50  +2.
           51  +.B Built-in Web Interface
           52  +- Fossil has a built-in and intuitive web interface that promotes
           53  +project situational awareness. Type "fossil ui" and Fossil automatically
           54  +opens a web browser to a page that shows detailed graphical history and
           55  +status information on that project.
           56  +
           57  +.HP
           58  +3.
           59  +.B Self-Contained
           60  +- Fossil is a single self-contained stand-alone executable. To install,
           61  +simply download a precompiled binary for Linux, Mac, OpenBSD, or Windows
           62  +and put it on your $PATH. Easy-to-compile source code is available for
           63  +users on other platforms.
           64  +
           65  +.HP
           66  +4.
           67  +.B Simple Networking
           68  +- No custom protocols or TCP ports. Fossil uses plain old HTTP (or HTTPS
           69  +or SSH) for all network communications, so it works fine from behind
           70  +restrictive firewalls, including proxies. The protocol is bandwidth
           71  +efficient to the point that Fossil can be used comfortably over dial-up.
           72  +
           73  +.HP
           74  +5.
           75  +.B CGI/SCGI Enabled
           76  +- No server is required, but if you want to set one up, Fossil supports
           77  +four simple server configurations.
           78  +
           79  +.HP
           80  +6.
           81  +.B Autosync
           82  +- Fossil supports "autosync" mode which helps to keep projects moving
           83  +forward by reducing the amount of needless forking and merging often
           84  +associated with distributed projects.
           85  +
           86  +.HP
           87  +7.
           88  +.B Robust & Reliable
           89  +- Fossil stores content using an enduring file format in an SQLite
           90  +database so that transactions are atomic even if interrupted by a
           91  +power loss or system crash. Automatic self-checks verify that all
           92  +aspects of the repository are consistent prior to each commit. In
           93  +over seven years of operation, no work has ever been lost after
           94  +having been committed to a Fossil repository.
           95  +
           96  +.SH DOCUMENTATION
           97  +http://www.fossil-scm.org/
           98  +.br
           99  +.B fossil
          100  +\fIui\fR

Added skins/README.md.

            1  +Built-in Skins
            2  +==============
            3  +
            4  +Each subdirectory under this folder describes a built-in "skin".
            5  +There are three files in each subdirectory for the CSS, the header,
            6  +and the footer for that skin.
            7  +
            8  +To improve an existing built-in skin, simply edit the appropriate
            9  +files and recompile.
           10  +
           11  +To add a new skin:
           12  +
           13  +   1.   Create a new subdirectory under skins/.  (The new directory is
           14  +        called "skins/newskin" below but you should use a new original
           15  +        name, of course.)
           16  +
           17  +   2.   Add files skins/newskin/css.txt, skins/newskin/header.txt,
           18  +        and skins/newskin/footer.txt.  Be sure to "fossil add" these files.
           19  +
           20  +   3.   Go to the src/ directory and rerun "tclsh makemake.tcl".  This
           21  +        step rebuilds the various makefiles so that they have dependencies
           22  +        on the skin files you just installed.
           23  +
           24  +   4.   Edit the BuiltinSkin[] array near the top of the src/skins.c source
           25  +        file so that it describes and references the "newskin" skin.
           26  +
           27  +   5.   Type "make" to rebuild.
           28  +
           29  +Development Hints
           30  +-----------------
           31  +
           32  +One way to develop a new skin is to copy the baseline files (css.txt,
           33  +footer.txt, and header.txt) into a working directory $WORKDIR then
           34  +launch Fossil with a command-line option "--skin $WORKDIR".  Example:
           35  +
           36  +        cp -r skins/default newskin
           37  +        fossil ui --skin ./newskin
           38  +
           39  +When the argument to --skin contains one or more '/' characters, the
           40  +appropriate skin files are read from disk from the directory specified.
           41  +So after launching fossil as shown above, you can edit the newskin/css.txt,
           42  +newskin/header.txt, and newskin/footer.txt files using your favorite
           43  +text editor, then press Reload on your browser to see immediate results.

Added skins/black_and_white/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +    margin:0px 0px 0px 0px;
            4  +    padding:0px;
            5  +    font-family:verdana, arial, helvetica, "sans serif";
            6  +    color:#333;
            7  +    background-color:white;
            8  +    -moz-text-size-adjust: none;
            9  +    -webkit-text-size-adjust: none;
           10  +    -mx-text-size-adjust: none;
           11  +}
           12  +
           13  +/* consistent colours */
           14  +h2 {
           15  +  color: #333;
           16  +}
           17  +h3 {
           18  +  color: #333;
           19  +}
           20  +
           21  +/* The project logo in the upper left-hand corner of each page */
           22  +div.logo {
           23  +  display: table-cell;
           24  +  text-align: left;
           25  +  vertical-align: bottom;
           26  +  font-weight: bold;
           27  +  color: #333;
           28  +  white-space: nowrap;
           29  +}
           30  +
           31  +/* The page title centered at the top of each page */
           32  +div.title {
           33  +  display: table-cell;
           34  +  font-size: 2em;
           35  +  font-weight: bold;
           36  +  text-align: center;
           37  +  color: #333;
           38  +  vertical-align: bottom;
           39  +  width: 100%;
           40  +}
           41  +
           42  +/* The login status message in the top right-hand corner */
           43  +div.status {
           44  +  display: table-cell;
           45  +  padding-right: 10px;
           46  +  text-align: right;
           47  +  vertical-align: bottom;
           48  +  padding-bottom: 5px;
           49  +  color: #333;
           50  +  font-size: 0.8em;
           51  +  font-weight: bold;
           52  +  white-space: nowrap;
           53  +}
           54  +
           55  +/* The header across the top of the page */
           56  +div.header {
           57  +    margin:10px 0px 10px 0px;
           58  +    padding:1px 0px 0px 20px;
           59  +    border-style:solid;
           60  +    border-color:black;
           61  +    border-width:1px 0px;
           62  +    background-color:#eee;
           63  +}
           64  +
           65  +/* The main menu bar that appears at the top left of the page beneath
           66  +** the header. Width must be co-ordinated with the container below */
           67  +div.mainmenu {
           68  +  float: left;
           69  +  margin-left: 10px;
           70  +  margin-right: 10px;
           71  +  font-size: 0.9em;
           72  +  font-weight: bold;
           73  +  padding:5px;
           74  +  background-color:#eee;
           75  +  border:1px solid #999;
           76  +  width:8em;
           77  +}
           78  +
           79  +/* Main menu is now a list */
           80  +div.mainmenu ul {
           81  +  padding: 0;
           82  +  list-style:none;
           83  +}
           84  +div.mainmenu a, div.mainmenu a:visited{
           85  +  padding: 1px 10px 1px 10px;
           86  +  color: #333;
           87  +  text-decoration: none;
           88  +}
           89  +div.mainmenu a:hover {
           90  +  color: #eee;
           91  +  background-color: #333;
           92  +}
           93  +
           94  +/* Container for the sub-menu and content so they don't spread
           95  +** out underneath the main menu */
           96  +#container {
           97  +  padding-left: 9em;
           98  +}
           99  +
          100  +/* The submenu bar that *sometimes* appears below the main menu */
          101  +div.submenu, div.sectionmenu {
          102  +  padding: 3px 10px 3px 10px;
          103  +  font-size: 0.9em;
          104  +  text-align: center;
          105  +  border:1px solid #999;
          106  +  border-width:1px 0px;
          107  +  background-color: #eee;
          108  +  color: #333;
          109  +}
          110  +div.submenu a, div.submenu a:visited, div.sectionmenu>a.button:link,
          111  +div.sectionmenu>a.button:visited {
          112  +  padding: 3px 10px 3px 10px;
          113  +  color: #333;
          114  +  text-decoration: none;
          115  +}
          116  +div.submenu a:hover, div.sectionmenu>a.button:hover {
          117  +  color: #eee;
          118  +  background-color: #333;
          119  +}
          120  +
          121  +/* All page content from the bottom of the menu or submenu down to
          122  +** the footer */
          123  +div.content {
          124  +  padding: 2ex 1ex 0ex 2ex;
          125  +}
          126  +
          127  +/* Some pages have section dividers */
          128  +div.section {
          129  +  margin-bottom: 0px;
          130  +  margin-top: 1em;
          131  +  padding: 1px 1px 1px 1px;
          132  +  font-size: 1.2em;
          133  +  font-weight: bold;
          134  +  border-style:solid;
          135  +  border-color:#999;
          136  +  border-width:1px 0px;
          137  +  background-color: #eee;
          138  +  color: #333;
          139  +  white-space: nowrap;
          140  +}
          141  +
          142  +/* The "Date" that occurs on the left hand side of timelines */
          143  +div.divider {
          144  +  background: #eee;
          145  +  border: 2px #999 solid;
          146  +  font-size: 1em; font-weight: normal;
          147  +  padding: .25em;
          148  +  margin: .2em 0 .2em 0;
          149  +  float: left;
          150  +  clear: left;
          151  +  color: #333;
          152  +  white-space: nowrap;
          153  +}
          154  +
          155  +/* The footer at the very bottom of the page */
          156  +div.footer {
          157  +  font-size: 0.8em;
          158  +  margin-top: 12px;
          159  +  padding: 5px 10px 5px 10px;
          160  +  text-align: right;
          161  +  background-color: #eee;
          162  +  color: #555;
          163  +}
          164  +
          165  +/* <verbatim> blocks */
          166  +pre.verbatim {
          167  +  background-color: #f5f5f5;
          168  +  padding: 0.5em;
          169  +  white-space: pre-wrap;
          170  +}
          171  +
          172  +/* The label/value pairs on (for example) the ci page */
          173  +table.label-value th {
          174  +  vertical-align: top;
          175  +  text-align: right;
          176  +  padding: 0.2ex 2ex;
          177  +}

Added skins/black_and_white/footer.txt.

            1  +<div class="footer">
            2  +Fossil version $manifest_version $manifest_date
            3  +</div>
            4  +</body></html>

Added skins/black_and_white/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss">
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen">
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="logo">
           13  +    <img src="$logo_image_url" alt="logo">
           14  +    <br />$<project_name>
           15  +  </div>
           16  +  <div class="title">$<title></div>
           17  +  <div class="status"><th1>
           18  +     if {[info exists login]} {
           19  +       puts "Logged in as $login"
           20  +     } else {
           21  +       puts "Not logged in"
           22  +     }
           23  +  </th1></div>
           24  +</div>
           25  +<div class="mainmenu">
           26  +<th1>
           27  +html "<a href='$home$index_page'>Home</a>\n"
           28  +if {[anycap jor]} {
           29  +  html "<a href='$home/timeline'>Timeline</a>\n"
           30  +}
           31  +if {[anoncap oh]} {
           32  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
           33  +}
           34  +if {[anoncap o]} {
           35  +  html "<a href='$home/brlist'>Branches</a>\n"
           36  +  html "<a href='$home/taglist'>Tags</a>\n"
           37  +}
           38  +if {[anoncap r]} {
           39  +  html "<a href='$home/ticket'>Tickets</a>\n"
           40  +}
           41  +if {[anoncap j]} {
           42  +  html "<a href='$home/wiki'>Wiki</a>\n"
           43  +}
           44  +if {[hascap s]} {
           45  +  html "<a href='$home/setup'>Admin</a>\n"
           46  +} elseif {[hascap a]} {
           47  +  html "<a href='$home/setup_ulist'>Users</a>\n"
           48  +}
           49  +if {[info exists login]} {
           50  +  html "<a href='$home/login'>Logout</a>\n"
           51  +} else {
           52  +  html "<a href='$home/login'>Login</a>\n"
           53  +}
           54  +</th1></ul></div>

Added skins/default/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +  margin: 0ex 1ex;
            4  +  padding: 0px;
            5  +  background-color: white;
            6  +  font-family: sans-serif;
            7  +  -moz-text-size-adjust: none;
            8  +  -webkit-text-size-adjust: none;
            9  +  -mx-text-size-adjust: none;
           10  +}
           11  +
           12  +/* The project logo in the upper left-hand corner of each page */
           13  +div.logo {
           14  +  display: table-cell;
           15  +  text-align: center;
           16  +  vertical-align: bottom;
           17  +  font-weight: bold;
           18  +  color: #558195;
           19  +  min-width: 200px;
           20  +  white-space: nowrap;
           21  +}
           22  +
           23  +/* The page title centered at the top of each page */
           24  +div.title {
           25  +  display: table-cell;
           26  +  font-size: 2em;
           27  +  font-weight: bold;
           28  +  text-align: center;
           29  +  padding: 0 0 0 1em;
           30  +  color: #558195;
           31  +  vertical-align: bottom;
           32  +  width: 100%;
           33  +}
           34  +
           35  +/* The login status message in the top right-hand corner */
           36  +div.status {
           37  +  display: table-cell;
           38  +  text-align: right;
           39  +  vertical-align: bottom;
           40  +  color: #558195;
           41  +  font-size: 0.8em;
           42  +  font-weight: bold;
           43  +  min-width: 200px;
           44  +  white-space: nowrap;
           45  +}
           46  +
           47  +/* The header across the top of the page */
           48  +div.header {
           49  +  display: table;
           50  +  width: 100%;
           51  +}
           52  +
           53  +/* The main menu bar that appears at the top of the page beneath
           54  +** the header */
           55  +div.mainmenu {
           56  +  padding: 5px 10px 5px 10px;
           57  +  font-size: 0.9em;
           58  +  font-weight: bold;
           59  +  text-align: center;
           60  +  letter-spacing: 1px;
           61  +  background-color: #558195;
           62  +  border-top-left-radius: 8px;
           63  +  border-top-right-radius: 8px;
           64  +  color: white;
           65  +}
           66  +
           67  +/* The submenu bar that *sometimes* appears below the main menu */
           68  +div.submenu, div.sectionmenu {
           69  +  padding: 3px 10px 3px 0px;
           70  +  font-size: 0.9em;
           71  +  text-align: center;
           72  +  background-color: #456878;
           73  +  color: white;
           74  +}
           75  +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
           76  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           77  +  padding: 3px 10px 3px 10px;
           78  +  color: white;
           79  +  text-decoration: none;
           80  +}
           81  +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
           82  +  color: #558195;
           83  +  background-color: white;
           84  +}
           85  +
           86  +/* All page content from the bottom of the menu or submenu down to
           87  +** the footer */
           88  +div.content {
           89  +  padding: 0ex 1ex 1ex 1ex;
           90  +  border: solid #aaa;
           91  +  border-width: 1px;
           92  +}
           93  +
           94  +/* Some pages have section dividers */
           95  +div.section {
           96  +  margin-bottom: 0px;
           97  +  margin-top: 1em;
           98  +  padding: 1px 1px 1px 1px;
           99  +  font-size: 1.2em;
          100  +  font-weight: bold;
          101  +  background-color: #558195;
          102  +  color: white;
          103  +  white-space: nowrap;
          104  +}
          105  +
          106  +/* The "Date" that occurs on the left hand side of timelines */
          107  +div.divider {
          108  +  background: #a1c4d4;
          109  +  border: 2px #558195 solid;
          110  +  font-size: 1em; font-weight: normal;
          111  +  padding: .25em;
          112  +  margin: .2em 0 .2em 0;
          113  +  float: left;
          114  +  clear: left;
          115  +  white-space: nowrap;
          116  +}
          117  +
          118  +/* The footer at the very bottom of the page */
          119  +div.footer {
          120  +  clear: both;
          121  +  font-size: 0.8em;
          122  +  padding: 5px 10px 5px 10px;
          123  +  text-align: right;
          124  +  background-color: #558195;
          125  +  border-bottom-left-radius: 8px;
          126  +  border-bottom-right-radius: 8px;
          127  +  color: white;
          128  +}
          129  +
          130  +/* Hyperlink colors in the footer */
          131  +div.footer a { color: white; }
          132  +div.footer a:link { color: white; }
          133  +div.footer a:visited { color: white; }
          134  +div.footer a:hover { background-color: white; color: #558195; }
          135  +
          136  +/* verbatim blocks */
          137  +pre.verbatim {
          138  +  background-color: #f5f5f5;
          139  +  padding: 0.5em;
          140  +  white-space: pre-wrap;
          141  +}
          142  +
          143  +/* The label/value pairs on (for example) the ci page */
          144  +table.label-value th {
          145  +  vertical-align: top;
          146  +  text-align: right;
          147  +  padding: 0.2ex 2ex;
          148  +}

Added skins/default/footer.txt.

            1  +<div class="footer">
            2  +This page was generated in about
            3  +<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
            4  +Fossil version $manifest_version $manifest_date
            5  +</div>
            6  +</body></html>

Added skins/default/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss" />
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen" />
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="logo">
           13  +    <img src="$logo_image_url" alt="logo" />
           14  +  </div>
           15  +  <div class="title"><small>$<project_name></small><br />$<title></div>
           16  +  <div class="status"><th1>
           17  +     if {[info exists login]} {
           18  +       puts "Logged in as $login"
           19  +     } else {
           20  +       puts "Not logged in"
           21  +     }
           22  +  </th1></div>
           23  +</div>
           24  +<div class="mainmenu">
           25  +<th1>
           26  +html "<a href='$home$index_page'>Home</a>\n"
           27  +if {[anycap jor]} {
           28  +  html "<a href='$home/timeline'>Timeline</a>\n"
           29  +}
           30  +if {[anoncap oh]} {
           31  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
           32  +}
           33  +if {[anoncap o]} {
           34  +  html "<a href='$home/brlist'>Branches</a>\n"
           35  +  html "<a href='$home/taglist'>Tags</a>\n"
           36  +}
           37  +if {[anoncap r]} {
           38  +  html "<a href='$home/ticket'>Tickets</a>\n"
           39  +}
           40  +if {[anoncap j]} {
           41  +  html "<a href='$home/wiki'>Wiki</a>\n"
           42  +}
           43  +if {[hascap s]} {
           44  +  html "<a href='$home/setup'>Admin</a>\n"
           45  +} elseif {[hascap a]} {
           46  +  html "<a href='$home/setup_ulist'>Users</a>\n"
           47  +}
           48  +if {[info exists login]} {
           49  +  html "<a href='$home/login'>Logout</a>\n"
           50  +} else {
           51  +  html "<a href='$home/login'>Login</a>\n"
           52  +}
           53  +</th1></div>

Added skins/eagle/README.md.

            1  +For this skin to look exactly as it was intended to, the **white-foreground**
            2  +setting should be enabled.
            3  +
            4  +This skin was contributed by Joe Mistachkin.

Added skins/eagle/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +  margin: 0ex 1ex;
            4  +  padding: 0px;
            5  +  background-color: #485D7B;
            6  +  font-family: sans-serif;
            7  +  color: white;
            8  +  -moz-text-size-adjust: none;
            9  +  -webkit-text-size-adjust: none;
           10  +  -mx-text-size-adjust: none;
           11  +}
           12  +
           13  +/* The project logo in the upper left-hand corner of each page */
           14  +div.logo {
           15  +  display: table-cell;
           16  +  text-align: center;
           17  +  vertical-align: bottom;
           18  +  font-weight: bold;
           19  +  color: white;
           20  +  padding: 5 0 5 0em;
           21  +  white-space: nowrap;
           22  +}
           23  +
           24  +/* The page title centered at the top of each page */
           25  +div.title {
           26  +  display: table-cell;
           27  +  font-size: 2em;
           28  +  font-weight: bold;
           29  +  text-align: left;
           30  +  padding: 0 0 0 1em;
           31  +  color: white;
           32  +  vertical-align: bottom;
           33  +  width: 100%;
           34  +}
           35  +
           36  +/* The login status message in the top right-hand corner */
           37  +div.status {
           38  +  display: table-cell;
           39  +  text-align: right;
           40  +  vertical-align: bottom;
           41  +  color: white;
           42  +  font-size: 0.8em;
           43  +  font-weight: bold;
           44  +  min-width: 200px;
           45  +  white-space: nowrap;
           46  +}
           47  +
           48  +/* The header across the top of the page */
           49  +div.header {
           50  +  display: table;
           51  +  width: 100%;
           52  +}
           53  +
           54  +/* The main menu bar that appears at the top of the page beneath
           55  +** the header */
           56  +div.mainmenu {
           57  +  padding: 5px 10px 5px 10px;
           58  +  font-size: 0.9em;
           59  +  font-weight: bold;
           60  +  text-align: center;
           61  +  letter-spacing: 1px;
           62  +  background-color: #76869D;
           63  +  border-top-left-radius: 8px;
           64  +  border-top-right-radius: 8px;
           65  +  color: white;
           66  +}
           67  +
           68  +/* The submenu bar that *sometimes* appears below the main menu */
           69  +div.submenu, div.sectionmenu {
           70  +  padding: 3px 10px 3px 0px;
           71  +  font-size: 0.9em;
           72  +  font-weight: bold;
           73  +  text-align: center;
           74  +  background-color: #485D7B;
           75  +  color: white;
           76  +}
           77  +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
           78  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           79  +  padding: 3px 10px 3px 10px;
           80  +  color: white;
           81  +  text-decoration: none;
           82  +}
           83  +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
           84  +  text-decoration: underline;
           85  +}
           86  +
           87  +/* All page content from the bottom of the menu or submenu down to
           88  +** the footer */
           89  +div.content {
           90  +  padding: 0ex 1ex 0ex 2ex;
           91  +}
           92  +
           93  +/* Some pages have section dividers */
           94  +div.section {
           95  +  margin-bottom: 0px;
           96  +  margin-top: 1em;
           97  +  padding: 1px 1px 1px 1px;
           98  +  font-size: 1.2em;
           99  +  font-weight: bold;
          100  +  background-color: #485D7B;
          101  +  color: white;
          102  +  white-space: nowrap;
          103  +}
          104  +
          105  +/* The "Date" that occurs on the left hand side of timelines */
          106  +div.divider {
          107  +  background: #9DB0CC;
          108  +  color: white;
          109  +  border: 2px white solid;
          110  +  font-size: 1em; font-weight: normal;
          111  +  padding: .25em;
          112  +  margin: .2em 0 .2em 0;
          113  +  float: left;
          114  +  clear: left;
          115  +  white-space: nowrap;
          116  +}
          117  +
          118  +/* The footer at the very bottom of the page */
          119  +div.footer {
          120  +  clear: both;
          121  +  font-size: 0.8em;
          122  +  margin-top: 12px;
          123  +  padding: 5px 10px 5px 10px;
          124  +  text-align: right;
          125  +  background-color: #485D7B;
          126  +  border-bottom-left-radius: 8px;
          127  +  border-bottom-right-radius: 8px;
          128  +  color: white;
          129  +}
          130  +
          131  +/* Hyperlink colors in the footer */
          132  +a { color: white; }
          133  +a:link { color: white; }
          134  +a:visited { color: white; }
          135  +a:hover { color: #9DB0CC; }
          136  +
          137  +/* verbatim blocks */
          138  +pre.verbatim {
          139  +  background-color: #485D7B;
          140  +  color: white;
          141  +  padding: 0.5em;
          142  +  white-space: pre-wrap;
          143  +}
          144  +
          145  +/* The label/value pairs on (for example) the ci page */
          146  +table.label-value th {
          147  +  vertical-align: top;
          148  +  text-align: right;
          149  +  padding: 0.2ex 2ex;
          150  +}
          151  +
          152  +/* The nomenclature sidebox for branches,.. */
          153  +div.sidebox {
          154  +  float: right;
          155  +  background-color: #485D7B;
          156  +  border-width: medium;
          157  +  border-style: double;
          158  +  margin: 10px;
          159  +}
          160  +
          161  +/* the format for the timeline data table */
          162  +table.timelineTable {
          163  +  cellspacing: 0;
          164  +  border: 0;
          165  +  cellpadding: 0;
          166  +  font-family: "courier new";
          167  +  border-collapse: collapse;
          168  +}
          169  +
          170  +tr.timelineSelected {
          171  +  background-color: #7EA2D9;
          172  +}
          173  +
          174  +/* Side-by-side diff */
          175  +table.sbsdiff {
          176  +  background-color: #485D7B;
          177  +  font-family: fixed, Dejavu Sans Mono, Monaco, Lucida Console, monospace;
          178  +  font-size: 8pt;
          179  +  border-collapse:collapse;
          180  +  white-space: pre;
          181  +  width: 98%;
          182  +  border: 1px #000 dashed;
          183  +  margin-left: auto;
          184  +  margin-right: auto;
          185  +}
          186  +
          187  +/* format for the layout table, used for the captcha display */
          188  +table.captcha {
          189  +  margin: auto;
          190  +  padding: 10px;
          191  +  border-width: 4px;
          192  +  border-style: double;
          193  +  border-color: white;
          194  +}
          195  +
          196  +/* format for the user list table on the user setup page */
          197  +table.usetupUserList {
          198  +  outline-style: double;
          199  +  outline-width: 1px;
          200  +  outline-color: white;
          201  +  padding: 10px;
          202  +}
          203  +
          204  +/* color for capabilities, inherited by reader */
          205  +span.ueditInheritReader {
          206  +  color: white;
          207  +}
          208  +
          209  +/* format for values on ticket display page */
          210  +td.tktDspValue {
          211  +  text-align: left;
          212  +  vertical-align: top;
          213  +  background-color: #485D7B;
          214  +}
          215  +
          216  +/* format for example table cells on the report edit page */
          217  +td.rpteditex {
          218  +  border-width: thin;
          219  +  border-color: white;
          220  +  border-style: solid;
          221  +}
          222  +
          223  +/* List of files in a timeline */
          224  +ul.filelist {
          225  +  margin-top: 3px;
          226  +  line-height: 100%;
          227  +}
          228  +
          229  +/* side-by-side diff display */
          230  +div.sbsdiff {
          231  +  font-family: monospace;
          232  +  font-size: smaller;
          233  +  white-space: pre;
          234  +}
          235  +
          236  +/* context diff display */
          237  +div.udiff {
          238  +  font-family: monospace;
          239  +  white-space: pre;
          240  +}
          241  +
          242  +/* changes in a diff */
          243  +span.diffchng {
          244  +  background-color: rgb(170, 170, 140);
          245  +}
          246  +
          247  +/* added code in a diff */
          248  +span.diffadd {
          249  +  background-color: rgb(100, 200, 100);
          250  +}
          251  +
          252  +/* deleted in a diff */
          253  +span.diffrm {
          254  +  background-color: rgb(230, 110, 110);
          255  +}
          256  +
          257  +/* suppressed lines in a diff */
          258  +span.diffhr {
          259  +  display: inline-block;
          260  +  margin: .5em 0 1em;
          261  +  color: rgb(150, 150, 140);
          262  +}
          263  +
          264  +/* line numbers in a diff */
          265  +span.diffln {
          266  +  color: white;
          267  +}
          268  +
          269  +#canvas {
          270  +  background-color: #485D7B;
          271  +}

Added skins/eagle/footer.txt.

            1  +<div class="footer">
            2  +  <th1>
            3  +  proc getTclVersion {} {
            4  +    if {[catch {tclEval info patchlevel} tclVersion] == 0} {
            5  +      return "<a href=\"http://www.tcl.tk/\">Tcl</a> version $tclVersion"
            6  +    }
            7  +    return ""
            8  +  }
            9  +  proc getVersion { version } {
           10  +    set length [string length $version]
           11  +    return [string range $version 1 [expr {$length - 2}]]
           12  +  }
           13  +  set version [getVersion $manifest_version]
           14  +  set tclVersion [getTclVersion]
           15  +  set fossilUrl https://www.fossil-scm.org
           16  +  set fossilDate [string range $manifest_date 0 9]T[string range $manifest_date 11 end]
           17  +  </th1>
           18  +  This page was generated in about
           19  +  <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
           20  +  <a href="$fossilUrl/">Fossil</a>
           21  +  version $release_version $tclVersion
           22  +  <a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
           23  +  <a href="$fossilUrl/index.html/timeline?c=$fossilDate&amp;y=ci">$manifest_date</a>
           24  +</div>
           25  +</body></html>

Added skins/eagle/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss" />
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen" />
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="logo">
           13  +    <th1>
           14  +    ##
           15  +    ## NOTE: The purpose of this procedure is to take the base URL of the
           16  +    ##       Fossil project and return the root of the entire web site using
           17  +    ##       the same URI scheme as the base URL (e.g. http or https).
           18  +    ##
           19  +    proc getLogoUrl { baseurl } {
           20  +      set idx(first) [string first // $baseurl]
           21  +      if {$idx(first) != -1} {
           22  +        ##
           23  +        ## NOTE: Skip second slash.
           24  +        ##
           25  +        set idx(first+1) [expr {$idx(first) + 2}]
           26  +        ##
           27  +        ## NOTE: (part 1) The [string first] command does NOT actually
           28  +        ##       support the optional startIndex argument as specified
           29  +        ##       in the TH1 support manual; therefore, we fake it by
           30  +        ##       using the [string range] command and then adding the
           31  +        ##       necessary offset to the resulting index manually
           32  +        ##       (below).  In Tcl, we could use the following instead:
           33  +        ##
           34  +        ##       set idx(next) [string first / $baseurl $idx(first+1)]
           35  +        ##
           36  +        set idx(nextRange) [string range $baseurl $idx(first+1) end]
           37  +        set idx(next) [string first / $idx(nextRange)]
           38  +        if {$idx(next) != -1} {
           39  +          ##
           40  +          ## NOTE: (part 2) Add the necessary offset to the result of
           41  +          ##       the search for the next slash (i.e. the one after
           42  +          ##       the initial search for the two slashes).
           43  +          ##
           44  +          set idx(next) [expr {$idx(next) + $idx(first+1)}]
           45  +          ##
           46  +          ## NOTE: Back up one character from the next slash.
           47  +          ##
           48  +          set idx(next-1) [expr {$idx(next) - 1}]
           49  +          ##
           50  +          ## NOTE: Extract the URI scheme and host from the base URL.
           51  +          ##
           52  +          set scheme [string range $baseurl 0 $idx(first)]
           53  +          set host [string range $baseurl $idx(first+1) $idx(next-1)]
           54  +          ##
           55  +          ## NOTE: Try to stay in SSL mode if we are there now.
           56  +          ##
           57  +          if {[string compare $scheme http:/] == 0} {
           58  +            set scheme http://
           59  +          } else {
           60  +            set scheme https://
           61  +          }
           62  +          set logourl $scheme$host/
           63  +        } else {
           64  +          set logourl $baseurl
           65  +        }
           66  +      } else {
           67  +        set logourl $baseurl
           68  +      }
           69  +      return $logourl
           70  +    }
           71  +    set logourl [getLogoUrl $baseurl]
           72  +    </th1>
           73  +    <a href="$logourl">
           74  +      <img src="$logo_image_url" border="0" alt="$project_name">
           75  +    </a>
           76  +  </div>
           77  +  <div class="title">$<title></div>
           78  +  <div class="status"><nobr><th1>
           79  +     if {[info exists login]} {
           80  +       puts "Logged in as $login"
           81  +     } else {
           82  +       puts "Not logged in"
           83  +     }
           84  +  </th1></nobr><small><div id="clock"></div></small></div>
           85  +</div>
           86  +<script>
           87  +function updateClock(){
           88  +  var e = document.getElementById("clock");
           89  +  if(e){
           90  +    var d = new Date();
           91  +    function f(n) {
           92  +      return n < 10 ? '0' + n : n;
           93  +    }
           94  +    e.innerHTML = d.getUTCFullYear()+ '-' +
           95  +      f(d.getUTCMonth() + 1) + '-' +
           96  +      f(d.getUTCDate())      + ' ' +
           97  +      f(d.getUTCHours())     + ':' +
           98  +      f(d.getUTCMinutes());
           99  +    setTimeout("updateClock();",(60-d.getUTCSeconds())*1000);
          100  +  }
          101  +}
          102  +updateClock();
          103  +</script>
          104  +<div class="mainmenu">
          105  +<th1>
          106  +html "<a href='$home$index_page'>Home</a>\n"
          107  +html "<a href='$home/help'>Help</a>\n"
          108  +if {[anycap jor]} {
          109  +  html "<a href='$home/timeline'>Timeline</a>\n"
          110  +}
          111  +if {[anoncap oh]} {
          112  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
          113  +}
          114  +if {[anoncap o]} {
          115  +  html "<a href='$home/brlist'>Branches</a>\n"
          116  +  html "<a href='$home/taglist'>Tags</a>\n"
          117  +}
          118  +if {[anoncap r]} {
          119  +  html "<a href='$home/ticket'>Tickets</a>\n"
          120  +}
          121  +if {[anoncap j]} {
          122  +  html "<a href='$home/wiki'>Wiki</a>\n"
          123  +}
          124  +if {[hascap s]} {
          125  +  html "<a href='$home/setup'>Admin</a>\n"
          126  +} elseif {[hascap a]} {
          127  +  html "<a href='$home/setup_ulist'>Users</a>\n"
          128  +}
          129  +if {[info exists login]} {
          130  +  html "<a href='$home/login'>Logout</a>\n"
          131  +} else {
          132  +  html "<a href='$home/login'>Login</a>\n"
          133  +}
          134  +</th1></div>

Added skins/enhanced1/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +  margin: 0ex 1ex;
            4  +  padding: 0px;
            5  +  background-color: white;
            6  +  font-family: sans-serif;
            7  +  -moz-text-size-adjust: none;
            8  +  -webkit-text-size-adjust: none;
            9  +  -mx-text-size-adjust: none;
           10  +}
           11  +
           12  +/* The project logo in the upper left-hand corner of each page */
           13  +div.logo {
           14  +  display: table-cell;
           15  +  text-align: center;
           16  +  vertical-align: bottom;
           17  +  font-weight: bold;
           18  +  color: #558195;
           19  +  min-width: 200px;
           20  +  white-space: nowrap;
           21  +}
           22  +
           23  +/* The page title centered at the top of each page */
           24  +div.title {
           25  +  display: table-cell;
           26  +  font-size: 2em;
           27  +  font-weight: bold;
           28  +  text-align: center;
           29  +  padding: 0 0 0 1em;
           30  +  color: #558195;
           31  +  vertical-align: bottom;
           32  +  width: 100%;
           33  +}
           34  +
           35  +/* The login status message in the top right-hand corner */
           36  +div.status {
           37  +  display: table-cell;
           38  +  text-align: right;
           39  +  vertical-align: bottom;
           40  +  color: #558195;
           41  +  font-size: 0.8em;
           42  +  font-weight: bold;
           43  +  min-width: 200px;
           44  +  white-space: nowrap;
           45  +}
           46  +
           47  +/* The header across the top of the page */
           48  +div.header {
           49  +  display: table;
           50  +  width: 100%;
           51  +}
           52  +
           53  +/* The main menu bar that appears at the top of the page beneath
           54  +** the header */
           55  +div.mainmenu {
           56  +  padding: 5px 10px 5px 10px;
           57  +  font-size: 0.9em;
           58  +  font-weight: bold;
           59  +  text-align: center;
           60  +  letter-spacing: 1px;
           61  +  background-color: #558195;
           62  +  border-top-left-radius: 8px;
           63  +  border-top-right-radius: 8px;
           64  +  color: white;
           65  +}
           66  +
           67  +/* The submenu bar that *sometimes* appears below the main menu */
           68  +div.submenu, div.sectionmenu {
           69  +  padding: 3px 10px 3px 0px;
           70  +  font-size: 0.9em;
           71  +  text-align: center;
           72  +  background-color: #456878;
           73  +  color: white;
           74  +}
           75  +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
           76  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           77  +  padding: 3px 10px 3px 10px;
           78  +  color: white;
           79  +  text-decoration: none;
           80  +}
           81  +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
           82  +  color: #558195;
           83  +  background-color: white;
           84  +}
           85  +
           86  +/* All page content from the bottom of the menu or submenu down to
           87  +** the footer */
           88  +div.content {
           89  +  padding: 0ex 1ex 1ex 1ex;
           90  +  border: solid #aaa;
           91  +  border-width: 1px;
           92  +}
           93  +
           94  +/* Some pages have section dividers */
           95  +div.section {
           96  +  margin-bottom: 0px;
           97  +  margin-top: 1em;
           98  +  padding: 1px 1px 1px 1px;
           99  +  font-size: 1.2em;
          100  +  font-weight: bold;
          101  +  background-color: #558195;
          102  +  color: white;
          103  +  white-space: nowrap;
          104  +}
          105  +
          106  +/* The "Date" that occurs on the left hand side of timelines */
          107  +div.divider {
          108  +  background: #a1c4d4;
          109  +  border: 2px #558195 solid;
          110  +  font-size: 1em; font-weight: normal;
          111  +  padding: .25em;
          112  +  margin: .2em 0 .2em 0;
          113  +  float: left;
          114  +  clear: left;
          115  +  white-space: nowrap;
          116  +}
          117  +
          118  +/* The footer at the very bottom of the page */
          119  +div.footer {
          120  +  clear: both;
          121  +  font-size: 0.8em;
          122  +  padding: 5px 10px 5px 10px;
          123  +  text-align: right;
          124  +  background-color: #558195;
          125  +  border-bottom-left-radius: 8px;
          126  +  border-bottom-right-radius: 8px;
          127  +  color: white;
          128  +}
          129  +
          130  +/* Hyperlink colors in the footer */
          131  +div.footer a { color: white; }
          132  +div.footer a:link { color: white; }
          133  +div.footer a:visited { color: white; }
          134  +div.footer a:hover { background-color: white; color: #558195; }
          135  +
          136  +/* verbatim blocks */
          137  +pre.verbatim {
          138  +  background-color: #f5f5f5;
          139  +  padding: 0.5em;
          140  +  white-space: pre-wrap;
          141  +}
          142  +
          143  +/* The label/value pairs on (for example) the ci page */
          144  +table.label-value th {
          145  +  vertical-align: top;
          146  +  text-align: right;
          147  +  padding: 0.2ex 2ex;
          148  +}

Added skins/enhanced1/footer.txt.

            1  +<div class="footer">
            2  +  <th1>
            3  +  proc getTclVersion {} {
            4  +    if {[catch {tclEval info patchlevel} tclVersion] == 0} {
            5  +      return "<a href=\"http://www.tcl.tk/\">Tcl</a> version $tclVersion"
            6  +    }
            7  +    return ""
            8  +  }
            9  +  proc getVersion { version } {
           10  +    set length [string length $version]
           11  +    return [string range $version 1 [expr {$length - 2}]]
           12  +  }
           13  +  set version [getVersion $manifest_version]
           14  +  set tclVersion [getTclVersion]
           15  +  set fossilUrl https://www.fossil-scm.org
           16  +  </th1>
           17  +  This page was generated in about
           18  +  <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
           19  +  <a href="$fossilUrl/">Fossil</a>
           20  +  version $release_version $tclVersion
           21  +  <a href="$fossilUrl/index.html/info/$version">$manifest_version</a>
           22  +  <a href="$fossilUrl/index.html/timeline?c=$manifest_date&amp;y=ci">$manifest_date</a>
           23  +</div>
           24  +</body></html>

Added skins/enhanced1/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss" />
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen" />
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="logo">
           13  +    <th1>
           14  +    ##
           15  +    ## NOTE: The purpose of this procedure is to take the base URL of the
           16  +    ##       Fossil project and return the root of the entire web site using
           17  +    ##       the same URI scheme as the base URL (e.g. http or https).
           18  +    ##
           19  +    proc getLogoUrl { baseurl } {
           20  +      set idx(first) [string first // $baseurl]
           21  +      if {$idx(first) != -1} {
           22  +        ##
           23  +        ## NOTE: Skip second slash.
           24  +        ##
           25  +        set idx(first+1) [expr {$idx(first) + 2}]
           26  +        ##
           27  +        ## NOTE: (part 1) The [string first] command does NOT actually
           28  +        ##       support the optional startIndex argument as specified
           29  +        ##       in the TH1 support manual; therefore, we fake it by
           30  +        ##       using the [string range] command and then adding the
           31  +        ##       necessary offset to the resulting index manually
           32  +        ##       (below).  In Tcl, we could use the following instead:
           33  +        ##
           34  +        ##       set idx(next) [string first / $baseurl $idx(first+1)]
           35  +        ##
           36  +        set idx(nextRange) [string range $baseurl $idx(first+1) end]
           37  +        set idx(next) [string first / $idx(nextRange)]
           38  +        if {$idx(next) != -1} {
           39  +          ##
           40  +          ## NOTE: (part 2) Add the necessary offset to the result of
           41  +          ##       the search for the next slash (i.e. the one after
           42  +          ##       the initial search for the two slashes).
           43  +          ##
           44  +          set idx(next) [expr {$idx(next) + $idx(first+1)}]
           45  +          ##
           46  +          ## NOTE: Back up one character from the next slash.
           47  +          ##
           48  +          set idx(next-1) [expr {$idx(next) - 1}]
           49  +          ##
           50  +          ## NOTE: Extract the URI scheme and host from the base URL.
           51  +          ##
           52  +          set scheme [string range $baseurl 0 $idx(first)]
           53  +          set host [string range $baseurl $idx(first+1) $idx(next-1)]
           54  +          ##
           55  +          ## NOTE: Try to stay in SSL mode if we are there now.
           56  +          ##
           57  +          if {[string compare $scheme http:/] == 0} {
           58  +            set scheme http://
           59  +          } else {
           60  +            set scheme https://
           61  +          }
           62  +          set logourl $scheme$host/
           63  +        } else {
           64  +          set logourl $baseurl
           65  +        }
           66  +      } else {
           67  +        set logourl $baseurl
           68  +      }
           69  +      return $logourl
           70  +    }
           71  +    set logourl [getLogoUrl $baseurl]
           72  +    </th1>
           73  +    <a href="$logourl">
           74  +      <img src="$logo_image_url" border="0" alt="$project_name">
           75  +    </a>
           76  +  </div>
           77  +  <div class="title">$<title></div>
           78  +  <div class="status"><th1>
           79  +     if {[info exists login]} {
           80  +       puts "Logged in as $login"
           81  +     } else {
           82  +       puts "Not logged in"
           83  +     }
           84  +  </th1></nobr><small><div id="clock"></div></small></div>
           85  +</div>
           86  +<script>
           87  +function updateClock(){
           88  +  var e = document.getElementById("clock");
           89  +  if(e){
           90  +    var d = new Date();
           91  +    function f(n) {
           92  +      return n < 10 ? '0' + n : n;
           93  +    }
           94  +    e.innerHTML = d.getUTCFullYear()+ '-' +
           95  +      f(d.getUTCMonth() + 1) + '-' +
           96  +      f(d.getUTCDate())      + ' ' +
           97  +      f(d.getUTCHours())     + ':' +
           98  +      f(d.getUTCMinutes());
           99  +    setTimeout("updateClock();",(60-d.getUTCSeconds())*1000);
          100  +  }
          101  +}
          102  +updateClock();
          103  +</script>
          104  +<div class="mainmenu">
          105  +<th1>
          106  +html "<a href='$home$index_page'>Home</a>\n"
          107  +html "<a href='$home/help'>Help</a>\n"
          108  +if {[anycap jor]} {
          109  +  html "<a href='$home/timeline'>Timeline</a>\n"
          110  +}
          111  +if {[anoncap oh]} {
          112  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
          113  +}
          114  +if {[anoncap o]} {
          115  +  html "<a href='$home/brlist'>Branches</a>\n"
          116  +  html "<a href='$home/taglist'>Tags</a>\n"
          117  +}
          118  +if {[anoncap r]} {
          119  +  html "<a href='$home/ticket'>Tickets</a>\n"
          120  +}
          121  +if {[anoncap j]} {
          122  +  html "<a href='$home/wiki'>Wiki</a>\n"
          123  +}
          124  +if {[hascap s]} {
          125  +  html "<a href='$home/setup'>Admin</a>\n"
          126  +} elseif {[hascap a]} {
          127  +  html "<a href='$home/setup_ulist'>Users</a>\n"
          128  +}
          129  +if {[info exists login]} {
          130  +  html "<a href='$home/login'>Logout</a>\n"
          131  +} else {
          132  +  html "<a href='$home/login'>Login</a>\n"
          133  +}
          134  +</th1></div>

Added skins/etienne1/README.md.

            1  +This skin was contributed by Étienne Deparis.

Added skins/etienne1/css.txt.

            1  +body {
            2  +    margin: 0 auto;
            3  +    min-width: 800px;
            4  +    padding: 0px 20px;
            5  +    background-color: white;
            6  +    font-family: sans-serif;
            7  +    font-size:14pt;
            8  +    -moz-text-size-adjust: none;
            9  +    -webkit-text-size-adjust: none;
           10  +    -mx-text-size-adjust: none;
           11  +}
           12  +
           13  +a {
           14  +    color: #4183C4;
           15  +    text-decoration: none;
           16  +}
           17  +a:hover {
           18  +    color: #4183C4;
           19  +    text-decoration: underline;
           20  +}
           21  +
           22  +hr {
           23  +    color: #eee;
           24  +}
           25  +
           26  +.title {
           27  +    color: #4183C4;
           28  +    float:left;
           29  +    padding-top: 30px;
           30  +    padding-bottom: 10px;
           31  +}
           32  +.title h1 {
           33  +    display:inline;
           34  +}
           35  +.title h1:after {
           36  +    content: " / ";
           37  +    color: #777;
           38  +    font-weight: normal;
           39  +}
           40  +
           41  +.content h1 {
           42  +    font-size: 1.25em;
           43  +}
           44  +.content h2 {
           45  +    font-size: 1.15em;
           46  +}
           47  +.content h2 {
           48  +    font-size: 1.05em;
           49  +    font-weight: bold;
           50  +}
           51  +
           52  +.section {
           53  +    font-size: 1em;
           54  +    font-weight: bold;
           55  +    background-color: #f5f5f5;
           56  +    border: 1px solid #d8d8d8;
           57  +    border-radius: 3px 3px 0 0;
           58  +    padding: 9px 10px 10px;
           59  +    margin: 10px 0;
           60  +}
           61  +
           62  +.sectionmenu {
           63  +    border: 1px solid #d8d8d8;
           64  +    border-radius: 0 0 3px 3px;
           65  +    border-top: 0;
           66  +    margin-top: -10px;
           67  +    margin-bottom: 10px;
           68  +    padding: 10px;
           69  +}
           70  +.sectionmenu a {
           71  +    display: inline-block;
           72  +    margin-right: 1em;
           73  +}
           74  +
           75  +.status {
           76  +    float:right;
           77  +    font-size:.7em;
           78  +    padding-top:50px;
           79  +}
           80  +
           81  +.mainmenu {
           82  +    font-size:.8em;
           83  +    clear:both;
           84  +    padding:10px;
           85  +    background:#eaeaea linear-gradient(#fafafa, #eaeaea) repeat-x;
           86  +    border:1px solid #eaeaea;
           87  +    border-radius:5px;
           88  +}
           89  +
           90  +.mainmenu a {
           91  +    padding: 10px 20px;
           92  +    text-decoration:none;
           93  +    color: #777;
           94  +    border-right:1px solid #eaeaea;
           95  +}
           96  +.mainmenu a.active,
           97  +.mainmenu a:hover {
           98  +    color: #000;
           99  +    border-bottom:2px solid #D26911;
          100  +}
          101  +
          102  +.submenu {
          103  +    font-size: .7em;
          104  +    margin-top: 10px;
          105  +    padding: 10px;
          106  +    border-bottom: 1px solid #ccc;
          107  +}
          108  +
          109  +.submenu a {
          110  +    padding: 10px 11px;
          111  +    text-decoration:none;
          112  +    color: #777;
          113  +}
          114  +
          115  +.submenu a:hover {
          116  +    padding: 6px 10px;
          117  +    border: 1px solid #ccc;
          118  +    border-radius: 5px;
          119  +    color: #000;
          120  +}
          121  +
          122  +.content {
          123  +    padding-top: 10px;
          124  +    font-size:.8em;
          125  +    color: #444;
          126  +}
          127  +
          128  +.udiff, .sbsdiff {
          129  +    font-size: .85em !important;
          130  +    overflow: auto;
          131  +    border: 1px solid #ccc;
          132  +    border-radius: 5px;
          133  +}
          134  +.content blockquote {
          135  +    padding: 0 15px;
          136  +}
          137  +
          138  +table.report {
          139  +    cursor: auto;
          140  +    border-radius: 5px;
          141  +    border: 1px solid #ccc;
          142  +    margin: 1em 0;
          143  +}
          144  +.report td, .report th {
          145  +   border: 0;
          146  +   font-size: .8em;
          147  +   padding: 10px;
          148  +}
          149  +.report td:first-child {
          150  +    border-top-left-radius: 5px;
          151  +}
          152  +.report tbody tr:last-child td:first-child {
          153  +    border-bottom-left-radius: 5px;
          154  +}
          155  +.report td:last-child {
          156  +    border-top-right-radius: 5px;
          157  +}
          158  +.report tbody tr:last-child {
          159  +    border-bottom-left-radius: 5px;
          160  +    border-bottom-right-radius: 5px;
          161  +}
          162  +.report tbody tr:last-child td:last-child {
          163  +    border-bottom-right-radius: 5px;
          164  +}
          165  +.report th {
          166  +    cursor: pointer;
          167  +}
          168  +.report thead+tbody tr:hover {
          169  +    background-color: #f5f9fc !important;
          170  +}
          171  +
          172  +td.tktDspLabel {
          173  +    width: 70px;
          174  +    text-align: right;
          175  +    overflow: hidden;
          176  +}
          177  +td.tktDspValue {
          178  +    text-align: left;
          179  +    vertical-align: top;
          180  +    background-color: #f8f8f8;
          181  +    border: 1px solid #ccc;
          182  +}
          183  +td.tktDspValue pre {
          184  +    white-space: pre-wrap;
          185  +}
          186  +
          187  +.footer {
          188  +    border-top: 1px solid #ccc;
          189  +    padding: 10px;
          190  +    font-size:.7em;
          191  +    margin-top: 10px;
          192  +    color: #ccc;
          193  +}
          194  +div.timelineDate {
          195  +    font-weight: bold;
          196  +    white-space: nowrap;
          197  +}
          198  +span.submenuctrl, span.submenuctrl input, select.submenuctrl {
          199  +  color: #777;
          200  +}

Added skins/etienne1/footer.txt.

            1  +<div class="footer">
            2  +This page was generated in about
            3  +<th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s by
            4  +Fossil version $manifest_version $manifest_date
            5  +</div>
            6  +</body></html>

Added skins/etienne1/header.txt.

            1  +<html>
            2  +  <head>
            3  +    <base href="$baseurl/$current_page" />
            4  +    <title>$<project_name>: $<title></title>
            5  +      <link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +            href="$home/timeline.rss" />
            7  +      <link rel="stylesheet" href="$home/style.css?default" type="text/css"
            8  +            media="screen" />
            9  +  </head>
           10  +
           11  +  <body>
           12  +    <div class="header">
           13  +      <div class="title"><h1>$<project_name></h1>$<title></div>
           14  +        <div class="status"><th1>
           15  +     if {[info exists login]} {
           16  +       html "$login — <a href='$home/login'>Logout</a>\n"
           17  +     } else {
           18  +       html "<a href='$home/login'>Login</a>\n"
           19  +     }
           20  +        </th1></div>
           21  +    </div>
           22  +
           23  +    <div class="mainmenu">
           24  +<th1>
           25  +proc menulink {url name} {
           26  +  upvar current_page current
           27  +  upvar home home
           28  +  if {[string range $url 0 [string length $current]] eq "/$current"} {
           29  +    html "<a href='$home$url' class='active'>$name</a>\n"
           30  +  } else {
           31  +    html "<a href='$home$url'>$name</a>\n"
           32  +  }
           33  +}
           34  +menulink $index_page Home
           35  +if {[anycap jor]} {
           36  +  menulink /timeline Timeline
           37  +}
           38  +if {[hascap oh]} {
           39  +  menulink /dir?ci=tip Files
           40  +}
           41  +if {[hascap o]} {
           42  +  menulink  /brlist Branches
           43  +  menulink  /taglist Tags
           44  +}
           45  +if {[hascap r]} {
           46  +  menulink /ticket Tickets
           47  +}
           48  +if {[hascap j]} {
           49  +  menulink /wiki Wiki
           50  +}
           51  +if {[hascap s]} {
           52  +  menulink /setup Admin
           53  +} elseif {[hascap a]} {
           54  +  menulink /setup_ulist Users
           55  +}
           56  +</th1></div>

Added skins/khaki/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +  margin: 0ex 0ex;
            4  +  padding: 0px;
            5  +  background-color: #fef3bc;
            6  +  font-family: sans-serif;
            7  +  -moz-text-size-adjust: none;
            8  +  -webkit-text-size-adjust: none;
            9  +  -mx-text-size-adjust: none;
           10  +}
           11  +
           12  +/* The project logo in the upper left-hand corner of each page */
           13  +div.logo {
           14  +  display: inline;
           15  +  text-align: center;
           16  +  vertical-align: bottom;
           17  +  font-weight: bold;
           18  +  font-size: 2.5em;
           19  +  color: #a09048;
           20  +  white-space: nowrap;
           21  +}
           22  +
           23  +/* The page title centered at the top of each page */
           24  +div.title {
           25  +  display: table-cell;
           26  +  font-size: 2em;
           27  +  font-weight: bold;
           28  +  text-align: left;
           29  +  padding: 0 0 0 5px;
           30  +  color: #a09048;
           31  +  vertical-align: bottom;
           32  +  width: 100%;
           33  +}
           34  +
           35  +/* The login status message in the top right-hand corner */
           36  +div.status {
           37  +  display: table-cell;
           38  +  text-align: right;
           39  +  vertical-align: bottom;
           40  +  color: #a09048;
           41  +  padding: 5px 5px 0 0;
           42  +  font-size: 0.8em;
           43  +  font-weight: bold;
           44  +  white-space: nowrap;
           45  +}
           46  +
           47  +/* The header across the top of the page */
           48  +div.header {
           49  +  display: table;
           50  +  width: 100%;
           51  +}
           52  +
           53  +/* The main menu bar that appears at the top of the page beneath
           54  +** the header */
           55  +div.mainmenu {
           56  +  padding: 5px 10px 5px 10px;
           57  +  font-size: 0.9em;
           58  +  font-weight: bold;
           59  +  text-align: center;
           60  +  letter-spacing: 1px;
           61  +  background-color: #a09048;
           62  +  color: black;
           63  +}
           64  +
           65  +/* The submenu bar that *sometimes* appears below the main menu */
           66  +div.submenu, div.sectionmenu {
           67  +  padding: 3px 10px 3px 0px;
           68  +  font-size: 0.9em;
           69  +  text-align: center;
           70  +  background-color: #c0af58;
           71  +  color: white;
           72  +}
           73  +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
           74  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           75  +  padding: 3px 10px 3px 10px;
           76  +  color: white;
           77  +  text-decoration: none;
           78  +}
           79  +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
           80  +  color: #a09048;
           81  +  background-color: white;
           82  +}
           83  +
           84  +/* All page content from the bottom of the menu or submenu down to
           85  +** the footer */
           86  +div.content {
           87  +  padding: 1ex 5px;
           88  +}
           89  +div.content a { color: #706532; }
           90  +div.content a:link { color: #706532; }
           91  +div.content a:visited { color: #704032; }
           92  +div.content a:hover { background-color: white; color: #706532; }
           93  +
           94  +/* Some pages have section dividers */
           95  +div.section {
           96  +  margin-bottom: 0px;
           97  +  margin-top: 1em;
           98  +  padding: 3px 3px 0 3px;
           99  +  font-size: 1.2em;
          100  +  font-weight: bold;
          101  +  background-color: #a09048;
          102  +  color: white;
          103  +  white-space: nowrap;
          104  +}
          105  +
          106  +/* The "Date" that occurs on the left hand side of timelines */
          107  +div.divider {
          108  +  background: #e1d498;
          109  +  border: 2px #a09048 solid;
          110  +  font-size: 1em; font-weight: normal;
          111  +  padding: .25em;
          112  +  margin: .2em 0 .2em 0;
          113  +  float: left;
          114  +  clear: left;
          115  +  white-space: nowrap;
          116  +}
          117  +
          118  +/* The footer at the very bottom of the page */
          119  +div.footer {
          120  +  font-size: 0.8em;
          121  +  margin-top: 12px;
          122  +  padding: 5px 10px 5px 10px;
          123  +  text-align: right;
          124  +  background-color: #a09048;
          125  +  color: white;
          126  +}
          127  +
          128  +/* Hyperlink colors */
          129  +div.footer a { color: white; }
          130  +div.footer a:link { color: white; }
          131  +div.footer a:visited { color: white; }
          132  +div.footer a:hover { background-color: white; color: #558195; }
          133  +
          134  +/* <verbatim> blocks */
          135  +pre.verbatim {
          136  +  background-color: #f5f5f5;
          137  +  padding: 0.5em;
          138  +  white-space: pre-wrap;
          139  +}
          140  +
          141  +/* The label/value pairs on (for example) the ci page */
          142  +table.label-value th {
          143  +  vertical-align: top;
          144  +  text-align: right;
          145  +  padding: 0.2ex 2ex;
          146  +}

Added skins/khaki/footer.txt.

            1  +<div class="footer">
            2  +Fossil version $manifest_version $manifest_date
            3  +</div>
            4  +</body></html>

Added skins/khaki/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss">
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen">
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="title">$<title></div>
           13  +  <div class="status">
           14  +    <div class="logo">$<project_name></div><br/>
           15  +    <th1>
           16  +     if {[info exists login]} {
           17  +       puts "Logged in as $login"
           18  +     } else {
           19  +       puts "Not logged in"
           20  +     }
           21  +  </th1></div>
           22  +</div>
           23  +<div class="mainmenu">
           24  +<th1>
           25  +html "<a href='$home$index_page'>Home</a>\n"
           26  +if {[anycap jor]} {
           27  +  html "<a href='$home/timeline'>Timeline</a>\n"
           28  +}
           29  +if {[anoncap oh]} {
           30  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
           31  +}
           32  +if {[anoncap o]} {
           33  +  html "<a href='$home/brlist'>Branches</a>\n"
           34  +  html "<a href='$home/taglist'>Tags</a>\n"
           35  +}
           36  +if {[anoncap r]} {
           37  +  html "<a href='$home/ticket'>Tickets</a>\n"
           38  +}
           39  +if {[anoncap j]} {
           40  +  html "<a href='$home/wiki'>Wiki</a>\n"
           41  +}
           42  +if {[hascap s]} {
           43  +  html "<a href='$home/setup'>Admin</a>\n"
           44  +} elseif {[hascap a]} {
           45  +  html "<a href='$home/setup_ulist'>Users</a>\n"
           46  +}
           47  +if {[info exists login]} {
           48  +  html "<a href='$home/login'>Logout</a>\n"
           49  +} else {
           50  +  html "<a href='$home/login'>Login</a>\n"
           51  +}
           52  +</th1></div>

Added skins/plain_gray/css.txt.

            1  +/* General settings for the entire page */
            2  +body {
            3  +  margin: 0ex 1ex;
            4  +  padding: 0px;
            5  +  background-color: white;
            6  +  font-family: sans-serif;
            7  +  -moz-text-size-adjust: none;
            8  +  -webkit-text-size-adjust: none;
            9  +  -mx-text-size-adjust: none;
           10  +}
           11  +
           12  +/* The project logo in the upper left-hand corner of each page */
           13  +div.logo {
           14  +  display: table-row;
           15  +  text-align: center;
           16  +  /* vertical-align: bottom;*/
           17  +  font-size: 2em;
           18  +  font-weight: bold;
           19  +  background-color: #707070;
           20  +  color: #ffffff;
           21  +  min-width: 200px;
           22  +  white-space: nowrap;
           23  +}
           24  +
           25  +/* The page title centered at the top of each page */
           26  +div.title {
           27  +  display: table-cell;
           28  +  font-size: 1.5em;
           29  +  font-weight: bold;
           30  +  text-align: center;
           31  +  padding: 0 0 0 10px;
           32  +  color: #404040;
           33  +  vertical-align: bottom;
           34  +  width: 100%;
           35  +}
           36  +
           37  +/* The login status message in the top right-hand corner */
           38  +div.status {
           39  +  display: table-cell;
           40  +  text-align: right;
           41  +  vertical-align: bottom;
           42  +  color: #404040;
           43  +  font-size: 0.8em;
           44  +  font-weight: bold;
           45  +  min-width: 200px;
           46  +  white-space: nowrap;
           47  +}
           48  +
           49  +/* The header across the top of the page */
           50  +div.header {
           51  +  display: table;
           52  +  width: 100%;
           53  +}
           54  +
           55  +/* The main menu bar that appears at the top of the page beneath
           56  +** the header */
           57  +div.mainmenu {
           58  +  padding: 5px 10px 5px 10px;
           59  +  font-size: 0.9em;
           60  +  font-weight: bold;
           61  +  text-align: center;
           62  +  letter-spacing: 1px;
           63  +  background-color: #404040;
           64  +  color: white;
           65  +}
           66  +
           67  +/* The submenu bar that *sometimes* appears below the main menu */
           68  +div.submenu, div.sectionmenu {
           69  +  padding: 3px 10px 3px 0px;
           70  +  font-size: 0.9em;
           71  +  text-align: center;
           72  +  background-color: #606060;
           73  +  color: white;
           74  +}
           75  +div.mainmenu a, div.mainmenu a:visited, div.submenu a, div.submenu a:visited,
           76  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           77  +  padding: 3px 10px 3px 10px;
           78  +  color: white;
           79  +  text-decoration: none;
           80  +}
           81  +div.mainmenu a:hover, div.submenu a:hover, div.sectionmenu>a.button:hover {
           82  +  color: #404040;
           83  +  background-color: white;
           84  +}
           85  +
           86  +/* All page content from the bottom of the menu or submenu down to
           87  +** the footer */
           88  +div.content {
           89  +  padding: 0ex 0ex 0ex 0ex;
           90  +}
           91  +/* Hyperlink colors */
           92  +div.content a { color: #604000; }
           93  +div.content a:link { color: #604000;}
           94  +div.content a:visited { color: #600000; }
           95  +
           96  +/* <verbatim> blocks */
           97  +pre.verbatim {
           98  +  background-color: #ffffff;
           99  +  padding: 0.5em;
          100  +  white-space: pre-wrap;
          101  +}
          102  +
          103  +/* Some pages have section dividers */
          104  +div.section {
          105  +  margin-bottom: 0px;
          106  +  margin-top: 1em;
          107  +  padding: 1px 1px 1px 1px;
          108  +  font-size: 1.2em;
          109  +  font-weight: bold;
          110  +  background-color: #404040;
          111  +  color: white;
          112  +  white-space: nowrap;
          113  +}
          114  +
          115  +/* The "Date" that occurs on the left hand side of timelines */
          116  +div.divider {
          117  +  background: #a0a0a0;
          118  +  border: 2px #505050 solid;
          119  +  font-size: 1em; font-weight: normal;
          120  +  padding: .25em;
          121  +  margin: .2em 0 .2em 0;
          122  +  float: left;
          123  +  clear: left;
          124  +  white-space: nowrap;
          125  +}
          126  +
          127  +/* The footer at the very bottom of the page */
          128  +div.footer {
          129  +  font-size: 0.8em;
          130  +  margin-top: 12px;
          131  +  padding: 5px 10px 5px 10px;
          132  +  text-align: right;
          133  +  background-color: #404040;
          134  +  color: white;
          135  +}
          136  +
          137  +/* The label/value pairs on (for example) the vinfo page */
          138  +table.label-value th {
          139  +  vertical-align: top;
          140  +  text-align: right;
          141  +  padding: 0.2ex 2ex;
          142  +}

Added skins/plain_gray/footer.txt.

            1  +<div class="footer">
            2  +Fossil version $manifest_version $manifest_date
            3  +</div>
            4  +</body></html>

Added skins/plain_gray/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss">
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen">
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="title"><small>$<project_name></small><br />$<title></div>
           13  +  <div class="status"><th1>
           14  +     if {[info exists login]} {
           15  +       puts "Logged in as $login"
           16  +     } else {
           17  +       puts "Not logged in"
           18  +     }
           19  +  </th1></div>
           20  +</div>
           21  +<div class="mainmenu">
           22  +<th1>
           23  +html "<a href='$home$index_page'>Home</a>\n"
           24  +if {[anycap jor]} {
           25  +  html "<a href='$home/timeline'>Timeline</a>\n"
           26  +}
           27  +if {[anoncap oh]} {
           28  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
           29  +}
           30  +if {[anoncap o]} {
           31  +  html "<a href='$home/brlist'>Branches</a>\n"
           32  +  html "<a href='$home/taglist'>Tags</a>\n"
           33  +}
           34  +if {[anoncap r]} {
           35  +  html "<a href='$home/ticket'>Tickets</a>\n"
           36  +}
           37  +if {[anoncap j]} {
           38  +  html "<a href='$home/wiki'>Wiki</a>\n"
           39  +}
           40  +if {[hascap s]} {
           41  +  html "<a href='$home/setup'>Admin</a>\n"
           42  +} elseif {[hascap a]} {
           43  +  html "<a href='$home/setup_ulist'>Users</a>\n"
           44  +}
           45  +if {[info exists login]} {
           46  +  html "<a href='$home/login'>Logout</a>\n"
           47  +} else {
           48  +  html "<a href='$home/login'>Login</a>\n"
           49  +}
           50  +</th1></div>

Added skins/rounded1/css.txt.

            1  +/* General settings for the entire page */
            2  +html {
            3  +  min-height: 100%;
            4  +}
            5  +body {
            6  +  margin: 0ex 1ex;
            7  +  padding: 0px;
            8  +  background-color: white;
            9  +  color: #333;
           10  +  font-family: Verdana, sans-serif;
           11  +  font-size: 0.8em;
           12  +  -moz-text-size-adjust: none;
           13  +  -webkit-text-size-adjust: none;
           14  +  -mx-text-size-adjust: none;
           15  +}
           16  +
           17  +/* The project logo in the upper left-hand corner of each page */
           18  +div.logo {
           19  +  display: table-cell;
           20  +  text-align: right;
           21  +  vertical-align: bottom;
           22  +  font-weight: normal;
           23  +  white-space: nowrap;
           24  +}
           25  +
           26  +/* Widths */
           27  +div.header, div.mainmenu, div.submenu, div.content, div.footer {
           28  +  max-width: 900px;
           29  +  margin: auto;
           30  +  padding: 3px 20px 3px 20px;
           31  +  clear: both;
           32  +}
           33  +
           34  +/* The page title at the top of each page */
           35  +div.title {
           36  +  display: table-cell;
           37  +  padding-left: 10px;
           38  +  font-size: 2em;
           39  +  margin: 10px 0 10px -20px;
           40  +  vertical-align: bottom;
           41  +  text-align: left;
           42  +  width: 80%;
           43  +  font-family: Verdana, sans-serif;
           44  +  font-weight: bold;
           45  +  color: #558195;
           46  +  text-shadow: 0px 2px 2px #999999;
           47  +}
           48  +
           49  +/* The login status message in the top right-hand corner */
           50  +div.status {
           51  +  display: table-cell;
           52  +  text-align: right;
           53  +  vertical-align: bottom;
           54  +  color: #333;
           55  +  margin-right: -20px;
           56  +  white-space: nowrap;
           57  +}
           58  +
           59  +/* The main menu bar that appears at the top of the page beneath
           60  + ** the header */
           61  +div.mainmenu {
           62  +  text-align: center;
           63  +  color: white;
           64  +  border-top-left-radius: 5px;
           65  +  border-top-right-radius: 5px;
           66  +  vertical-align: middle;
           67  +  padding-top: 8px;
           68  +  padding-bottom: 8px;
           69  +  background-color: #446979;
           70  +  box-shadow: 0px 3px 4px #333333;
           71  +}
           72  +
           73  +/* The submenu bar that *sometimes* appears below the main menu */
           74  +div.submenu {
           75  +  padding-top:10px;
           76  +  padding-bottom:0;
           77  +  text-align: right;
           78  +  color: #000;
           79  +  background-color: #fff;
           80  +  height: 1.5em;
           81  +  vertical-align:middle;
           82  +  box-shadow: 0px 3px 4px #999;
           83  +}
           84  +div.mainmenu a, div.mainmenu a:visited {
           85  +  padding: 3px 10px 3px 10px;
           86  +  color: white;
           87  +  text-decoration: none;
           88  +}
           89  +div.submenu a, div.submenu a:visited, a.button,
           90  +div.sectionmenu>a.button:link, div.sectionmenu>a.button:visited {
           91  +  padding: 2px 8px;
           92  +  color: #000;
           93  +  font-family: Arial;
           94  +  text-decoration: none;
           95  +  margin:auto;
           96  +  border-radius: 5px;
           97  +  background-color: #e0e0e0;
           98  +  text-shadow: 0px -1px 0px #eee;
           99  +  border: 1px solid #000;
          100  +}
          101  +
          102  +div.mainmenu a:hover {
          103  +  color: #000;
          104  +  background-color: white;
          105  +}
          106  +
          107  +div.submenu a:hover, div.sectionmenu>a.button:hover {
          108  +  background-color: #c0c0c0;
          109  +}
          110  +
          111  +/* All page content from the bottom of the menu or submenu down to
          112  + ** the footer */
          113  +div.content {
          114  +  background-color: #fff;
          115  +  box-shadow: 0px 3px 4px #999;
          116  +  border-bottom-right-radius: 5px;
          117  +  border-bottom-left-radius: 5px;
          118  +  padding-bottom: 1em;
          119  +  min-height:40%;
          120  +}
          121  +
          122  +
          123  +/* Some pages have section dividers */
          124  +div.section {
          125  +  margin-bottom: 0.5em;
          126  +  margin-top: 1em;
          127  +  margin-right: auto;
          128  +  padding: 1px 1px 1px 1px;
          129  +  font-size: 1.2em;
          130  +  font-weight: bold;
          131  +  text-align: center;
          132  +  color: white;
          133  +  border-radius: 5px;
          134  +  background-color: #446979;
          135  +  box-shadow: 0px 3px 4px #333333;
          136  +  white-space: nowrap;
          137  +}
          138  +
          139  +/* The "Date" that occurs on the left hand side of timelines */
          140  +div.divider {
          141  +  font-size: 1.2em;
          142  +  font-family: Georgia, serif;
          143  +  font-weight: bold;
          144  +  margin-top: 1em;
          145  +  white-space: nowrap;
          146  +}
          147  +
          148  +/* The footer at the very bottom of the page */
          149  +div.footer {
          150  +  font-size: 0.9em;
          151  +  text-align: right;
          152  +  margin-bottom: 1em;
          153  +  color: #666;
          154  +}
          155  +
          156  +/* Hyperlink colors in the footer */
          157  +div.footer a { color: white; }
          158  +div.footer a:link { color: white; }
          159  +div.footer a:visited { color: white; }
          160  +div.footer a:hover { background-color: white; color: #558195; }
          161  +
          162  +/* <verbatim> blocks */
          163  +pre.verbatim, blockquote pre {
          164  +  font-family: Dejavu Sans Mono, Monaco, Lucida Console, monospace;
          165  +  background-color: #f3f3f3;
          166  +  padding: 0.5em;
          167  +  white-space: pre-wrap;
          168  +}
          169  +
          170  +blockquote pre {
          171  +  border: 1px #000 dashed;
          172  +}
          173  +
          174  +/* The label/value pairs on (for example) the ci page */
          175  +table.label-value th {
          176  +  vertical-align: top;
          177  +  text-align: right;
          178  +  padding: 0.2ex 2ex;
          179  +}
          180  +
          181  +table.report tr th {
          182  +  padding: 3px 5px;
          183  +  text-transform: capitalize;
          184  +  cursor: pointer;
          185  +}
          186  +
          187  +table.report tr td {
          188  +  padding: 3px 5px;
          189  +}
          190  +
          191  +textarea {
          192  +  font-size: 1em;
          193  +}
          194  +
          195  +.fullsize-text {
          196  +  font-size: 1.25em;
          197  +}

Added skins/rounded1/footer.txt.

            1  +<div class="footer">
            2  +Fossil version $manifest_version $manifest_date
            3  +</div>
            4  +</body></html>

Added skins/rounded1/header.txt.

            1  +<html>
            2  +<head>
            3  +<base href="$baseurl/$current_page" />
            4  +<title>$<project_name>: $<title></title>
            5  +<link rel="alternate" type="application/rss+xml" title="RSS Feed"
            6  +      href="$home/timeline.rss">
            7  +<link rel="stylesheet" href="$stylesheet_url" type="text/css"
            8  +      media="screen">
            9  +</head>
           10  +<body>
           11  +<div class="header">
           12  +  <div class="logo">
           13  +    <img src="$logo_image_url" alt="logo">
           14  +    <br />$<project_name>
           15  +  </div>
           16  +  <div class="title">$<title></div>
           17  +  <div class="status"><th1>
           18  +     if {[info exists login]} {
           19  +       puts "Logged in as $login"
           20  +     } else {
           21  +       puts "Not logged in"
           22  +     }
           23  +  </th1></div>
           24  +</div>
           25  +<div class="mainmenu">
           26  +<th1>
           27  +html "<a href='$home$index_page'>Home</a>\n"
           28  +if {[anycap jor]} {
           29  +  html "<a href='$home/timeline'>Timeline</a>\n"
           30  +}
           31  +if {[anoncap oh]} {
           32  +  html "<a href='$home/tree?ci=tip'>Files</a>\n"
           33  +}
           34  +if {[anoncap o]} {
           35  +  html "<a href='$home/brlist'>Branches</a>\n"
           36  +  html "<a href='$home/taglist'>Tags</a>\n"
           37  +}
           38  +if {[anoncap r]} {
           39  +  html "<a href='$home/ticket'>Tickets</a>\n"
           40  +}
           41  +if {[anoncap j]} {
           42  +  html "<a href='$home/wiki'>Wiki</a>\n"
           43  +}
           44  +if {[hascap s]} {
           45  +  html "<a href='$home/setup'>Admin</a>\n"
           46  +} elseif {[hascap a]} {
           47  +  html "<a href='$home/setup_ulist'>Users</a>\n"
           48  +}
           49  +if {[info exists login]} {
           50  +  html "<a href='$home/login'>Logout</a>\n"
           51  +} else {
           52  +  html "<a href='$home/login'>Login</a>\n"
           53  +}
           54  +</th1></div>

Changes to src/add.c.

   151    151     }
   152    152     if( db_exists("SELECT 1 FROM vfile"
   153    153                   " WHERE pathname=%Q %s", zPath, filename_collation()) ){
   154    154       db_multi_exec("UPDATE vfile SET deleted=0"
   155    155                     " WHERE pathname=%Q %s", zPath, filename_collation());
   156    156     }else{
   157    157       char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
          158  +    int isExe = file_wd_isexe(zFullname);
   158    159       db_multi_exec(
   159    160         "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)"
   160    161         "VALUES(%d,0,0,0,%Q,%d,%d)",
   161         -      vid, zPath, file_wd_isexe(zFullname), file_wd_islink(zFullname));
          162  +      vid, zPath, isExe, file_wd_islink(0));
   162    163       fossil_free(zFullname);
   163    164     }
   164    165     if( db_changes() ){
   165    166       fossil_print("ADDED  %s\n", zPath);
   166    167       return 1;
   167    168     }else{
   168    169       fossil_print("SKIP   %s\n", zPath);
................................................................................
   234    235   **
   235    236   ** The --case-sensitive option determines whether or not filenames should
   236    237   ** be treated case sensitive or not. If the option is not given, the default
   237    238   ** depends on the global setting, or the operating system default, if not set.
   238    239   **
   239    240   ** Options:
   240    241   **
   241         -**    --case-sensitive <BOOL> override case-sensitive setting
          242  +**    --case-sensitive <BOOL> Override the case-sensitive setting.
   242    243   **    --dotfiles              include files beginning with a dot (".")
   243    244   **    -f|--force              Add files without prompting
   244         -**    --ignore <CSG>          ignore files matching patterns from the
          245  +**    --ignore <CSG>          Ignore files matching patterns from the
   245    246   **                            comma separated list of glob patterns.
   246         -**    --clean <CSG>           also ignore files matching patterns from
          247  +**    --clean <CSG>           Also ignore files matching patterns from
   247    248   **                            the comma separated list of glob patterns.
   248    249   **
   249    250   ** See also: addremove, rm
   250    251   */
   251    252   void add_cmd(void){
   252    253     int i;                     /* Loop counter */
   253    254     int vid;                   /* Currently checked out version */
................................................................................
   342    343   ** Remove one or more files or directories from the repository.
   343    344   **
   344    345   ** This command does NOT remove the files from disk.  It just marks the
   345    346   ** files as no longer being part of the project.  In other words, future
   346    347   ** changes to the named files will not be versioned.
   347    348   **
   348    349   ** Options:
   349         -**   --case-sensitive <BOOL> override case-sensitive setting
          350  +**   --case-sensitive <BOOL> Override the case-sensitive setting.
   350    351   **
   351    352   ** See also: addremove, add
   352    353   */
   353    354   void delete_cmd(void){
   354    355     int i;
   355    356     Stmt loop;
   356    357   
................................................................................
   487    488   ** the --dotfiles option is used.
   488    489   **
   489    490   ** The --ignore option overrides the "ignore-glob" setting, as do the
   490    491   ** --case-sensitive option with the "case-sensitive" setting and the
   491    492   ** --clean option with the "clean-glob" setting. See the documentation
   492    493   ** on the "settings" command for further information.
   493    494   **
   494         -** The -n|--dry-run option shows what would happen without actually doing anything.
          495  +** The -n|--dry-run option shows what would happen without actually doing
          496  +** anything.
   495    497   **
   496    498   ** This command can be used to track third party software.
   497    499   **
   498    500   ** Options:
   499         -**   --case-sensitive <BOOL> override case-sensitive setting
   500         -**   --dotfiles              include files beginning with a dot (".")
   501         -**   --ignore <CSG>          ignore files matching patterns from the
          501  +**   --case-sensitive <BOOL> Override the case-sensitive setting.
          502  +**   --dotfiles              Include files beginning with a dot (".")
          503  +**   --ignore <CSG>          Ignore files matching patterns from the
   502    504   **                           comma separated list of glob patterns.
   503         -**   --clean <CSG>           also ignore files matching patterns from
          505  +**   --clean <CSG>           Also ignore files matching patterns from
   504    506   **                           the comma separated list of glob patterns.
   505         -**   -n|--dry-run            If given, display instead of run actions
          507  +**   -n|--dry-run            If given, display instead of run actions.
   506    508   **
   507    509   ** See also: add, rm
   508    510   */
   509    511   void addremove_cmd(void){
   510    512     Blob path;
   511    513     const char *zCleanFlag = find_option("clean",0,1);
   512    514     const char *zIgnoreFlag = find_option("ignore",0,1);
................................................................................
   584    586   
   585    587   
   586    588   /*
   587    589   ** Rename a single file.
   588    590   **
   589    591   ** The original name of the file is zOrig.  The new filename is zNew.
   590    592   */
   591         -static void mv_one_file(int vid, const char *zOrig, const char *zNew){
          593  +static void mv_one_file(
          594  +  int vid,
          595  +  const char *zOrig,
          596  +  const char *zNew
          597  +){
   592    598     int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s",
   593    599                            zNew, filename_collation());
   594    600     if( x>=0 ){
   595    601       if( x==0 ){
   596    602         fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'"
   597    603                      " is currently under management", zOrig, zNew, zNew);
   598    604       }else{
................................................................................
   618    624   ** You can either rename a file or directory or move it to another subdirectory.
   619    625   **
   620    626   ** This command does NOT rename or move the files on disk.  This command merely
   621    627   ** records the fact that filenames have changed so that appropriate notations
   622    628   ** can be made at the next commit/checkin.
   623    629   **
   624    630   ** Options:
   625         -**   --case-sensitive <BOOL> override case-sensitive setting
          631  +**   --case-sensitive <BOOL> Override the case-sensitive setting.
   626    632   **
   627    633   ** See also: changes, status
   628    634   */
   629    635   void mv_cmd(void){
   630    636     int i;
   631    637     int vid;
   632    638     char *zDest;

Changes to src/allrepo.c.

   103    103   **
   104    104   **    dbstat     Run the "dbstat" command on all repositories.
   105    105   **
   106    106   **    extras     Shows "extra" files from all local checkouts.  The command
   107    107   **               line options supported by the extra command itself, if any
   108    108   **               are present, are passed along verbatim.
   109    109   **
   110         -**    ignore     Arguments are repositories that should be ignored by
   111         -**               subsequent clean, extras, list, pull, push, rebuild, and
   112         -**               sync operations.  The -c|--ckout option causes the listed
   113         -**               local checkouts to be ignored instead.
   114         -**
   115    110   **    info       Run the "info" command on all repositories.
   116    111   **
   117         -**    list | ls  Display the location of all repositories.  The -c|--ckout
   118         -**               option causes all local checkouts to be listed instead.
   119         -**
   120    112   **    pull       Run a "pull" operation on all repositories.  Only the
   121    113   **               --verbose option is supported.
   122    114   **
   123    115   **    push       Run a "push" on all repositories.  Only the --verbose
   124    116   **               option is supported.
   125    117   **
   126    118   **    rebuild    Rebuild on all repositories.  The command line options
................................................................................
   132    124   **               option is supported.
   133    125   **
   134    126   **    setting    Run the "setting", "set", or "unset" commands on all
   135    127   **    set        repositories.  These command are particularly useful in
   136    128   **    unset      conjunction with the "max-loadavg" setting which cannot
   137    129   **               otherwise be set globally.
   138    130   **
          131  +** In addition, the following maintenance operations are supported:
          132  +**
          133  +**    add        Add all the repositories named to the set of repositories
          134  +**               tracked by Fossil.  Normally Fossil is able to keep up with
          135  +**               this list by itself, but sometime it can benefit from this
          136  +**               hint if you rename repositories.
          137  +**
          138  +**    ignore     Arguments are repositories that should be ignored by
          139  +**               subsequent clean, extras, list, pull, push, rebuild, and
          140  +**               sync operations.  The -c|--ckout option causes the listed
          141  +**               local checkouts to be ignored instead.
          142  +**
          143  +**    list | ls  Display the location of all repositories.  The -c|--ckout
          144  +**               option causes all local checkouts to be listed instead.
          145  +**
   139    146   ** Repositories are automatically added to the set of known repositories
   140    147   ** when one of the following commands are run against the repository:
   141    148   ** clone, info, pull, push, or sync.  Even previously ignored repositories
   142    149   ** are added back to the list of repositories by these commands.
   143    150   **
   144    151   ** Options:
   145    152   **   --showfile     Show the repository or checkout being operated upon.
................................................................................
   227    234       collect_argument(&extra, "noverify",0);
   228    235       collect_argument_value(&extra, "pagesize");
   229    236       collect_argument(&extra, "vacuum",0);
   230    237       collect_argument(&extra, "deanalyze",0);
   231    238       collect_argument(&extra, "analyze",0);
   232    239       collect_argument(&extra, "wal",0);
   233    240       collect_argument(&extra, "stats",0);
          241  +    collect_argument(&extra, "index",0);
          242  +    collect_argument(&extra, "noindex",0);
          243  +    collect_argument(&extra, "ifneeded", 0);
   234    244     }else if( strncmp(zCmd, "setting", n)==0 ){
   235    245       zCmd = "setting -R";
   236    246       collect_argv(&extra, 3);
   237    247     }else if( strncmp(zCmd, "unset", n)==0 ){
   238    248       zCmd = "unset -R";
          249  +    collect_argv(&extra, 3);
          250  +  }else if( strncmp(zCmd, "fts-config", n)==0 ){
          251  +    zCmd = "fts-config -R";
   239    252       collect_argv(&extra, 3);
   240    253     }else if( strncmp(zCmd, "sync", n)==0 ){
   241    254       zCmd = "sync -autourl -R";
   242    255       collect_argument(&extra, "verbose","v");
   243    256     }else if( strncmp(zCmd, "test-integrity", n)==0 ){
   244    257       collect_argument(&extra, "parse", 0);
   245    258       zCmd = "test-integrity";
................................................................................
   251    264     }else if( strncmp(zCmd, "changes", n)==0 ){
   252    265       zCmd = "changes --quiet --header --chdir";
   253    266       useCheckouts = 1;
   254    267       stopOnError = 0;
   255    268       quiet = 1;
   256    269     }else if( strncmp(zCmd, "ignore", n)==0 ){
   257    270       int j;
          271  +    Blob fn = BLOB_INITIALIZER;
          272  +    Blob sql = BLOB_INITIALIZER;
   258    273       useCheckouts = find_option("ckout","c",0)!=0;
   259    274       verify_all_options();
   260    275       db_begin_transaction();
   261         -    for(j=3; j<g.argc; j++){
   262         -      Blob sql;
   263         -      blob_zero(&sql);
          276  +    for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){
          277  +      file_canonical_name(g.argv[j], &fn, 0);
   264    278         blob_append_sql(&sql, 
   265    279            "DELETE FROM global_config WHERE name GLOB '%s:%q'",
   266         -         useCheckouts?"ckout":"repo", g.argv[j]
          280  +         useCheckouts?"ckout":"repo", blob_str(&fn)
   267    281         );
   268    282         if( dryRunFlag ){
   269    283           fossil_print("%s\n", blob_sql_text(&sql));
   270    284         }else{
   271    285           db_multi_exec("%s", blob_sql_text(&sql));
   272    286         }
   273         -      blob_reset(&sql);
          287  +    }
          288  +    db_end_transaction(0);
          289  +    return;
          290  +  }else if( strncmp(zCmd, "add", n)==0 ){
          291  +    int j;
          292  +    Blob fn = BLOB_INITIALIZER;
          293  +    Blob sql = BLOB_INITIALIZER;
          294  +    verify_all_options();
          295  +    db_begin_transaction();
          296  +    for(j=3; j<g.argc; j++, blob_reset(&fn), blob_reset(&sql)){
          297  +      sqlite3 *db;
          298  +      int rc;
          299  +      const char *z;
          300  +      file_canonical_name(g.argv[j], &fn, 0);
          301  +      z = blob_str(&fn);
          302  +      if( !file_isfile(z) ) continue;
          303  +      rc = sqlite3_open(z, &db);
          304  +      if( rc!=SQLITE_OK ){ sqlite3_close(db); continue; }
          305  +      rc = sqlite3_exec(db, "SELECT rcvid FROM blob, delta LIMIT 1", 0, 0, 0);
          306  +      sqlite3_close(db);
          307  +      if( rc!=SQLITE_OK ) continue;
          308  +      blob_append_sql(&sql,
          309  +         "INSERT INTO global_config(name,value)VALUES('repo:%q',1)", z
          310  +      );
          311  +      if( dryRunFlag ){
          312  +        fossil_print("%s\n", blob_sql_text(&sql));
          313  +      }else{
          314  +        db_multi_exec("%s", blob_sql_text(&sql));
          315  +      }
   274    316       }
   275    317       db_end_transaction(0);
   276    318       return;
   277    319     }else if( strncmp(zCmd, "info", n)==0 ){
   278    320       zCmd = "info";
   279    321       showLabel = 1;
   280    322       quiet = 1;

Changes to src/attach.c.

    46     46        "SELECT datetime(mtime%s), src, target, filename,"
    47     47        "       comment, user,"
    48     48        "       (SELECT uuid FROM blob WHERE rid=attachid), attachid"
    49     49        "  FROM attachment",
    50     50        timeline_utc()
    51     51     );
    52     52     if( zPage ){
    53         -    if( g.perm.RdWiki==0 ) login_needed();
           53  +    if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
    54     54       style_header("Attachments To %h", zPage);
    55     55       blob_append_sql(&sql, " WHERE target=%Q", zPage);
    56     56     }else if( zTkt ){
    57         -    if( g.perm.RdTkt==0 ) login_needed();
           57  +    if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
    58     58       style_header("Attachments To Ticket %S", zTkt);
    59     59       blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
    60     60     }else{
    61         -    if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ) login_needed();
           61  +    if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
           62  +      login_needed(g.anon.RdTkt || g.anon.RdWiki);
           63  +      return;
           64  +    }
    62     65       style_header("All Attachments");
    63     66     }
    64     67     blob_append_sql(&sql, " ORDER BY mtime DESC");
    65     68     db_prepare(&q, "%s", blob_sql_text(&sql));
    66     69     @ <ol>
    67     70     while( db_step(&q)==SQLITE_ROW ){
    68     71       const char *zDate = db_column_text(&q, 0);
................................................................................
    84     87       }
    85     88       if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
    86     89         zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
    87     90       }else{
    88     91         zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    89     92       }
    90     93       @ <li><p>
    91         -    @ Attachment %z(href("%R/ainfo/%s",zUuid))%S(zUuid)</a>
           94  +    @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
    92     95       if( moderation_pending(attachid) ){
    93     96         @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
    94     97       }
    95         -    @ <br><a href="/attachview?%s(zUrlTail)">%h(zFilename)</a>
    96         -    @ [<a href="/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
           98  +    @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
           99  +    @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
    97    100       if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
    98    101       if( zComment && zComment[0] ){
    99    102         @ %!w(zComment)<br />
   100    103       }
   101    104       if( zPage==0 && zTkt==0 ){
   102    105         if( zSrc==0 || zSrc[0]==0 ){
   103    106           zSrc = "Deleted from";
   104    107         }else {
   105    108           zSrc = "Added to";
   106    109         }
   107    110         if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
   108         -        @ %s(zSrc) ticket <a href="%s(g.zTop)/tktview?name=%s(zTarget)">
          111  +        @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
   109    112           @ %S(zTarget)</a>
   110    113         }else{
   111         -        @ %s(zSrc) wiki page <a href="%s(g.zTop)/wiki?name=%t(zTarget)">
          114  +        @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
   112    115           @ %h(zTarget)</a>
   113    116         }
   114    117       }else{
   115    118         if( zSrc==0 || zSrc[0]==0 ){
   116    119           @ Deleted
   117    120         }else {
   118    121           @ Added
................................................................................
   148    151     int attachid = atoi(PD("attachid","0"));
   149    152     char *zUUID;
   150    153   
   151    154     if( zPage && zTkt ) zTkt = 0;
   152    155     if( zFile==0 ) fossil_redirect_home();
   153    156     login_check_credentials();
   154    157     if( zPage ){
   155         -    if( g.perm.RdWiki==0 ) login_needed();
          158  +    if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
   156    159       zTarget = zPage;
   157    160     }else if( zTkt ){
   158         -    if( g.perm.RdTkt==0 ) login_needed();
          161  +    if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
   159    162       zTarget = zTkt;
   160    163     }else{
   161    164       fossil_redirect_home();
   162    165     }
   163    166     if( attachid>0 ){
   164    167       zUUID = db_text(0,
   165    168          "SELECT coalesce(src,'x') FROM attachment"
................................................................................
   241    244     int goodCaptcha = 1;
   242    245   
   243    246     if( P("cancel") ) cgi_redirect(zFrom);
   244    247     if( zPage && zTkt ) fossil_redirect_home();
   245    248     if( zPage==0 && zTkt==0 ) fossil_redirect_home();
   246    249     login_check_credentials();
   247    250     if( zPage ){
   248         -    if( g.perm.ApndWiki==0 || g.perm.Attach==0 ) login_needed();
          251  +    if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
          252  +      login_needed(g.anon.ApndWiki && g.anon.Attach);
          253  +      return;
          254  +    }
   249    255       if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
   250    256         fossil_redirect_home();
   251    257       }
   252    258       zTarget = zPage;
   253         -    zTargetType = mprintf("Wiki Page <a href=\"%s/wiki?name=%h\">%h</a>",
   254         -                           g.zTop, zPage, zPage);
          259  +    zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
          260  +                           zPage, zPage);
   255    261     }else{
   256         -    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
          262  +    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
          263  +      login_needed(g.anon.ApndTkt && g.anon.Attach);
          264  +      return;
          265  +    }
   257    266       if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
   258    267         zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
   259    268                           " WHERE tagname GLOB 'tkt-%q*'", zTkt);
   260    269         if( zTkt==0 ) fossil_redirect_home();
   261    270       }
   262    271       zTarget = zTkt;
   263         -    zTargetType = mprintf("Ticket <a href=\"%s/tktview/%s\">%S</a>",
   264         -                          g.zTop, zTkt, zTkt);
          272  +    zTargetType = mprintf("Ticket <a href=\"%R/tktview/%s\">%S</a>",
          273  +                          zTkt, zTkt);
   265    274     }
   266    275     if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
   267    276     if( P("cancel") ){
   268    277       cgi_redirect(zFrom);
   269    278     }
   270    279     if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
   271    280       Blob content;
................................................................................
   367    376     int isModerator;               /* TRUE if user is the moderator */
   368    377     const char *zMime;             /* MIME Type */
   369    378     Blob attach;                   /* Content of the attachment */
   370    379     int fShowContent = 0;
   371    380     const char *zLn = P("ln");
   372    381   
   373    382     login_check_credentials();
   374         -  if( !g.perm.RdTkt && !g.perm.RdWiki ){ login_needed(); return; }
          383  +  if( !g.perm.RdTkt && !g.perm.RdWiki ){
          384  +    login_needed(g.anon.RdTkt || g.anon.RdWiki);
          385  +    return;
          386  +  }
   375    387     rid = name_to_rid_www("name");
   376    388     if( rid==0 ){ fossil_redirect_home(); }
   377    389     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
   378    390   #if 0
   379    391     /* Shunning here needs to get both the attachment control artifact and
   380    392     ** the object that is attached. */
   381    393     if( g.perm.Admin ){
................................................................................
   397    409     zDesc = pAttach->zComment;
   398    410     zMime = mimetype_from_name(zName);
   399    411     fShowContent = zMime ? strncmp(zMime,"text/", 5)==0 : 0;
   400    412     if( validate16(zTarget, strlen(zTarget))
   401    413      && db_exists("SELECT 1 FROM ticket WHERE tkt_uuid='%q'", zTarget)
   402    414     ){
   403    415       zTktUuid = zTarget;
   404         -    if( !g.perm.RdTkt ){ login_needed(); return; }
          416  +    if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
   405    417       if( g.perm.WrTkt ){
   406    418         style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
   407    419       }
   408    420     }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
   409    421       zWikiName = zTarget;
   410         -    if( !g.perm.RdWiki ){ login_needed(); return; }
          422  +    if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
   411    423       if( g.perm.WrWiki ){
   412    424         style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
   413    425       }
   414    426     }
   415    427     zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
   416    428   
   417    429     if( P("confirm")
................................................................................
   441    453       db_end_transaction(0);
   442    454       @ <p>The attachment below has been deleted.</p>
   443    455     }
   444    456   
   445    457     if( P("del")
   446    458      && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
   447    459     ){
   448         -    form_begin(0, "%R/ainfo/%s", zUuid);
          460  +    form_begin(0, "%R/ainfo/%!S", zUuid);
   449    461       @ <p>Confirm you want to delete the attachment shown below.
   450    462       @ <input type="submit" name="confirm" value="Confirm">
   451    463       @ </form>
   452    464     }
   453    465   
   454    466     isModerator = g.perm.Admin ||
   455    467                   (zTktUuid && g.perm.ModTkt) ||
   456    468                   (zWikiName && g.perm.ModWiki);
   457    469     if( isModerator && (zModAction = P("modaction"))!=0 ){
   458    470       if( strcmp(zModAction,"delete")==0 ){
   459    471         moderation_disapprove(rid);
   460    472         if( zTktUuid ){
   461         -        cgi_redirectf("%R/tktview/%s", zTktUuid);
          473  +        cgi_redirectf("%R/tktview/%!S", zTktUuid);
   462    474         }else{
   463    475           cgi_redirectf("%R/wiki?name=%t", zWikiName);
   464    476         }
   465    477         return;
   466    478       }
   467    479       if( strcmp(zModAction,"approve")==0 ){
   468    480         moderation_approve(rid);
................................................................................
   475    487                             "%R/ainfo/%s%s",zUuid,
   476    488                             ((zLn&&*zLn) ? "" : "?ln=0"));
   477    489     }
   478    490   
   479    491     @ <div class="section">Overview</div>
   480    492     @ <p><table class="label-value">
   481    493     @ <tr><th>Artifact&nbsp;ID:</th>
   482         -  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
          494  +  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
   483    495     if( g.perm.Setup ){
   484    496       @ (%d(rid))
   485    497     }
   486    498     modPending = moderation_pending(rid);
   487    499     if( modPending ){
   488    500       @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
   489    501     }
................................................................................
   577    589       const char *zSrc = db_column_text(&q, 4);
   578    590       const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
   579    591       if( cnt==0 ){
   580    592         @ %s(zHeader)
   581    593       }
   582    594       cnt++;
   583    595       @ <li>
   584         -    @ %z(href("%R/artifact/%s",zSrc))%h(zFile)</a>
          596  +    @ %z(href("%R/artifact/%!S",zSrc))%h(zFile)</a>
   585    597       @ added by %h(zDispUser) on
   586    598       hyperlink_to_date(zDate, ".");
   587         -    @ [%z(href("%R/ainfo/%s",zUuid))details</a>]
          599  +    @ [%z(href("%R/ainfo/%!S",zUuid))details</a>]
   588    600       @ </li>
   589    601     }
   590    602     if( cnt ){
   591    603       @ </ul>
   592    604     }
   593    605     db_finalize(&q);
   594    606   
   595    607   }

Changes to src/blob.c.

   751    751   
   752    752   /*
   753    753   ** Initialize a blob to be the content of a file.  If the filename
   754    754   ** is blank or "-" then read from standard input.
   755    755   **
   756    756   ** Any prior content of the blob is discarded, not freed.
   757    757   **
   758         -** Return the number of bytes read. Calls fossil_fatal() error (i.e.
          758  +** Return the number of bytes read. Calls fossil_fatal() on error (i.e.
   759    759   ** it exit()s and does not return).
   760    760   */
   761    761   int blob_read_from_file(Blob *pBlob, const char *zFilename){
   762    762     int size, got;
   763    763     FILE *in;
   764    764     if( zFilename==0 || zFilename[0]==0
   765    765           || (zFilename[0]=='-' && zFilename[1]==0) ){

Changes to src/branch.c.

   157    157     db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", brid);
   158    158     if( manifest_crosslink(brid, &branch, MC_PERMIT_HOOKS)==0 ){
   159    159       fossil_fatal("%s\n", g.zErrMsg);
   160    160     }
   161    161     assert( blob_is_reset(&branch) );
   162    162     content_deltify(rootid, brid, 0);
   163    163     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", brid);
   164         -  fossil_print("New branch: %s\n", zUuid);
          164  +  fossil_print("New branch: %S\n", zUuid);
   165    165     if( g.argc==3 ){
   166    166       fossil_print(
   167    167         "\n"
   168    168         "Note: the local check-out has not been updated to the new\n"
   169    169         "      branch.  To begin working on the new branch, do this:\n"
   170    170         "\n"
   171    171         "      %s update %s\n",
................................................................................
   300    300       db_finalize(&q);
   301    301     }else{
   302    302       fossil_fatal("branch subcommand should be one of: "
   303    303                    "new list ls");
   304    304     }
   305    305   }
   306    306   
   307         -static char brlistQuery[] = 
          307  +static const char brlistQuery[] =
   308    308   @ SELECT
   309    309   @   tagxref.value,
   310    310   @   max(event.mtime),
   311    311   @   EXISTS(SELECT 1 FROM tagxref AS tx
   312    312   @           WHERE tx.rid=tagxref.rid
   313    313   @             AND tx.tagid=(SELECT tagid FROM tag WHERE tagname='closed')
   314    314   @             AND tx.tagtype>0),
................................................................................
   337    337   ** Control jumps to this routine from brlist_page() (the /brlist handler)
   338    338   ** if there are no query parameters.
   339    339   */
   340    340   static void new_brlist_page(void){
   341    341     Stmt q;
   342    342     double rNow;
   343    343     login_check_credentials();
   344         -  if( !g.perm.Read ){ login_needed(); return; }
          344  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   345    345     style_header("Branches");
          346  +  style_adunit_config(ADUNIT_RIGHT_OK);
   346    347     login_anonymous_available();
   347         -  
          348  +
   348    349     db_prepare(&q, brlistQuery/*works-like:""*/);
   349    350     rNow = db_double(0.0, "SELECT julianday('now')");
   350    351     @ <div class="brlist"><table id="branchlisttable">
   351    352     @ <thead><tr>
   352    353     @ <th>Branch Name</th>
   353    354     @ <th>Age</th>
   354    355     @ <th>Checkins</th>
................................................................................
   364    365       const char *zLastCkin = db_column_text(&q, 5);
   365    366       char *zAge = human_readable_age(rNow - rMtime);
   366    367       sqlite3_int64 iMtime = (sqlite3_int64)(rMtime*86400.0);
   367    368       if( zMergeTo && zMergeTo[0]==0 ) zMergeTo = 0;
   368    369       @ <tr>
   369    370       @ <td>%z(href("%R/timeline?n=100&r=%T",zBranch))%h(zBranch)</a></td>
   370    371       @ <td data-sortkey="%016llx(-iMtime)">%s(zAge)</td>
   371         -    @ <td data-sortkey="%08x(-nCkin)">%d(nCkin)</td>
          372  +    @ <td>%d(nCkin)</td>
   372    373       fossil_free(zAge);
   373    374       @ <td>%s(isClosed?"closed":"")</td>
   374    375       if( zMergeTo ){
   375    376         @ <td>merged into
   376         -      @ %z(href("%R/timeline?f=%s",zLastCkin))%h(zMergeTo)</a></td>
          377  +      @ %z(href("%R/timeline?f=%!S",zLastCkin))%h(zMergeTo)</a></td>
   377    378       }else{
   378    379         @ <td></td>
   379    380       }
   380    381       @ </tr>
   381    382     }
   382    383     @ </tbody></table></div>
   383    384     db_finalize(&q);
   384         -  output_table_sorting_javascript("branchlisttable","tkktt",2);
          385  +  output_table_sorting_javascript("branchlisttable","tkNtt",2);
   385    386     style_footer();
   386    387   }
   387    388   
   388    389   /*
   389    390   ** WEBPAGE: brlist
   390    391   ** Show a list of branches
   391    392   ** Query parameters:
................................................................................
   405    406     int brFlags = BRL_OPEN_ONLY;
   406    407   
   407    408     if( showClosed==0 && showAll==0 && showOpen==0 && colorTest==0 ){
   408    409       new_brlist_page();
   409    410       return;
   410    411     }
   411    412     login_check_credentials();
   412         -  if( !g.perm.Read ){ login_needed(); return; }
          413  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   413    414     if( colorTest ){
   414    415       showClosed = 0;
   415    416       showAll = 1;
   416    417     }
   417    418     if( showAll ) brFlags = BRL_BOTH;
   418    419     if( showClosed ) brFlags = BRL_CLOSED_ONLY;
   419    420   
................................................................................
   512    513   **
   513    514   ** Show a timeline of all branches
   514    515   */
   515    516   void brtimeline_page(void){
   516    517     Stmt q;
   517    518   
   518    519     login_check_credentials();
   519         -  if( !g.perm.Read ){ login_needed(); return; }
          520  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   520    521   
   521    522     style_header("Branches");
   522    523     style_submenu_element("List", "List", "brlist");
   523    524     login_anonymous_available();
   524    525     @ <h2>The initial check-in for each branch:</h2>
   525    526     db_prepare(&q,
   526    527       "%s AND blob.rid IN (SELECT rid FROM tagxref"
   527    528       "                     WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
   528    529       " ORDER BY event.mtime DESC",
   529    530       timeline_query_for_www(), TAG_BRANCH
   530    531     );
   531         -  www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
          532  +  www_print_timeline(&q, 0, 0, 0, 0, brtimeline_extra);
   532    533     db_finalize(&q);
   533    534     style_footer();
   534    535   }

Changes to src/browse.c.

    83     83     int i, j;
    84     84     char *zSep = "";
    85     85   
    86     86     for(i=0; zPath[i]; i=j){
    87     87       for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    88     88       if( zPath[j] && g.perm.Hyperlink ){
    89     89         if( zCI ){
    90         -        char *zLink = href("%R/%s?name=%#T%s&ci=%s", zURI, j, zPath, zREx, zCI);
           90  +        char *zLink = href("%R/%s?name=%#T%s&ci=%!S", zURI, j, zPath, zREx,zCI);
    91     91           blob_appendf(pOut, "%s%z%#h</a>",
    92     92                        zSep, zLink, j-i, &zPath[i]);
    93     93         }else{
    94     94           char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
    95     95           blob_appendf(pOut, "%s%z%#h</a>",
    96     96                        zSep, zLink, j-i, &zPath[i]);
    97     97         }
................................................................................
   126    126     Blob dirname;
   127    127     Manifest *pM = 0;
   128    128     const char *zSubdirLink;
   129    129     int linkTrunk = 1;
   130    130     int linkTip = 1;
   131    131     HQuery sURI;
   132    132   
   133         -  if( strcmp(PD("type",""),"tree")==0 ){ page_tree(); return; }
          133  +  if( strcmp(PD("type","flat"),"tree")==0 ){ page_tree(); return; }
   134    134     login_check_credentials();
   135         -  if( !g.perm.Read ){ login_needed(); return; }
          135  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   136    136     while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
   137    137     style_header("File List");
          138  +  style_adunit_config(ADUNIT_RIGHT_OK);
   138    139     sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
   139    140                             pathelementFunc, 0, 0);
   140    141     url_initialize(&sURI, "dir");
          142  +  cgi_query_parameters_to_url(&sURI);
   141    143   
   142    144     /* If the name= parameter is an empty string, make it a NULL pointer */
   143    145     if( zD && strlen(zD)==0 ){ zD = 0; }
   144    146   
   145    147     /* If a specific check-in is requested, fetch and parse it.  If the
   146    148     ** specific check-in does not exist, clear zCI.  zCI==0 will cause all
   147    149     ** files from all check-ins to be displayed.
................................................................................
   149    151     if( zCI ){
   150    152       pM = manifest_get_by_name(zCI, &rid);
   151    153       if( pM ){
   152    154         int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
   153    155         linkTrunk = trunkRid && rid != trunkRid;
   154    156         linkTip = rid != symbolic_name_to_rid("tip", "ci");
   155    157         zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   156         -      url_add_parameter(&sURI, "ci", zCI);
   157    158       }else{
   158    159         zCI = 0;
   159    160       }
   160    161     }
   161    162   
   162    163     /* Compute the title of the page */
   163    164     blob_zero(&dirname);
   164    165     if( zD ){
   165         -    url_add_parameter(&sURI, "name", zD);
   166    166       blob_append(&dirname, "in directory ", -1);
   167    167       hyperlinked_path(zD, &dirname, zCI, "dir", "");
   168    168       zPrefix = mprintf("%s/", zD);
   169    169       style_submenu_element("Top-Level", "Top-Level", "%s",
   170    170                             url_render(&sURI, "name", 0, 0, 0));
   171    171     }else{
   172    172       blob_append(&dirname, "in the top-level directory", -1);
................................................................................
   177    177                             url_render(&sURI, "ci", "trunk", 0, 0));
   178    178     }
   179    179     if( linkTip ){
   180    180       style_submenu_element("Tip", "Tip", "%s",
   181    181                             url_render(&sURI, "ci", "tip", 0, 0));
   182    182     }
   183    183     if( zCI ){
   184         -    @ <h2>Files of check-in [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>]
          184  +    @ <h2>Files of check-in [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>]
   185    185       @ %s(blob_str(&dirname))</h2>
   186         -    zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
          186  +    zSubdirLink = mprintf("%R/dir?ci=%!S&name=%T", zUuid, zPrefix);
   187    187       if( nD==0 ){
   188         -      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
          188  +      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%!S",
   189    189                               zUuid);
   190    190       }
   191    191     }else{
   192    192       @ <h2>The union of all files from all check-ins
   193    193       @ %s(blob_str(&dirname))</h2>
   194    194       zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
   195    195     }
................................................................................
   279    279       if( zFN[0]=='/' ){
   280    280         zFN++;
   281    281         @ <li class="dir">%z(href("%s%T",zSubdirLink,zFN))%h(zFN)</a></li>
   282    282       }else{
   283    283         const char *zLink;
   284    284         if( zCI ){
   285    285           const char *zUuid = db_column_text(&q, 1);
   286         -        zLink = href("%R/artifact/%s",zUuid);
          286  +        zLink = href("%R/artifact/%!S",zUuid);
   287    287         }else{
   288    288           zLink = href("%R/finfo?name=%T%T",zPrefix,zFN);
   289    289         }
   290    290         @ <li class="%z(fileext_class(zFN))">%z(zLink)%h(zFN)</a></li>
   291    291       }
   292    292     }
   293    293     db_finalize(&q);
................................................................................
   537    537     FileTree sTree;          /* The complete tree of files */
   538    538     HQuery sURI;             /* Hyperlink */
   539    539     int startExpanded;       /* True to start out with the tree expanded */
   540    540     int showDirOnly;         /* Show directories only.  Omit files */
   541    541     int nDir = 0;            /* Number of directories. Used for ID attributes */
   542    542     char *zProjectName = db_get("project-name", 0);
   543    543   
   544         -  if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
          544  +  if( strcmp(PD("type","flat"),"flat")==0 ){ page_dir(); return; }
   545    545     memset(&sTree, 0, sizeof(sTree));
   546    546     login_check_credentials();
   547         -  if( !g.perm.Read ){ login_needed(); return; }
          547  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   548    548     while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
   549    549     sqlite3_create_function(g.db, "pathelement", 2, SQLITE_UTF8, 0,
   550    550                             pathelementFunc, 0, 0);
   551    551     url_initialize(&sURI, "tree");
   552         -  if( P("nofiles")!=0 ){
          552  +  cgi_query_parameters_to_url(&sURI);
          553  +  if( PB("nofiles") ){
   553    554       showDirOnly = 1;
   554         -    url_add_parameter(&sURI, "nofiles", "1");
   555    555       style_header("Folder Hierarchy");
   556    556     }else{
   557    557       showDirOnly = 0;
   558    558       style_header("File Tree");
   559    559     }
   560         -  if( P("expand")!=0 ){
          560  +  style_adunit_config(ADUNIT_RIGHT_OK);
          561  +  if( PB("expand") ){
   561    562       startExpanded = 1;
   562         -    url_add_parameter(&sURI, "expand", "1");
   563    563     }else{
   564    564       startExpanded = 0;
   565    565     }
   566    566   
   567    567     /* If a regular expression is specified, compile it */
   568    568     zRE = P("re");
   569    569     if( zRE ){
   570    570       re_compile(&pRE, zRE, 0);
   571         -    url_add_parameter(&sURI, "re", zRE);
   572    571       zREx = mprintf("&re=%T", zRE);
   573    572     }
   574    573   
   575    574     /* If the name= parameter is an empty string, make it a NULL pointer */
   576    575     if( zD && strlen(zD)==0 ){ zD = 0; }
   577    576   
   578    577     /* If a specific check-in is requested, fetch and parse it.  If the
................................................................................
   582    581     if( zCI ){
   583    582       pM = manifest_get_by_name(zCI, &rid);
   584    583       if( pM ){
   585    584         int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
   586    585         linkTrunk = trunkRid && rid != trunkRid;
   587    586         linkTip = rid != symbolic_name_to_rid("tip", "ci");
   588    587         zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   589         -      url_add_parameter(&sURI, "ci", zCI);
   590    588         rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
   591    589         zNow = db_text("", "SELECT datetime(mtime,'localtime')"
   592    590                            " FROM event WHERE objid=%d", rid);
   593    591       }else{
   594    592         zCI = 0;
   595    593       }
   596    594     }
................................................................................
   598    596       rNow = db_double(0.0, "SELECT max(mtime) FROM event");
   599    597       zNow = db_text("", "SELECT datetime(max(mtime),'localtime') FROM event");
   600    598     }
   601    599   
   602    600     /* Compute the title of the page */
   603    601     blob_zero(&dirname);
   604    602     if( zD ){
   605         -    url_add_parameter(&sURI, "name", zD);
   606    603       blob_append(&dirname, "within directory ", -1);
   607    604       hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
   608    605       if( zRE ) blob_appendf(&dirname, " matching \"%s\"", zRE);
   609    606       style_submenu_element("Top-Level", "Top-Level", "%s",
   610    607                             url_render(&sURI, "name", 0, 0, 0));
   611    608     }else{
   612    609       if( zRE ){
   613    610         blob_appendf(&dirname, "matching \"%s\"", zRE);
   614    611       }
   615    612     }
   616         -  if( useMtime ){
   617         -    style_submenu_element("Sort By Filename","Sort By Filename", "%s",
   618         -                           url_render(&sURI, 0, 0, 0, 0));
   619         -    url_add_parameter(&sURI, "mtime", "1");
   620         -  }else{
   621         -    style_submenu_element("Sort By Time","Sort By Time", "%s",
   622         -                           url_render(&sURI, "mtime", "1", 0, 0));
   623         -  }
          613  +  style_submenu_binary("mtime","Sort By Time","Sort By Filename", 0);
   624    614     if( zCI ){
   625    615       style_submenu_element("All", "All", "%s",
   626    616                             url_render(&sURI, "ci", 0, 0, 0));
   627    617       if( nD==0 && !showDirOnly ){
   628    618         style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
   629    619                               zUuid);
   630    620       }
................................................................................
   633    623       style_submenu_element("Trunk", "Trunk", "%s",
   634    624                             url_render(&sURI, "ci", "trunk", 0, 0));
   635    625     }
   636    626     if( linkTip ){
   637    627       style_submenu_element("Tip", "Tip", "%s",
   638    628                             url_render(&sURI, "ci", "tip", 0, 0));
   639    629     }
          630  +  style_submenu_element("Flat-View", "Flat-View", "%s",
          631  +                        url_render(&sURI, "type", "flat", 0, 0));
   640    632   
   641    633     /* Compute the file hierarchy.
   642    634     */
   643    635     if( zCI ){
   644    636       Stmt q;
   645    637       compute_fileage(rid, 0);
   646    638       db_prepare(&q,
................................................................................
   650    642          "   AND blob.rid=fileage.fid\n"
   651    643          " ORDER BY filename.name COLLATE nocase;"
   652    644       );
   653    645       while( db_step(&q)==SQLITE_ROW ){
   654    646         const char *zFile = db_column_text(&q,0);
   655    647         const char *zUuid = db_column_text(&q,1);
   656    648         double mtime = db_column_double(&q,2);
          649  +      if( nD>0 && (fossil_strncmp(zFile, zD, nD-1)!=0 || zFile[nD-1]!='/') ){
          650  +        continue;
          651  +      }
   657    652         if( pRE && re_match(pRE, (const unsigned char*)zFile, -1)==0 ) continue;
   658    653         tree_add_node(&sTree, zFile, zUuid, mtime);
   659    654         nFile++;
   660    655       }
   661    656       db_finalize(&q);
   662    657     }else{
   663    658       Stmt q;
................................................................................
   696    691     }
   697    692   
   698    693     if( zCI ){
   699    694       @ <h2>%s(zObjType) from
   700    695       if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
   701    696         @ "%h(zCI)"
   702    697       }
   703         -    @ [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
          698  +    @ [%z(href("vinfo?name=%!S",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))
   704    699     }else{
   705    700       int n = db_int(0, "SELECT count(*) FROM plink");
   706    701       @ <h2>%s(zObjType) from all %d(n) check-ins %s(blob_str(&dirname))
   707    702     }
   708    703     if( useMtime ){
   709    704       @ sorted by modification time</h2>
   710    705     }else{
................................................................................
   758    753           @ <ul id="dir%d(nDir)" class="collapsed">
   759    754         }
   760    755         nDir++;
   761    756       }else if( !showDirOnly ){
   762    757         const char *zFileClass = fileext_class(p->zName);
   763    758         char *zLink;
   764    759         if( zCI ){
   765         -        zLink = href("%R/artifact/%.16s",p->zUuid);
          760  +        zLink = href("%R/artifact/%!S",p->zUuid);
   766    761         }else{
   767    762           zLink = href("%R/finfo?name=%T",p->zFullName);
   768    763         }
   769    764         @ <li class="%z(zFileClass)%s(zLastClass)"><div class="filetreeline">
   770    765         @ %z(zLink)%h(p->zName)</a>
   771    766         if( p->mtime>0 ){
   772    767           char *zAge = human_readable_age(rNow - p->mtime);
................................................................................
   879    874     return zClass;
   880    875   }
   881    876   
   882    877   /*
   883    878   ** SQL used to compute the age of all files in checkin :ckin whose
   884    879   ** names match :glob
   885    880   */
   886         -static const char zComputeFileAgeSetup[] = 
          881  +static const char zComputeFileAgeSetup[] =
   887    882   @ CREATE TABLE IF NOT EXISTS temp.fileage(
   888    883   @   fnid INTEGER PRIMARY KEY,
   889    884   @   fid INTEGER,
   890    885   @   mid INTEGER,
   891    886   @   mtime DATETIME,
   892    887   @   pathname TEXT
   893    888   @ );
   894    889   @ CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;
   895    890   ;
   896    891   
   897         -static const char zComputeFileAgeRun[] = 
          892  +static const char zComputeFileAgeRun[] =
   898    893   @ WITH RECURSIVE
   899         -@   ckin(x) AS (VALUES(:ckin) UNION ALL
   900         -@                 SELECT pid FROM ckin, plink WHERE cid=x AND isprim)
          894  +@   ckin(x,m) AS (SELECT objid, mtime FROM event WHERE objid=:ckin
          895  +@                 UNION
          896  +@                 SELECT plink.pid, event.mtime
          897  +@                   FROM ckin, plink, event
          898  +@                  WHERE plink.cid=ckin.x AND event.objid=plink.pid
          899  +@                  ORDER BY 2 DESC)
   901    900   @ INSERT OR IGNORE INTO fileage(fnid, fid, mid, mtime, pathname)
   902         -@   SELECT mlink.fnid, mlink.fid, x, event.mtime, filename.name
   903         -@     FROM ckin, mlink, event, filename
   904         -@    WHERE mlink.mid=ckin.x
   905         -@      AND mlink.fnid IN (SELECT fnid FROM foci, filename
   906         -@                          WHERE foci.checkinID=:ckin
   907         -@                            AND filename.name=foci.filename
   908         -@                            AND filename.name GLOB :glob)
   909         -@      AND filename.fnid=mlink.fnid
   910         -@      AND event.objid=mlink.mid;
          901  +@   SELECT filename.fnid, mlink.fid, mlink.mid, event.mtime, filename.name
          902  +@     FROM foci, filename, blob, mlink, event
          903  +@    WHERE foci.checkinID=:ckin
          904  +@      AND foci.filename GLOB :glob
          905  +@      AND filename.name=foci.filename
          906  +@      AND blob.uuid=foci.uuid
          907  +@      AND mlink.fid=blob.rid
          908  +@      AND mlink.fid!=mlink.pid
          909  +@      AND mlink.mid IN (SELECT x FROM ckin)
          910  +@      AND event.objid=mlink.mid
          911  +@  ORDER BY event.mtime ASC;
   911    912   ;
   912         -
   913    913   
   914    914   /*
   915    915   ** Look at all file containing in the version "vid".  Construct a
   916    916   ** temporary table named "fileage" that contains the file-id for each
   917    917   ** files, the pathname, the check-in where the file was added, and the
   918    918   ** mtime on that checkin. If zGlob and *zGlob then only files matching
   919    919   ** the given glob are computed.
   920    920   */
   921    921   int compute_fileage(int vid, const char* zGlob){
   922    922     Stmt q;
   923    923     db_multi_exec(zComputeFileAgeSetup /*works-like:"constant"*/);
   924         -  db_prepare(&q, zComputeFileAgeRun /*works-like:"constant"*/);
          924  +  db_prepare(&q, zComputeFileAgeRun  /*works-like:"constant"*/);
   925    925     db_bind_int(&q, ":ckin", vid);
   926    926     db_bind_text(&q, ":glob", zGlob && zGlob[0] ? zGlob : "*");
   927    927     db_exec(&q);
   928    928     db_finalize(&q);
   929    929     return 0;
   930    930   }
   931    931   
................................................................................
   988    988   /*
   989    989   ** WEBPAGE:  fileage
   990    990   **
   991    991   ** Parameters:
   992    992   **   name=VERSION   Selects the checkin version (default=tip).
   993    993   **   glob=STRING    Only shows files matching this glob pattern
   994    994   **                  (e.g. *.c or *.txt).
          995  +**   showid         Show RID values for debugging
   995    996   */
   996    997   void fileage_page(void){
   997    998     int rid;
   998    999     const char *zName;
   999   1000     const char *zGlob;
  1000   1001     const char *zUuid;
  1001   1002     const char *zNow;            /* Time of checkin */
         1003  +  int showId = PB("showid");
  1002   1004     Stmt q1, q2;
  1003   1005     double baseTime;
  1004   1006     login_check_credentials();
  1005         -  if( !g.perm.Read ){ login_needed(); return; }
         1007  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1006   1008     zName = P("name");
  1007   1009     if( zName==0 ) zName = "tip";
  1008   1010     rid = symbolic_name_to_rid(zName, "ci");
  1009   1011     if( rid==0 ){
  1010   1012       fossil_fatal("not a valid check-in: %s", zName);
  1011   1013     }
  1012   1014     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1013   1015     baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
  1014   1016     zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event"
  1015   1017                        " WHERE objid=%d", rid);
  1016         -  style_submenu_element("Tree-View", "Tree-View", "%R/tree?ci=%T&mtime=1",
         1018  +  style_submenu_element("Tree-View", "Tree-View",
         1019  +                        "%R/tree?ci=%T&mtime=1&type=tree",
  1017   1020                           zName);
  1018   1021     style_header("File Ages");
  1019   1022     zGlob = P("glob");
  1020   1023     compute_fileage(rid,zGlob);
  1021   1024     db_multi_exec("CREATE INDEX fileage_ix1 ON fileage(mid,pathname);");
  1022   1025   
  1023         -  @ <h2>Files in 
  1024         -  @ %z(href("%R/info?name=%T",zUuid))[%S(zUuid)]</a>
         1026  +  @ <h2>Files in
         1027  +  @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
  1025   1028     if( zGlob && zGlob[0] ){
  1026   1029       @ that match "%h(zGlob)" and
  1027   1030     }
  1028   1031     @ ordered by check-in time</h2>
  1029   1032     @
  1030   1033     @ <p>Times are relative to the checkin time for
  1031         -  @ %z(href("%R/ci/%s",zUuid))[%S(zUuid)]</a> which is
         1034  +  @ %z(href("%R/ci/%!S",zUuid))[%S(zUuid)]</a> which is
  1032   1035     @ %z(href("%R/timeline?c=%t",zNow))%s(zNow)</a>.</p>
  1033   1036     @
  1034   1037     @ <div class='fileage'><table>
  1035   1038     @ <tr><th>Time</th><th>Files</th><th>Checkin</th></tr>
  1036   1039     db_prepare(&q1,
  1037   1040       "SELECT event.mtime, event.objid, blob.uuid,\n"
  1038   1041       "       coalesce(event.ecomment,event.comment),\n"
................................................................................
  1043   1046       "  FROM event, blob\n"
  1044   1047       " WHERE event.objid IN (SELECT mid FROM fileage)\n"
  1045   1048       "   AND blob.rid=event.objid\n"
  1046   1049       " ORDER BY event.mtime DESC;",
  1047   1050       TAG_BRANCH
  1048   1051     );
  1049   1052     db_prepare(&q2,
  1050         -    "SELECT blob.uuid, filename.name\n"
         1053  +    "SELECT blob.uuid, filename.name, fileage.fid\n"
  1051   1054       "  FROM fileage, blob, filename\n"
  1052   1055       " WHERE fileage.mid=:mid AND filename.fnid=fileage.fnid"
  1053   1056       "   AND blob.rid=fileage.fid;"
  1054   1057     );
  1055   1058     while( db_step(&q1)==SQLITE_ROW ){
  1056   1059       double age = baseTime - db_column_double(&q1, 0);
  1057   1060       int mid = db_column_int(&q1, 1);
................................................................................
  1062   1065       char *zAge = human_readable_age(age);
  1063   1066       @ <tr><td>%s(zAge)</td>
  1064   1067       @ <td>
  1065   1068       db_bind_int(&q2, ":mid", mid);
  1066   1069       while( db_step(&q2)==SQLITE_ROW ){
  1067   1070         const char *zFUuid = db_column_text(&q2,0);
  1068   1071         const char *zFile = db_column_text(&q2,1);
  1069         -      @ %z(href("%R/artifact/%s",zFUuid))%h(zFile)</a><br>
         1072  +      int fid = db_column_int(&q2,2);
         1073  +      if( showId ){
         1074  +        @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a> (%d(fid))<br>
         1075  +      }else{
         1076  +        @ %z(href("%R/artifact/%!S",zFUuid))%h(zFile)</a><br>
         1077  +      }
  1070   1078       }
  1071   1079       db_reset(&q2);
  1072   1080       @ </td>
  1073   1081       @ <td>
  1074         -    @ %z(href("%R/info/%s",zUuid))[%S(zUuid)]</a>
         1082  +    @ %z(href("%R/info/%!S",zUuid))[%S(zUuid)]</a>
         1083  +    if( showId ){
         1084  +      @ (%d(mid))
         1085  +    }
  1075   1086       @ %W(zComment) (user:
  1076         -    @ %z(href("%R/timeline?u=%t&c=%t&nd&n=200",zUser,zUuid))%h(zUser)</a>,
         1087  +    @ %z(href("%R/timeline?u=%t&c=%!S&nd&n=200",zUser,zUuid))%h(zUser)</a>,
  1077   1088       @ branch:
  1078         -    @ %z(href("%R/timeline?r=%t&c=%t&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
         1089  +    @ %z(href("%R/timeline?r=%t&c=%!S&nd&n=200",zBranch,zUuid))%h(zBranch)</a>)
  1079   1090       @ </td></tr>
  1080   1091       @
  1081   1092       fossil_free(zAge);
  1082   1093     }
  1083   1094     @ </table></div>
  1084   1095     db_finalize(&q1);
  1085   1096     db_finalize(&q2);
  1086   1097     style_footer();
  1087   1098   }

Changes to src/builtin.c.

    29     29   */
    30     30   #include "builtin_data.h"
    31     31   
    32     32   /*
    33     33   ** Return a pointer to built-in content
    34     34   */
    35     35   const unsigned char *builtin_file(const char *zFilename, int *piSize){
    36         -  int lwr, upr, i;
           36  +  int lwr, upr, i, c;
    37     37     lwr = 0;
    38     38     upr = sizeof(aBuiltinFiles)/sizeof(aBuiltinFiles[0]) - 1;
    39     39     while( upr>=lwr ){
    40     40       i = (upr+lwr)/2;
    41         -    if( strcmp(aBuiltinFiles[i].zName,zFilename)==0 ){
           41  +    c = strcmp(aBuiltinFiles[i].zName,zFilename);
           42  +    if( c<0 ){
           43  +      lwr = i+1;
           44  +    }else if( c>0 ){
           45  +      upr = i-1;
           46  +    }else{
    42     47         if( piSize ) *piSize = aBuiltinFiles[i].nByte;
    43     48         return aBuiltinFiles[i].pData;
    44     49       }
    45     50     }
    46     51     if( piSize ) *piSize = 0;
    47     52     return 0;
           53  +}
           54  +const char *builtin_text(const char *zFilename){
           55  +  return (char*)builtin_file(zFilename, 0);
    48     56   }
    49     57   
    50     58   /*
    51     59   ** COMMAND: test-builtin-list
    52     60   **
    53     61   ** List the names and sizes of all built-in resources
    54     62   */

Changes to src/cache.c.

   328    328   */
   329    329   void cache_page(void){
   330    330     sqlite3 *db;
   331    331     sqlite3_stmt *pStmt;
   332    332     char zBuf[100];
   333    333   
   334    334     login_check_credentials();
   335         -  if( !g.perm.Setup ){ login_needed(); return; }
          335  +  if( !g.perm.Setup ){ login_needed(0); return; }
   336    336     style_header("Web Cache Status");
   337    337     db = cacheOpen(0);
   338    338     if( db==0 ){
   339    339       @ The web-page cache is disabled for this repository
   340    340     }else{
   341    341       char *zDbName = cacheName();
   342    342       cache_register_sizename(db);
................................................................................
   376    376   ** This page is normally a hyperlink from the /cachestat page.
   377    377   */
   378    378   void cache_getpage(void){
   379    379     const char *zKey;
   380    380     Blob content;
   381    381   
   382    382     login_check_credentials();
   383         -  if( !g.perm.Setup ){ login_needed(); return; }
          383  +  if( !g.perm.Setup ){ login_needed(0); return; }
   384    384     zKey = PD("key","");
   385    385     blob_zero(&content);
   386    386     if( cache_read(&content, zKey)==0 ){
   387    387       style_header("Cache Download Error");
   388    388       @ The cache does not contain any entry with this key: "%h(zKey)"
   389    389       style_footer();
   390    390       return;
   391    391     }
   392    392     cgi_set_content(&content);
   393    393     cgi_set_content_type("application/x-compressed");
   394    394   }

Changes to src/cgi.c.

    50     50   ** or cookie "x", or NULL if there is no such parameter or cookie.  PD("x","y")
    51     51   ** does the same except "y" is returned in place of NULL if there is not match.
    52     52   */
    53     53   #define P(x)        cgi_parameter((x),0)
    54     54   #define PD(x,y)     cgi_parameter((x),(y))
    55     55   #define PT(x)       cgi_parameter_trimmed((x),0)
    56     56   #define PDT(x,y)    cgi_parameter_trimmed((x),(y))
           57  +#define PB(x)       cgi_parameter_boolean(x)
    57     58   
    58     59   
    59     60   /*
    60     61   ** Destinations for output text.
    61     62   */
    62     63   #define CGI_HEADER   0
    63     64   #define CGI_BODY     1
................................................................................
   106    107   /*
   107    108   ** Check to see if the header contains the zNeedle string.  Return true
   108    109   ** if it does and false if it does not.
   109    110   */
   110    111   int cgi_header_contains(const char *zNeedle){
   111    112     return strstr(blob_str(&cgiContent[0]), zNeedle)!=0;
   112    113   }
          114  +int cgi_body_contains(const char *zNeedle){
          115  +  return strstr(blob_str(&cgiContent[1]), zNeedle)!=0;
          116  +}
   113    117   
   114    118   /*
   115    119   ** Append reply content to what already exists.
   116    120   */
   117    121   void cgi_append_content(const char *zData, int nAmt){
   118    122     blob_append(pContent, zData, nAmt);
   119    123   }
................................................................................
   431    435   static int nUsedQP = 0;  /* Space actually used in aParamQP[] */
   432    436   static int sortQP = 0;   /* True if aParamQP[] needs sorting */
   433    437   static int seqQP = 0;    /* Sequence numbers */
   434    438   static struct QParam {   /* One entry for each query parameter or cookie */
   435    439     const char *zName;        /* Parameter or cookie name */
   436    440     const char *zValue;       /* Value of the query parameter or cookie */
   437    441     int seq;                  /* Order of insertion */
   438         -  int isQP;                 /* True for query parameters */
          442  +  char isQP;                /* True for query parameters */
          443  +  char cTag;                /* Tag on query parameters */
   439    444   } *aParamQP;             /* An array of all parameters and cookies */
   440    445   
   441    446   /*
   442    447   ** Add another query parameter or cookie to the parameter set.
   443    448   ** zName is the name of the query parameter or cookie and zValue
   444    449   ** is its fully decoded value.
   445    450   **
................................................................................
   458    463     aParamQP[nUsedQP].zName = zName;
   459    464     aParamQP[nUsedQP].zValue = zValue;
   460    465     if( g.fHttpTrace ){
   461    466       fprintf(stderr, "# cgi: %s = [%s]\n", zName, zValue);
   462    467     }
   463    468     aParamQP[nUsedQP].seq = seqQP++;
   464    469     aParamQP[nUsedQP].isQP = isQP;
          470  +  aParamQP[nUsedQP].cTag = 0;
   465    471     nUsedQP++;
   466    472     sortQP = 1;
   467    473   }
   468    474   
   469    475   /*
   470    476   ** Add another query parameter or cookie to the parameter set.
   471    477   ** zName is the name of the query parameter or cookie and zValue
................................................................................
   485    491     for(i=0; i<nUsedQP; i++){
   486    492       if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
   487    493         aParamQP[i].zValue = zValue;
   488    494         return;
   489    495       }
   490    496     }
   491    497     cgi_set_parameter_nocopy(zName, zValue, 0);
          498  +}
          499  +void cgi_replace_query_parameter(const char *zName, const char *zValue){
          500  +  int i;
          501  +  for(i=0; i<nUsedQP; i++){
          502  +    if( fossil_strcmp(aParamQP[i].zName,zName)==0 ){
          503  +      aParamQP[i].zValue = zValue;
          504  +      assert( aParamQP[i].isQP );
          505  +      return;
          506  +    }
          507  +  }
          508  +  cgi_set_parameter_nocopy(zName, zValue, 1);
   492    509   }
   493    510   
   494    511   /*
   495    512   ** Add a query parameter.  The zName portion is fixed but a copy
   496    513   ** must be made of zValue.
   497    514   */
   498    515   void cgi_setenv(const char *zName, const char *zValue){
................................................................................
  1059   1076     if( zIn==0 ) zIn = zDefault;
  1060   1077     while( fossil_isspace(zIn[0]) ) zIn++;
  1061   1078     zOut = fossil_strdup(zIn);
  1062   1079     for(i=0; zOut[i]; i++){}
  1063   1080     while( i>0 && fossil_isspace(zOut[i-1]) ) zOut[--i] = 0;
  1064   1081     return zOut;
  1065   1082   }
         1083  +
         1084  +/*
         1085  +** Return true if the CGI parameter zName exists and is not equal to 0,
         1086  +** or "no" or "off".
         1087  +*/
         1088  +int cgi_parameter_boolean(const char *zName){
         1089  +  const char *zIn = cgi_parameter(zName, 0);
         1090  +  if( zIn==0 ) return 0;
         1091  +  return zIn[0]==0 || is_truth(zIn);
         1092  +}
  1066   1093   
  1067   1094   /*
  1068   1095   ** Return the name of the i-th CGI parameter.  Return NULL if there
  1069   1096   ** are fewer than i registered CGI parameters.
  1070   1097   */
  1071   1098   const char *cgi_parameter_name(int i){
  1072   1099     if( i>=0 && i<nUsedQP ){
................................................................................
  1137   1164         if( fossil_strnicmp("fossil-",zName,7)==0 ) continue;
  1138   1165       }
  1139   1166       cgi_printf("%h = %h  <br />\n", zName, aParamQP[i].zValue);
  1140   1167     }
  1141   1168   }
  1142   1169   
  1143   1170   /*
  1144         -** Export all query parameters (but not cookies or environment variables)
  1145         -** as hidden values of a form.
         1171  +** Export all untagged query parameters (but not cookies or environment
         1172  +** variables) as hidden values of a form.
  1146   1173   */
  1147   1174   void cgi_query_parameters_to_hidden(void){
  1148   1175     int i;
  1149   1176     const char *zN, *zV;
  1150   1177     for(i=0; i<nUsedQP; i++){
  1151         -    if( aParamQP[i].isQP==0 ) continue;
         1178  +    if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
  1152   1179       zN = aParamQP[i].zName;
  1153   1180       zV = aParamQP[i].zValue;
  1154   1181       @ <input type="hidden" name="%h(zN)" value="%h(zV)">
  1155   1182     }
  1156   1183   }
         1184  +
         1185  +/*
         1186  +** Export all untagged query parameters (but not cookies or environment
         1187  +** variables) to the HQuery object.
         1188  +*/
         1189  +void cgi_query_parameters_to_url(HQuery *p){
         1190  +  int i;
         1191  +  for(i=0; i<nUsedQP; i++){
         1192  +    if( aParamQP[i].isQP==0 || aParamQP[i].cTag ) continue;
         1193  +    url_add_parameter(p, aParamQP[i].zName, aParamQP[i].zValue);
         1194  +  }
         1195  +}
         1196  +
         1197  +/*
         1198  +** Tag query parameter zName so that it is not exported by
         1199  +** cgi_query_parameters_to_hidden().  Or if zName==0, then
         1200  +** untag all query parameters.
         1201  +*/
         1202  +void cgi_tag_query_parameter(const char *zName){
         1203  +  int i;
         1204  +  if( zName==0 ){
         1205  +    for(i=0; i<nUsedQP; i++) aParamQP[i].cTag = 0;
         1206  +  }else{
         1207  +    for(i=0; i<nUsedQP; i++){
         1208  +      if( strcmp(zName,aParamQP[i].zName)==0 ) aParamQP[i].cTag = 1;
         1209  +    }
         1210  +  }
         1211  +}
  1157   1212   
  1158   1213   /*
  1159   1214   ** This routine works like "printf" except that it has the
  1160   1215   ** extra formatting capabilities such as %h and %t.
  1161   1216   */
  1162   1217   void cgi_printf(const char *zFormat, ...){
  1163   1218     va_list ap;
................................................................................
  1624   1679   /*
  1625   1680   ** Bitmap values for the flags parameter to cgi_http_server().
  1626   1681   */
  1627   1682   #define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
  1628   1683   #define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */
  1629   1684   #define HTTP_SERVER_HAD_REPOSITORY 0x0004     /* Was the repository open? */
  1630   1685   #define HTTP_SERVER_HAD_CHECKOUT   0x0008     /* Was a checkout open? */
         1686  +#define HTTP_SERVER_REPOLIST       0x0010     /* Allow repo listing */
  1631   1687   
  1632   1688   #endif /* INTERFACE */
  1633   1689   
  1634   1690   /*
  1635   1691   ** Maximum number of child processes that we can have running
  1636   1692   ** at one time before we start slowing things down.
  1637   1693   */
................................................................................
  1751   1807             int nErr = 0, fd;
  1752   1808             close(0);
  1753   1809             fd = dup(connection);
  1754   1810             if( fd!=0 ) nErr++;
  1755   1811             close(1);
  1756   1812             fd = dup(connection);
  1757   1813             if( fd!=1 ) nErr++;
  1758         -          if( !g.fHttpTrace && !g.fSqlTrace ){
         1814  +          if( !g.fAnyTrace ){
  1759   1815               close(2);
  1760   1816               fd = dup(connection);
  1761   1817               if( fd!=2 ) nErr++;
  1762   1818             }
  1763   1819             close(connection);
  1764   1820             return nErr;
  1765   1821           }

Changes to src/checkin.c.

  1934   1934     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nvid);
  1935   1935   
  1936   1936     db_prepare(&q, "SELECT uuid,merge FROM vmerge JOIN blob ON merge=rid"
  1937   1937                    " WHERE id=-4");
  1938   1938     while( db_step(&q)==SQLITE_ROW ){
  1939   1939       const char *zIntegrateUuid = db_column_text(&q, 0);
  1940   1940       if( is_a_leaf(db_column_int(&q, 1)) ){
  1941         -      fossil_print("Closed: %s\n", zIntegrateUuid);
         1941  +      fossil_print("Closed: %S\n", zIntegrateUuid);
  1942   1942       }else{
  1943         -      fossil_print("Not_Closed: %s (not a leaf any more)\n", zIntegrateUuid);
         1943  +      fossil_print("Not_Closed: %S (not a leaf any more)\n", zIntegrateUuid);
  1944   1944       }
  1945   1945     }
  1946   1946     db_finalize(&q);
  1947   1947   
  1948         -  fossil_print("New_Version: %s\n", zUuid);
         1948  +  fossil_print("New_Version: %S\n", zUuid);
  1949   1949     if( outputManifest ){
  1950   1950       zManifestFile = mprintf("%smanifest.uuid", g.zLocalRoot);
  1951   1951       blob_zero(&muuid);
  1952   1952       blob_appendf(&muuid, "%s\n", zUuid);
  1953   1953       blob_write_to_file(&muuid, zManifestFile);
  1954   1954       free(zManifestFile);
  1955   1955       blob_reset(&muuid);

Changes to src/clone.c.

   136    136     /* We should be done with options.. */
   137    137     verify_all_options();
   138    138   
   139    139     if( g.argc < 4 ){
   140    140       usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
   141    141     }
   142    142     db_open_config(0);
   143         -  if( file_size(g.argv[3])>0 ){
          143  +  if( -1 != file_size(g.argv[3]) ){
   144    144       fossil_fatal("file already exists: %s", g.argv[3]);
   145    145     }
   146    146   
   147    147     url_parse(g.argv[2], urlFlags);
   148    148     if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
   149    149     if( g.url.isFile ){
   150    150       file_copy(g.url.name, g.argv[3]);

Changes to src/codecheck1.c.

   274    274     /* A string literal is safe for use with %s */
   275    275     if( is_string_lit(z) ) return 1;
   276    276   
   277    277     /* Certain functions are guaranteed to return a string that is safe
   278    278     ** for use with %s */
   279    279     z = next_non_whitespace(z, &len, &eType);
   280    280     for(i=0; i<sizeof(azSafeFunc)/sizeof(azSafeFunc[0]); i++){
   281         -    if( eType==TK_ID 
          281  +    if( eType==TK_ID
   282    282        && strncmp(z, azSafeFunc[i], len)==0
   283    283        && strlen(azSafeFunc[i])==len
   284    284       ){
   285    285         return 1;
   286    286       }
   287    287     }
   288    288   
................................................................................
   289    289     /* Expressions of the form:  EXPR ? "..." : "...." can count as
   290    290     ** a string literal. */
   291    291     if( is_string_expr(z) ) return 1;
   292    292   
   293    293     /* If the "safe-for-%s" comment appears in the argument, then
   294    294     ** let it through */
   295    295     if( strstr(z, "/*safe-for-%s*/")!=0 ) return 1;
   296         -    
          296  +
   297    297     return 0;
   298    298   }
   299    299   
   300    300   /*
   301    301   ** Processing flags
   302    302   */
   303    303   #define FMT_NO_S   0x00001     /* Do not allow %s substitutions */
................................................................................
   458    458       if( z[len]==0 ) break;
   459    459       z[len] = 0;
   460    460       for(i=len-1; i>0 && isspace(z[i]); i--){ z[i] = 0; }
   461    461       z += len + 1;
   462    462     }
   463    463     acType = (char*)&azArg[nArg];
   464    464     if( fmtArg>nArg ){
   465         -    printf("%s:%d: too few arguments to %.*s()\n", 
          465  +    printf("%s:%d: too few arguments to %.*s()\n",
   466    466              zFilename, lnFCall, szFName, zFCall);
   467    467       nErr++;
   468    468     }else{
   469    469       const char *zFmt = azArg[fmtArg-1];
   470    470       const char *zOverride = strstr(zFmt, "/*works-like:");
   471    471       if( zOverride ) zFmt = zOverride + sizeof("/*works-like:")-1;
   472    472       if( !is_string_lit(zFmt) ){
................................................................................
   535    535           nCurly++;
   536    536         }else if( z[0]=='}' ){
   537    537           nCurly--;
   538    538         }else if( nCurly>0 && z[0]=='(' && ePrev==TK_ID
   539    539               && (x = isFormatFunc(zPrev,szPrev,&fmtFlags))>0 ){
   540    540           nErr += checkFormatFunc(zName, zPrev, lnPrev, x, fmtFlags);
   541    541         }
   542         -    }    
          542  +    }
   543    543       zPrev = z;
   544    544       ePrev = eToken;
   545    545       szPrev = szToken;
   546    546       lnPrev = ln;
   547    547     }
   548    548     return nErr;
   549    549   }

Changes to src/configure.c.

    88     88     { "css",                    CONFIGSET_CSS  },
    89     89     { "header",                 CONFIGSET_SKIN },
    90     90     { "footer",                 CONFIGSET_SKIN },
    91     91     { "logo-mimetype",          CONFIGSET_SKIN },
    92     92     { "logo-image",             CONFIGSET_SKIN },
    93     93     { "background-mimetype",    CONFIGSET_SKIN },
    94     94     { "background-image",       CONFIGSET_SKIN },
    95         -  { "index-page",             CONFIGSET_SKIN },
    96     95     { "timeline-block-markup",  CONFIGSET_SKIN },
    97     96     { "timeline-max-comment",   CONFIGSET_SKIN },
    98     97     { "timeline-plaintext",     CONFIGSET_SKIN },
    99     98     { "adunit",                 CONFIGSET_SKIN },
   100     99     { "adunit-omit-if-admin",   CONFIGSET_SKIN },
   101    100     { "adunit-omit-if-user",    CONFIGSET_SKIN },
          101  +  { "white-foreground",       CONFIGSET_SKIN },
   102    102   #ifdef FOSSIL_ENABLE_TH1_DOCS
   103    103     { "th1-docs",               CONFIGSET_TH1 },
   104    104   #endif
   105    105   #ifdef FOSSIL_ENABLE_TH1_HOOKS
   106    106     { "th1-hooks",              CONFIGSET_TH1 },
   107    107   #endif
   108    108     { "th1-setup",              CONFIGSET_TH1 },
................................................................................
   112    112     { "tcl",                    CONFIGSET_TH1 },
   113    113     { "tcl-setup",              CONFIGSET_TH1 },
   114    114   #endif
   115    115   
   116    116     { "project-name",           CONFIGSET_PROJ },
   117    117     { "short-project-name",     CONFIGSET_PROJ },
   118    118     { "project-description",    CONFIGSET_PROJ },
          119  +  { "index-page",             CONFIGSET_PROJ },
   119    120     { "manifest",               CONFIGSET_PROJ },
   120    121     { "binary-glob",            CONFIGSET_PROJ },
   121    122     { "clean-glob",             CONFIGSET_PROJ },
   122    123     { "ignore-glob",            CONFIGSET_PROJ },
   123    124     { "keep-glob",              CONFIGSET_PROJ },
   124    125     { "crnl-glob",              CONFIGSET_PROJ },
   125    126     { "encoding-glob",          CONFIGSET_PROJ },
................................................................................
   987    988     }else
   988    989     {
   989    990       fossil_fatal("METHOD should be one of:"
   990    991                    " export import merge pull push reset");
   991    992     }
   992    993     configure_rebuild();
   993    994   }
          995  +
          996  +
          997  +/*
          998  +** COMMAND: test-var-list
          999  +**
         1000  +** Usage: %fossil test-var-list ?PATTERN? ?--unset? ?--mtime?
         1001  +**
         1002  +** Show the content of the CONFIG table in a repository.  If PATTERN is
         1003  +** specified, then only show the entries that match that glob pattern.
         1004  +** Last modification time is shown if the --mtime option is present.
         1005  +**
         1006  +** If the --unset option is included, then entries are deleted rather than
         1007  +** being displayed.  WARNING! This cannot be undone.  Be sure you know what
         1008  +** you are doing!  The --unset option only works if there is a PATTERN.
         1009  +** Probably you should run the command once without --unset to make sure
         1010  +** you know exactly what is being deleted.
         1011  +**
         1012  +** If not in an open check-out, use the -R REPO option to specify a
         1013  +** a repository.
         1014  +*/
         1015  +void test_var_list_cmd(void){
         1016  +  Stmt q;
         1017  +  int i, j;
         1018  +  const char *zPattern = 0;
         1019  +  int doUnset;
         1020  +  int showMtime;
         1021  +  Blob sql;
         1022  +  Blob ans;
         1023  +  unsigned char zTrans[1000];
         1024  +
         1025  +  doUnset = find_option("unset",0,0)!=0;
         1026  +  showMtime = find_option("mtime",0,0)!=0;
         1027  +  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
         1028  +  verify_all_options();
         1029  +  if( g.argc>=3 ){
         1030  +    zPattern = g.argv[2];
         1031  +  }
         1032  +  blob_init(&sql,0,0);
         1033  +  blob_appendf(&sql, "SELECT name, value, datetime(mtime,'unixepoch')"
         1034  +                     " FROM config");
         1035  +  if( zPattern ){
         1036  +    blob_appendf(&sql, " WHERE name GLOB %Q", zPattern);
         1037  +  }
         1038  +  if( showMtime ){
         1039  +    blob_appendf(&sql, " ORDER BY mtime, name");
         1040  +  }else{
         1041  +    blob_appendf(&sql, " ORDER BY name");
         1042  +  }
         1043  +  db_prepare(&q, "%s", blob_str(&sql)/*safe-for-%s*/);
         1044  +  blob_reset(&sql);
         1045  +#define MX_VAL 40
         1046  +#define MX_NM  28
         1047  +#define MX_LONGNM 60
         1048  +  while( db_step(&q)==SQLITE_ROW ){
         1049  +    const char *zName = db_column_text(&q,0);
         1050  +    int nName = db_column_bytes(&q,0);
         1051  +    const char *zValue = db_column_text(&q,1);
         1052  +    int szValue = db_column_bytes(&q,1);
         1053  +    const char *zMTime = db_column_text(&q,2);
         1054  +    for(i=j=0; j<MX_VAL && zValue[i]; i++){
         1055  +      unsigned char c = (unsigned char)zValue[i];
         1056  +      if( c>=' ' && c<='~' ){
         1057  +        zTrans[j++] = c;
         1058  +      }else{
         1059  +        zTrans[j++] = '\\';
         1060  +        if( c=='\n' ){
         1061  +          zTrans[j++] = 'n';
         1062  +        }else if( c=='\r' ){
         1063  +          zTrans[j++] = 'r';
         1064  +        }else if( c=='\t' ){
         1065  +          zTrans[j++] = 't';
         1066  +        }else{
         1067  +          zTrans[j++] = '0' + ((c>>6)&7);
         1068  +          zTrans[j++] = '0' + ((c>>3)&7);
         1069  +          zTrans[j++] = '0' + (c&7);
         1070  +        }
         1071  +      }
         1072  +    }
         1073  +    zTrans[j] = 0;
         1074  +    if( i<szValue ){
         1075  +      sqlite3_snprintf(sizeof(zTrans)-j, (char*)zTrans+j, "...+%d", szValue-i);
         1076  +      j += (int)strlen((char*)zTrans+j);
         1077  +    }
         1078  +    if( showMtime ){
         1079  +      fossil_print("%s:%*s%s\n", zName, 58-nName, "", zMTime);
         1080  +    }else if( nName<MX_NM-2 ){
         1081  +      fossil_print("%s:%*s%s\n", zName, MX_NM-1-nName, "", zTrans);
         1082  +    }else if( nName<MX_LONGNM-2 && j<10 ){
         1083  +      fossil_print("%s:%*s%s\n", zName, MX_LONGNM-1-nName, "", zTrans);
         1084  +    }else{
         1085  +      fossil_print("%s:\n%*s%s\n", zName, MX_NM, "", zTrans);
         1086  +    }
         1087  +  }
         1088  +  db_finalize(&q);
         1089  +  if( zPattern && doUnset ){
         1090  +    prompt_user("Delete all of the above? (y/N)? ", &ans);
         1091  +    if( blob_str(&ans)[0]=='y' || blob_str(&ans)[0]=='Y' ){
         1092  +      db_multi_exec("DELETE FROM config WHERE name GLOB %Q", zPattern);
         1093  +    }
         1094  +    blob_reset(&ans);
         1095  +  }
         1096  +}
         1097  +
         1098  +/*
         1099  +** COMMAND: test-var-get
         1100  +**
         1101  +** Usage: %fossil test-var-get VAR ?FILE?
         1102  +**
         1103  +** Write the text of the VAR variable into FILE.  If FILE is "-"
         1104  +** or is omitted then output goes to standard output.  VAR can be a
         1105  +** GLOB pattern.
         1106  +**
         1107  +** If not in an open check-out, use the -R REPO option to specify a
         1108  +** a repository.
         1109  +*/
         1110  +void test_var_get_cmd(void){
         1111  +  const char *zVar;
         1112  +  const char *zFile;
         1113  +  int n;
         1114  +  Blob x;
         1115  +  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
         1116  +  verify_all_options();
         1117  +  if( g.argc<3 ){
         1118  +    usage("VAR ?FILE?");
         1119  +  }
         1120  +  zVar = g.argv[2];
         1121  +  zFile = g.argc>=4 ? g.argv[3] : "-";
         1122  +  n = db_int(0, "SELECT count(*) FROM config WHERE name GLOB %Q", zVar);
         1123  +  if( n==0 ){
         1124  +    fossil_fatal("no match for %Q", zVar);
         1125  +  }
         1126  +  if( n>1 ){
         1127  +    fossil_fatal("multiple matches: %s",
         1128  +      db_text(0, "SELECT group_concat(quote(name),', ') FROM ("
         1129  +                 " SELECT name FROM config WHERE name GLOB %Q ORDER BY 1)",
         1130  +                 zVar));
         1131  +  }
         1132  +  blob_init(&x,0,0);
         1133  +  db_blob(&x, "SELECT value FROM config WHERE name GLOB %Q", zVar);
         1134  +  blob_write_to_file(&x, zFile);
         1135  +}
         1136  +
         1137  +/*
         1138  +** COMMAND: test-var-set
         1139  +**
         1140  +** Usage: %fossil test-var-set VAR ?VALUE? ?--file FILE?
         1141  +**
         1142  +** Store VALUE or the content of FILE (exactly one of which must be
         1143  +** supplied) into variable VAR.  Use a FILE of "-" to read from
         1144  +** standard input.
         1145  +**
         1146  +** WARNING: changing the value of a variable can interfere with the
         1147  +** operation of Fossil.  Be sure you know what you are doing.
         1148  +**
         1149  +** Use "--blob FILE" instead of "--file FILE" to load a binary blob
         1150  +** such as a GIF.
         1151  +*/
         1152  +void test_var_set_cmd(void){
         1153  +  const char *zVar;
         1154  +  const char *zFile;
         1155  +  const char *zBlob;
         1156  +  Blob x;
         1157  +  Stmt ins;
         1158  +  zFile = find_option("file",0,1);
         1159  +  zBlob = find_option("blob",0,1);
         1160  +  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
         1161  +  verify_all_options();
         1162  +  if( g.argc<3 || (zFile==0 && zBlob==0 && g.argc<4) ){
         1163  +    usage("VAR ?VALUE? ?--file FILE?");
         1164  +  }
         1165  +  zVar = g.argv[2];
         1166  +  if( zFile ){
         1167  +    if( zBlob ) fossil_fatal("cannot do both --file or --blob");
         1168  +    blob_read_from_file(&x, zFile);
         1169  +  }else if( zBlob ){
         1170  +    blob_read_from_file(&x, zBlob);
         1171  +  }else{
         1172  +    blob_init(&x,g.argv[3],-1);
         1173  +  }
         1174  +  db_prepare(&ins,
         1175  +     "REPLACE INTO config(name,value,mtime)"
         1176  +     "VALUES(%Q,:val,now())", zVar);
         1177  +  if( zBlob ){
         1178  +    db_bind_blob(&ins, ":val", &x);
         1179  +  }else{
         1180  +    db_bind_text(&ins, ":val", blob_str(&x));
         1181  +  }
         1182  +  db_step(&ins);
         1183  +  db_finalize(&ins);
         1184  +  blob_reset(&x);
         1185  +}

Changes to src/db.c.

    63     63   /*
    64     64   ** Call this routine when a database error occurs.
    65     65   */
    66     66   static void db_err(const char *zFormat, ...){
    67     67     va_list ap;
    68     68     char *z;
    69     69     int rc = 1;
    70         -  static const char zRebuildMsg[] =
    71         -      "If you have recently updated your fossil executable, you might\n"
    72         -      "need to run \"fossil all rebuild\" to bring the repository\n"
    73         -      "schemas up to date.\n";
    74     70     va_start(ap, zFormat);
    75     71     z = vmprintf(zFormat, ap);
    76     72     va_end(ap);
    77     73   #ifdef FOSSIL_ENABLE_JSON
    78     74     if( g.json.isJsonMode ){
    79     75       json_err( 0, z, 1 );
    80     76       if( g.isHTTP ){
................................................................................
    86     82     if( g.xferPanic ){
    87     83       cgi_reset_content();
    88     84       @ error Database\serror:\s%F(z)
    89     85         cgi_reply();
    90     86     }
    91     87     else if( g.cgiOutput ){
    92     88       g.cgiOutput = 0;
    93         -    cgi_printf("<h1>Database Error</h1>\n"
    94         -               "<pre>%h</pre>\n<p>%s</p>\n", z, zRebuildMsg);
           89  +    cgi_printf("<h1>Database Error</h1>\n<p>%h</p>\n", z);
    95     90       cgi_reply();
    96     91     }else{
    97         -    fprintf(stderr, "%s: %s\n\n%s", g.argv[0], z, zRebuildMsg);
           92  +    fprintf(stderr, "%s: %s\n", g.argv[0], z);
    98     93     }
    99     94     free(z);
   100     95     db_force_rollback();
   101     96     fossil_exit(rc);
   102     97   }
   103     98   
   104     99   /*
................................................................................
   162    157     if( g.db==0 ) return;
   163    158     if( db.nBegin<=0 ) return;
   164    159     if( rollbackFlag ) db.doRollback = 1;
   165    160     db.nBegin--;
   166    161     if( db.nBegin==0 ){
   167    162       int i;
   168    163       if( db.doRollback==0 && db.nPriorChanges<sqlite3_total_changes(g.db) ){
          164  +      i = 0;
   169    165         while( db.nBeforeCommit ){
   170    166           db.nBeforeCommit--;
   171         -        sqlite3_exec(g.db, db.azBeforeCommit[db.nBeforeCommit], 0, 0, 0);
   172         -        sqlite3_free(db.azBeforeCommit[db.nBeforeCommit]);
          167  +        sqlite3_exec(g.db, db.azBeforeCommit[i], 0, 0, 0);
          168  +        sqlite3_free(db.azBeforeCommit[i]);
          169  +        i++;
   173    170         }
   174    171         leaf_do_pending_checks();
   175    172       }
   176    173       for(i=0; db.doRollback==0 && i<db.nCommitHook; i++){
   177    174         db.doRollback |= db.aHook[i].xHook();
   178    175       }
   179    176       while( db.pAllStmt ){
................................................................................
   545    542     va_start(ap, zSql);
   546    543     blob_vappendf(&sql, zSql, ap);
   547    544     va_end(ap);
   548    545     z = blob_str(&sql);
   549    546     while( rc==SQLITE_OK && z[0] ){
   550    547       pStmt = 0;
   551    548       rc = sqlite3_prepare_v2(g.db, z, -1, &pStmt, &zEnd);
   552         -    if( rc!=SQLITE_OK ) break;
   553         -    if( pStmt ){
          549  +    if( rc ){
          550  +      db_err("%s: {%s}", sqlite3_errmsg(g.db), z);
          551  +    }else if( pStmt ){
   554    552         db.nPrepare++;
   555    553         while( sqlite3_step(pStmt)==SQLITE_ROW ){}
   556    554         rc = sqlite3_finalize(pStmt);
   557    555         if( rc ) db_err("%s: {%.*s}", sqlite3_errmsg(g.db), (int)(zEnd-z), z);
   558    556       }
   559    557       z = zEnd;
   560    558     }
................................................................................
   768    766   **
   769    767   */
   770    768   void db_sym2rid_function(
   771    769     sqlite3_context *context,
   772    770     int argc,
   773    771     sqlite3_value **argv
   774    772   ){
   775         -  char const * arg;
   776         -  char const * type;
          773  +  const char *arg;
          774  +  const char *type;
   777    775     if(1 != argc && 2 != argc){
   778    776       sqlite3_result_error(context, "Expecting one or two arguments", -1);
   779    777       return;
   780    778     }
   781    779     arg = (const char*)sqlite3_value_text(argv[0]);
   782    780     if(!arg){
   783    781       sqlite3_result_error(context, "Expecting a STRING argument", -1);
................................................................................
   793    791       }else{
   794    792         sqlite3_result_int64(context, rid);
   795    793       }
   796    794     }
   797    795   }
   798    796   
   799    797   /*
   800         -** Register the SQL functions that are useful both to the internal 
          798  +** Register the SQL functions that are useful both to the internal
   801    799   ** representation and to the "fossil sql" command.
   802    800   */
   803    801   void db_add_aux_functions(sqlite3 *db){
   804    802     sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0,
   805    803                             db_checkin_mtime_function, 0, 0);
   806    804     sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0,
   807    805                             db_sym2rid_function, 0, 0);
   808    806     sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0,
   809    807                             db_sym2rid_function, 0, 0);
          808  +  sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
          809  +                                 db_now_function, 0, 0);
   810    810   }
   811    811   
   812    812   
   813    813   /*
   814    814   ** Open a database file.  Return a pointer to the new database
   815    815   ** connection.  An error results in process abort.
   816    816   */
................................................................................
   825    825          g.zVfsName
   826    826     );
   827    827     if( rc!=SQLITE_OK ){
   828    828       db_err("[%s]: %s", zDbName, sqlite3_errmsg(db));
   829    829     }
   830    830     sqlite3_busy_timeout(db, 5000);
   831    831     sqlite3_wal_autocheckpoint(db, 1);  /* Set to checkpoint frequently */
   832         -  sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
   833         -                                 db_now_function, 0, 0);
   834    832     sqlite3_create_function(db, "user", 0, SQLITE_UTF8, 0, db_sql_user, 0, 0);
   835    833     sqlite3_create_function(db, "cgi", 1, SQLITE_UTF8, 0, db_sql_cgi, 0, 0);
   836    834     sqlite3_create_function(db, "cgi", 2, SQLITE_UTF8, 0, db_sql_cgi, 0, 0);
   837    835     sqlite3_create_function(db, "print", -1, SQLITE_UTF8, 0,db_sql_print,0,0);
   838    836     sqlite3_create_function(
   839    837       db, "is_selected", 1, SQLITE_UTF8, 0, file_is_selected,0,0
   840    838     );
   841    839     sqlite3_create_function(
   842    840       db, "if_selected", 3, SQLITE_UTF8, 0, file_is_selected,0,0
   843    841     );
   844    842     if( g.fSqlTrace ) sqlite3_trace(db, db_sql_trace, 0);
   845         -  db_add_aux_functions(db);  
          843  +  db_add_aux_functions(db);
   846    844     re_add_sql_func(db);  /* The REGEXP operator */
   847    845     foci_register(db);    /* The "files_of_checkin" virtual table */
   848    846     sqlite3_exec(db, "PRAGMA foreign_keys=OFF;", 0, 0, 0);
   849    847     return db;
   850    848   }
   851    849   
   852    850   
................................................................................
   925    923   void db_open_config(int useAttach){
   926    924     char *zDbName;
   927    925     char *zHome;
   928    926     if( g.zConfigDbName ){
   929    927       if( useAttach==g.useAttach ) return;
   930    928       db_close_config();
   931    929     }
          930  +  zHome = fossil_getenv("FOSSIL_HOME");
   932    931   #if defined(_WIN32) || defined(__CYGWIN__)
   933         -  zHome = fossil_getenv("LOCALAPPDATA");
   934    932     if( zHome==0 ){
   935         -    zHome = fossil_getenv("APPDATA");
          933  +    zHome = fossil_getenv("LOCALAPPDATA");
   936    934       if( zHome==0 ){
   937         -      char *zDrive = fossil_getenv("HOMEDRIVE");
   938         -      zHome = fossil_getenv("HOMEPATH");
   939         -      if( zDrive && zHome ) zHome = mprintf("%s%s", zDrive, zHome);
          935  +      zHome = fossil_getenv("APPDATA");
          936  +      if( zHome==0 ){
          937  +        char *zDrive = fossil_getenv("HOMEDRIVE");
          938  +        char *zPath = fossil_getenv("HOMEPATH");
          939  +        if( zDrive && zPath ) zHome = mprintf("%s%s", zDrive, zPath);
          940  +      }
   940    941       }
   941    942     }
   942    943     if( zHome==0 ){
   943         -    fossil_fatal("cannot locate home directory - "
   944         -                "please set the LOCALAPPDATA or APPDATA or HOMEPATH "
   945         -                "environment variables");
          944  +    fossil_fatal("cannot locate home directory - please set the "
          945  +                 "FOSSIL_HOME, LOCALAPPDATA, APPDATA, or HOMEPATH "
          946  +                 "environment variables");
   946    947     }
   947    948   #else
   948         -  zHome = fossil_getenv("HOME");
          949  +  if( zHome==0 ){
          950  +    zHome = fossil_getenv("HOME");
          951  +  }
   949    952     if( zHome==0 ){
   950         -    fossil_fatal("cannot locate home directory - "
   951         -                 "please set the HOME environment variable");
          953  +    fossil_fatal("cannot locate home directory - please set the "
          954  +                 "FOSSIL_HOME or HOME environment variables");
   952    955     }
   953    956   #endif
   954    957     if( file_isdir(zHome)!=1 ){
   955    958       fossil_fatal("invalid home directory: %s", zHome);
   956    959     }
   957    960   #if defined(_WIN32) || defined(__CYGWIN__)
   958    961     /* . filenames give some window systems problems and many apps problems */
................................................................................
  1169   1172     }
  1170   1173     g.zRepositoryName = mprintf("%s", zDbName);
  1171   1174     db_open_or_attach(g.zRepositoryName, "repository", 0);
  1172   1175     g.repositoryOpen = 1;
  1173   1176     /* Cache "allow-symlinks" option, because we'll need it on every stat call */
  1174   1177     g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
  1175   1178     g.zAuxSchema = db_get("aux-schema","");
         1179  +
         1180  +  /* Verify that the PLINK table has a new column added by the
         1181  +  ** 2014-11-28 schema change.  Create it if necessary.  This code
         1182  +  ** can be removed in the future, once all users have upgraded to the
         1183  +  ** 2014-11-28 or later schema.
         1184  +  */
         1185  +  if( !db_table_has_column("repository","plink","baseid") ){
         1186  +    db_multi_exec(
         1187  +      "ALTER TABLE %s.plink ADD COLUMN baseid;", db_name("repository")
         1188  +    );
         1189  +  }
  1176   1190   
  1177   1191     /* Verify that the MLINK table has the newer columns added by the
  1178   1192     ** 2015-01-24 schema change.  Create them if necessary.  This code
  1179   1193     ** can be removed in the future, once all users have upgraded to the
  1180         -  ** 2015-01-24 schema.
  1181         -  */  
         1194  +  ** 2015-01-24 or later schema.
         1195  +  */
  1182   1196     if( !db_table_has_column("repository","mlink","isaux") ){
  1183   1197       db_begin_transaction();
  1184   1198       db_multi_exec(
  1185   1199         "ALTER TABLE %s.mlink ADD COLUMN pmid INTEGER DEFAULT 0;"
  1186         -      "ALTER TABLE %s.mlink ADD COLUMN isaux INTEGER DEFAULT 0;",
         1200  +      "ALTER TABLE %s.mlink ADD COLUMN isaux BOOLEAN DEFAULT 0;",
  1187   1201         db_name("repository"), db_name("repository")
  1188   1202       );
  1189   1203       db_end_transaction(0);
  1190   1204     }
  1191   1205   }
  1192   1206   
  1193   1207   /*
................................................................................
  1252   1266   }
  1253   1267   
  1254   1268   /*
  1255   1269   ** Return TRUE if the schema is out-of-date
  1256   1270   */
  1257   1271   int db_schema_is_outofdate(void){
  1258   1272     return strcmp(g.zAuxSchema,AUX_SCHEMA_MIN)<0
  1259         -      || strcmp(g.zAuxSchema,"2015-01-24")>0;
         1273  +      || strcmp(g.zAuxSchema,AUX_SCHEMA_MAX)>0;
  1260   1274   }
  1261   1275   
  1262   1276   /*
  1263   1277   ** Return true if the database is writeable
  1264   1278   */
  1265   1279   int db_is_writeable(const char *zName){
  1266   1280     return g.db!=0 && !sqlite3_db_readonly(g.db, db_name(zName));
................................................................................
  1362   1376       fprintf(stderr, "-- prepared statements    %10d\n", db.nPrepare);
  1363   1377     }
  1364   1378     while( db.pAllStmt ){
  1365   1379       db_finalize(db.pAllStmt);
  1366   1380     }
  1367   1381     db_end_transaction(1);
  1368   1382     pStmt = 0;
  1369         -  if( reportErrors ){
  1370         -    while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){
  1371         -      fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt));
  1372         -    }
  1373         -  }
  1374   1383     db_close_config();
         1384  +
         1385  +  /* If the localdb (the check-out database) is open and if it has
         1386  +  ** a lot of unused free space, then VACUUM it as we shut down.
         1387  +  */
         1388  +  if( g.localOpen && strcmp(db_name("localdb"),"main")==0 ){
         1389  +    int nFree = db_int(0, "PRAGMA main.freelist_count");
         1390  +    int nTotal = db_int(0, "PRAGMA main.page_count");
         1391  +    if( nFree>nTotal/4 ){
         1392  +      db_multi_exec("VACUUM;");
         1393  +    }
         1394  +  }
         1395  +
  1375   1396     if( g.db ){
         1397  +    int rc;
  1376   1398       sqlite3_wal_checkpoint(g.db, 0);
  1377         -    sqlite3_close(g.db);
         1399  +    rc = sqlite3_close(g.db);
         1400  +    if( rc==SQLITE_BUSY && reportErrors ){
         1401  +      while( (pStmt = sqlite3_next_stmt(g.db, pStmt))!=0 ){
         1402  +        fossil_warning("unfinalized SQL statement: [%s]", sqlite3_sql(pStmt));
         1403  +      }
         1404  +    }
  1378   1405       g.db = 0;
  1379   1406       g.zMainDbType = 0;
  1380   1407     }
  1381   1408     g.repositoryOpen = 0;
  1382   1409     g.localOpen = 0;
  1383   1410     assert( g.dbConfig==0 );
  1384   1411     assert( g.useAttach==0 );
................................................................................
  1435   1462     db_multi_exec(
  1436   1463        "UPDATE user SET cap='s', pw=lower(hex(randomblob(3)))"
  1437   1464        " WHERE login=%Q", zUser
  1438   1465     );
  1439   1466     if( !setupUserOnly ){
  1440   1467       db_multi_exec(
  1441   1468          "INSERT OR IGNORE INTO user(login,pw,cap,info)"
  1442         -       "   VALUES('anonymous',hex(randomblob(8)),'hmncz','Anon');"
         1469  +       "   VALUES('anonymous',hex(randomblob(8)),'hmnc','Anon');"
  1443   1470          "INSERT OR IGNORE INTO user(login,pw,cap,info)"
  1444         -       "   VALUES('nobody','','gjor','Nobody');"
         1471  +       "   VALUES('nobody','','gjorz','Nobody');"
  1445   1472          "INSERT OR IGNORE INTO user(login,pw,cap,info)"
  1446   1473          "   VALUES('developer','','dei','Dev');"
  1447   1474          "INSERT OR IGNORE INTO user(login,pw,cap,info)"
  1448   1475          "   VALUES('reader','','kptw','Reader');"
  1449   1476       );
  1450   1477     }
  1451   1478   }
................................................................................
  1490   1517   const char *db_setting_inop_rhs(){
  1491   1518     Blob x;
  1492   1519     int i;
  1493   1520     const char *zSep = "";
  1494   1521   
  1495   1522     blob_zero(&x);
  1496   1523     blob_append_sql(&x, "(");
  1497         -  for(i=0; ctrlSettings[i].name; i++){
  1498         -    blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, ctrlSettings[i].name);
         1524  +  for(i=0; aSetting[i].name; i++){
         1525  +    blob_append_sql(&x, "%s%Q", zSep/*safe-for-%s*/, aSetting[i].name);
  1499   1526       zSep = ",";
  1500   1527     }
  1501   1528     blob_append_sql(&x, ")");
  1502   1529     return blob_sql_text(&x);
  1503   1530   }
  1504   1531   
  1505   1532   /*
................................................................................
  1652   1679     find_option("empty", 0, 0); /* deprecated */
  1653   1680     /* We should be done with options.. */
  1654   1681     verify_all_options();
  1655   1682   
  1656   1683     if( g.argc!=3 ){
  1657   1684       usage("REPOSITORY-NAME");
  1658   1685     }
         1686  + 
         1687  +  if( -1 != file_size(g.argv[2]) ){
         1688  +    fossil_fatal("file already exists: %s", g.argv[2]);
         1689  +  }
         1690  +
  1659   1691     db_create_repository(g.argv[2]);
  1660   1692     db_open_repository(g.argv[2]);
  1661   1693     db_open_config(0);
  1662   1694     if( zTemplate ) db_attach(zTemplate, "settingSrc");
  1663   1695     db_begin_transaction();
  1664   1696     db_initial_setup(zTemplate, zDate, zDefaultUser, makeServerCodes);
  1665   1697     db_end_transaction(0);
................................................................................
  1883   1915       g.zMainDbType = g.zConfigDbType;
  1884   1916       g.dbConfig = dbTemp;
  1885   1917       g.zConfigDbType = zTempDbType;
  1886   1918     }
  1887   1919   }
  1888   1920   
  1889   1921   /*
  1890         -** Logic for reading potentially versioned settings from
  1891         -** .fossil-settings/<name> , and emits warnings if necessary.
  1892         -** Returns the non-versioned value without modification if there is no
  1893         -** versioned value.
         1922  +** Try to read a versioned setting string from .fossil-settings/<name>.
         1923  +**
         1924  +** Return the text of the string if it is found.  Return NULL if not
         1925  +** found.
         1926  +**
         1927  +** If the zNonVersionedSetting parameter is not NULL then it holds the
         1928  +** non-versioned value for this setting.  If both a versioned and ad
         1929  +** non-versioned value exist and are not equal, then a warning message
         1930  +** might be generated.
  1894   1931   */
  1895         -char *db_get_do_versionable(const char *zName, char *zNonVersionedSetting){
         1932  +char *db_get_versioned(const char *zName, char *zNonVersionedSetting){
  1896   1933     char *zVersionedSetting = 0;
  1897   1934     int noWarn = 0;
  1898   1935     struct _cacheEntry {
  1899   1936       struct _cacheEntry *next;
  1900   1937       const char *zName, *zValue;
  1901   1938     } *cacheEntry = 0;
  1902   1939     static struct _cacheEntry *cache = 0;
................................................................................
  1963   2000     return ( zVersionedSetting!=0 ) ? zVersionedSetting : zNonVersionedSetting;
  1964   2001   }
  1965   2002   
  1966   2003   
  1967   2004   /*
  1968   2005   ** Get and set values from the CONFIG, GLOBAL_CONFIG and VVAR table in the
  1969   2006   ** repository and local databases.
         2007  +**
         2008  +** If no such variable exists, return zDefault.  Or, if zName is the name
         2009  +** of a setting, then the zDefault is ignored and the default value of the
         2010  +** setting is returned instead.  If zName is a versioned setting, then
         2011  +** versioned value takes priority.
  1970   2012   */
  1971   2013   char *db_get(const char *zName, char *zDefault){
  1972   2014     char *z = 0;
  1973         -  int i;
  1974         -  const struct stControlSettings *ctrlSetting = 0;
  1975         -  /* Is this a setting? */
  1976         -  for(i=0; ctrlSettings[i].name; i++){
  1977         -    if( strcmp(ctrlSettings[i].name, zName)==0 ){
  1978         -      ctrlSetting = &(ctrlSettings[i]);
  1979         -      break;
  1980         -    }
  1981         -  }
         2015  +  const Setting *pSetting = db_find_setting(zName, 0);
  1982   2016     if( g.repositoryOpen ){
  1983   2017       z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
  1984   2018     }
  1985   2019     if( z==0 && g.zConfigDbName ){
  1986   2020       db_swap_connections();
  1987   2021       z = db_text(0, "SELECT value FROM global_config WHERE name=%Q", zName);
  1988   2022       db_swap_connections();
  1989   2023     }
  1990         -  if( ctrlSetting!=0 && ctrlSetting->versionable ){
         2024  +  if( pSetting!=0 && pSetting->versionable ){
  1991   2025       /* This is a versionable setting, try and get the info from a
  1992   2026       ** checked out file */
  1993         -    z = db_get_do_versionable(zName, z);
         2027  +    z = db_get_versioned(zName, z);
  1994   2028     }
  1995   2029     if( z==0 ){
  1996         -    z = zDefault;
         2030  +    if( zDefault==0 && pSetting && pSetting->def[0] ){
         2031  +      z = fossil_strdup(pSetting->def);
         2032  +    }else{
         2033  +      z = zDefault;
         2034  +    }
  1997   2035     }
  1998   2036     return z;
  1999   2037   }
  2000   2038   char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){
  2001   2039     char *z = 0;
  2002   2040     if( g.repositoryOpen ){
  2003   2041       z = db_text(0, "SELECT mtime FROM config WHERE name=%Q", zName);
................................................................................
  2255   2293       checkout_cmd();
  2256   2294     }
  2257   2295     g.argc = 2;
  2258   2296     info_cmd();
  2259   2297   }
  2260   2298   
  2261   2299   /*
  2262         -** Print the value of a setting named zName
         2300  +** Print the current value of a setting identified by the pSetting
         2301  +** pointer.
  2263   2302   */
  2264         -static void print_setting(
  2265         -  const struct stControlSettings *ctrlSetting,
  2266         -  int localOpen
  2267         -){
         2303  +static void print_setting(const Setting *pSetting){
  2268   2304     Stmt q;
  2269   2305     if( g.repositoryOpen ){
  2270   2306       db_prepare(&q,
  2271   2307          "SELECT '(local)', value FROM config WHERE name=%Q"
  2272   2308          " UNION ALL "
  2273   2309          "SELECT '(global)', value FROM global_config WHERE name=%Q",
  2274         -       ctrlSetting->name, ctrlSetting->name
         2310  +       pSetting->name, pSetting->name
  2275   2311       );
  2276   2312     }else{
  2277   2313       db_prepare(&q,
  2278   2314         "SELECT '(global)', value FROM global_config WHERE name=%Q",
  2279         -      ctrlSetting->name
         2315  +      pSetting->name
  2280   2316       );
  2281   2317     }
  2282   2318     if( db_step(&q)==SQLITE_ROW ){
  2283         -    fossil_print("%-20s %-8s %s\n", ctrlSetting->name, db_column_text(&q, 0),
         2319  +    fossil_print("%-20s %-8s %s\n", pSetting->name, db_column_text(&q, 0),
  2284   2320           db_column_text(&q, 1));
  2285   2321     }else{
  2286         -    fossil_print("%-20s\n", ctrlSetting->name);
         2322  +    fossil_print("%-20s\n", pSetting->name);
  2287   2323     }
  2288         -  if( ctrlSetting->versionable && localOpen ){
         2324  +  if( pSetting->versionable && g.localOpen ){
  2289   2325       /* Check to see if this is overridden by a versionable settings file */
  2290   2326       Blob versionedPathname;
  2291   2327       blob_zero(&versionedPathname);
  2292   2328       blob_appendf(&versionedPathname, "%s/.fossil-settings/%s",
  2293         -                 g.zLocalRoot, ctrlSetting->name);
         2329  +                 g.zLocalRoot, pSetting->name);
  2294   2330       if( file_size(blob_str(&versionedPathname))>=0 ){
  2295   2331         fossil_print("  (overridden by contents of file .fossil-settings/%s)\n",
  2296         -                   ctrlSetting->name);
         2332  +                   pSetting->name);
  2297   2333       }
  2298   2334     }
  2299   2335     db_finalize(&q);
  2300   2336   }
  2301   2337   
  2302   2338   
         2339  +#if INTERFACE
  2303   2340   /*
  2304         -** define all settings, which can be controlled via the set/unset
  2305         -** command. var is the name of the internal configuration name for db_(un)set.
         2341  +** Define all settings, which can be controlled via the set/unset
         2342  +** command.
         2343  +**
         2344  +** var is the name of the internal configuration name for db_(un)set.
  2306   2345   ** If var is 0, the settings name is used.
         2346  +**
  2307   2347   ** width is the length for the edit field on the behavior page, 0
  2308   2348   ** is used for on/off checkboxes.
         2349  +**
  2309   2350   ** The behaviour page doesn't use a special layout. It lists all
  2310   2351   ** set-commands and displays the 'set'-help as info.
  2311   2352   */
  2312         -#if INTERFACE
  2313         -struct stControlSettings {
         2353  +struct Setting {
  2314   2354     const char *name;     /* Name of the setting */
  2315   2355     const char *var;      /* Internal variable name used by db_set() */
  2316   2356     int width;            /* Width of display.  0 for boolean values. */
  2317   2357     int versionable;      /* Is this setting versionable? */
  2318   2358     int forceTextArea;    /* Force using a text area for display? */
  2319   2359     const char *def;      /* Default value */
  2320   2360   };
  2321   2361   #endif /* INTERFACE */
  2322         -struct stControlSettings const ctrlSettings[] = {
         2362  +
         2363  +const Setting aSetting[] = {
  2323   2364     { "access-log",       0,              0, 0, 0, "off"                 },
  2324   2365     { "admin-log",        0,              0, 0, 0, "off"                 },
  2325   2366     { "allow-symlinks",   0,              0, 1, 0, "off"                 },
  2326   2367     { "auto-captcha",     "autocaptcha",  0, 0, 0, "on"                  },
  2327   2368     { "auto-hyperlink",   0,              0, 0, 0, "on",                 },
  2328   2369     { "auto-shun",        0,              0, 0, 0, "on"                  },
  2329   2370     { "autosync",         0,              0, 0, 0, "on"                  },
  2330   2371     { "autosync-tries",   0,             16, 0, 0, "1"                   },
  2331   2372     { "binary-glob",      0,             40, 1, 0, ""                    },
  2332         -  { "clearsign",        0,              0, 0, 0, "off"                 },
  2333   2373   #if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \
  2334   2374       defined(__APPLE__)
  2335   2375     { "case-sensitive",   0,              0, 0, 0, "off"                 },
  2336   2376   #else
  2337   2377     { "case-sensitive",   0,              0, 0, 0, "on"                  },
  2338   2378   #endif
  2339   2379     { "clean-glob",       0,             40, 1, 0, ""                    },
         2380  +  { "clearsign",        0,              0, 0, 0, "off"                 },
  2340   2381     { "crnl-glob",        0,             40, 1, 0, ""                    },
  2341   2382     { "default-perms",    0,             16, 0, 0, "u"                   },
  2342   2383     { "diff-binary",      0,              0, 0, 0, "on"                  },
  2343   2384     { "diff-command",     0,             40, 0, 0, ""                    },
  2344   2385     { "dont-push",        0,              0, 0, 0, "off"                 },
  2345   2386     { "editor",           0,             32, 0, 0, ""                    },
  2346   2387     { "empty-dirs",       0,             40, 1, 0, ""                    },
  2347   2388     { "encoding-glob",    0,             40, 1, 0, ""                    },
  2348   2389     { "gdiff-command",    0,             40, 0, 0, "gdiff"               },
  2349   2390     { "gmerge-command",   0,             40, 0, 0, ""                    },
         2391  +  { "hash-digits",      0,              5, 0, 0, "10"                  },
  2350   2392     { "http-port",        0,             16, 0, 0, "8080"                },
  2351   2393     { "https-login",      0,              0, 0, 0, "off"                 },
  2352   2394     { "ignore-glob",      0,             40, 1, 0, ""                    },
  2353   2395     { "keep-glob",        0,             40, 1, 0, ""                    },
  2354   2396     { "localauth",        0,              0, 0, 0, "off"                 },
  2355   2397     { "main-branch",      0,             40, 0, 0, "trunk"               },
  2356   2398     { "manifest",         0,              0, 1, 0, "off"                 },
................................................................................
  2377   2419   #endif
  2378   2420     { "th1-setup",        0,             40, 1, 1, ""                    },
  2379   2421     { "th1-uri-regexp",   0,             40, 1, 0, ""                    },
  2380   2422     { "web-browser",      0,             32, 0, 0, ""                    },
  2381   2423     { "white-foreground", 0,              0, 0, 0, "off"                 },
  2382   2424     { 0,0,0,0,0,0 }
  2383   2425   };
         2426  +
         2427  +/*
         2428  +** Look up a control setting by its name.  Return a pointer to the Setting
         2429  +** object, or NULL if there is no such setting.
         2430  +**
         2431  +** If allowPrefix is true, then the Setting returned is the first one for
         2432  +** which zName is a prefix of the Setting name.
         2433  +*/
         2434  +const Setting *db_find_setting(const char *zName, int allowPrefix){
         2435  +  int lwr, mid, upr, c;
         2436  +  int n = (int)strlen(zName) + !allowPrefix;
         2437  +  lwr = 0;
         2438  +  upr = ArraySize(aSetting)-2;
         2439  +  while( upr>=lwr ){
         2440  +    mid = (upr+lwr)/2;
         2441  +    c = fossil_strncmp(zName, aSetting[mid].name, n);
         2442  +    if( c<0 ){
         2443  +      upr = mid - 1;
         2444  +    }else if( c>0 ){
         2445  +      lwr = mid + 1;
         2446  +    }else{
         2447  +      if( allowPrefix ){
         2448  +        while( mid>lwr && fossil_strncmp(zName, aSetting[mid-1].name, n)==0 ){
         2449  +          mid--;
         2450  +        }
         2451  +      }
         2452  +      return &aSetting[mid];
         2453  +    }
         2454  +  }
         2455  +  return 0;
         2456  +}
  2384   2457   
  2385   2458   /*
  2386   2459   ** COMMAND: settings
  2387   2460   ** COMMAND: unset*
  2388   2461   **
  2389   2462   ** %fossil settings ?PROPERTY? ?VALUE? ?OPTIONS?
  2390   2463   ** %fossil unset PROPERTY ?OPTIONS?
................................................................................
  2488   2561   **                     diff. If undefined, text diff will be used.
  2489   2562   **
  2490   2563   **    gmerge-command   A graphical merge conflict resolver command operating
  2491   2564   **                     on four files.
  2492   2565   **                     Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
  2493   2566   **                     Ex: xxdiff "%original" "%baseline" "%merge" -M "%output"
  2494   2567   **                     Ex: meld "%baseline" "%original" "%merge" "%output"
         2568  +**
         2569  +**    hash-digits      The number of hexadecimal digits of the SHA1 hash to
         2570  +**                     display.  (Default: 10; Minimum: 6)
  2495   2571   **
  2496   2572   **    http-port        The TCP/IP port number to use by the "server"
  2497   2573   **                     and "ui" commands.  Default: 8080
  2498   2574   **
  2499   2575   **    https-login      Send login credentials using HTTPS instead of HTTP
  2500   2576   **                     even if the login page request came via HTTP.
  2501   2577   **
................................................................................
  2628   2704     }
  2629   2705     if( !g.repositoryOpen ){
  2630   2706       globalFlag = 1;
  2631   2707     }
  2632   2708     if( unsetFlag && g.argc!=3 ){
  2633   2709       usage("PROPERTY ?-global?");
  2634   2710     }
         2711  +
         2712  +  /* Verify that the aSetting[] entries are in sorted order.  This is
         2713  +  ** necessary for the binary search in db_find_setting() to work correctly.
         2714  +  */
         2715  +  for(i=1; aSetting[i].name; i++){
         2716  +    if( fossil_strcmp(aSetting[i-1].name, aSetting[i].name)>=0 ){
         2717  +      fossil_panic("Internal Error: aSetting[] entries for \"%s\""
         2718  +                   " and \"%s\" are out of order.",
         2719  +                   aSetting[i-1].name, aSetting[i].name);
         2720  +    }
         2721  +  }
         2722  +
  2635   2723     if( g.argc==2 ){
  2636         -    int openLocal = db_open_local(0);
  2637         -    for(i=0; ctrlSettings[i].name; i++){
  2638         -      print_setting(&ctrlSettings[i], openLocal);
         2724  +    for(i=0; aSetting[i].name; i++){
         2725  +      print_setting(&aSetting[i]);
  2639   2726       }
  2640   2727     }else if( g.argc==3 || g.argc==4 ){
  2641   2728       const char *zName = g.argv[2];
  2642         -    int isManifest;
  2643         -    int n = strlen(zName);
  2644         -    for(i=0; ctrlSettings[i].name; i++){
  2645         -      if( strncmp(ctrlSettings[i].name, zName, n)==0 ) break;
  2646         -    }
  2647         -    if( !ctrlSettings[i].name ){
         2729  +    int n = (int)strlen(zName);
         2730  +    const Setting *pSetting = db_find_setting(zName, 1);
         2731  +    if( pSetting==0 ){
  2648   2732         fossil_fatal("no such setting: %s", zName);
  2649   2733       }
  2650         -    isManifest = fossil_strcmp(ctrlSettings[i].name, "manifest")==0;
  2651         -    if( isManifest && globalFlag ){
         2734  +    if( globalFlag && fossil_strcmp(pSetting->name, "manifest")==0 ){
  2652   2735         fossil_fatal("cannot set 'manifest' globally");
  2653   2736       }
  2654   2737       if( unsetFlag || g.argc==4 ){
  2655         -      if( ctrlSettings[i+1].name
  2656         -       && strncmp(ctrlSettings[i+1].name, zName, n)==0
  2657         -       && ctrlSettings[i].name[n]!=0
  2658         -      ){
  2659         -        fossil_print("ambiguous property prefix: %s\nMatching properties:\n",
  2660         -                     zName);
  2661         -        while( ctrlSettings[i].name
  2662         -            && strncmp(ctrlSettings[i].name, zName, n)==0
  2663         -        ){
  2664         -          fossil_print("%s\n", ctrlSettings[i].name);
  2665         -          i++;
         2738  +      int isManifest = fossil_strcmp(pSetting->name, "manifest")==0;
         2739  +      if( n!=strlen(pSetting[0].name) && pSetting[1].name &&
         2740  +          fossil_strncmp(pSetting[1].name, zName, n)==0 ){
         2741  +        Blob x;
         2742  +        int i;
         2743  +        blob_init(&x,0,0);
         2744  +        for(i=0; pSetting[i].name; i++){
         2745  +          if( fossil_strncmp(pSetting[i].name,zName,n)!=0 ) break;
         2746  +          blob_appendf(&x, " %s", pSetting[i].name);
  2666   2747           }
  2667         -        fossil_exit(1);
         2748  +        fossil_fatal("ambiguous setting \"%s\" - might be:%s",
         2749  +                     zName, blob_str(&x));
         2750  +      }
         2751  +      if( globalFlag && isManifest ){
         2752  +        fossil_fatal("cannot set 'manifest' globally");
         2753  +      }
         2754  +      if( unsetFlag ){
         2755  +        db_unset(pSetting->name, globalFlag);
  2668   2756         }else{
  2669         -        if( unsetFlag ){
  2670         -          db_unset(ctrlSettings[i].name, globalFlag);
  2671         -        }else{
  2672         -          db_set(ctrlSettings[i].name, g.argv[3], globalFlag);
  2673         -        }
         2757  +        db_set(pSetting->name, g.argv[3], globalFlag);
         2758  +      }
         2759  +      if( isManifest && g.localOpen ){
         2760  +        manifest_to_disk(db_lget_int("checkout", 0));
  2674   2761         }
  2675   2762       }else{
  2676         -      isManifest = 0;
  2677         -      while( ctrlSettings[i].name
  2678         -          && strncmp(ctrlSettings[i].name, zName, n)==0
  2679         -      ){
  2680         -        print_setting(&ctrlSettings[i], db_open_local(0));
  2681         -        i++;
         2763  +      while( pSetting->name && fossil_strncmp(pSetting->name,zName,n)==0 ){
         2764  +        print_setting(pSetting);
         2765  +        pSetting++;
  2682   2766         }
  2683   2767       }
  2684         -    if( isManifest && g.localOpen ){
  2685         -      manifest_to_disk(db_lget_int("checkout", 0));
  2686         -    }
  2687   2768     }else{
  2688   2769       usage("?PROPERTY? ?VALUE? ?-global?");
  2689   2770     }
  2690   2771   }
  2691   2772   
  2692   2773   /*
  2693   2774   ** The input in a timespan measured in days.  Return a string which

Changes to src/descendants.c.

   437    437   void leaves_page(void){
   438    438     Blob sql;
   439    439     Stmt q;
   440    440     int showAll = P("all")!=0;
   441    441     int showClosed = P("closed")!=0;
   442    442   
   443    443     login_check_credentials();
   444         -  if( !g.perm.Read ){ login_needed(); return; }
          444  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   445    445   
   446    446     if( !showAll ){
   447    447       style_submenu_element("All", "All", "leaves?all");
   448    448     }
   449    449     if( !showClosed ){
   450    450       style_submenu_element("Closed", "Closed", "leaves?closed");
   451    451     }
................................................................................
   480    480     if( showClosed ){
   481    481       blob_append_sql(&sql," AND %z", leaf_is_closed_sql("blob.rid"));
   482    482     }else if( !showAll ){
   483    483       blob_append_sql(&sql," AND NOT %z", leaf_is_closed_sql("blob.rid"));
   484    484     }
   485    485     db_prepare(&q, "%s ORDER BY event.mtime DESC", blob_sql_text(&sql));
   486    486     blob_reset(&sql);
   487         -  www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0);
          487  +  www_print_timeline(&q, TIMELINE_LEAFONLY, 0, 0, 0, 0);
   488    488     db_finalize(&q);
   489    489     @ <br />
   490    490     style_footer();
   491    491   }
   492    492   
   493    493   #if INTERFACE
   494    494   /* Flag parameters to compute_uses_file() */

Changes to src/diff.c.

  2232   2232     struct AnnVers *p;
  2233   2233     unsigned clr1, clr2, clr;
  2234   2234     int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */
  2235   2235   
  2236   2236     /* Gather query parameters */
  2237   2237     showLog = atoi(PD("log","1"));
  2238   2238     login_check_credentials();
  2239         -  if( !g.perm.Read ){ login_needed(); return; }
         2239  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  2240   2240     if( exclude_spiders("annotate") ) return;
  2241   2241     load_control();
  2242   2242     mid = name_to_typed_rid(PD("checkin","0"),"ci");
  2243   2243     zFilename = P("filename");
  2244   2244     fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  2245   2245     if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
  2246   2246     iLimit = atoi(PD("limit","20"));
................................................................................
  2292   2292       z2 = sqlite3_mprintf("%d", iLimit+20);
  2293   2293       style_submenu_element(z1, z1, "%s", url_render(&url, "limit", z2, 0, 0));
  2294   2294     }
  2295   2295     if( iLimit>20 ){
  2296   2296       style_submenu_element("20 Ancestors", "20 Ancestors",
  2297   2297          "%s", url_render(&url, "limit", "20", 0, 0));
  2298   2298     }
  2299         -  if( db_get_boolean("white-foreground", 0) ){
         2299  +  if( skin_white_foreground() ){
  2300   2300       clr1 = 0xa04040;
  2301   2301       clr2 = 0x4059a0;
  2302   2302     }else{
  2303   2303       clr1 = 0xffb5b5;  /* Recent changes: red (hot) */
  2304   2304       clr2 = 0xb5e0ff;  /* Older changes: blue (cold) */
  2305   2305     }
  2306   2306     for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
  2307   2307       clr = gradient_color(clr1, clr2, ann.nVers-1, i);
  2308   2308       ann.aVers[i].zBgColor = mprintf("#%06x", clr);
  2309   2309     }
  2310   2310   
  2311   2311     if( showLog ){
  2312         -    char *zLink = href("%R/finfo?name=%t&ci=%s",zFilename,zCI);
         2312  +    char *zLink = href("%R/finfo?name=%t&ci=%!S",zFilename,zCI);
  2313   2313       @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
  2314   2314       @ <ol>
  2315   2315       for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
  2316   2316         @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
  2317         -      @ check-in %z(href("%R/info/%s",p->zMUuid))%S(p->zMUuid)</a>
  2318         -      @ artifact %z(href("%R/artifact/%s",p->zFUuid))%S(p->zFUuid)</a>
         2317  +      @ check-in %z(href("%R/info/%!S",p->zMUuid))%S(p->zMUuid)</a>
         2318  +      @ artifact %z(href("%R/artifact/%!S",p->zFUuid))%S(p->zFUuid)</a>
  2319   2319         @ </span>
  2320   2320   #if 0
  2321   2321         if( i>0 ){
  2322   2322           char *zLink = xhref("target='infowindow'",
  2323   2323                               "%R/fdiff?v1=%S&v2=%S&sbs=1",
  2324   2324                               p->zFUuid,ann.aVers[0].zFUuid);
  2325   2325           @ %z(zLink)[diff-to-top]</a>
................................................................................
  2333   2333   #endif
  2334   2334       }
  2335   2335       @ </ol>
  2336   2336       @ <hr>
  2337   2337     }
  2338   2338     if( !ann.bLimit ){
  2339   2339       @ <h2>Origin for each line in
  2340         -    @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
  2341         -    @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
         2340  +    @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
         2341  +    @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
  2342   2342       iLimit = ann.nVers+10;
  2343   2343     }else{
  2344   2344       @ <h2>Lines added by the %d(iLimit) most recent ancestors of
  2345         -    @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
  2346         -    @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
         2345  +    @ %z(href("%R/finfo?name=%h&ci=%!S", zFilename, zCI))%h(zFilename)</a>
         2346  +    @ from check-in %z(href("%R/info/%!S",zCI))%S(zCI)</a>:</h2>
  2347   2347     }
  2348   2348     @ <pre>
  2349   2349     for(i=0; i<ann.nOrig; i++){
  2350   2350       int iVers = ann.aOrig[i].iVers;
  2351   2351       char *z = (char*)ann.aOrig[i].z;
  2352   2352       int n = ann.aOrig[i].n;
  2353   2353       char zPrefix[300];
  2354   2354       z[n] = 0;
  2355   2355       if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;
  2356   2356   
  2357   2357       if( bBlame ){
  2358   2358         if( iVers>=0 ){
  2359   2359           struct AnnVers *p = ann.aVers+iVers;
  2360         -        char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
         2360  +        char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
  2361   2361           sqlite3_snprintf(sizeof(zPrefix), zPrefix,
  2362   2362                "<span style='background-color:%s'>"
  2363   2363                "%s%.10s</a> %s</span> %13.13s:",
  2364   2364                p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
  2365   2365           fossil_free(zLink);
  2366   2366         }else{
  2367   2367           sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
  2368   2368         }
  2369   2369       }else{
  2370   2370         if( iVers>=0 ){
  2371   2371           struct AnnVers *p = ann.aVers+iVers;
  2372         -        char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
         2372  +        char *zLink = xhref("target='infowindow'", "%R/info/%!S", p->zMUuid);
  2373   2373           sqlite3_snprintf(sizeof(zPrefix), zPrefix,
  2374   2374                "<span style='background-color:%s'>"
  2375   2375                "%s%.10s</a> %s</span> %4d:",
  2376   2376                p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
  2377   2377           fossil_free(zLink);
  2378   2378         }else{
  2379   2379           sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1);
................................................................................
  2432   2432       annFlags = DIFF_IGNORE_EOLWS;
  2433   2433     }
  2434   2434     if( find_option("ignore-all-space","w",0)!=0 ){
  2435   2435       annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
  2436   2436     }
  2437   2437     fileVers = find_option("filevers",0,0)!=0;
  2438   2438     db_must_be_within_tree();
  2439         - 
         2439  +
  2440   2440     /* We should be done with options.. */
  2441   2441     verify_all_options();
  2442   2442   
  2443   2443     if( g.argc<3 ) {
  2444   2444       usage("FILENAME");
  2445   2445     }
  2446   2446     file_tree_name(g.argv[2], &treename, 1);

Changes to src/diff.tcl.

            1  +# The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
            2  +# to this file, then runs this file using "tclsh" in order to display the
            3  +# graphical diff in a separate window.  A typical "set fossilcmd" line
            4  +# looks like this:
            5  +#
            6  +#     set fossilcmd {| "./fossil" diff --html -y -i -v}
            7  +#
            8  +# This header comment is stripped off by the "mkbuiltin.c" program.
            9  +#
     1     10   set prog {
     2     11   package require Tk
     3     12   
     4     13   array set CFG {
     5     14     TITLE      {Fossil Diff}
     6     15     LN_COL_BG  #dddddd
     7     16     LN_COL_FG  #444444

Changes to src/diffcmd.c.

   107    107       const char *zName2;       /* Name of zFile2 for display */
   108    108   
   109    109       /* Read content of zFile2 into memory */
   110    110       blob_zero(&file2);
   111    111       if( file_wd_size(zFile2)<0 ){
   112    112         zName2 = NULL_DEVICE;
   113    113       }else{
   114         -      if( file_wd_islink(zFile2) ){
          114  +      if( file_wd_islink(0) ){
   115    115           blob_read_link(&file2, zFile2);
   116    116         }else{
   117    117           blob_read_from_file(&file2, zFile2);
   118    118         }
   119    119         zName2 = zName;
   120    120       }
   121    121   
................................................................................
   154    154             glob_free(pBinary);
   155    155             return;
   156    156           }
   157    157           glob_free(pBinary);
   158    158         }
   159    159         blob_zero(&file2);
   160    160         if( file_wd_size(zFile2)>=0 ){
   161         -        if( file_wd_islink(zFile2) ){
          161  +        if( file_wd_islink(0) ){
   162    162             blob_read_link(&file2, zFile2);
   163    163           }else{
   164    164             blob_read_from_file(&file2, zFile2);
   165    165           }
   166    166         }
   167    167         if( looks_like_binary(&file2) ){
   168    168           fossil_print("%s",DIFF_CANNOT_COMPUTE_BINARY);
................................................................................
   839    839   ** WEBPAGE: vpatch
   840    840   ** URL vpatch?from=UUID&to=UUID
   841    841   */
   842    842   void vpatch_page(void){
   843    843     const char *zFrom = P("from");
   844    844     const char *zTo = P("to");
   845    845     login_check_credentials();
   846         -  if( !g.perm.Read ){ login_needed(); return; }
          846  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   847    847     if( zFrom==0 || zTo==0 ) fossil_redirect_home();
   848    848   
   849    849     cgi_set_content_type("text/plain");
   850    850     diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE);
   851    851   }

Changes to src/doc.c.

    63     63       if( isBinary[c] ){
    64     64         break;
    65     65       }
    66     66     }
    67     67     if( i>=n ){
    68     68       return 0;   /* Plain text */
    69     69     }
    70         -  for(i=0; i<sizeof(aMime)/sizeof(aMime[0]); i++){
           70  +  for(i=0; i<ArraySize(aMime); i++){
    71     71       if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){
    72     72         return aMime[i].zMimetype;
    73     73       }
    74     74     }
    75     75     return "unknown/unknown";
    76     76   }
           77  +
           78  +/* A table of mimetypes based on file suffixes.
           79  +** Suffixes must be in sorted order so that we can do a binary
           80  +** search to find the mime-type
           81  +*/
           82  +static const struct {
           83  +  const char *zSuffix;       /* The file suffix */
           84  +  int size;                  /* Length of the suffix */
           85  +  const char *zMimetype;     /* The corresponding mimetype */
           86  +} aMime[] = {
           87  +  { "ai",         2, "application/postscript"            },
           88  +  { "aif",        3, "audio/x-aiff"                      },
           89  +  { "aifc",       4, "audio/x-aiff"                      },
           90  +  { "aiff",       4, "audio/x-aiff"                      },
           91  +  { "arj",        3, "application/x-arj-compressed"      },
           92  +  { "asc",        3, "text/plain"                        },
           93  +  { "asf",        3, "video/x-ms-asf"                    },
           94  +  { "asx",        3, "video/x-ms-asx"                    },
           95  +  { "au",         2, "audio/ulaw"                        },
           96  +  { "avi",        3, "video/x-msvideo"                   },
           97  +  { "bat",        3, "application/x-msdos-program"       },
           98  +  { "bcpio",      5, "application/x-bcpio"               },
           99  +  { "bin",        3, "application/octet-stream"          },
          100  +  { "c",          1, "text/plain"                        },
          101  +  { "cc",         2, "text/plain"                        },
          102  +  { "ccad",       4, "application/clariscad"             },
          103  +  { "cdf",        3, "application/x-netcdf"              },
          104  +  { "class",      5, "application/octet-stream"          },
          105  +  { "cod",        3, "application/vnd.rim.cod"           },
          106  +  { "com",        3, "application/x-msdos-program"       },
          107  +  { "cpio",       4, "application/x-cpio"                },
          108  +  { "cpt",        3, "application/mac-compactpro"        },
          109  +  { "csh",        3, "application/x-csh"                 },
          110  +  { "css",        3, "text/css"                          },
          111  +  { "csv",        3, "text/csv"                          },
          112  +  { "dcr",        3, "application/x-director"            },
          113  +  { "deb",        3, "application/x-debian-package"      },
          114  +  { "dir",        3, "application/x-director"            },
          115  +  { "dl",         2, "video/dl"                          },
          116  +  { "dms",        3, "application/octet-stream"          },
          117  +  { "doc",        3, "application/msword"                },
          118  +  { "docx",       4, "application/vnd.openxmlformats-"
          119  +                     "officedocument.wordprocessingml.document"},
          120  +  { "dot",        3, "application/msword"                },
          121  +  { "dotx",       4, "application/vnd.openxmlformats-"
          122  +                     "officedocument.wordprocessingml.template"},
          123  +  { "drw",        3, "application/drafting"              },
          124  +  { "dvi",        3, "application/x-dvi"                 },
          125  +  { "dwg",        3, "application/acad"                  },
          126  +  { "dxf",        3, "application/dxf"                   },
          127  +  { "dxr",        3, "application/x-director"            },
          128  +  { "eps",        3, "application/postscript"            },
          129  +  { "etx",        3, "text/x-setext"                     },
          130  +  { "exe",        3, "application/octet-stream"          },
          131  +  { "ez",         2, "application/andrew-inset"          },
          132  +  { "f",          1, "text/plain"                        },
          133  +  { "f90",        3, "text/plain"                        },
          134  +  { "fli",        3, "video/fli"                         },
          135  +  { "flv",        3, "video/flv"                         },
          136  +  { "gif",        3, "image/gif"                         },
          137  +  { "gl",         2, "video/gl"                          },
          138  +  { "gtar",       4, "application/x-gtar"                },
          139  +  { "gz",         2, "application/x-gzip"                },
          140  +  { "h",          1, "text/plain"                        },
          141  +  { "hdf",        3, "application/x-hdf"                 },
          142  +  { "hh",         2, "text/plain"                        },
          143  +  { "hqx",        3, "application/mac-binhex40"          },
          144  +  { "htm",        3, "text/html"                         },
          145  +  { "html",       4, "text/html"                         },
          146  +  { "ice",        3, "x-conference/x-cooltalk"           },
          147  +  { "ief",        3, "image/ief"                         },
          148  +  { "iges",       4, "model/iges"                        },
          149  +  { "igs",        3, "model/iges"                        },
          150  +  { "ips",        3, "application/x-ipscript"            },
          151  +  { "ipx",        3, "application/x-ipix"                },
          152  +  { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
          153  +  { "jar",        3, "application/java-archive"          },
          154  +  { "jpe",        3, "image/jpeg"                        },
          155  +  { "jpeg",       4, "image/jpeg"                        },
          156  +  { "jpg",        3, "image/jpeg"                        },
          157  +  { "js",         2, "application/x-javascript"          },
          158  +  { "kar",        3, "audio/midi"                        },
          159  +  { "latex",      5, "application/x-latex"               },
          160  +  { "lha",        3, "application/octet-stream"          },
          161  +  { "lsp",        3, "application/x-lisp"                },
          162  +  { "lzh",        3, "application/octet-stream"          },
          163  +  { "m",          1, "text/plain"                        },
          164  +  { "m3u",        3, "audio/x-mpegurl"                   },
          165  +  { "man",        3, "application/x-troff-man"           },
          166  +  { "markdown",   8, "text/x-markdown"                   },
          167  +  { "md",         2, "text/x-markdown"                   },
          168  +  { "me",         2, "application/x-troff-me"            },
          169  +  { "mesh",       4, "model/mesh"                        },
          170  +  { "mid",        3, "audio/midi"                        },
          171  +  { "midi",       4, "audio/midi"                        },
          172  +  { "mif",        3, "application/x-mif"                 },
          173  +  { "mime",       4, "www/mime"                          },
          174  +  { "mkd",        3, "text/x-markdown"                   },
          175  +  { "mov",        3, "video/quicktime"                   },
          176  +  { "movie",      5, "video/x-sgi-movie"                 },
          177  +  { "mp2",        3, "audio/mpeg"                        },
          178  +  { "mp3",        3, "audio/mpeg"                        },
          179  +  { "mp4",        3, "video/mp4"                         },
          180  +  { "mpe",        3, "video/mpeg"                        },
          181  +  { "mpeg",       4, "video/mpeg"                        },
          182  +  { "mpg",        3, "video/mpeg"                        },
          183  +  { "mpga",       4, "audio/mpeg"                        },
          184  +  { "ms",         2, "application/x-troff-ms"            },
          185  +  { "msh",        3, "model/mesh"                        },
          186  +  { "nc",         2, "application/x-netcdf"              },
          187  +  { "oda",        3, "application/oda"                   },
          188  +  { "odp",        3, "application/vnd.oasis.opendocument.presentation" },
          189  +  { "ods",        3, "application/vnd.oasis.opendocument.spreadsheet" },
          190  +  { "odt",        3, "application/vnd.oasis.opendocument.text" },
          191  +  { "ogg",        3, "application/ogg"                   },
          192  +  { "ogm",        3, "application/ogg"                   },
          193  +  { "pbm",        3, "image/x-portable-bitmap"           },
          194  +  { "pdb",        3, "chemical/x-pdb"                    },
          195  +  { "pdf",        3, "application/pdf"                   },
          196  +  { "pgm",        3, "image/x-portable-graymap"          },
          197  +  { "pgn",        3, "application/x-chess-pgn"           },
          198  +  { "pgp",        3, "application/pgp"                   },
          199  +  { "pl",         2, "application/x-perl"                },
          200  +  { "pm",         2, "application/x-perl"                },
          201  +  { "png",        3, "image/png"                         },
          202  +  { "pnm",        3, "image/x-portable-anymap"           },
          203  +  { "pot",        3, "application/mspowerpoint"          },
          204  +  { "potx",       4, "application/vnd.openxmlformats-"
          205  +                     "officedocument.presentationml.template"},
          206  +  { "ppm",        3, "image/x-portable-pixmap"           },
          207  +  { "pps",        3, "application/mspowerpoint"          },
          208  +  { "ppsx",       4, "application/vnd.openxmlformats-"
          209  +                     "officedocument.presentationml.slideshow"},
          210  +  { "ppt",        3, "application/mspowerpoint"          },
          211  +  { "pptx",       4, "application/vnd.openxmlformats-"
          212  +                     "officedocument.presentationml.presentation"},
          213  +  { "ppz",        3, "application/mspowerpoint"          },
          214  +  { "pre",        3, "application/x-freelance"           },
          215  +  { "prt",        3, "application/pro_eng"               },
          216  +  { "ps",         2, "application/postscript"            },
          217  +  { "qt",         2, "video/quicktime"                   },
          218  +  { "ra",         2, "audio/x-realaudio"                 },
          219  +  { "ram",        3, "audio/x-pn-realaudio"              },
          220  +  { "rar",        3, "application/x-rar-compressed"      },
          221  +  { "ras",        3, "image/cmu-raster"                  },
          222  +  { "rgb",        3, "image/x-rgb"                       },
          223  +  { "rm",         2, "audio/x-pn-realaudio"              },
          224  +  { "roff",       4, "application/x-troff"               },
          225  +  { "rpm",        3, "audio/x-pn-realaudio-plugin"       },
          226  +  { "rtf",        3, "text/rtf"                          },
          227  +  { "rtx",        3, "text/richtext"                     },
          228  +  { "scm",        3, "application/x-lotusscreencam"      },
          229  +  { "set",        3, "application/set"                   },
          230  +  { "sgm",        3, "text/sgml"                         },
          231  +  { "sgml",       4, "text/sgml"                         },
          232  +  { "sh",         2, "application/x-sh"                  },
          233  +  { "shar",       4, "application/x-shar"                },
          234  +  { "silo",       4, "model/mesh"                        },
          235  +  { "sit",        3, "application/x-stuffit"             },
          236  +  { "skd",        3, "application/x-koan"                },
          237  +  { "skm",        3, "application/x-koan"                },
          238  +  { "skp",        3, "application/x-koan"                },
          239  +  { "skt",        3, "application/x-koan"                },
          240  +  { "smi",        3, "application/smil"                  },
          241  +  { "smil",       4, "application/smil"                  },
          242  +  { "snd",        3, "audio/basic"                       },
          243  +  { "sol",        3, "application/solids"                },
          244  +  { "spl",        3, "application/x-futuresplash"        },
          245  +  { "src",        3, "application/x-wais-source"         },
          246  +  { "step",       4, "application/STEP"                  },
          247  +  { "stl",        3, "application/SLA"                   },
          248  +  { "stp",        3, "application/STEP"                  },
          249  +  { "sv4cpio",    7, "application/x-sv4cpio"             },
          250  +  { "sv4crc",     6, "application/x-sv4crc"              },
          251  +  { "svg",        3, "image/svg+xml"                     },
          252  +  { "swf",        3, "application/x-shockwave-flash"     },
          253  +  { "t",          1, "application/x-troff"               },
          254  +  { "tar",        3, "application/x-tar"                 },
          255  +  { "tcl",        3, "application/x-tcl"                 },
          256  +  { "tex",        3, "application/x-tex"                 },
          257  +  { "texi",       4, "application/x-texinfo"             },
          258  +  { "texinfo",    7, "application/x-texinfo"             },
          259  +  { "tgz",        3, "application/x-tar-gz"              },
          260  +  { "th1",        3, "application/x-th1"                 },
          261  +  { "tif",        3, "image/tiff"                        },
          262  +  { "tiff",       4, "image/tiff"                        },
          263  +  { "tr",         2, "application/x-troff"               },
          264  +  { "tsi",        3, "audio/TSP-audio"                   },
          265  +  { "tsp",        3, "application/dsptype"               },
          266  +  { "tsv",        3, "text/tab-separated-values"         },
          267  +  { "txt",        3, "text/plain"                        },
          268  +  { "unv",        3, "application/i-deas"                },
          269  +  { "ustar",      5, "application/x-ustar"               },
          270  +  { "vcd",        3, "application/x-cdlink"              },
          271  +  { "vda",        3, "application/vda"                   },
          272  +  { "viv",        3, "video/vnd.vivo"                    },
          273  +  { "vivo",       4, "video/vnd.vivo"                    },
          274  +  { "vrml",       4, "model/vrml"                        },
          275  +  { "wav",        3, "audio/x-wav"                       },
          276  +  { "wax",        3, "audio/x-ms-wax"                    },
          277  +  { "wiki",       4, "text/x-fossil-wiki"                },
          278  +  { "wma",        3, "audio/x-ms-wma"                    },
          279  +  { "wmv",        3, "video/x-ms-wmv"                    },
          280  +  { "wmx",        3, "video/x-ms-wmx"                    },
          281  +  { "wrl",        3, "model/vrml"                        },
          282  +  { "wvx",        3, "video/x-ms-wvx"                    },
          283  +  { "xbm",        3, "image/x-xbitmap"                   },
          284  +  { "xlc",        3, "application/vnd.ms-excel"          },
          285  +  { "xll",        3, "application/vnd.ms-excel"          },
          286  +  { "xlm",        3, "application/vnd.ms-excel"          },
          287  +  { "xls",        3, "application/vnd.ms-excel"          },
          288  +  { "xlsx",       4, "application/vnd.openxmlformats-"
          289  +                     "officedocument.spreadsheetml.sheet"},
          290  +  { "xlw",        3, "application/vnd.ms-excel"          },
          291  +  { "xml",        3, "text/xml"                          },
          292  +  { "xpm",        3, "image/x-xpixmap"                   },
          293  +  { "xwd",        3, "image/x-xwindowdump"               },
          294  +  { "xyz",        3, "chemical/x-pdb"                    },
          295  +  { "zip",        3, "application/zip"                   },
          296  +};
          297  +
          298  +/*
          299  +** Verify that all entries in the aMime[] table are in sorted order.
          300  +** Abort with a fatal error if any is out-of-order.
          301  +*/
          302  +static void mimetype_verify(void){
          303  +  int i;
          304  +  for(i=1; i<ArraySize(aMime); i++){
          305  +    if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
          306  +      fossil_fatal("mimetypes out of sequence: %s before %s",
          307  +                   aMime[i-1].zSuffix, aMime[i].zSuffix);
          308  +    }
          309  +  }
          310  +}
    77    311   
    78    312   /*
    79    313   ** Guess the mime-type of a document based on its name.
    80    314   */
    81    315   const char *mimetype_from_name(const char *zName){
    82    316     const char *z;
    83    317     int i;
    84    318     int first, last;
    85    319     int len;
    86    320     char zSuffix[20];
    87    321   
    88         -  /* A table of mimetypes based on file suffixes.
    89         -  ** Suffixes must be in sorted order so that we can do a binary
    90         -  ** search to find the mime-type
    91         -  */
    92         -  static const struct {
    93         -    const char *zSuffix;       /* The file suffix */
    94         -    int size;                  /* Length of the suffix */
    95         -    const char *zMimetype;     /* The corresponding mimetype */
    96         -  } aMime[] = {
    97         -    { "ai",         2, "application/postscript"            },
    98         -    { "aif",        3, "audio/x-aiff"                      },
    99         -    { "aifc",       4, "audio/x-aiff"                      },
   100         -    { "aiff",       4, "audio/x-aiff"                      },
   101         -    { "arj",        3, "application/x-arj-compressed"      },
   102         -    { "asc",        3, "text/plain"                        },
   103         -    { "asf",        3, "video/x-ms-asf"                    },
   104         -    { "asx",        3, "video/x-ms-asx"                    },
   105         -    { "au",         2, "audio/ulaw"                        },
   106         -    { "avi",        3, "video/x-msvideo"                   },
   107         -    { "bat",        3, "application/x-msdos-program"       },
   108         -    { "bcpio",      5, "application/x-bcpio"               },
   109         -    { "bin",        3, "application/octet-stream"          },
   110         -    { "c",          1, "text/plain"                        },
   111         -    { "cc",         2, "text/plain"                        },
   112         -    { "ccad",       4, "application/clariscad"             },
   113         -    { "cdf",        3, "application/x-netcdf"              },
   114         -    { "class",      5, "application/octet-stream"          },
   115         -    { "cod",        3, "application/vnd.rim.cod"           },
   116         -    { "com",        3, "application/x-msdos-program"       },
   117         -    { "cpio",       4, "application/x-cpio"                },
   118         -    { "cpt",        3, "application/mac-compactpro"        },
   119         -    { "csh",        3, "application/x-csh"                 },
   120         -    { "css",        3, "text/css"                          },
   121         -    { "dcr",        3, "application/x-director"            },
   122         -    { "deb",        3, "application/x-debian-package"      },
   123         -    { "dir",        3, "application/x-director"            },
   124         -    { "dl",         2, "video/dl"                          },
   125         -    { "dms",        3, "application/octet-stream"          },
   126         -    { "doc",        3, "application/msword"                },
   127         -    { "docx",       4, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
   128         -    { "dot",        3, "application/msword"                },
   129         -    { "dotx",       4, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"},
   130         -    { "drw",        3, "application/drafting"              },
   131         -    { "dvi",        3, "application/x-dvi"                 },
   132         -    { "dwg",        3, "application/acad"                  },
   133         -    { "dxf",        3, "application/dxf"                   },
   134         -    { "dxr",        3, "application/x-director"            },
   135         -    { "eps",        3, "application/postscript"            },
   136         -    { "etx",        3, "text/x-setext"                     },
   137         -    { "exe",        3, "application/octet-stream"          },
   138         -    { "ez",         2, "application/andrew-inset"          },
   139         -    { "f",          1, "text/plain"                        },
   140         -    { "f90",        3, "text/plain"                        },
   141         -    { "fli",        3, "video/fli"                         },
   142         -    { "flv",        3, "video/flv"                         },
   143         -    { "gif",        3, "image/gif"                         },
   144         -    { "gl",         2, "video/gl"                          },
   145         -    { "gtar",       4, "application/x-gtar"                },
   146         -    { "gz",         2, "application/x-gzip"                },
   147         -    { "h",          1, "text/plain"                        },
   148         -    { "hdf",        3, "application/x-hdf"                 },
   149         -    { "hh",         2, "text/plain"                        },
   150         -    { "hqx",        3, "application/mac-binhex40"          },
   151         -    { "htm",        3, "text/html"                         },
   152         -    { "html",       4, "text/html"                         },
   153         -    { "ice",        3, "x-conference/x-cooltalk"           },
   154         -    { "ief",        3, "image/ief"                         },
   155         -    { "iges",       4, "model/iges"                        },
   156         -    { "igs",        3, "model/iges"                        },
   157         -    { "ips",        3, "application/x-ipscript"            },
   158         -    { "ipx",        3, "application/x-ipix"                },
   159         -    { "jad",        3, "text/vnd.sun.j2me.app-descriptor"  },
   160         -    { "jar",        3, "application/java-archive"          },
   161         -    { "jpe",        3, "image/jpeg"                        },
   162         -    { "jpeg",       4, "image/jpeg"                        },
   163         -    { "jpg",        3, "image/jpeg"                        },
   164         -    { "js",         2, "application/x-javascript"          },
   165         -    { "kar",        3, "audio/midi"                        },
   166         -    { "latex",      5, "application/x-latex"               },
   167         -    { "lha",        3, "application/octet-stream"          },
   168         -    { "lsp",        3, "application/x-lisp"                },
   169         -    { "lzh",        3, "application/octet-stream"          },
   170         -    { "m",          1, "text/plain"                        },
   171         -    { "m3u",        3, "audio/x-mpegurl"                   },
   172         -    { "man",        3, "application/x-troff-man"           },
   173         -    { "markdown",   8, "text/x-markdown"                   },
   174         -    { "md",         2, "text/x-markdown"                   },
   175         -    { "me",         2, "application/x-troff-me"            },
   176         -    { "mesh",       4, "model/mesh"                        },
   177         -    { "mid",        3, "audio/midi"                        },
   178         -    { "midi",       4, "audio/midi"                        },
   179         -    { "mif",        3, "application/x-mif"                 },
   180         -    { "mime",       4, "www/mime"                          },
   181         -    { "mkd",        3, "text/x-markdown"                   },
   182         -    { "mov",        3, "video/quicktime"                   },
   183         -    { "movie",      5, "video/x-sgi-movie"                 },
   184         -    { "mp2",        3, "audio/mpeg"                        },
   185         -    { "mp3",        3, "audio/mpeg"                        },
   186         -    { "mp4",        3, "video/mp4"                         },
   187         -    { "mpe",        3, "video/mpeg"                        },
   188         -    { "mpeg",       4, "video/mpeg"                        },
   189         -    { "mpg",        3, "video/mpeg"                        },
   190         -    { "mpga",       4, "audio/mpeg"                        },
   191         -    { "ms",         2, "application/x-troff-ms"            },
   192         -    { "msh",        3, "model/mesh"                        },
   193         -    { "nc",         2, "application/x-netcdf"              },
   194         -    { "oda",        3, "application/oda"                   },
   195         -    { "ogg",        3, "application/ogg"                   },
   196         -    { "ogm",        3, "application/ogg"                   },
   197         -    { "pbm",        3, "image/x-portable-bitmap"           },
   198         -    { "pdb",        3, "chemical/x-pdb"                    },
   199         -    { "pdf",        3, "application/pdf"                   },
   200         -    { "pgm",        3, "image/x-portable-graymap"          },
   201         -    { "pgn",        3, "application/x-chess-pgn"           },
   202         -    { "pgp",        3, "application/pgp"                   },
   203         -    { "pl",         2, "application/x-perl"                },
   204         -    { "pm",         2, "application/x-perl"                },
   205         -    { "png",        3, "image/png"                         },
   206         -    { "pnm",        3, "image/x-portable-anymap"           },
   207         -    { "pot",        3, "application/mspowerpoint"          },
   208         -    { "potx",       4, "application/vnd.openxmlformats-officedocument.presentationml.template"},
   209         -    { "ppm",        3, "image/x-portable-pixmap"           },
   210         -    { "pps",        3, "application/mspowerpoint"          },
   211         -    { "ppsx",       4, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"},
   212         -    { "ppt",        3, "application/mspowerpoint"          },
   213         -    { "pptx",       4, "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
   214         -    { "ppz",        3, "application/mspowerpoint"          },
   215         -    { "pre",        3, "application/x-freelance"           },
   216         -    { "prt",        3, "application/pro_eng"               },
   217         -    { "ps",         2, "application/postscript"            },
   218         -    { "qt",         2, "video/quicktime"                   },
   219         -    { "ra",         2, "audio/x-realaudio"                 },
   220         -    { "ram",        3, "audio/x-pn-realaudio"              },
   221         -    { "rar",        3, "application/x-rar-compressed"      },
   222         -    { "ras",        3, "image/cmu-raster"                  },
   223         -    { "rgb",        3, "image/x-rgb"                       },
   224         -    { "rm",         2, "audio/x-pn-realaudio"              },
   225         -    { "roff",       4, "application/x-troff"               },
   226         -    { "rpm",        3, "audio/x-pn-realaudio-plugin"       },
   227         -    { "rtf",        3, "text/rtf"                          },
   228         -    { "rtx",        3, "text/richtext"                     },
   229         -    { "scm",        3, "application/x-lotusscreencam"      },
   230         -    { "set",        3, "application/set"                   },
   231         -    { "sgm",        3, "text/sgml"                         },
   232         -    { "sgml",       4, "text/sgml"                         },
   233         -    { "sh",         2, "application/x-sh"                  },
   234         -    { "shar",       4, "application/x-shar"                },
   235         -    { "silo",       4, "model/mesh"                        },
   236         -    { "sit",        3, "application/x-stuffit"             },
   237         -    { "skd",        3, "application/x-koan"                },
   238         -    { "skm",        3, "application/x-koan"                },
   239         -    { "skp",        3, "application/x-koan"                },
   240         -    { "skt",        3, "application/x-koan"                },
   241         -    { "smi",        3, "application/smil"                  },
   242         -    { "smil",       4, "application/smil"                  },
   243         -    { "snd",        3, "audio/basic"                       },
   244         -    { "sol",        3, "application/solids"                },
   245         -    { "spl",        3, "application/x-futuresplash"        },
   246         -    { "src",        3, "application/x-wais-source"         },
   247         -    { "step",       4, "application/STEP"                  },
   248         -    { "stl",        3, "application/SLA"                   },
   249         -    { "stp",        3, "application/STEP"                  },
   250         -    { "sv4cpio",    7, "application/x-sv4cpio"             },
   251         -    { "sv4crc",     6, "application/x-sv4crc"              },
   252         -    { "svg",        3, "image/svg+xml"                     },
   253         -    { "swf",        3, "application/x-shockwave-flash"     },
   254         -    { "t",          1, "application/x-troff"               },
   255         -    { "tar",        3, "application/x-tar"                 },
   256         -    { "tcl",        3, "application/x-tcl"                 },
   257         -    { "tex",        3, "application/x-tex"                 },
   258         -    { "texi",       4, "application/x-texinfo"             },
   259         -    { "texinfo",    7, "application/x-texinfo"             },
   260         -    { "tgz",        3, "application/x-tar-gz"              },
   261         -    { "th1",        3, "application/x-th1"                 },
   262         -    { "tif",        3, "image/tiff"                        },
   263         -    { "tiff",       4, "image/tiff"                        },
   264         -    { "tr",         2, "application/x-troff"               },
   265         -    { "tsi",        3, "audio/TSP-audio"                   },
   266         -    { "tsp",        3, "application/dsptype"               },
   267         -    { "tsv",        3, "text/tab-separated-values"         },
   268         -    { "txt",        3, "text/plain"                        },
   269         -    { "unv",        3, "application/i-deas"                },
   270         -    { "ustar",      5, "application/x-ustar"               },
   271         -    { "vcd",        3, "application/x-cdlink"              },
   272         -    { "vda",        3, "application/vda"                   },
   273         -    { "viv",        3, "video/vnd.vivo"                    },
   274         -    { "vivo",       4, "video/vnd.vivo"                    },
   275         -    { "vrml",       4, "model/vrml"                        },
   276         -    { "wav",        3, "audio/x-wav"                       },
   277         -    { "wax",        3, "audio/x-ms-wax"                    },
   278         -    { "wiki",       4, "text/x-fossil-wiki"                },
   279         -    { "wma",        3, "audio/x-ms-wma"                    },
   280         -    { "wmv",        3, "video/x-ms-wmv"                    },
   281         -    { "wmx",        3, "video/x-ms-wmx"                    },
   282         -    { "wrl",        3, "model/vrml"                        },
   283         -    { "wvx",        3, "video/x-ms-wvx"                    },
   284         -    { "xbm",        3, "image/x-xbitmap"                   },
   285         -    { "xlc",        3, "application/vnd.ms-excel"          },
   286         -    { "xll",        3, "application/vnd.ms-excel"          },
   287         -    { "xlm",        3, "application/vnd.ms-excel"          },
   288         -    { "xls",        3, "application/vnd.ms-excel"          },
   289         -    { "xlsx",       4, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
   290         -    { "xlw",        3, "application/vnd.ms-excel"          },
   291         -    { "xml",        3, "text/xml"                          },
   292         -    { "xpm",        3, "image/x-xpixmap"                   },
   293         -    { "xwd",        3, "image/x-xwindowdump"               },
   294         -    { "xyz",        3, "chemical/x-pdb"                    },
   295         -    { "zip",        3, "application/zip"                   },
   296         -  };
   297    322   
   298    323   #ifdef FOSSIL_DEBUG
   299    324     /* This is test code to make sure the table above is in the correct
   300    325     ** order
   301    326     */
   302    327     if( fossil_strcmp(zName, "mimetype-test")==0 ){
   303         -    for(i=1; i<sizeof(aMime)/sizeof(aMime[0]); i++){
   304         -      if( fossil_strcmp(aMime[i-1].zSuffix,aMime[i].zSuffix)>=0 ){
   305         -        fossil_fatal("mimetypes out of sequence: %s before %s",
   306         -                     aMime[i-1].zSuffix, aMime[i].zSuffix);
   307         -      }
   308         -    }
          328  +    mimetype_verify();
   309    329       return "ok";
   310    330     }
   311    331   #endif
   312    332   
   313    333     z = zName;
   314    334     for(i=0; zName[i]; i++){
   315    335       if( zName[i]=='.' ) z = &zName[i+1];
   316    336     }
   317    337     len = strlen(z);
   318    338     if( len<sizeof(zSuffix)-1 ){
   319    339       sqlite3_snprintf(sizeof(zSuffix), zSuffix, "%s", z);
   320    340       for(i=0; zSuffix[i]; i++) zSuffix[i] = fossil_tolower(zSuffix[i]);
   321    341       first = 0;
   322         -    last = sizeof(aMime)/sizeof(aMime[0]) - 1;
          342  +    last = ArraySize(aMime) - 1;
   323    343       while( first<=last ){
   324    344         int c;
   325    345         i = (first+last)/2;
   326    346         c = fossil_strcmp(zSuffix, aMime[i].zSuffix);
   327    347         if( c==0 ) return aMime[i].zMimetype;
   328    348         if( c<0 ){
   329    349           last = i-1;
................................................................................
   344    364   **
   345    365   ** If Fossil is compiled with -DFOSSIL_DEBUG then the "mimetype-test"
   346    366   ** filename is special and verifies the integrity of the mimetype table.
   347    367   ** It should return "ok".
   348    368   */
   349    369   void mimetype_test_cmd(void){
   350    370     int i;
          371  +  mimetype_verify();
   351    372     for(i=2; i<g.argc; i++){
   352    373       fossil_print("%-20s -> %s\n", g.argv[i], mimetype_from_name(g.argv[i]));
   353    374     }
   354    375   }
          376  +
          377  +/*
          378  +** WEBPAGE: mimetype_list
          379  +**
          380  +** Show the built-in table used to guess embedded document mimetypes
          381  +** from file suffixes.
          382  +*/
          383  +void mimetype_list_page(void){
          384  +  int i;
          385  +  mimetype_verify();
          386  +  style_header("Mimetype List");
          387  +  @ <p>The Fossil <a href="%R/help?cmd=/doc">/doc</a> page uses filename
          388  +  @ suffixes and the following table to guess at the appropriate mimetype
          389  +  @ for each document.</p>
          390  +  @ <table id='mimeTable' border=1 cellpadding=0 class='mimetypetable'>
          391  +  @ <thead>
          392  +  @ <tr><th>Suffix<th>Mimetype
          393  +  @ </thead>
          394  +  @ <tbody>
          395  +  for(i=0; i<ArraySize(aMime); i++){
          396  +    @ <tr><td>%h(aMime[i].zSuffix)<td>%h(aMime[i].zMimetype)</tr>
          397  +  }
          398  +  @ </tbody></table>
          399  +  output_table_sorting_javascript("mimeTable","tt",1);
          400  +  style_footer();
          401  +}
          402  +
          403  +/*
          404  +** Check to see if the file in the pContent blob is "embedded HTML".  Return
          405  +** true if it is, and fill pTitle with the document title.
          406  +**
          407  +** An "embedded HTML" file is HTML that lacks a header and a footer.  The
          408  +** standard Fossil header is prepended and the standard Fossil footer is
          409  +** appended.  Otherwise, the file is displayed without change.
          410  +**
          411  +** Embedded HTML must be contained in a <div class='fossil-doc'> element.
          412  +** If that <div> also contains a data-title attribute, then the
          413  +** value of that attribute is extracted into pTitle and becomes the title
          414  +** of the document.
          415  +*/
          416  +int doc_is_embedded_html(Blob *pContent, Blob *pTitle){
          417  +  const char *zIn = blob_str(pContent);
          418  +  const char *zAttr;
          419  +  const char *zValue;
          420  +  int nAttr, nValue;
          421  +  int seenClass = 0;
          422  +  int seenTitle = 0;
          423  +
          424  +  while( fossil_isspace(zIn[0]) ) zIn++;
          425  +  if( fossil_strnicmp(zIn,"<div",4)!=0 ) return 0;
          426  +  zIn += 4;
          427  +  while( zIn[0] ){
          428  +    if( fossil_isspace(zIn[0]) ) zIn++;
          429  +    if( zIn[0]=='>' ) return 0;
          430  +    zAttr = zIn;
          431  +    while( fossil_isalnum(zIn[0]) || zIn[0]=='-' ) zIn++;
          432  +    nAttr = (int)(zIn - zAttr);
          433  +    while( fossil_isspace(zIn[0]) ) zIn++;
          434  +    if( zIn[0]!='=' ) continue;
          435  +    zIn++;
          436  +    while( fossil_isspace(zIn[0]) ) zIn++;
          437  +    if( zIn[0]=='"' || zIn[0]=='\'' ){
          438  +      char cDelim = zIn[0];
          439  +      zIn++;
          440  +      zValue = zIn;
          441  +      while( zIn[0] && zIn[0]!=cDelim ) zIn++;
          442  +      if( zIn[0]==0 ) return 0;
          443  +      nValue = (int)(zIn - zValue);
          444  +      zIn++;
          445  +    }else{
          446  +      zValue = zIn;
          447  +      while( zIn[0]!=0 && zIn[0]!='>' && zIn[0]!='/'
          448  +            && !fossil_isspace(zIn[0]) ) zIn++;
          449  +      if( zIn[0]==0 ) return 0;
          450  +      nValue = (int)(zIn - zValue);
          451  +    }
          452  +    if( nAttr==5 && fossil_strnicmp(zAttr,"class",5)==0 ){
          453  +      if( nValue!=10 || fossil_strnicmp(zValue,"fossil-doc",10)!=0 ) return 0;
          454  +      seenClass = 1;
          455  +      if( seenTitle ) return 1;
          456  +    }
          457  +    if( nAttr==10 && fossil_strnicmp(zAttr,"data-title",10)==0 ){
          458  +      blob_append(pTitle, zValue, nValue);
          459  +      seenTitle = 1;
          460  +      if( seenClass ) return 1;
          461  +    }
          462  +  }
          463  +  return seenClass;
          464  +}
          465  +
          466  +/*
          467  +** Look for a file named zName in the checkin with RID=vid.  Load the content
          468  +** of that file into pContent and return the RID for the file.  Or return 0
          469  +** if the file is not found or could not be loaded.
          470  +*/
          471  +int doc_load_content(int vid, const char *zName, Blob *pContent){
          472  +  int rid;   /* The RID of the file being loaded */
          473  +  if( !db_table_exists("repository","vcache") ){
          474  +    db_multi_exec(
          475  +      "CREATE TABLE IF NOT EXISTS vcache(\n"
          476  +      "  vid INTEGER,         -- checkin ID\n"
          477  +      "  fname TEXT,          -- filename\n"
          478  +      "  rid INTEGER,         -- artifact ID\n"
          479  +      "  PRIMARY KEY(vid,fname)\n"
          480  +      ") WITHOUT ROWID"
          481  +    );
          482  +  }
          483  +  if( !db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
          484  +    db_multi_exec(
          485  +      "DELETE FROM vcache;\n"
          486  +      "CREATE VIRTUAL TABLE IF NOT EXISTS temp.foci USING files_of_checkin;\n"
          487  +      "INSERT INTO vcache(vid,fname,rid)"
          488  +      "  SELECT checkinID, filename, blob.rid FROM foci, blob"
          489  +      "   WHERE blob.uuid=foci.uuid"
          490  +      "     AND foci.checkinID=%d;",
          491  +      vid
          492  +    );
          493  +  }
          494  +  rid = db_int(0, "SELECT rid FROM vcache"
          495  +                  " WHERE vid=%d AND fname=%Q", vid, zName);
          496  +  if( rid && content_get(rid, pContent)==0 ){
          497  +    rid = 0;
          498  +  }
          499  +  return rid;
          500  +}
   355    501   
   356    502   /*
   357    503   ** WEBPAGE: doc
   358         -** URL: /doc?name=BASELINE/PATH
   359         -** URL: /doc/BASELINE/PATH
          504  +** URL: /doc?name=CHECKIN/FILE
          505  +** URL: /doc/CHECKIN/FILE
          506  +**
          507  +** CHECKIN can be either tag or SHA1 hash or timestamp identifying a
          508  +** particular check, or the name of a branch (meaning the most recent
          509  +** check-in on that branch) or one of various magic words:
          510  +**
          511  +**     "tip"      means the most recent check-in
          512  +**
          513  +**     "ckout"    means the current check-out, if the server is run from
          514  +**                within a check-out, otherwise it is the same as "tip"
          515  +**
          516  +** FILE is the name of a file to delivered up as a webpage.  FILE is relative
          517  +** to the root of the source tree of the repository. The FILE must
          518  +** be a part of CHECKIN, except when CHECKIN=="ckout" when FILE is read
          519  +** directly from disk and need not be a managed file.
          520  +**
          521  +** The "ckout" CHECKIN is intended for development - to provide a mechanism
          522  +** for looking at what a file will look like using the /doc webpage after
          523  +** it gets checked in.
   360    524   **
   361         -** BASELINE can be either a baseline uuid prefix or magic words "tip"
   362         -** to mean the most recently checked in baseline or "ckout" to mean the
   363         -** content of the local checkout, if any.  PATH is the relative pathname
   364         -** of some file.  This method returns the file content.
          525  +** The file extension is used to decide how to render the file.
   365    526   **
   366         -** If PATH matches the patterns *.wiki or *.txt then formatting content
   367         -** is added before returning the file.  For all other names, the content
   368         -** is returned straight without any interpretation or processing.
          527  +** If FILE ends in "/" then names "FILE/index.html", "FILE/index.wiki",
          528  +** and "FILE/index.md" are  in that order.  If none of those are found,
          529  +** then FILE is completely replaced by "404.md" and tried.  If that is
          530  +** not found, then a default 404 screen is generated.
   369    531   */
   370    532   void doc_page(void){
   371    533     const char *zName;                /* Argument to the /doc page */
          534  +  const char *zOrigName = "?";      /* Original document name */
   372    535     const char *zMime;                /* Document MIME type */
   373         -  int vid = 0;                      /* Artifact of baseline */
          536  +  char *zCheckin = "tip";           /* The checkin holding the document */
          537  +  int vid = 0;                      /* Artifact of checkin */
   374    538     int rid = 0;                      /* Artifact of file */
   375    539     int i;                            /* Loop counter */
   376    540     Blob filebody;                    /* Content of the documentation file */
   377         -  char zBaseline[UUID_SIZE+1];      /* Baseline UUID */
          541  +  Blob title;                       /* Document title */
          542  +  int nMiss = (-1);                 /* Failed attempts to find the document */
          543  +  static const char *const azSuffix[] = {
          544  +     "index.html", "index.wiki", "index.md"
          545  +  };
   378    546   
   379    547     login_check_credentials();
   380         -  if( !g.perm.Read ){ login_needed(); return; }
   381         -  zName = PD("name", "tip/index.wiki");
   382         -  for(i=0; zName[i] && zName[i]!='/'; i++){}
   383         -  if( zName[i]==0 || i>UUID_SIZE ){
   384         -    zName = "index.html";
   385         -    goto doc_not_found;
   386         -  }
   387         -  g.zPath = mprintf("%s/%s", g.zPath, zName);
   388         -  memcpy(zBaseline, zName, i);
   389         -  zBaseline[i] = 0;
   390         -  zName += i;
   391         -  while( zName[0]=='/' ){ zName++; }
   392         -  if( !file_is_simple_pathname(zName, 1) ){
   393         -    int n = strlen(zName);
   394         -    if( n>0 && zName[n-1]=='/' ){
   395         -      zName = mprintf("%sindex.html", zName);
   396         -      if( !file_is_simple_pathname(zName, 1) ){
          548  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
          549  +  blob_init(&title, 0, 0);
          550  +  db_begin_transaction();
          551  +  while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
          552  +    zName = PD("name", "tip/index.wiki");
          553  +    for(i=0; zName[i] && zName[i]!='/'; i++){}
          554  +    zCheckin = mprintf("%.*s", i, zName);
          555  +    if( fossil_strcmp(zCheckin,"ckout")==0 && db_open_local(0)==0 ){
          556  +      zCheckin = "tip";
          557  +    }
          558  +    if( nMiss==ArraySize(azSuffix) ){
          559  +      zName = "404.md";
          560  +    }else if( zName[i]==0 ){
          561  +      assert( nMiss>=0 && nMiss<ArraySize(azSuffix) );
          562  +      zName = azSuffix[nMiss];
          563  +    }else{
          564  +      zName += i;
          565  +    }
          566  +    while( zName[0]=='/' ){ zName++; }
          567  +    g.zPath = mprintf("%s/%s/%s", g.zPath, zCheckin, zName);
          568  +    if( nMiss==0 ) zOrigName = zName;
          569  +    if( !file_is_simple_pathname(zName, 1) ){
          570  +      if( sqlite3_strglob("*/", zName)==0 ){
          571  +        assert( nMiss>=0 && nMiss<ArraySize(azSuffix) );
          572  +        zName = mprintf("%s%s", zName, azSuffix[nMiss]);
          573  +        if( !file_is_simple_pathname(zName, 1) ){
          574  +          goto doc_not_found;
          575  +        }
          576  +      }else{
   397    577           goto doc_not_found;
   398    578         }
          579  +    }
          580  +    if( fossil_strcmp(zCheckin,"ckout")==0 ){
          581  +      /* Read from the local checkout */
          582  +      char *zFullpath;
          583  +      db_must_be_within_tree();
          584  +      zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
          585  +      if( file_isfile(zFullpath)
          586  +       && blob_read_from_file(&filebody, zFullpath)>0 ){
          587  +        rid = 1;  /* Fake RID just to get the loop to end */
          588  +      }
          589  +      fossil_free(zFullpath);
   399    590       }else{
   400         -      goto doc_not_found;
          591  +      vid = name_to_typed_rid(zCheckin, "ci");
          592  +      rid = doc_load_content(vid, zName, &filebody);
   401    593       }
   402    594     }
   403         -  if( fossil_strcmp(zBaseline,"ckout")==0 && db_open_local(0)==0 ){
   404         -    sqlite3_snprintf(sizeof(zBaseline), zBaseline, "tip");
   405         -  }
   406         -  if( fossil_strcmp(zBaseline,"ckout")==0 ){
   407         -    /* Read from the local checkout */
   408         -    char *zFullpath;
   409         -    db_must_be_within_tree();
   410         -    zFullpath = mprintf("%s/%s", g.zLocalRoot, zName);
   411         -    if( !file_isfile(zFullpath) ){
   412         -      goto doc_not_found;
   413         -    }
   414         -    if( blob_read_from_file(&filebody, zFullpath)<0 ){
   415         -      goto doc_not_found;
   416         -    }
   417         -  }else{
   418         -    db_begin_transaction();
   419         -    if( fossil_strcmp(zBaseline,"tip")==0 ){
   420         -      vid = db_int(0, "SELECT objid FROM event WHERE type='ci'"
   421         -                      " ORDER BY mtime DESC LIMIT 1");
   422         -    }else{
   423         -      vid = name_to_typed_rid(zBaseline, "ci");
   424         -    }
   425         -
   426         -    /* Create the baseline cache if it does not already exist */
   427         -    db_multi_exec(
   428         -      "CREATE TABLE IF NOT EXISTS vcache(\n"
   429         -      "  vid INTEGER,         -- baseline ID\n"
   430         -      "  fname TEXT,          -- filename\n"
   431         -      "  rid INTEGER,         -- artifact ID\n"
   432         -      "  UNIQUE(vid,fname,rid)\n"
   433         -      ")"
   434         -    );
   435         -
   436         -
   437         -
   438         -    /* Check to see if the documentation file artifact ID is contained
   439         -    ** in the baseline cache */
   440         -    rid = db_int(0, "SELECT rid FROM vcache"
   441         -                    " WHERE vid=%d AND fname=%Q", vid, zName);
   442         -    if( rid==0 && db_exists("SELECT 1 FROM vcache WHERE vid=%d", vid) ){
   443         -      goto doc_not_found;
   444         -    }
   445         -
   446         -    if( rid==0 ){
   447         -      Stmt s;
   448         -      Manifest *pM;
   449         -      ManifestFile *pFile;
   450         -
   451         -      /* Add the vid baseline to the cache */
   452         -      if( db_int(0, "SELECT count(*) FROM vcache")>10000 ){
   453         -        db_multi_exec("DELETE FROM vcache");
   454         -      }
   455         -      pM = manifest_get(vid, CFTYPE_MANIFEST, 0);
   456         -      if( pM==0 ){
   457         -        goto doc_not_found;
   458         -      }
   459         -      db_prepare(&s,
   460         -        "INSERT INTO vcache(vid,fname,rid)"
   461         -        " SELECT %d, :fname, rid FROM blob"
   462         -        "  WHERE uuid=:uuid",
   463         -        vid
   464         -      );
   465         -      manifest_file_rewind(pM);
   466         -      while( (pFile = manifest_file_next(pM,0))!=0 ){
   467         -        db_bind_text(&s, ":fname", pFile->zName);
   468         -        db_bind_text(&s, ":uuid", pFile->zUuid);
   469         -        db_step(&s);
   470         -        db_reset(&s);
   471         -      }
   472         -      db_finalize(&s);
   473         -      manifest_destroy(pM);
   474         -
   475         -      /* Try again to find the file */
   476         -      rid = db_int(0, "SELECT rid FROM vcache"
   477         -                      " WHERE vid=%d AND fname=%Q", vid, zName);
   478         -    }
   479         -    if( rid==0 ){
   480         -      goto doc_not_found;
   481         -    }
   482         -
   483         -    /* Get the file content */
   484         -    if( content_get(rid, &filebody)==0 ){
   485         -      goto doc_not_found;
   486         -    }
   487         -    db_end_transaction(0);
   488         -  }
          595  +  if( rid==0 ) goto doc_not_found;
   489    596     blob_to_utf8_no_bom(&filebody, 0);
   490    597   
   491    598     /* The file is now contained in the filebody blob.  Deliver the
   492    599     ** file to the user
   493    600     */
   494         -  zMime = P("mimetype");
          601  +  zMime = nMiss==0 ? P("mimetype") : 0;
   495    602     if( zMime==0 ){
   496    603       zMime = mimetype_from_name(zName);
   497    604     }
   498    605     Th_Store("doc_name", zName);
   499    606     Th_Store("doc_version", db_text(0, "SELECT '[' || substr(uuid,1,10) || ']'"
   500    607                                        "  FROM blob WHERE rid=%d", vid));
   501    608     Th_Store("doc_date", db_text(0, "SELECT datetime(mtime) FROM event"
   502    609                                     " WHERE objid=%d AND type='ci'", vid));
   503    610     if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){
   504         -    Blob title, tail;
          611  +    Blob tail;
          612  +    style_adunit_config(ADUNIT_RIGHT_OK);
   505    613       if( wiki_find_title(&filebody, &title, &tail) ){
   506    614         style_header("%s", blob_str(&title));
   507    615         wiki_convert(&tail, 0, WIKI_BUTTONS);
   508    616       }else{
   509    617         style_header("Documentation");
   510    618         wiki_convert(&filebody, 0, WIKI_BUTTONS);
   511    619       }
   512    620       style_footer();
   513    621     }else if( fossil_strcmp(zMime, "text/x-markdown")==0 ){
   514         -    Blob title = BLOB_INITIALIZER;
   515    622       Blob tail = BLOB_INITIALIZER;
   516    623       markdown_to_html(&filebody, &title, &tail);
   517    624       if( blob_size(&title)>0 ){
   518    625         style_header("%s", blob_str(&title));
   519    626       }else{
   520         -      style_header("Documentation");
          627  +      style_header("%s", nMiss>=ArraySize(azSuffix)?
          628  +                        "Not Found" : "Documentation");
   521    629       }
   522    630       blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
   523    631       style_footer();
   524    632     }else if( fossil_strcmp(zMime, "text/plain")==0 ){
   525    633       style_header("Documentation");
   526    634       @ <blockquote><pre>
   527    635       @ %h(blob_str(&filebody))
   528    636       @ </pre></blockquote>
   529    637       style_footer();
          638  +  }else if( fossil_strcmp(zMime, "text/html")==0
          639  +            && doc_is_embedded_html(&filebody, &title) ){
          640  +    if( blob_size(&title)==0 ) blob_append(&title,zName,-1);
          641  +    style_header("%s", blob_str(&title));
          642  +    blob_append(cgi_output_blob(), blob_buffer(&filebody),blob_size(&filebody));
          643  +    style_footer();
   530    644   #ifdef FOSSIL_ENABLE_TH1_DOCS
   531    645     }else if( db_get_boolean("th1-docs", 0) &&
   532    646               fossil_strcmp(zMime, "application/x-th1")==0 ){
   533    647       style_header("%h", zName);
   534    648       Th_Render(blob_str(&filebody));
   535    649       style_footer();
   536    650   #endif
   537    651     }else{
   538    652       cgi_set_content_type(zMime);
   539    653       cgi_set_content(&filebody);
   540    654     }
          655  +  if( nMiss>=ArraySize(azSuffix) ) cgi_set_status(404, "Not Found");
          656  +  db_end_transaction(0);
   541    657     return;
   542    658   
   543         -doc_not_found:
   544    659     /* Jump here when unable to locate the document */
          660  +doc_not_found:
   545    661     db_end_transaction(0);
   546         -  style_header("Document Not Found");
   547         -  @ <p>No such document: %h(zName)</p>
          662  +  cgi_set_status(404, "Not Found");
          663  +  style_header("Not Found");
          664  +  @ <p>Document %h(zOrigName) not found
          665  +  if( fossil_strcmp(zCheckin,"ckout")!=0 ){
          666  +    @ in %z(href("%R/tree?ci=%T",zCheckin))%h(zCheckin)</a>
          667  +  }
   548    668     style_footer();
          669  +  db_end_transaction(0);
   549    670     return;
   550    671   }
   551    672   
   552    673   /*
   553    674   ** The default logo.
   554    675   */
   555    676   static const unsigned char aLogo[] = {
................................................................................
   667    788     if( blob_size(&bgimg)==0 ){
   668    789       blob_init(&bgimg, (char*)aBackground, sizeof(aBackground));
   669    790     }
   670    791     cgi_set_content_type(zMime);
   671    792     cgi_set_content(&bgimg);
   672    793     g.isConst = 1;
   673    794   }
          795  +
          796  +
          797  +/*
          798  +** WEBPAGE: /docsrch
          799  +**
          800  +** Search for documents that match a user-supplied pattern.
          801  +*/
          802  +void doc_search_page(void){
          803  +  login_check_credentials();
          804  +  style_header("Document Search");
          805  +  search_screen(SRCH_DOC, 0);
          806  +  style_footer();
          807  +}

Changes to src/event.c.

    13     13   **   drh@hwaci.com
    14     14   **   http://www.hwaci.com/drh/
    15     15   **
    16     16   *******************************************************************************
    17     17   **
    18     18   ** This file contains code to do formatting of event messages:
    19     19   **
           20  +**     Technical Notes
    20     21   **     Milestones
    21     22   **     Blog posts
    22     23   **     New articles
    23     24   **     Process checkpoints
    24     25   **     Announcements
           26  +**
           27  +** Do not confuse "event" artifacts with the "event" table in the
           28  +** repository database.  An "event" artifact is a technical-note: a
           29  +** wiki- or blog-like essay that appears on the timeline.  The "event"
           30  +** table records all entries on the timeline, including tech-notes.
           31  +**
           32  +** (2015-02-14):  Changing the name to "tech-note" most everywhere.
    25     33   */
    26     34   #include "config.h"
    27     35   #include <assert.h>
    28     36   #include <ctype.h>
    29     37   #include "event.h"
    30     38   
    31     39   /*
    32         -** Output a hyperlink to an event given its tagid.
           40  +** Output a hyperlink to an technote given its tagid.
    33     41   */
    34     42   void hyperlink_to_event_tagid(int tagid){
    35         -  char *zEventId;
    36         -  zEventId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
           43  +  char *zId;
           44  +  zId = db_text(0, "SELECT substr(tagname, 7) FROM tag WHERE tagid=%d",
    37     45                        tagid);
    38         -  @ [%z(href("%R/event/%s",zEventId))%S(zEventId)</a>]
    39         -  free(zEventId);
           46  +  @ [%z(href("%R/technote/%s",zId))%S(zId)</a>]
           47  +  free(zId);
    40     48   }
    41     49   
    42     50   /*
           51  +** WEBPAGE: technote
    43     52   ** WEBPAGE: event
    44         -** URL: /event
           53  +**
           54  +** Display a "technical note" or "tech-note" (formerly called an "event").
           55  +**
    45     56   ** PARAMETERS:
    46     57   **
    47         -**  name=EVENTID      // Identify the event to display EVENTID must be complete
    48         -**  aid=ARTIFACTID    // Which specific version of the event.  Optional.
    49         -**  v=BOOLEAN         // Show details if TRUE.  Default is FALSE.  Optional.
           58  +**  name=ID          // Identify the tech-note to display. ID must be complete
           59  +**  aid=ARTIFACTID   // Which specific version of the tech-note.  Optional.
           60  +**  v=BOOLEAN        // Show details if TRUE.  Default is FALSE.  Optional.
    50     61   **
    51     62   ** Display an existing event identified by EVENTID
    52     63   */
    53     64   void event_page(void){
    54     65     int rid = 0;             /* rid of the event artifact */
    55     66     char *zUuid;             /* UUID corresponding to rid */
    56         -  const char *zEventId;    /* Event identifier */
           67  +  const char *zId;    /* Event identifier */
    57     68     const char *zVerbose;    /* Value of verbose option */
    58         -  char *zETime;            /* Time of the event */
           69  +  char *zETime;            /* Time of the tech-note */
    59     70     char *zATime;            /* Time the artifact was created */
    60     71     int specRid;             /* rid specified by aid= parameter */
    61         -  int prevRid, nextRid;    /* Previous or next edits of this event */
    62         -  Manifest *pEvent;        /* Parsed event artifact */
    63         -  Blob fullbody;           /* Complete content of the event body */
    64         -  Blob title;              /* Title extracted from the event body */
           72  +  int prevRid, nextRid;    /* Previous or next edits of this tech-note */
           73  +  Manifest *pTNote;        /* Parsed technote artifact */
           74  +  Blob fullbody;           /* Complete content of the technote body */
           75  +  Blob title;              /* Title extracted from the technote body */
    65     76     Blob tail;               /* Event body that comes after the title */
    66         -  Stmt q1;                 /* Query to search for the event */
           77  +  Stmt q1;                 /* Query to search for the technote */
    67     78     int verboseFlag;         /* True to show details */
           79  +  const char *zMimetype = 0;  /* Mimetype of the document */
    68     80   
    69     81   
    70         -  /* wiki-read privilege is needed in order to read events.
           82  +  /* wiki-read privilege is needed in order to read tech-notes.
    71     83     */
    72     84     login_check_credentials();
    73     85     if( !g.perm.RdWiki ){
    74         -    login_needed();
           86  +    login_needed(g.anon.RdWiki);
    75     87       return;
    76     88     }
    77     89   
    78         -  zEventId = P("name");
    79         -  if( zEventId==0 ){ fossil_redirect_home(); return; }
           90  +  zId = P("name");
           91  +  if( zId==0 ){ fossil_redirect_home(); return; }
    80     92     zUuid = (char*)P("aid");
    81     93     specRid = zUuid ? uuid_to_rid(zUuid, 0) : 0;
    82     94     rid = nextRid = prevRid = 0;
    83     95     db_prepare(&q1,
    84     96        "SELECT rid FROM tagxref"
    85     97        " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB 'event-%q*')"
    86     98        " ORDER BY mtime DESC",
    87         -     zEventId
           99  +     zId
    88    100     );
    89    101     while( db_step(&q1)==SQLITE_ROW ){
    90    102       nextRid = rid;
    91    103       rid = db_column_int(&q1, 0);
    92    104       if( specRid==0 || specRid==rid ){
    93    105         if( db_step(&q1)==SQLITE_ROW ){
    94    106           prevRid = db_column_int(&q1, 0);
    95    107         }
    96    108         break;
    97    109       }
    98    110     }
    99    111     db_finalize(&q1);
   100    112     if( rid==0 || (specRid!=0 && specRid!=rid) ){
   101         -    style_header("No Such Event");
   102         -    @ Cannot locate specified event
          113  +    style_header("No Such Tech-Note");
          114  +    @ Cannot locate a technical note called <b>%h(zId)</b>.
   103    115       style_footer();
   104    116       return;
   105    117     }
   106    118     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   107    119     zVerbose = P("v");
   108    120     if( !zVerbose ){
   109    121       zVerbose = P("verbose");
................................................................................
   111    123     if( !zVerbose ){
   112    124       zVerbose = P("detail"); /* deprecated */
   113    125     }
   114    126     verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
   115    127   
   116    128     /* Extract the event content.
   117    129     */
   118         -  pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
   119         -  if( pEvent==0 ){
   120         -    fossil_fatal("Object #%d is not an event", rid);
          130  +  pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
          131  +  if( pTNote==0 ){
          132  +    fossil_fatal("Object #%d is not a tech-note", rid);
   121    133     }
   122         -  blob_init(&fullbody, pEvent->zWiki, -1);
   123         -  if( wiki_find_title(&fullbody, &title, &tail) ){
   124         -    style_header("%s", blob_str(&title));
          134  +  zMimetype = wiki_filter_mimetypes(PD("mimetype",pTNote->zMimetype));
          135  +  blob_init(&fullbody, pTNote->zWiki, -1);
          136  +  blob_init(&title, 0, 0);
          137  +  blob_init(&tail, 0, 0);
          138  +  if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
          139  +    if( !wiki_find_title(&fullbody, &title, &tail) ){
          140  +      blob_appendf(&title, "Tech-note %S", zId);
          141  +      tail = fullbody;
          142  +    }
          143  +  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
          144  +    markdown_to_html(&fullbody, &title, &tail);
          145  +    if( blob_size(&title)==0 ){
          146  +      blob_appendf(&title, "Tech-note %S", zId);
          147  +    }
   125    148     }else{
   126         -    style_header("Event %S", zEventId);
          149  +    blob_appendf(&title, "Tech-note %S", zId);
   127    150       tail = fullbody;
   128    151     }
          152  +  style_header("%s", blob_str(&title));
   129    153     if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
   130         -    style_submenu_element("Edit", "Edit", "%s/eventedit?name=%s",
   131         -                          g.zTop, zEventId);
          154  +    style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
   132    155     }
   133         -  zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
   134         -  style_submenu_element("Context", "Context", "%s/timeline?c=%T",
   135         -                        g.zTop, zETime);
          156  +  zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
          157  +  style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
   136    158     if( g.perm.Hyperlink ){
   137    159       if( verboseFlag ){
   138         -      style_submenu_element("Plain", "Plain", "%s/event?name=%s&aid=%s",
   139         -                            g.zTop, zEventId, zUuid);
          160  +      style_submenu_element("Plain", 0,
          161  +                            "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
          162  +                            zId, zUuid);
   140    163         if( nextRid ){
   141    164           char *zNext;
   142    165           zNext = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nextRid);
   143         -        style_submenu_element("Next", "Next",
   144         -                              "%s/event?name=%s&aid=%s&v",
   145         -                              g.zTop, zEventId, zNext);
          166  +        style_submenu_element("Next", 0,"%R/technote?name=%!S&aid=%s&v",
          167  +                              zId, zNext);
   146    168           free(zNext);
   147    169         }
   148    170         if( prevRid ){
   149    171           char *zPrev;
   150    172           zPrev = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", prevRid);
   151         -        style_submenu_element("Prev", "Prev",
   152         -                              "%s/event?name=%s&aid=%s&v",
   153         -                              g.zTop, zEventId, zPrev);
          173  +        style_submenu_element("Prev", 0, "%R/technote?name=%!S&aid=%s&v",
          174  +                              zId, zPrev);
   154    175           free(zPrev);
   155    176         }
   156    177       }else{
   157         -      style_submenu_element("Detail", "Detail",
   158         -                            "%s/event?name=%s&aid=%s&v",
   159         -                            g.zTop, zEventId, zUuid);
          178  +      style_submenu_element("Detail", 0, "%R/technote?name=%!S&aid=%s&v",
          179  +                            zId, zUuid);
   160    180       }
   161    181     }
   162    182   
   163    183     if( verboseFlag && g.perm.Hyperlink ){
   164    184       int i;
   165    185       const char *zClr = 0;
   166    186       Blob comment;
   167    187   
   168         -    zATime = db_text(0, "SELECT datetime(%.17g)", pEvent->rDate);
   169         -    @ <p>Event [%z(href("%R/artifact/%s",zUuid))%S(zUuid)</a>] at
          188  +    zATime = db_text(0, "SELECT datetime(%.17g)", pTNote->rDate);
          189  +    @ <p>Tech-note [%z(href("%R/artifact/%!S",zUuid))%S(zUuid)</a>] at
   170    190       @ [%z(href("%R/timeline?c=%T",zETime))%s(zETime)</a>]
   171         -    @ entered by user <b>%h(pEvent->zUser)</b> on
          191  +    @ entered by user <b>%h(pTNote->zUser)</b> on
   172    192       @ [%z(href("%R/timeline?c=%T",zATime))%s(zATime)</a>]:</p>
   173    193       @ <blockquote>
   174         -    for(i=0; i<pEvent->nTag; i++){
   175         -      if( fossil_strcmp(pEvent->aTag[i].zName,"+bgcolor")==0 ){
   176         -        zClr = pEvent->aTag[i].zValue;
          194  +    for(i=0; i<pTNote->nTag; i++){
          195  +      if( fossil_strcmp(pTNote->aTag[i].zName,"+bgcolor")==0 ){
          196  +        zClr = pTNote->aTag[i].zValue;
   177    197         }
   178    198       }
   179    199       if( zClr && zClr[0]==0 ) zClr = 0;
   180    200       if( zClr ){
   181    201         @ <div style="background-color: %h(zClr);">
   182    202       }else{
   183    203         @ <div>
   184    204       }
   185         -    blob_init(&comment, pEvent->zComment, -1);
          205  +    blob_init(&comment, pTNote->zComment, -1);
   186    206       wiki_convert(&comment, 0, WIKI_INLINE);
   187    207       blob_reset(&comment);
   188    208       @ </div>
   189    209       @ </blockquote><hr />
   190    210     }
   191    211   
   192         -  wiki_convert(&tail, 0, 0);
          212  +  if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
          213  +    wiki_convert(&fullbody, 0, 0);
          214  +  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
          215  +    cgi_append_content(blob_buffer(&tail), blob_size(&tail));
          216  +  }else{
          217  +    @ <pre>
          218  +    @ %h(blob_str(&fullbody))
          219  +    @ </pre>
          220  +  }
   193    221     style_footer();
   194         -  manifest_destroy(pEvent);
          222  +  manifest_destroy(pTNote);
   195    223   }
   196    224   
   197    225   /*
          226  +** WEBPAGE: technoteedit
   198    227   ** WEBPAGE: eventedit
   199         -** URL: /eventedit?name=EVENTID
          228  +**
          229  +** Revise or create a technical note (formerly called an 'event').
          230  +**
          231  +** Parameters:
   200    232   **
   201         -** Edit an event.  If name is omitted, create a new event.
          233  +**    name=ID           Hex hash ID of the tech-note.  If omitted, a new
          234  +**                      tech-note is created.
   202    235   */
   203    236   void eventedit_page(void){
   204    237     char *zTag;
   205    238     int rid = 0;
   206    239     Blob event;
   207         -  const char *zEventId;
          240  +  const char *zId;
   208    241     int n;
   209    242     const char *z;
   210    243     char *zBody = (char*)P("w");
   211    244     char *zETime = (char*)P("t");
   212    245     const char *zComment = P("c");
   213    246     const char *zTags = P("g");
   214    247     const char *zClr;
          248  +  const char *zMimetype = P("mimetype");
          249  +  int isNew = 0;
   215    250   
   216    251     if( zBody ){
   217    252       zBody = mprintf("%s", zBody);
   218    253     }
   219    254     login_check_credentials();
   220         -  zEventId = P("name");
   221         -  if( zEventId==0 ){
   222         -    zEventId = db_text(0, "SELECT lower(hex(randomblob(20)))");
          255  +  zId = P("name");
          256  +  if( zId==0 ){
          257  +    zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
          258  +    isNew = 1;
   223    259     }else{
   224         -    int nEventId = strlen(zEventId);
   225         -    if( nEventId!=40 || !validate16(zEventId, 40) ){
          260  +    int nId = strlen(zId);
          261  +    if( !validate16(zId, nId) ){
   226    262         fossil_redirect_home();
   227    263         return;
   228    264       }
   229    265     }
   230         -  zTag = mprintf("event-%s", zEventId);
          266  +  zTag = mprintf("event-%s", zId);
   231    267     rid = db_int(0,
   232    268       "SELECT rid FROM tagxref"
   233         -    " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
          269  +    " WHERE tagid=(SELECT tagid FROM tag WHERE tagname GLOB '%q*')"
   234    270       " ORDER BY mtime DESC", zTag
   235    271     );
          272  +  if( rid && strlen(zId)<40 ){
          273  +    zId = db_text(0,
          274  +      "SELECT substr(tagname,7) FROM tag WHERE tagname GLOB '%q*'",
          275  +      zTag
          276  +    );
          277  +  }
   236    278     free(zTag);
   237    279   
   238    280     /* Need both check-in and wiki-write or wiki-create privileges in order
   239    281     ** to edit/create an event.
   240    282     */
   241    283     if( !g.perm.Write || (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
   242         -    login_needed();
          284  +    login_needed(g.anon.Write && (rid ? g.anon.WrWiki : g.anon.NewWiki));
   243    285       return;
   244    286     }
   245    287   
   246    288     /* Figure out the color */
   247    289     if( rid ){
   248         -    zClr = db_text("", "SELECT bgcolor  FROM event WHERE objid=%d", rid);
          290  +    zClr = db_text("", "SELECT bgcolor FROM event WHERE objid=%d", rid);
   249    291     }else{
   250    292       zClr = "";
          293  +    isNew = 1;
   251    294     }
   252    295     zClr = PD("clr",zClr);
   253    296     if( fossil_strcmp(zClr,"##")==0 ) zClr = PD("cclr","");
   254    297   
   255    298   
   256    299     /* If editing an existing event, extract the key fields to use as
   257    300     ** a starting point for the edit.
   258    301     */
   259         -  if( rid && (zBody==0 || zETime==0 || zComment==0 || zTags==0) ){
   260         -    Manifest *pEvent;
   261         -    pEvent = manifest_get(rid, CFTYPE_EVENT, 0);
   262         -    if( pEvent && pEvent->type==CFTYPE_EVENT ){
   263         -      if( zBody==0 ) zBody = pEvent->zWiki;
          302  +  if( rid
          303  +   && (zBody==0 || zETime==0 || zComment==0 || zTags==0 || zMimetype==0)
          304  +  ){
          305  +    Manifest *pTNote;
          306  +    pTNote = manifest_get(rid, CFTYPE_EVENT, 0);
          307  +    if( pTNote && pTNote->type==CFTYPE_EVENT ){
          308  +      if( zBody==0 ) zBody = pTNote->zWiki;
   264    309         if( zETime==0 ){
   265         -        zETime = db_text(0, "SELECT datetime(%.17g)", pEvent->rEventDate);
          310  +        zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
   266    311         }
   267         -      if( zComment==0 ) zComment = pEvent->zComment;
          312  +      if( zComment==0 ) zComment = pTNote->zComment;
          313  +      if( zMimetype==0 ) zMimetype = pTNote->zMimetype;
   268    314       }
   269    315       if( zTags==0 ){
   270    316         zTags = db_text(0,
   271    317           "SELECT group_concat(substr(tagname,5),', ')"
   272    318           "  FROM tagxref, tag"
   273    319           " WHERE tagxref.rid=%d"
   274    320           "   AND tagxref.tagid=tag.tagid"
................................................................................
   278    324       }
   279    325     }
   280    326     zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
   281    327     if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
   282    328       char *zDate;
   283    329       Blob cksum;
   284    330       int nrid, n;
   285         -    blob_zero(&event);
          331  +    blob_init(&event, 0, 0);
   286    332       db_begin_transaction();
   287    333       login_verify_csrf_secret();
   288    334       while( fossil_isspace(zComment[0]) ) zComment++;
   289    335       n = strlen(zComment);
   290    336       while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
   291    337       if( n>0 ){
   292    338         blob_appendf(&event, "C %#F\n", n, zComment);
   293    339       }
   294    340       zDate = date_in_standard_format("now");
   295    341       blob_appendf(&event, "D %s\n", zDate);
   296    342       free(zDate);
   297    343       zETime[10] = 'T';
   298         -    blob_appendf(&event, "E %s %s\n", zETime, zEventId);
          344  +    blob_appendf(&event, "E %s %s\n", zETime, zId);
   299    345       zETime[10] = ' ';
   300    346       if( rid ){
   301    347         char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   302    348         blob_appendf(&event, "P %s\n", zUuid);
   303    349         free(zUuid);
   304    350       }
          351  +    if( zMimetype && zMimetype[0] ){
          352  +      blob_appendf(&event, "N %s\n", zMimetype);
          353  +    }
   305    354       if( zClr && zClr[0] ){
   306    355         blob_appendf(&event, "T +bgcolor * %F\n", zClr);
   307    356       }
   308    357       if( zTags && zTags[0] ){
   309    358         Blob tags, one;
   310    359         int i, j;
   311    360         Stmt q;
................................................................................
   348    397       }
   349    398       blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
   350    399       md5sum_blob(&event, &cksum);
   351    400       blob_appendf(&event, "Z %b\n", &cksum);
   352    401       blob_reset(&cksum);
   353    402       nrid = content_put(&event);
   354    403       db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
   355         -    manifest_crosslink(nrid, &event, MC_NONE);
          404  +    if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
          405  +      db_end_transaction(1);
          406  +      style_header("Error");
          407  +      @ Internal error:  Fossil tried to make an invalid artifact for
          408  +      @ the edited technode.
          409  +      style_footer();
          410  +      return;
          411  +    }
   356    412       assert( blob_is_reset(&event) );
   357    413       content_deltify(rid, nrid, 0);
   358    414       db_end_transaction(0);
   359         -    cgi_redirectf("event?name=%T", zEventId);
          415  +    cgi_redirectf("technote?name=%T", zId);
   360    416     }
   361    417     if( P("cancel")!=0 ){
   362         -    cgi_redirectf("event?name=%T", zEventId);
          418  +    cgi_redirectf("technote?name=%T", zId);
   363    419       return;
   364    420     }
   365    421     if( zBody==0 ){
   366         -    zBody = mprintf("<i>Event Text</i>");
          422  +    zBody = mprintf("Insert new content here...");
   367    423     }
   368         -  style_header("Edit Event %S", zEventId);
          424  +  if( isNew ){
          425  +    style_header("New Tech-note %S", zId);
          426  +  }else{
          427  +    style_header("Edit Tech-note %S", zId);
          428  +  }
   369    429     if( P("preview")!=0 ){
   370         -    Blob title, tail, com;
          430  +    Blob com;
   371    431       @ <p><b>Timeline comment preview:</b></p>
   372    432       @ <blockquote>
   373    433       @ <table border="0">
   374    434       if( zClr && zClr[0] ){
   375    435         @ <tr><td style="background-color: %h(zClr);">
   376    436       }else{
   377    437         @ <tr><td>
................................................................................
   379    439       blob_zero(&com);
   380    440       blob_append(&com, zComment, -1);
   381    441       wiki_convert(&com, 0, WIKI_INLINE|WIKI_NOBADLINKS);
   382    442       @ </td></tr></table>
   383    443       @ </blockquote>
   384    444       @ <p><b>Page content preview:</b><p>
   385    445       @ <blockquote>
   386         -    blob_zero(&event);
          446  +    blob_init(&event, 0, 0);
   387    447       blob_append(&event, zBody, -1);
   388         -    if( wiki_find_title(&event, &title, &tail) ){
   389         -      @ <h2 align="center">%h(blob_str(&title))</h2>
   390         -      wiki_convert(&tail, 0, 0);
   391         -    }else{
   392         -      wiki_convert(&event, 0, 0);
   393         -    }
          448  +    wiki_render_by_mimetype(&event, zMimetype);
   394    449       @ </blockquote><hr />
   395    450       blob_reset(&event);
   396    451     }
   397    452     for(n=2, z=zBody; z[0]; z++){
   398    453       if( z[0]=='\n' ) n++;
   399    454     }
   400    455     if( n<20 ) n = 20;
   401    456     if( n>40 ) n = 40;
   402         -  @ <form method="post" action="%s(g.zTop)/eventedit"><div>
          457  +  @ <form method="post" action="%R/technoteedit"><div>
   403    458     login_insert_csrf_secret();
   404         -  @ <input type="hidden" name="name" value="%h(zEventId)" />
          459  +  @ <input type="hidden" name="name" value="%h(zId)" />
   405    460     @ <table border="0" cellspacing="10">
   406    461   
   407         -  @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
          462  +  @ <tr><th align="right" valign="top">Timestamp (UTC):</th>
   408    463     @ <td valign="top">
   409    464     @   <input type="text" name="t" size="25" value="%h(zETime)" />
   410    465     @ </td></tr>
   411    466   
   412         -  @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
          467  +  @ <tr><th align="right" valign="top">Timeline Comment:</th>
   413    468     @ <td valign="top">
   414         -  @ <textarea name="c" class="eventedit" cols="80"
          469  +  @ <textarea name="c" class="technoteedit" cols="80"
   415    470     @  rows="3" wrap="virtual">%h(zComment)</textarea>
   416    471     @ </td></tr>
   417    472   
   418         -  @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
          473  +  @ <tr><th align="right" valign="top">Timeline Background Color:</th>
   419    474     @ <td valign="top">
   420    475     render_color_chooser(0, zClr, 0, "clr", "cclr");
   421    476     @ </td></tr>
   422    477   
   423    478     @ <tr><th align="right" valign="top">Tags:</th>
   424    479     @ <td valign="top">
   425    480     @   <input type="text" name="g" size="40" value="%h(zTags)" />
   426    481     @ </td></tr>
          482  +
          483  +  @ <tr><th align="right" valign="top">Markup Style:</th>
          484  +  @ <td valign="top">
          485  +  mimetype_option_menu(zMimetype);
          486  +  @ </td></tr>
   427    487   
   428    488     @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
   429    489     @ <td valign="top">
   430         -  @ <textarea name="w" class="eventedit" cols="80"
          490  +  @ <textarea name="w" class="technoteedit" cols="80"
   431    491     @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
   432    492     @ </td></tr>
   433    493   
   434    494     @ <tr><td colspan="2">
   435    495     @ <input type="submit" name="preview" value="Preview Your Changes" />
   436    496     @ <input type="submit" name="submit" value="Apply These Changes" />
   437    497     @ <input type="submit" name="cancel" value="Cancel" />
   438    498     @ </td></tr></table>
   439    499     @ </div></form>
   440    500     style_footer();
   441    501   }

Changes to src/file.c.

   230    230     blob_read_link(&content, zFrom);
   231    231     symlink_create(blob_str(&content), zTo);
   232    232     blob_reset(&content);
   233    233   }
   234    234   
   235    235   /*
   236    236   ** Return file permissions (normal, executable, or symlink):
   237         -**   - PERM_EXE if file is executable;
          237  +**   - PERM_EXE on Unix if file is executable;
   238    238   **   - PERM_LNK on Unix if file is symlink and allow-symlinks option is on;
   239    239   **   - PERM_REG for all other cases (regular file, directory, fifo, etc).
   240    240   */
   241    241   int file_wd_perm(const char *zFilename){
   242         -  if( getStat(zFilename, 1) ) return PERM_REG;
   243         -#if defined(_WIN32)
   244         -#  ifndef S_IXUSR
   245         -#    define S_IXUSR  _S_IEXEC
   246         -#  endif
   247         -  if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
   248         -    return PERM_EXE;
   249         -  else
   250         -    return PERM_REG;
   251         -#else
   252         -  if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
   253         -    return PERM_EXE;
   254         -  else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
   255         -    return PERM_LNK;
   256         -  else
   257         -    return PERM_REG;
          242  +#if !defined(_WIN32)
          243  +  if( !getStat(zFilename, 1) ){
          244  +     if( S_ISREG(fileStat.st_mode) && ((S_IXUSR)&fileStat.st_mode)!=0 )
          245  +      return PERM_EXE;
          246  +    else if( g.allowSymlinks && S_ISLNK(fileStat.st_mode) )
          247  +      return PERM_LNK;
          248  +  }
   258    249   #endif
          250  +  return PERM_REG;
   259    251   }
   260    252   
   261    253   /*
   262    254   ** Return TRUE if the named file is an executable.  Return false
   263    255   ** for directories, devices, fifos, symlinks, etc.
   264    256   */
   265    257   int file_wd_isexe(const char *zFilename){
................................................................................
  1259   1251     fossil_unicode_free(uName);
  1260   1252   #else
  1261   1253     char *zValue = getenv(zName);
  1262   1254   #endif
  1263   1255     if( zValue ) zValue = fossil_filename_to_utf8(zValue);
  1264   1256     return zValue;
  1265   1257   }
         1258  +
         1259  +/*
         1260  +** Sets the value of an environment variable as UTF8.
         1261  +*/
         1262  +int fossil_setenv(const char *zName, const char *zValue){
         1263  +  int rc;
         1264  +  char *zString = mprintf("%s=%s", zName, zValue);
         1265  +#ifdef _WIN32
         1266  +  wchar_t *uString = fossil_utf8_to_unicode(zString);
         1267  +  rc = _wputenv(uString);
         1268  +  fossil_unicode_free(uString);
         1269  +  fossil_free(zString);
         1270  +#else
         1271  +  rc = putenv(zString);
         1272  +  /* NOTE: Cannot free the string on POSIX. */
         1273  +  /* fossil_free(zString); */
         1274  +#endif
         1275  +  return rc;
         1276  +}
  1266   1277   
  1267   1278   /*
  1268   1279   ** Like fopen() but always takes a UTF8 argument.
  1269   1280   */
  1270   1281   FILE *fossil_fopen(const char *zName, const char *zMode){
  1271   1282   #ifdef _WIN32
  1272   1283     wchar_t *uMode = fossil_utf8_to_unicode(zMode);

Changes to src/finfo.c.

   213    213           zOut = mprintf(
   214    214              "[%S] %s (user: %s, artifact: [%S], branch: %s)",
   215    215              zCiUuid, zCom, zUser, zFileUuid, zBr);
   216    216           comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags);
   217    217           fossil_free(zOut);
   218    218         }else{
   219    219           blob_reset(&line);
   220         -        blob_appendf(&line, "%.10s ", zCiUuid);
          220  +        blob_appendf(&line, "%S ", zCiUuid);
   221    221           blob_appendf(&line, "%.10s ", zDate);
   222    222           blob_appendf(&line, "%8.8s ", zUser);
   223    223           blob_appendf(&line, "%8.8s ", zBr);
   224    224           blob_appendf(&line,"%-39.39s", zCom );
   225    225           comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags);
   226    226         }
   227    227       }
................................................................................
   282    282   **
   283    283   **    a=DATE     Only show changes after DATE
   284    284   **    b=DATE     Only show changes before DATE
   285    285   **    n=NUM      Show the first NUM changes only
   286    286   **    brbg       Background color by branch name
   287    287   **    ubg        Background color by user name
   288    288   **    ci=UUID    Ancestors of a particular check-in
   289         -**    fco=BOOL   Show only first occurrence of each version if true (default)
   290    289   */
   291    290   void finfo_page(void){
   292    291     Stmt q;
   293    292     const char *zFilename;
   294    293     char zPrevDate[20];
   295    294     const char *zA;
   296    295     const char *zB;
   297    296     int n;
   298    297     int baseCheckin;
   299         -
          298  +  int fnid;
          299  +  Bag ancestor;
   300    300     Blob title;
   301    301     Blob sql;
   302    302     HQuery url;
   303    303     GraphContext *pGraph;
   304    304     int brBg = P("brbg")!=0;
   305    305     int uBg = P("ubg")!=0;
   306         -  int firstChngOnly = atoi(PD("fco","1"))!=0;
   307    306     int fDebug = atoi(PD("debug","0"));
          307  +  int fShowId = P("showid")!=0;
   308    308   
   309    309     login_check_credentials();
   310         -  if( !g.perm.Read ){ login_needed(); return; }
          310  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   311    311     style_header("File History");
   312    312     login_anonymous_available();
   313    313     url_initialize(&url, "finfo");
   314    314     if( brBg ) url_add_parameter(&url, "brbg", 0);
   315    315     if( uBg ) url_add_parameter(&url, "ubg", 0);
   316    316     baseCheckin = name_to_rid_www("ci");
   317         -  if( baseCheckin ) firstChngOnly = 1;
   318         -  if( !firstChngOnly ) url_add_parameter(&url, "fco", "0");
   319         -
   320    317     zPrevDate[0] = 0;
   321    318     zFilename = PD("name","");
          319  +  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
          320  +  if( fnid==0 ){
          321  +    @ No such file: %h(zFilename)
          322  +    style_footer();
          323  +    return;
          324  +  }
          325  +  if( baseCheckin ){
          326  +    int baseFid = db_int(0,
          327  +      "SELECT fid FROM mlink WHERE fnid=%d AND mid=%d",
          328  +      fnid, baseCheckin
          329  +    );
          330  +    bag_init(&ancestor);
          331  +    if( baseFid ) bag_insert(&ancestor, baseFid);
          332  +  }
   322    333     url_add_parameter(&url, "name", zFilename);
   323    334     blob_zero(&sql);
   324    335     blob_append_sql(&sql,
   325    336       "SELECT"
   326         -    " datetime(event.mtime%s),"                      /* Date of change */
          337  +    " datetime(min(event.mtime)%s),"                 /* Date of change */
   327    338       " coalesce(event.ecomment, event.comment),"      /* Check-in comment */
   328    339       " coalesce(event.euser, event.user),"            /* User who made chng */
   329    340       " mlink.pid,"                                    /* Parent file rid */
   330    341       " mlink.fid,"                                    /* File rid */
   331    342       " (SELECT uuid FROM blob WHERE rid=mlink.pid),"  /* Parent file uuid */
   332    343       " (SELECT uuid FROM blob WHERE rid=mlink.fid),"  /* Current file uuid */
   333    344       " (SELECT uuid FROM blob WHERE rid=mlink.mid),"  /* Check-in uuid */
   334    345       " event.bgcolor,"                                /* Background color */
   335    346       " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
   336         -                                " AND tagxref.rid=mlink.mid)," /* Tags */
          347  +                                " AND tagxref.rid=mlink.mid)," /* Branchname */
   337    348       " mlink.mid,"                                    /* check-in ID */
   338         -    " mlink.pfnid",                                  /* Previous filename */
   339         -    timeline_utc(), TAG_BRANCH
   340         -  );
   341         -  if( firstChngOnly ){
   342         -#if 0
   343         -    blob_append_sql(&sql, ", min(event.mtime)");
   344         -#else
   345         -    blob_append_sql(&sql,
   346         -        ", min(CASE (SELECT value FROM tagxref"
   347         -                    " WHERE tagtype>0 AND tagid=%d"
   348         -                    "   AND tagxref.rid=mlink.mid)"
   349         -             " WHEN 'trunk' THEN event.mtime-10000 ELSE event.mtime END)",
   350         -    TAG_BRANCH);
   351         -#endif
   352         -  }
   353         -  blob_append_sql(&sql,
          349  +    " mlink.pfnid"                                   /* Previous filename */
   354    350       "  FROM mlink, event"
   355         -    " WHERE mlink.fnid IN (SELECT fnid FROM filename WHERE name=%Q)"
          351  +    " WHERE mlink.fnid=%d"
   356    352       "   AND event.objid=mlink.mid",
   357         -    zFilename
          353  +    timeline_utc(), TAG_BRANCH, fnid
   358    354     );
   359         -  if( baseCheckin ){
   360         -    compute_direct_ancestors(baseCheckin, 10000000);
   361         -    blob_append_sql(&sql,"  AND mlink.mid IN (SELECT rid FROM ancestor)");
   362         -  }
   363    355     if( (zA = P("a"))!=0 ){
   364    356       blob_append_sql(&sql, " AND event.mtime>=julianday('%q')", zA);
   365    357       url_add_parameter(&url, "a", zA);
   366    358     }
   367    359     if( (zB = P("b"))!=0 ){
   368    360       blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB);
   369    361       url_add_parameter(&url, "b", zB);
   370    362     }
   371         -  if( firstChngOnly ){
   372         -    blob_append_sql(&sql, " GROUP BY mlink.fid");
   373         -  }
   374         -  blob_append_sql(&sql," ORDER BY event.mtime DESC /*sort*/");
          363  +  /* We only want each version of a file to appear on the graph once,
          364  +  ** at its earliest appearance.  All the other times that it gets merged
          365  +  ** into this or that branch can be ignored.  An exception is for when
          366  +  ** files are deleted (when they have mlink.fid==0).  If the same file
          367  +  ** is deleted in multiple places, we want to show each deletion, so
          368  +  ** use a "fake fid" which is derived from the parent-fid for grouping.
          369  +  ** The same fake-fid must be used on the graph.
          370  +  */
          371  +  blob_append_sql(&sql,
          372  +    " GROUP BY"
          373  +    "   CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END"
          374  +    " ORDER BY event.mtime DESC /*sort*/"
          375  +  );
   375    376     if( (n = atoi(PD("n","0")))>0 ){
   376    377       blob_append_sql(&sql, " LIMIT %d", n);
   377    378       url_add_parameter(&url, "n", P("n"));
   378    379     }
   379         -  if( baseCheckin==0 ){
   380         -    if( firstChngOnly ){
   381         -      style_submenu_element("Full", "Show all changes","%s",
   382         -                            url_render(&url, "fco", "0", 0, 0));
   383         -    }else{
   384         -      style_submenu_element("Simplified",
   385         -                            "Show only first use of a change","%s",
   386         -                            url_render(&url, "fco", 0, 0, 0));
   387         -    }
   388         -  }
   389    380     db_prepare(&q, "%s", blob_sql_text(&sql));
   390    381     if( P("showsql")!=0 ){
   391    382       @ <p>SQL: %h(blob_str(&sql))</p>
   392    383     }
   393    384     blob_reset(&sql);
   394    385     blob_zero(&title);
   395    386     if( baseCheckin ){
   396    387       char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
   397         -    char *zLink = href("%R/info/%s", zUuid);
          388  +    char *zLink = 	href("%R/info/%!S", zUuid);
   398    389       blob_appendf(&title, "Ancestors of file ");
   399    390       hyperlinked_path(zFilename, &title, zUuid, "tree", "");
          391  +    if( fShowId ) blob_appendf(&title, " (%d)", fnid);
   400    392       blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
          393  +    if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
   401    394       fossil_free(zUuid);
   402    395     }else{
   403    396       blob_appendf(&title, "History of files named ");
   404    397       hyperlinked_path(zFilename, &title, 0, "tree", "");
          398  +    if( fShowId ) blob_appendf(&title, " (%d)", fnid);
   405    399     }
   406    400     @ <h2>%b(&title)</h2>
   407    401     blob_reset(&title);
   408    402     pGraph = graph_init();
   409    403     @ <div id="canvas" style="position:relative;width:1px;height:1px;"
   410    404     @  onclick="clickOnGraph(event)"></div>
   411    405     @ <table id="timelineTable" class="timelineTable">
................................................................................
   420    414       const char *zCkin = db_column_text(&q,7);
   421    415       const char *zBgClr = db_column_text(&q, 8);
   422    416       const char *zBr = db_column_text(&q, 9);
   423    417       int fmid = db_column_int(&q, 10);
   424    418       int pfnid = db_column_int(&q, 11);
   425    419       int gidx;
   426    420       char zTime[10];
          421  +    int nParent = 0;
          422  +    int aParent[GR_MAX_RAIL];
          423  +    static Stmt qparent;
          424  +
          425  +    if( baseCheckin && frid && !bag_find(&ancestor, frid) ) continue;
          426  +    db_static_prepare(&qparent,
          427  +      "SELECT DISTINCT pid FROM mlink"
          428  +      " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
          429  +      " ORDER BY isaux /*sort*/"
          430  +    );
          431  +    db_bind_int(&qparent, ":fid", frid);
          432  +    db_bind_int(&qparent, ":mid", fmid);
          433  +    db_bind_int(&qparent, ":fnid", fnid);
          434  +    while( db_step(&qparent)==SQLITE_ROW && nParent<ArraySize(aParent) ){
          435  +      aParent[nParent] = db_column_int(&qparent, 0);
          436  +      if( baseCheckin ) bag_insert(&ancestor, aParent[nParent]);
          437  +      nParent++;
          438  +    }
          439  +    db_reset(&qparent);
   427    440       if( zBr==0 ) zBr = "trunk";
   428    441       if( uBg ){
   429    442         zBgClr = hash_color(zUser);
   430    443       }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
   431    444         zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
   432    445       }
   433         -    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr,
          446  +    gidx = graph_add_row(pGraph, frid>0 ? frid : fpid+1000000000,
          447  +                         nParent, aParent, zBr, zBgClr,
   434    448                            zUuid, 0);
   435    449       if( strncmp(zDate, zPrevDate, 10) ){
   436    450         sqlite3_snprintf(sizeof(zPrevDate), zPrevDate, "%.10s", zDate);
   437    451         @ <tr><td>
   438    452         @   <div class="divider">%s(zPrevDate)</div>
   439    453         @ </td><td></td><td></td></tr>
   440    454       }
................................................................................
   445    459       @ <td class="timelineGraph"><div id="m%d(gidx)"></div></td>
   446    460       if( zBgClr && zBgClr[0] ){
   447    461         @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
   448    462       }else{
   449    463         @ <td class="timelineTableCell">
   450    464       }
   451    465       if( zUuid ){
   452         -      if( fpid==0 ){
          466  +      if( nParent==0 ){
   453    467           @ <b>Added</b>
   454    468         }else if( pfnid ){
   455    469           char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
   456    470                                     pfnid);
   457    471           @ <b>Renamed</b> from
   458    472           @ %z(href("%R/finfo?name=%t", zPrevName))%h(zPrevName)</a>
   459    473         }
   460         -      @ %z(href("%R/artifact/%s",zUuid))[%S(zUuid)]</a> part of check-in
          474  +      @ %z(href("%R/artifact/%!S",zUuid))[%S(zUuid)]</a>
          475  +      if( fShowId ){
          476  +        @ (%d(frid))
          477  +      }
          478  +      @ part of check-in
   461    479       }else{
   462    480         char *zNewName;
   463    481         zNewName = db_text(0,
   464    482           "SELECT name FROM filename WHERE fnid = "
   465    483           "   (SELECT fnid FROM mlink"
   466    484           "     WHERE mid=%d"
   467    485           "       AND pfnid IN (SELECT fnid FROM filename WHERE name=%Q))",
................................................................................
   471    489           @ %z(href("%R/finfo?name=%t",zNewName))%h(zNewName)</a> by check-in
   472    490           fossil_free(zNewName);
   473    491         }else{
   474    492           @ <b>Deleted</b> by check-in
   475    493         }
   476    494       }
   477    495       hyperlink_to_uuid(zCkin);
          496  +    if( fShowId ){
          497  +      @ (%d(fmid))
          498  +    }
   478    499       @ %W(zCom) (user:
   479    500       hyperlink_to_user(zUser, zDate, "");
   480         -    @ branch: %h(zBr))
          501  +    @ branch: %z(href("%R/timeline?t=%T&n=200",zBr))%h(zBr)</a>)
   481    502       if( g.perm.Hyperlink && zUuid ){
   482    503         const char *z = zFilename;
   483    504         @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
   484    505         @ [annotate]</a>
   485    506         @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
   486    507         @ [blame]</a>
   487         -      @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins&nbsp;using]</a>
          508  +      @ %z(href("%R/timeline?n=200&uf=%!S",zUuid))[checkins&nbsp;using]</a>
   488    509         if( fpid ){
   489         -        @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
          510  +        @ %z(href("%R/fdiff?sbs=1&v1=%!S&v2=%!S",zPUuid,zUuid))[diff]</a>
   490    511         }
   491    512       }
   492    513       if( fDebug & FINFO_DEBUG_MLINK ){
   493         -      int srcid = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", frid);
   494         -      int sz = db_int(0, "SELECT length(content) FROM blob WHERE rid=%d", frid);
   495         -      @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) sz=%d(sz)
   496         -      if( srcid ){
   497         -        @ srcid=%d(srcid)
          514  +      int ii;
          515  +      char *zAncLink;
          516  +      @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid)
          517  +      if( nParent>0 ){
          518  +        @ parents=%d(aParent[0])
          519  +        for(ii=1; ii<nParent; ii++){
          520  +          @ %d(aParent[ii])
          521  +        }
   498    522         }
          523  +      zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin);
          524  +      @ %z(zAncLink)[ancestry]</a>
   499    525       }
   500    526       tag_private_status(frid);
   501    527       @ </td></tr>
   502    528     }
   503    529     db_finalize(&q);
   504    530     if( pGraph ){
   505         -    graph_finish(pGraph, 0);
          531  +    graph_finish(pGraph, 1);
   506    532       if( pGraph->nErr ){
   507    533         graph_free(pGraph);
   508    534         pGraph = 0;
   509    535       }else{
   510    536         int w = (pGraph->mxRail+1)*pGraph->iRailPitch + 10;
   511    537         @ <tr><td></td><td>
   512    538         @ <div id="grbtm" style="width:%d(w)px;"></div>

Changes to src/glob.c.

    42     42   char *glob_expr(const char *zVal, const char *zGlobList){
    43     43     Blob expr;
    44     44     const char *zSep = "(";
    45     45     int nTerm = 0;
    46     46     int i;
    47     47     int cTerm;
    48     48   
    49         -  if( zGlobList==0 || zGlobList[0]==0 ) return "0";
           49  +  if( zGlobList==0 || zGlobList[0]==0 ) return fossil_strdup("0");
    50     50     blob_zero(&expr);
    51     51     while( zGlobList[0] ){
    52     52       while( fossil_isspace(zGlobList[0]) || zGlobList[0]==',' ){
    53     53         zGlobList++;  /* Skip leading commas, spaces, and newlines */
    54     54       }
    55     55       if( zGlobList[0]==0 ) break;
    56     56       if( zGlobList[0]=='\'' || zGlobList[0]=='"' ){
................................................................................
    71     71       if( zGlobList[0] ) zGlobList++;
    72     72       nTerm++;
    73     73     }
    74     74     if( nTerm ){
    75     75       blob_appendf(&expr, ")");
    76     76       return blob_str(&expr);
    77     77     }else{
    78         -    return "0";
           78  +    return fossil_strdup("0");
    79     79     }
    80     80   }
    81     81   
    82     82   #if INTERFACE
    83     83   /*
    84     84   ** A Glob object holds a set of patterns read to be matched against
    85     85   ** a string.

Changes to src/graph.c.

   376    376           if( hashFind(p, pRow->aParent[i])==0 ){
   377    377             pRow->aParent[i] = pRow->aParent[--pRow->nParent];
   378    378             i--;
   379    379           }
   380    380         }
   381    381       }
   382    382     }
          383  +
          384  +  /* If the primary parent is in a different branch, but there are
          385  +  ** other parents in the same branch, reorder the parents to make
          386  +  ** the parent from the same branch the primary parent.
          387  +  */
          388  +  for(pRow=p->pFirst; pRow; pRow=pRow->pNext){
          389  +    if( pRow->isDup ) continue;
          390  +    if( pRow->nParent<2 ) continue;                    /* Not a fork */
          391  +    pParent = hashFind(p, pRow->aParent[0]);
          392  +    if( pParent==0 ) continue;                         /* Parent off-screen */
          393  +    if( pParent->zBranch==pRow->zBranch ) continue;    /* Same branch */
          394  +    for(i=1; i<pRow->nParent; i++){
          395  +      pParent = hashFind(p, pRow->aParent[i]);
          396  +      if( pParent && pParent->zBranch==pRow->zBranch ){
          397  +        int t = pRow->aParent[0];
          398  +        pRow->aParent[0] = pRow->aParent[i];
          399  +        pRow->aParent[i] = t;
          400  +        break;
          401  +      }
          402  +    }
          403  +  }
   383    404   
   384    405   
   385    406     /* Find the pChild pointer for each node.
   386    407     **
   387    408     ** The pChild points to the node directly above on the same rail.
   388    409     ** The pChild must be in the same branch.  Leaf nodes have a NULL
   389    410     ** pChild.

Changes to src/http.c.

   153    153     prompt_user("Remember Basic Authorization credentials (Y/n)? ", &x);
   154    154     c = blob_str(&x)[0];
   155    155     blob_reset(&x);
   156    156     return ( c!='n' && c!='N' );
   157    157   }
   158    158   
   159    159   /*
   160         -** Get the HTTP Basic Authorization credentials from the user 
          160  +** Get the HTTP Basic Authorization credentials from the user
   161    161   ** when 401 is received.
   162    162   */
   163    163   char *prompt_for_httpauth_creds(void){
   164    164     Blob x;
   165    165     char *zUser;
   166    166     char *zPw;
   167    167     char *zPrompt;
................................................................................
   264    264     ** Send the request to the server.
   265    265     */
   266    266     transport_send(&g.url, &hdr);
   267    267     transport_send(&g.url, &payload);
   268    268     blob_reset(&hdr);
   269    269     blob_reset(&payload);
   270    270     transport_flip(&g.url);
   271         -  
          271  +
   272    272     /*
   273    273     ** Read and interpret the server reply
   274    274     */
   275    275     closeConnection = 1;
   276    276     iLength = -1;
   277    277     while( (zLine = transport_receive_line(&g.url))!=0 && zLine[0]!=0 ){
   278    278       /* printf("[%s]\n", zLine); fflush(stdout); */
................................................................................
   331    331           goto write_err;
   332    332         }
   333    333         for(i=9; zLine[i] && zLine[i]==' '; i++){}
   334    334         if( zLine[i]==0 ){
   335    335           fossil_warning("malformed redirect: %s", zLine);
   336    336           goto write_err;
   337    337         }
   338         -      j = strlen(zLine) - 1; 
          338  +      j = strlen(zLine) - 1;
   339    339         while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){
   340    340            j -= 4;
   341    341            zLine[j] = 0;
   342    342         }
   343    343         transport_close(&g.url);
   344    344         transport_global_shutdown(&g.url);
   345    345         fossil_print("redirect with status %d to %s\n", rc, &zLine[i]);
................................................................................
   347    347         fSeenHttpAuth = 0;
   348    348         if( g.zHttpAuth ) free(g.zHttpAuth);
   349    349         g.zHttpAuth = get_httpauth();
   350    350         return http_exchange(pSend, pReply, useLogin, maxRedirect);
   351    351       }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
   352    352         if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
   353    353           isCompressed = 0;
   354         -      }else if( fossil_strnicmp(&zLine[14], 
          354  +      }else if( fossil_strnicmp(&zLine[14],
   355    355                             "application/x-fossil-uncompressed", -1)==0 ){
   356    356           isCompressed = 0;
   357    357         }else if( fossil_strnicmp(&zLine[14], "application/x-fossil", -1)!=0 ){
   358    358           isError = 1;
   359    359         }
   360    360       }
   361    361     }
................................................................................
   409    409     if( closeConnection ){
   410    410       transport_close(&g.url);
   411    411     }else{
   412    412       transport_rewind(&g.url);
   413    413     }
   414    414     return 0;
   415    415   
   416         -  /* 
          416  +  /*
   417    417     ** Jump to here if an error is seen.
   418    418     */
   419    419   write_err:
   420    420     transport_close(&g.url);
   421         -  return 1;  
          421  +  return 1;
   422    422   }

Changes to src/http_socket.c.

    25     25   ** Low-level sockets are abstracted out into this module because they
    26     26   ** are handled different on Unix and windows.
    27     27   */
    28     28   
    29     29   #include "config.h"
    30     30   #include "http_socket.h"
    31     31   #if defined(_WIN32)
           32  +#  if !defined(_WIN32_WINNT)
           33  +#    define _WIN32_WINNT 0x0501
           34  +#  endif
    32     35   #  include <winsock2.h>
    33     36   #  include <ws2tcpip.h>
    34     37   #else
    35     38   #  include <netinet/in.h>
    36     39   #  include <arpa/inet.h>
    37     40   #  include <sys/socket.h>
    38     41   #  include <netdb.h>
................................................................................
    43     46   
    44     47   /*
    45     48   ** There can only be a single socket connection open at a time.
    46     49   ** State information about that socket is stored in the following
    47     50   ** local variables:
    48     51   */
    49     52   static int socketIsInit = 0;    /* True after global initialization */
    50         -static int addrIsInit = 0;      /* True once addr is initialized */
    51     53   #if defined(_WIN32)
    52     54   static WSADATA socketInfo;      /* Windows socket initialize data */
    53     55   #endif
    54     56   static int iSocket = -1;        /* The socket on which we talk to the server */
    55     57   static char *socketErrMsg = 0;  /* Text of most recent socket error */
    56     58   
    57     59   
................................................................................
   104    106     if( socketIsInit ){
   105    107   #if defined(_WIN32)
   106    108       WSACleanup();
   107    109   #endif
   108    110       socket_clear_errmsg();
   109    111       socketIsInit = 0;
   110    112     }
   111         -  addrIsInit = 0;
   112    113   }
   113    114   
   114    115   /*
   115    116   ** Close the currently open socket.  If no socket is open, this routine
   116    117   ** is a no-op.
   117    118   */
   118    119   void socket_close(void){
................................................................................
   132    133   **
   133    134   **    pUrlDAta->name       Name of the server.  Ex: www.fossil-scm.org
   134    135   **    pUrlDAta->port       TCP/IP port to use.  Ex: 80
   135    136   **
   136    137   ** Return the number of errors.
   137    138   */
   138    139   int socket_open(UrlData *pUrlData){
   139         -  static struct sockaddr_in addr;  /* The server address */
          140  +  int rc = 0;
          141  +  struct addrinfo *ai = 0;
          142  +  struct addrinfo *p;
          143  +  struct addrinfo hints;
          144  +  char zPort[30];
          145  +  char zRemote[NI_MAXHOST];
   140    146   
   141    147     socket_global_init();
   142         -  if( !addrIsInit ){
   143         -    memset(&addr, 0, sizeof(addr));
   144         -    addr.sin_family = AF_INET;
   145         -    addr.sin_port = htons(pUrlData->port);
   146         -    *(int*)&addr.sin_addr = inet_addr(pUrlData->name);
   147         -    if( -1 == *(int*)&addr.sin_addr ){
   148         -#ifndef FOSSIL_STATIC_LINK
   149         -      struct hostent *pHost;
   150         -      pHost = gethostbyname(pUrlData->name);
   151         -      if( pHost!=0 ){
   152         -        memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
   153         -      }else
   154         -#endif
   155         -      {
   156         -        socket_set_errmsg("can't resolve host name: %s", pUrlData->name);
   157         -        return 1;
   158         -      }
          148  +  memset(&hints, 0, sizeof(struct addrinfo));
          149  +  assert( iSocket<0 );
          150  +  hints.ai_family = g.fIPv4 ? AF_INET : AF_UNSPEC;
          151  +  hints.ai_socktype = SOCK_STREAM;
          152  +  hints.ai_protocol = IPPROTO_TCP;
          153  +  sqlite3_snprintf(sizeof(zPort),zPort,"%d", pUrlData->port);
          154  +  rc = getaddrinfo(pUrlData->name, zPort, &hints, &ai);
          155  +  if( rc ){
          156  +    socket_set_errmsg("getaddrinfo() fails: %s", gai_strerror(rc));
          157  +    goto end_socket_open;
          158  +  }
          159  +  for(p=ai; p; p=p->ai_next){
          160  +    iSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
          161  +    if( iSocket<0 ) continue;
          162  +    if( connect(iSocket,p->ai_addr,p->ai_addrlen)<0 ){
          163  +      socket_close();
          164  +      continue;
          165  +    }
          166  +    rc = getnameinfo(p->ai_addr, p->ai_addrlen, zRemote, sizeof(zRemote),
          167  +                     0, 0, NI_NUMERICHOST);
          168  +    if( rc ){
          169  +      socket_set_errmsg("getnameinfo() failed: %s", gai_strerror(rc));
          170  +      goto end_socket_open;
   159    171       }
   160         -    addrIsInit = 1;
   161         -
   162         -    /* Set the Global.zIpAddr variable to the server we are talking to.
   163         -    ** This is used to populate the ipaddr column of the rcvfrom table,
   164         -    ** if any files are received from the server.
   165         -    */
   166         -    g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
          172  +    g.zIpAddr = mprintf("%s", zRemote);
          173  +    break;
   167    174     }
   168         -  iSocket = socket(AF_INET,SOCK_STREAM,0);
   169         -  if( iSocket<0 ){
   170         -    socket_set_errmsg("cannot create a socket");
   171         -    return 1;
   172         -  }
   173         -  if( connect(iSocket,(struct sockaddr*)&addr,sizeof(addr))<0 ){
          175  +  if( p==0 ){
   174    176       socket_set_errmsg("cannot connect to host %s:%d", pUrlData->name,
   175    177                         pUrlData->port);
   176         -    socket_close();
   177         -    return 1;
          178  +    rc = 1;
   178    179     }
   179    180   #if !defined(_WIN32)
   180    181     signal(SIGPIPE, SIG_IGN);
   181    182   #endif
   182         -  return 0;
          183  +end_socket_open:
          184  +  if( rc && iSocket>=0 ) socket_close();
          185  +  if( ai ) freeaddrinfo(ai);
          186  +  return rc;
   183    187   }
   184    188   
   185    189   /*
   186    190   ** Send content out over the open socket connection.
   187    191   */
   188    192   size_t socket_send(void *NotUsed, void *pContent, size_t N){
   189    193     size_t sent;
................................................................................
   218    222   /*
   219    223   ** Attempt to resolve pUrlData->name to an IP address and setup g.zIpAddr
   220    224   ** so rcvfrom gets populated. For hostnames with more than one IP (or
   221    225   ** if overridden in ~/.ssh/config) the rcvfrom may not match the host
   222    226   ** to which we connect.
   223    227   */
   224    228   void socket_ssh_resolve_addr(UrlData *pUrlData){
   225         -  struct hostent *pHost;        /* Used to make best effort for rcvfrom */
   226         -  struct sockaddr_in addr;
   227         -
   228         -  memset(&addr, 0, sizeof(addr));
   229         -  pHost = gethostbyname(pUrlData->name);
   230         -  if( pHost!=0 ){
   231         -    memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
   232         -    g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
          229  +  struct addrinfo *ai = 0;
          230  +  struct addrinfo hints;
          231  +  char zRemote[NI_MAXHOST];
          232  +  hints.ai_family = AF_UNSPEC;
          233  +  hints.ai_socktype = SOCK_STREAM;
          234  +  hints.ai_protocol = IPPROTO_TCP;
          235  +  if( getaddrinfo(pUrlData->name, NULL, &hints, &ai)==0
          236  +   && ai!=0
          237  +   && getnameinfo(ai->ai_addr, ai->ai_addrlen, zRemote,
          238  +                  sizeof(zRemote), 0, 0, NI_NUMERICHOST)==0 ){
          239  +    g.zIpAddr = mprintf("%s (%s)", zRemote, pUrlData->name);
          240  +  }
          241  +  if( ai ) freeaddrinfo(ai);
          242  +  if( g.zIpAddr==0 ){
          243  +    g.zIpAddr = mprintf("%s", pUrlData->name);
   233    244     }
   234    245   }

Changes to src/http_transport.c.

    85     85   static char zDefaultSshCmd[] = "ssh -e none -T";
    86     86   #endif
    87     87   
    88     88   /*
    89     89   ** SSH initialization of the transport layer
    90     90   */
    91     91   int transport_ssh_open(UrlData *pUrlData){
    92         -  /* For SSH we need to create and run SSH fossil http 
           92  +  /* For SSH we need to create and run SSH fossil http
    93     93     ** to talk to the remote machine.
    94     94     */
    95     95     const char *zSsh;  /* The base SSH command */
    96     96     Blob zCmd;         /* The SSH command */
    97     97     char *zHost;       /* The host name to contact */
    98     98     int n;             /* Size of prefix string */
    99     99   

Changes to src/import.c.

   718    718     fossil_fatal("bad fast-import line: [%s]", zLine);
   719    719     return;
   720    720   }
   721    721   
   722    722   /*
   723    723   ** COMMAND: import
   724    724   **
   725         -** Usage: %fossil import --git ?OPTIONS? NEW-REPOSITORY
          725  +** Usage: %fossil import ?OPTIONS? NEW-REPOSITORY ?INPUT-FILE?
   726    726   **
   727         -** Read text generated by the git-fast-export command and use it to
          727  +** Read interchange format generated by another VCS and use it to
   728    728   ** construct a new Fossil repository named by the NEW-REPOSITORY
   729         -** argument.  The git-fast-export text is read from standard input.
          729  +** argument.  If no input file is supplied the interchange format
          730  +** data is read from standard input.
   730    731   **
   731    732   ** The git-fast-export file format is currently the only VCS interchange
   732    733   ** format that is understood, though other interchange formats may be added
   733    734   ** in the future.
   734    735   **
   735    736   ** The --incremental option allows an existing repository to be extended
   736    737   ** with new content.
   737    738   **
   738    739   ** Options:
          740  +**   --git          import from the git-fast-export file format (default)
   739    741   **   --incremental  allow importing into an existing repository
   740    742   **
   741    743   ** See also: export
   742    744   */
   743         -void git_import_cmd(void){
          745  +void import_cmd(void){
   744    746     char *zPassword;
   745    747     FILE *pIn;
   746    748     Stmt q;
   747    749     int forceFlag = find_option("force", "f", 0)!=0;
   748    750     int incrFlag = find_option("incremental", "i", 0)!=0;
          751  +  int svnFlag = find_option("svn", 0, 0)!=0;
   749    752   
   750    753     find_option("git",0,0);  /* Skip the --git option for now */
   751    754     verify_all_options();
   752    755     if( g.argc!=3  && g.argc!=4 ){
   753         -    usage("REPOSITORY-NAME");
          756  +    usage("NEW-REPOSITORY ?INPUT-FILE?");
   754    757     }
   755    758     if( g.argc==4 ){
   756    759       pIn = fossil_fopen(g.argv[3], "rb");
   757    760     }else{
   758    761       pIn = stdin;
   759    762       fossil_binary_mode(pIn);
   760    763     }
   761    764     if( !incrFlag ){
   762    765       if( forceFlag ) file_delete(g.argv[2]);
   763    766       db_create_repository(g.argv[2]);
   764    767     }
   765    768     db_open_repository(g.argv[2]);
   766    769     db_open_config(0);
   767         -
   768         -  /* The following temp-tables are used to hold information needed for
   769         -  ** the import.
   770         -  **
   771         -  ** The XMARK table provides a mapping from fast-import "marks" and symbols
   772         -  ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
   773         -  ** Given any valid fast-import symbol, the corresponding fossil rid and
   774         -  ** uuid can found by searching against the xmark.tname field.
   775         -  **
   776         -  ** The XBRANCH table maps commit marks and symbols into the branch those
   777         -  ** commits belong to.  If xbranch.tname is a fast-import symbol for a
   778         -  ** checkin then xbranch.brnm is the branch that checkin is part of.
   779         -  **
   780         -  ** The XTAG table records information about tags that need to be applied
   781         -  ** to various branches after the import finishes.  The xtag.tcontent field
   782         -  ** contains the text of an artifact that will add a tag to a check-in.
   783         -  ** The git-fast-export file format might specify the same tag multiple
   784         -  ** times but only the last tag should be used.  And we do not know which
   785         -  ** occurrence of the tag is the last until the import finishes.
   786         -  */
   787         -  db_multi_exec(
   788         -     "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
   789         -     "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
   790         -     "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
   791         -  );
   792         -
   793    770   
   794    771     db_begin_transaction();
   795    772     if( !incrFlag ) db_initial_setup(0, 0, 0, 1);
   796         -  git_fast_import(pIn);
   797         -  db_prepare(&q, "SELECT tcontent FROM xtag");
   798         -  while( db_step(&q)==SQLITE_ROW ){
   799         -    Blob record;
   800         -    db_ephemeral_blob(&q, 0, &record);
   801         -    fast_insert_content(&record, 0, 0);
   802         -    import_reset(0);
          773  +
          774  +  if( svnFlag ){
          775  +    fossil_fatal("--svn format not implemented yet");
          776  +  }else{
          777  +    /* The following temp-tables are used to hold information needed for
          778  +    ** the import.
          779  +    **
          780  +    ** The XMARK table provides a mapping from fast-import "marks" and symbols
          781  +    ** into artifact ids (UUIDs - the 40-byte hex SHA1 hash of artifacts).
          782  +    ** Given any valid fast-import symbol, the corresponding fossil rid and
          783  +    ** uuid can found by searching against the xmark.tname field.
          784  +    **
          785  +    ** The XBRANCH table maps commit marks and symbols into the branch those
          786  +    ** commits belong to.  If xbranch.tname is a fast-import symbol for a
          787  +    ** checkin then xbranch.brnm is the branch that checkin is part of.
          788  +    **
          789  +    ** The XTAG table records information about tags that need to be applied
          790  +    ** to various branches after the import finishes.  The xtag.tcontent field
          791  +    ** contains the text of an artifact that will add a tag to a check-in.
          792  +    ** The git-fast-export file format might specify the same tag multiple
          793  +    ** times but only the last tag should be used.  And we do not know which
          794  +    ** occurrence of the tag is the last until the import finishes.
          795  +    */
          796  +    db_multi_exec(
          797  +       "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
          798  +       "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
          799  +       "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
          800  +    );
          801  +
          802  +    git_fast_import(pIn);
          803  +    db_prepare(&q, "SELECT tcontent FROM xtag");
          804  +    while( db_step(&q)==SQLITE_ROW ){
          805  +      Blob record;
          806  +      db_ephemeral_blob(&q, 0, &record);
          807  +      fast_insert_content(&record, 0, 0);
          808  +      import_reset(0);
          809  +    }
          810  +    db_finalize(&q);
   803    811     }
   804         -  db_finalize(&q);
   805    812     db_end_transaction(0);
   806    813     db_begin_transaction();
   807    814     fossil_print("Rebuilding repository meta-data...\n");
   808    815     rebuild_db(0, 1, !incrFlag);
   809    816     verify_cancel();
   810    817     db_end_transaction(0);
   811    818     fossil_print("Vacuuming..."); fflush(stdout);

Changes to src/info.c.

   196    196   void info_cmd(void){
   197    197     i64 fsize;
   198    198     int verboseFlag = find_option("verbose","v",0)!=0;
   199    199     if( !verboseFlag ){
   200    200       verboseFlag = find_option("detail","l",0)!=0; /* deprecated */
   201    201     }
   202    202   
   203         -  /* We should be done with options.. */
   204         -  verify_all_options();
   205         -
   206    203     if( g.argc==3 && (fsize = file_size(g.argv[2]))>0 && (fsize&0x1ff)==0 ){
   207    204       db_open_config(0);
   208    205       db_open_repository(g.argv[2]);
   209    206       db_record_repository_filename(g.argv[2]);
   210    207       fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
   211    208       fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
   212    209       extraRepoInfo();
   213    210       return;
   214    211     }
   215    212     db_find_and_open_repository(0,0);
          213  +  verify_all_options();
   216    214     if( g.argc==2 ){
   217    215       int vid;
   218    216            /* 012345678901234 */
   219    217       db_record_repository_filename(0);
   220    218       fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
   221    219       if( g.localOpen ){
   222    220         fossil_print("repository:   %s\n", db_repository_filename());
................................................................................
   240    238         fossil_fatal("no such object: %s\n", g.argv[2]);
   241    239       }
   242    240       show_common_info(rid, "uuid:", 1, 1);
   243    241     }
   244    242   }
   245    243   
   246    244   /*
   247         -** Show information about all tags on a given node.
          245  +** Show information about all tags on a given check-in.
   248    246   */
   249         -static void showTags(int rid, const char *zNotGlob){
          247  +static void showTags(int rid){
   250    248     Stmt q;
   251    249     int cnt = 0;
   252    250     db_prepare(&q,
   253    251       "SELECT tag.tagid, tagname, "
   254    252       "       (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
   255    253       "       value, datetime(tagxref.mtime%s), tagtype,"
   256    254       "       (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
   257    255       "  FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
   258         -    " WHERE tagxref.rid=%d AND tagname NOT GLOB '%q'"
   259         -    " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid, zNotGlob
          256  +    " WHERE tagxref.rid=%d"
          257  +    " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid
   260    258     );
   261    259     while( db_step(&q)==SQLITE_ROW ){
   262    260       const char *zTagname = db_column_text(&q, 1);
   263    261       const char *zSrcUuid = db_column_text(&q, 2);
   264    262       const char *zValue = db_column_text(&q, 3);
   265    263       const char *zDate = db_column_text(&q, 4);
   266    264       int tagtype = db_column_int(&q, 5);
................................................................................
   305    303       @ </li>
   306    304     }
   307    305     db_finalize(&q);
   308    306     if( cnt ){
   309    307       @ </ul>
   310    308     }
   311    309   }
          310  +
          311  +/*
          312  +** Show the context graph (immediate parents and children) for
          313  +** check-in rid.
          314  +*/
          315  +static void showContext(int rid){
          316  +  Blob sql;
          317  +  Stmt q;
          318  +  @ <div class="section">Context</div>
          319  +  blob_zero(&sql);
          320  +  blob_append(&sql, timeline_query_for_www(), -1);
          321  +  db_multi_exec(
          322  +     "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
          323  +     "INSERT INTO ok VALUES(%d);"
          324  +     "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
          325  +     "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
          326  +     rid, rid, rid
          327  +  );
          328  +  blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
          329  +  db_prepare(&q, "%s", blob_sql_text(&sql));
          330  +  www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0);
          331  +  db_finalize(&q);
          332  +}
   312    333   
   313    334   
   314    335   /*
   315    336   ** Append the difference between artifacts to the output
   316    337   */
   317    338   static void append_diff(
   318    339     const char *zFrom,    /* Diff from this artifact */
................................................................................
   380    401       if( diffFlags ){
   381    402         append_diff(zOld, zNew, diffFlags, pRe);
   382    403       }
   383    404     }else{
   384    405       if( zOld && zNew ){
   385    406         if( fossil_strcmp(zOld, zNew)!=0 ){
   386    407           @ <p>Modified %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
   387         -        @ from %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
   388         -        @ to %z(href("%R/artifact/%s",zNew))[%S(zNew)].</a>
          408  +        @ from %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
          409  +        @ to %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>.
   389    410         }else if( zOldName!=0 && fossil_strcmp(zName,zOldName)!=0 ){
   390    411           @ <p>Name change
   391    412           @ from %z(href("%R/finfo?name=%T",zOldName))%h(zOldName)</a>
   392    413           @ to %z(href("%R/finfo?name=%T",zName))%h(zName)</a>.
   393    414         }else{
   394    415           @ <p>Execute permission %s(( mperm==PERM_EXE )?"set":"cleared") for
   395    416           @ %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
   396    417         }
   397    418       }else if( zOld ){
   398         -      @ <p>Deleted %z(href("%s/finfo?name=%T",g.zTop,zName))%h(zName)</a>
   399         -      @ version %z(href("%R/artifact/%s",zOld))[%S(zOld)]</a>
          419  +      @ <p>Deleted %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
          420  +      @ version %z(href("%R/artifact/%!S",zOld))[%S(zOld)]</a>
   400    421       }else{
   401    422         @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
   402         -      @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
          423  +      @ version %z(href("%R/artifact/%!S",zNew))[%S(zNew)]</a>
   403    424       }
   404    425       if( diffFlags ){
   405    426         append_diff(zOld, zNew, diffFlags, pRe);
   406    427       }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
   407    428         @ &nbsp;&nbsp;
   408         -      @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
          429  +      @ %z(href("%R/fdiff?v1=%!S&v2=%!S&sbs=1",zOld,zNew))[diff]</a>
   409    430       }
   410    431     }
   411    432   }
   412    433   
   413    434   /*
   414    435   ** Generate javascript to enhance HTML diffs.
   415    436   */
................................................................................
   504    525     const char *zRe;     /* regex parameter */
   505    526     ReCompiled *pRe = 0; /* regex */
   506    527     const char *zW;      /* URL param for ignoring whitespace */
   507    528     const char *zPage = "vinfo";  /* Page that shows diffs */
   508    529     const char *zPageHide = "ci"; /* Page that hides diffs */
   509    530   
   510    531     login_check_credentials();
   511         -  if( !g.perm.Read ){ login_needed(); return; }
          532  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   512    533     zName = P("name");
   513    534     rid = name_to_rid_www("name");
   514    535     if( rid==0 ){
   515    536       style_header("Check-in Information Error");
   516    537       @ No such object: %h(g.argv[2])
   517    538       style_footer();
   518    539       return;
................................................................................
   612    633         zPJ = blob_str(&projName);
   613    634         for(jj=0; zPJ[jj]; jj++){
   614    635           if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
   615    636             zPJ[jj] = '_';
   616    637           }
   617    638         }
   618    639         @ <tr><th>Timelines:</th><td>
   619         -      @   %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
          640  +      @   %z(href("%R/timeline?f=%!S&unhide",zUuid))family</a>
   620    641         if( zParent ){
   621         -        @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
          642  +        @ | %z(href("%R/timeline?p=%!S&unhide",zUuid))ancestors</a>
   622    643         }
   623    644         if( !isLeaf ){
   624         -        @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
          645  +        @ | %z(href("%R/timeline?d=%!S&unhide",zUuid))descendants</a>
   625    646         }
   626    647         if( zParent && !isLeaf ){
   627         -        @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
          648  +        @ | %z(href("%R/timeline?dp=%!S&unhide",zUuid))both</a>
   628    649         }
   629    650         db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
   630    651                        " WHERE rid=%d AND tagtype>0 "
   631    652                        "   AND tag.tagid=tagxref.tagid "
   632    653                        "   AND +tag.tagname GLOB 'sym-*'", rid);
   633    654         while( db_step(&q2)==SQLITE_ROW ){
   634    655           const char *zTagName = db_column_text(&q2, 0);
   635    656           @  | %z(href("%R/timeline?r=%T&unhide",zTagName))%h(zTagName)</a>
   636    657         }
   637    658         db_finalize(&q2);
   638    659   
   639    660   
   640    661         /* The Download: line */
   641         -      if( g.perm.Zip ){
          662  +      if( g.anon.Zip ){
   642    663           char *zUrl = mprintf("%R/tarball/%t-%S.tar.gz?uuid=%s",
   643    664                                zPJ, zUuid, zUuid);
   644    665           @ </td></tr>
   645    666           @ <tr><th>Downloads:</th><td>
   646    667           @ %z(href("%s",zUrl))Tarball</a>
   647         -        @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
          668  +        @ | %z(href("%R/zip/%t-%S.zip?uuid=%!S",zPJ,zUuid,zUuid))
   648    669           @         ZIP archive</a>
   649    670           fossil_free(zUrl);
   650    671         }
   651    672         @ </td></tr>
   652    673         @ <tr><th>Other&nbsp;Links:</th>
   653    674         @   <td>
   654         -      @     %z(href("%R/tree?ci=%s",zUuid))files</a>
   655         -      @   | %z(href("%R/fileage?name=%s",zUuid))file ages</a>
   656         -      @   | %z(href("%R/tree?ci=%s&nofiles",zUuid))folders</a>
   657         -      @   | %z(href("%R/artifact/%s",zUuid))manifest</a>
   658         -      if( g.perm.Write ){
   659         -        @   | %z(href("%R/ci_edit?r=%s",zUuid))edit</a>
          675  +      @     %z(href("%R/tree?ci=%!S",zUuid))files</a>
          676  +      @   | %z(href("%R/fileage?name=%!S",zUuid))file ages</a>
          677  +      @   | %z(href("%R/tree?nofiles&type=tree&ci=%!S",zUuid))folders</a>
          678  +      @   | %z(href("%R/artifact/%!S",zUuid))manifest</a>
          679  +      if( g.anon.Write ){
          680  +        @   | %z(href("%R/ci_edit?r=%!S",zUuid))edit</a>
   660    681         }
   661    682         @   </td>
   662    683         @ </tr>
   663    684         blob_reset(&projName);
   664    685       }
   665    686       @ </table>
   666    687     }else{
   667    688       style_header("Check-in Information");
   668    689       login_anonymous_available();
   669    690     }
   670    691     db_finalize(&q1);
   671         -  showTags(rid, "");
          692  +  showTags(rid);
          693  +  showContext(rid);
   672    694     @ <div class="section">Changes</div>
   673    695     @ <div class="sectionmenu">
   674    696     verboseFlag = g.zPath[0]!='c';
   675    697     if( db_get_boolean("show-version-diffs", 0)==0 ){
   676    698       verboseFlag = !verboseFlag;
   677    699       zPage = "ci";
   678    700       zPageHide = "vinfo";
................................................................................
   699    721     }else{
   700    722       @ %z(xhref("class='button'","%R/%s/%T?sbs=0",zPage,zName))
   701    723       @ Show&nbsp;Unified&nbsp;Diffs</a>
   702    724       @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
   703    725       @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
   704    726     }
   705    727     if( zParent ){
   706         -    @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
          728  +    @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
   707    729       @ Patch</a>
   708    730     }
   709    731     @</div>
   710    732     if( pRe ){
   711    733       @ <p><b>Only differences that match regular expression "%h(zRe)"
   712    734       @ are shown.</b></p>
   713    735     }
................................................................................
   714    736     db_prepare(&q3,
   715    737       "SELECT name,"
   716    738       "       mperm,"
   717    739       "       (SELECT uuid FROM blob WHERE rid=mlink.pid),"
   718    740       "       (SELECT uuid FROM blob WHERE rid=mlink.fid),"
   719    741       "       (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
   720    742       "  FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
   721         -    " WHERE mlink.mid=%d"
          743  +    " WHERE mlink.mid=%d AND NOT mlink.isaux"
   722    744       "   AND (mlink.fid>0"
   723    745              " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
   724    746       " ORDER BY name /*sort*/",
   725    747       rid, rid
   726    748     );
   727    749     while( db_step(&q3)==SQLITE_ROW ){
   728    750       const char *zName = db_column_text(&q3,0);
................................................................................
   749    771     char *zUuid;
   750    772     char *zDate;
   751    773     Blob wiki;
   752    774     int modPending;
   753    775     const char *zModAction;
   754    776   
   755    777     login_check_credentials();
   756         -  if( !g.perm.RdWiki ){ login_needed(); return; }
          778  +  if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
   757    779     rid = name_to_rid_www("name");
   758    780     if( rid==0 || (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))==0 ){
   759    781       style_header("Wiki Page Information Error");
   760    782       @ No such object: %h(P("name"))
   761    783       style_footer();
   762    784       return;
   763    785     }
................................................................................
   789    811                           pWiki->zWikiTitle);
   790    812     style_submenu_element("Page", "Page", "wiki?name=%t",
   791    813                           pWiki->zWikiTitle);
   792    814     login_anonymous_available();
   793    815     @ <div class="section">Overview</div>
   794    816     @ <p><table class="label-value">
   795    817     @ <tr><th>Artifact&nbsp;ID:</th>
   796         -  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
          818  +  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
   797    819     if( g.perm.Setup ){
   798    820       @ (%d(rid))
   799    821     }
   800    822     modPending = moderation_pending(rid);
   801    823     if( modPending ){
   802    824       @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
   803    825     }
   804    826     @ </td></tr>
   805    827     @ <tr><th>Page&nbsp;Name:</th><td>%h(pWiki->zWikiTitle)</td></tr>
   806    828     @ <tr><th>Date:</th><td>
   807    829     hyperlink_to_date(zDate, "</td></tr>");
   808    830     @ <tr><th>Original&nbsp;User:</th><td>
   809    831     hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
          832  +  if( pWiki->zMimetype ){
          833  +    @ <tr><th>Mimetype:</th><td>%h(pWiki->zMimetype)</td></tr>
          834  +  }
   810    835     if( pWiki->nParent>0 ){
   811    836       int i;
   812    837       @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
   813    838       for(i=0; i<pWiki->nParent; i++){
   814    839         char *zParent = pWiki->azParent[i];
   815         -      @ %z(href("info/%s",zParent))%s(zParent)</a>
          840  +      @ %z(href("info/%!S",zParent))%s(zParent)</a>
   816    841       }
   817    842       @ </td></tr>
   818    843     }
   819    844     @ </table>
   820    845   
   821    846     if( g.perm.ModWiki && modPending ){
   822    847       @ <div class="section">Moderation</div>
................................................................................
   830    855       @ </form>
   831    856       @ </blockquote>
   832    857     }
   833    858   
   834    859   
   835    860     @ <div class="section">Content</div>
   836    861     blob_init(&wiki, pWiki->zWiki, -1);
   837         -  wiki_convert(&wiki, 0, 0);
          862  +  wiki_render_by_mimetype(&wiki, pWiki->zMimetype);
   838    863     blob_reset(&wiki);
   839    864     manifest_destroy(pWiki);
   840    865     style_footer();
   841    866   }
   842    867   
   843    868   /*
   844    869   ** Show a webpage error message
................................................................................
   967    992     const char *zTo;
   968    993     const char *zRe;
   969    994     const char *zW;
   970    995     const char *zVerbose;
   971    996     const char *zGlob;
   972    997     ReCompiled *pRe = 0;
   973    998     login_check_credentials();
   974         -  if( !g.perm.Read ){ login_needed(); return; }
          999  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   975   1000     login_anonymous_available();
   976   1001     zRe = P("regex");
   977   1002     if( zRe ) re_compile(&pRe, zRe, 0);
   978   1003     zBranch = P("branch");
   979   1004     if( zBranch && zBranch[0] ){
   980   1005       cgi_replace_parameter("from", mprintf("root:%s", zBranch));
   981   1006       cgi_replace_parameter("to", zBranch);
................................................................................
  1182   1207       const char *zUser = db_column_text(&q, 3);
  1183   1208       const char *zVers = db_column_text(&q, 4);
  1184   1209       int mPerm = db_column_int(&q, 5);
  1185   1210       const char *zBr = db_column_text(&q, 6);
  1186   1211       int sameFilename = prevName!=0 && fossil_strcmp(zName,prevName)==0;
  1187   1212       if( sameFilename && !showDetail ){
  1188   1213         if( cnt==1 ){
  1189         -        @ %z(href("%R/whatis/%s",zUuid))[more...]</a>
         1214  +        @ %z(href("%R/whatis/%!S",zUuid))[more...]</a>
  1190   1215         }
  1191   1216         cnt++;
  1192   1217         continue;
  1193   1218       }
  1194   1219       if( !sameFilename ){
  1195         -      if( prevName ) {
         1220  +      if( prevName && showDetail ) {
  1196   1221           @ </ul>
  1197   1222         }
  1198   1223         if( mPerm==PERM_LNK ){
  1199   1224           @ <li>Symbolic link
  1200   1225           objType |= OBJTYPE_SYMLINK;
  1201   1226         }else if( mPerm==PERM_EXE ){
  1202   1227           @ <li>Executable file
................................................................................
  1225   1250       }
  1226   1251       if( zBr && zBr[0] ){
  1227   1252         @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
  1228   1253       }
  1229   1254       @ &mdash; %!w(zCom) (user:
  1230   1255       hyperlink_to_user(zUser,zDate,")");
  1231   1256       if( g.perm.Hyperlink ){
  1232         -      @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
  1233         -      @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
         1257  +      @ %z(href("%R/finfo?name=%T&ci=%!S",zName,zVers))[ancestry]</a>
         1258  +      @ %z(href("%R/annotate?filename=%T&checkin=%!S",zName,zVers))
  1234   1259         @ [annotate]</a>
  1235         -      @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
         1260  +      @ %z(href("%R/blame?filename=%T&checkin=%!S",zName,zVers))
  1236   1261         @ [blame]</a>
  1237   1262       }
  1238   1263       cnt++;
  1239   1264       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1240   1265         blob_append(pDownloadName, zName, -1);
  1241   1266       }
  1242   1267     }
................................................................................
  1312   1337         if( zType[0]!='e' ){
  1313   1338           hyperlink_to_uuid(zUuid);
  1314   1339         }
  1315   1340         @ - %!w(zCom) by
  1316   1341         hyperlink_to_user(zUser,zDate," on");
  1317   1342         hyperlink_to_date(zDate, ".");
  1318   1343         if( pDownloadName && blob_size(pDownloadName)==0 ){
  1319         -        blob_appendf(pDownloadName, "%.10s.txt", zUuid);
         1344  +        blob_appendf(pDownloadName, "%S.txt", zUuid);
  1320   1345         }
  1321   1346         tag_private_status(rid);
  1322   1347         cnt++;
  1323   1348       }
  1324   1349       db_finalize(&q);
  1325   1350     }
  1326   1351     db_prepare(&q,
................................................................................
  1339   1364       if( cnt>0 ){
  1340   1365         @ Also attachment "%h(zFilename)" to
  1341   1366       }else{
  1342   1367         @ Attachment "%h(zFilename)" to
  1343   1368       }
  1344   1369       objType |= OBJTYPE_ATTACHMENT;
  1345   1370       if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
  1346         -      if( g.perm.Hyperlink && g.perm.RdTkt ){
  1347         -        @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
         1371  +      if( g.perm.Hyperlink && g.anon.RdTkt ){
         1372  +        @ ticket [%z(href("%R/tktview?name=%!S",zTarget))%S(zTarget)</a>]
  1348   1373         }else{
  1349   1374           @ ticket [%S(zTarget)]
  1350   1375         }
  1351   1376       }else{
  1352         -      if( g.perm.Hyperlink && g.perm.RdWiki ){
         1377  +      if( g.perm.Hyperlink && g.anon.RdWiki ){
  1353   1378           @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
  1354   1379         }else{
  1355   1380           @ wiki page [%h(zTarget)]
  1356   1381         }
  1357   1382       }
  1358   1383       @ added by
  1359   1384       hyperlink_to_user(zUser,zDate," on");
................................................................................
  1364   1389       }
  1365   1390       tag_private_status(rid);
  1366   1391     }
  1367   1392     db_finalize(&q);
  1368   1393     if( cnt==0 ){
  1369   1394       @ Control artifact.
  1370   1395       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1371         -      blob_appendf(pDownloadName, "%.10s.txt", zUuid);
         1396  +      blob_appendf(pDownloadName, "%S.txt", zUuid);
  1372   1397       }
  1373   1398       tag_private_status(rid);
  1374   1399     }
  1375   1400     return objType;
  1376   1401   }
  1377   1402   
  1378   1403   
................................................................................
  1397   1422     const char *zRe;
  1398   1423     const char *zW;      /* URL param for ignoring whitespace */
  1399   1424     ReCompiled *pRe = 0;
  1400   1425     u64 diffFlags;
  1401   1426     u32 objdescFlags = 0;
  1402   1427   
  1403   1428     login_check_credentials();
  1404         -  if( !g.perm.Read ){ login_needed(); return; }
         1429  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1405   1430     v1 = name_to_rid_www("v1");
  1406   1431     v2 = name_to_rid_www("v2");
  1407   1432     if( v1==0 || v2==0 ) fossil_redirect_home();
  1408   1433     zRe = P("regex");
  1409   1434     if( zRe ) re_compile(&pRe, zRe, 0);
  1410   1435     if( P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  1411   1436     isPatch = P("patch")!=0;
................................................................................
  1448   1473       style_submenu_element("Unified Diff", "udiff",
  1449   1474                             "%s/fdiff?v1=%T&v2=%T&sbs=0%s",
  1450   1475                             g.zTop, P("v1"), P("v2"), zW);
  1451   1476     }
  1452   1477   
  1453   1478     if( P("smhdr")!=0 ){
  1454   1479       @ <h2>Differences From Artifact
  1455         -    @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
  1456         -    @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
         1480  +    @ %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a> To
         1481  +    @ %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>.</h2>
  1457   1482     }else{
  1458   1483       @ <h2>Differences From
  1459         -    @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
         1484  +    @ Artifact %z(href("%R/artifact/%!S",zV1))[%S(zV1)]</a>:</h2>
  1460   1485       object_description(v1, objdescFlags, 0);
  1461         -    @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
         1486  +    @ <h2>To Artifact %z(href("%R/artifact/%!S",zV2))[%S(zV2)]</a>:</h2>
  1462   1487       object_description(v2, objdescFlags, 0);
  1463   1488     }
  1464   1489     if( pRe ){
  1465   1490       @ <b>Only differences that match regular expression "%h(zRe)"
  1466   1491       @ are shown.</b>
  1467   1492     }
  1468   1493     @ <hr />
................................................................................
  1482   1507     int rid;
  1483   1508     char *zUuid;
  1484   1509     const char *zMime;
  1485   1510     Blob content;
  1486   1511   
  1487   1512     rid = name_to_rid_www("name");
  1488   1513     login_check_credentials();
  1489         -  if( !g.perm.Read ){ login_needed(); return; }
         1514  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1490   1515     if( rid==0 ) fossil_redirect_home();
  1491   1516     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  1492   1517     if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
  1493   1518       g.isConst = 1;
  1494   1519     }
  1495   1520     free(zUuid);
  1496   1521     zMime = P("m");
................................................................................
  1579   1604     Blob content;
  1580   1605     Blob downloadName;
  1581   1606     char *zUuid;
  1582   1607     u32 objdescFlags = 0;
  1583   1608   
  1584   1609     rid = name_to_rid_www("name");
  1585   1610     login_check_credentials();
  1586         -  if( !g.perm.Read ){ login_needed(); return; }
         1611  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1587   1612     if( rid==0 ) fossil_redirect_home();
  1588   1613     if( g.perm.Admin ){
  1589   1614       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1590   1615       if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
  1591   1616         style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
  1592   1617               g.zTop, zUuid);
  1593   1618       }else{
................................................................................
  1765   1790       rid = artifact_from_ci_and_filename_www();
  1766   1791     }
  1767   1792     if( rid==0 ){
  1768   1793       rid = name_to_rid_www("name");
  1769   1794     }
  1770   1795   
  1771   1796     login_check_credentials();
  1772         -  if( !g.perm.Read ){ login_needed(); return; }
         1797  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1773   1798     if( rid==0 ) fossil_redirect_home();
  1774   1799     if( g.perm.Admin ){
  1775   1800       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1776   1801       if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
  1777   1802         style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
  1778   1803               g.zTop, zUuid);
  1779   1804       }else{
................................................................................
  1880   1905     const char *zUuid;
  1881   1906     char zTktName[UUID_SIZE+1];
  1882   1907     Manifest *pTktChng;
  1883   1908     int modPending;
  1884   1909     const char *zModAction;
  1885   1910     char *zTktTitle;
  1886   1911     login_check_credentials();
  1887         -  if( !g.perm.RdTkt ){ login_needed(); return; }
         1912  +  if( !g.perm.RdTkt ){ login_needed(g.anon.RdTkt); return; }
  1888   1913     rid = name_to_rid_www("name");
  1889   1914     if( rid==0 ){ fossil_redirect_home(); }
  1890   1915     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1891   1916     if( g.perm.Admin ){
  1892   1917       if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
  1893   1918         style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
  1894   1919               g.zTop, zUuid);
................................................................................
  1935   1960       style_submenu_element("Plaintext", "Plaintext",
  1936   1961                             "%R/info/%s?plaintext", zUuid);
  1937   1962     }
  1938   1963   
  1939   1964     @ <div class="section">Overview</div>
  1940   1965     @ <p><table class="label-value">
  1941   1966     @ <tr><th>Artifact&nbsp;ID:</th>
  1942         -  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
         1967  +  @ <td>%z(href("%R/artifact/%!S",zUuid))%s(zUuid)</a>
  1943   1968     if( g.perm.Setup ){
  1944   1969       @ (%d(rid))
  1945   1970     }
  1946   1971     modPending = moderation_pending(rid);
  1947   1972     if( modPending ){
  1948   1973       @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
  1949   1974     }
................................................................................
  2252   2277     const char *zChngTime = 0;     /* Value of chngtime= query param, if any */
  2253   2278     char *zUuid;
  2254   2279     Blob comment;
  2255   2280     char *zBranchName = 0;
  2256   2281     Stmt q;
  2257   2282   
  2258   2283     login_check_credentials();
  2259         -  if( !g.perm.Write ){ login_needed(); return; }
         2284  +  if( !g.perm.Write ){ login_needed(g.anon.Write); return; }
  2260   2285     rid = name_to_typed_rid(P("r"), "ci");
  2261   2286     zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  2262   2287     zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
  2263   2288                           "  FROM event WHERE objid=%d", rid);
  2264   2289     if( zComment==0 ) fossil_redirect_home();
  2265   2290     if( P("cancel") ){
  2266   2291       cgi_redirectf("ci?name=%s", zUuid);
................................................................................
  2454   2479         @ will be overridden as: %s(date_in_standard_format(zChngTime))</p>
  2455   2480       }
  2456   2481       @ </blockquote>
  2457   2482       @ <hr />
  2458   2483       blob_reset(&suffix);
  2459   2484     }
  2460   2485     @ <p>Make changes to attributes of check-in
  2461         -  @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
         2486  +  @ [%z(href("%R/ci/%!S",zUuid))%s(zUuid)</a>]:</p>
  2462   2487     form_begin(0, "%R/ci_edit");
  2463   2488     login_insert_csrf_secret();
  2464   2489     @ <div><input type="hidden" name="r" value="%s(zUuid)" />
  2465   2490     @ <table border="0" cellspacing="10">
  2466   2491   
  2467   2492     @ <tr><th align="right" valign="top">User:</th>
  2468   2493     @ <td valign="top">
................................................................................
  2572   2597         @ </td></tr>
  2573   2598       }else if( zBranchName ){
  2574   2599         @ <tr><th align="right" valign="top">Branch Closure:</th>
  2575   2600         @ <td valign="top">
  2576   2601         @ <label><input type="checkbox" name="close"%s(zCloseFlag) />
  2577   2602         @ Mark branch
  2578   2603         @ <span style="font-weight:bold" id="cbranch">%h(zBranchName)</span>
  2579         -      @ as "closed" so that its leafs no longer appear on the "leaves" page
  2580         -      @ and are no longer labeled as a leaf "<b>Leaf</b>"</label>
         2604  +      @ as "closed".</label>
  2581   2605         @ </td></tr>
  2582   2606       }
  2583   2607     }
  2584   2608     if( zBranchName ) fossil_free(zBranchName);
  2585   2609   
  2586   2610   
  2587   2611     @ <tr><td colspan="2">

Changes to src/json_config.c.

    58     58   { "css",                    CONFIGSET_CSS },
    59     59   { "header",                 CONFIGSET_SKIN },
    60     60   { "footer",                 CONFIGSET_SKIN },
    61     61   { "logo-mimetype",          CONFIGSET_SKIN },
    62     62   { "logo-image",             CONFIGSET_SKIN },
    63     63   { "background-mimetype",    CONFIGSET_SKIN },
    64     64   { "background-image",       CONFIGSET_SKIN },
    65         -{ "index-page",             CONFIGSET_SKIN },
    66     65   { "timeline-block-markup",  CONFIGSET_SKIN },
    67     66   { "timeline-max-comment",   CONFIGSET_SKIN },
    68     67   { "timeline-plaintext",     CONFIGSET_SKIN },
           68  +{ "adunit",                 CONFIGSET_SKIN },
           69  +{ "adunit-omit-if-admin",   CONFIGSET_SKIN },
           70  +{ "adunit-omit-if-user",    CONFIGSET_SKIN },
           71  +{ "white-foreground",       CONFIGSET_SKIN },
    69     72   
    70     73   { "project-name",           CONFIGSET_PROJ },
           74  +{ "short-project-name",     CONFIGSET_PROJ },
    71     75   { "project-description",    CONFIGSET_PROJ },
           76  +{ "index-page",             CONFIGSET_PROJ },
    72     77   { "manifest",               CONFIGSET_PROJ },
    73     78   { "binary-glob",            CONFIGSET_PROJ },
    74     79   { "clean-glob",             CONFIGSET_PROJ },
    75         -{ "encoding-glob",          CONFIGSET_PROJ },
    76     80   { "ignore-glob",            CONFIGSET_PROJ },
    77     81   { "keep-glob",              CONFIGSET_PROJ },
    78     82   { "crnl-glob",              CONFIGSET_PROJ },
           83  +{ "encoding-glob",          CONFIGSET_PROJ },
    79     84   { "empty-dirs",             CONFIGSET_PROJ },
    80     85   { "allow-symlinks",         CONFIGSET_PROJ },
    81     86   
    82     87   { "ticket-table",           CONFIGSET_TKT  },
    83     88   { "ticket-common",          CONFIGSET_TKT  },
    84     89   { "ticket-change",          CONFIGSET_TKT  },
    85     90   { "ticket-newpage",         CONFIGSET_TKT  },

Changes to src/login.c.

   350    350       /* To logout, change the cookie value to an empty string */
   351    351       cgi_set_cookie(cookie, "",
   352    352                      login_cookie_path(), -86400);
   353    353       db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
   354    354                     "  cexpire=0 WHERE uid=%d"
   355    355                     "  AND login NOT IN ('anonymous','nobody',"
   356    356                     "  'developer','reader')", g.userUid);
   357         -    cgi_replace_parameter(cookie, NULL)
   358         -      /* At the time of this writing, cgi_replace_parameter() was
   359         -      ** "NULL-value-safe", and I'm hoping the NULL doesn't cause any
   360         -      ** downstream problems here. We could alternately use "" here.
   361         -      */
   362         -      ;
          357  +    cgi_replace_parameter(cookie, NULL);
          358  +    cgi_replace_parameter("anon", NULL);
   363    359     }
   364    360   }
   365    361   
   366    362   /*
   367    363   ** Return true if the prefix of zStr matches zPattern.  Return false if
   368    364   ** they are different.
   369    365   **
................................................................................
   449    445         rc = rc | (buf1[i] ^ buf2[i]);
   450    446       }
   451    447     }
   452    448     sqlite3_result_int(context, rc);
   453    449   }
   454    450   
   455    451   /*
   456         -** WEBPAGE: login
   457         -** WEBPAGE: logout
   458         -** WEBPAGE: my
   459         -**
   460         -** Generate the login page.
   461         -**
          452  +** Return true if the current page was reached by a redirect from the /login
          453  +** page.
          454  +*/
          455  +int referred_from_login(void){
          456  +  const char *zReferer = P("HTTP_REFERER");
          457  +  char *zPattern;
          458  +  int rc;
          459  +  if( zReferer==0 ) return 0;
          460  +  zPattern = mprintf("%s/login*", g.zBaseURL);
          461  +  rc = sqlite3_strglob(zPattern, zReferer)==0;
          462  +  fossil_free(zPattern);
          463  +  return rc;
          464  +}
          465  +
          466  +/*
   462    467   ** There used to be a page named "my" that was designed to show information
   463    468   ** about a specific user.  The "my" page was linked from the "Logged in as USER"
   464    469   ** line on the title bar.  The "my" page was never completed so it is now
   465    470   ** removed.  Use this page as a placeholder in older installations.
          471  +**
          472  +** WEBPAGE: login
          473  +** WEBPAGE: logout
          474  +** WEBPAGE: my
          475  +**
          476  +** The login/logout page.  Parameters:
          477  +**
          478  +**    g=URL             Jump back to this URL after login completes
          479  +**    anon              The g=URL is not accessible by "nobody" but is
          480  +**                      accessible by "anonymous"
   466    481   */
   467    482   void login_page(void){
   468    483     const char *zUsername, *zPasswd;
   469    484     const char *zNew1, *zNew2;
   470    485     const char *zAnonPw = 0;
   471    486     const char *zGoto = P("g");
   472         -  int anonFlag;
          487  +  int anonFlag;                /* Login as "anonymous" would be useful */
   473    488     char *zErrMsg = "";
   474    489     int uid;                     /* User id logged in user */
   475    490     char *zSha1Pw;
   476    491     const char *zIpAddr;         /* IP address of requestor */
   477    492     const char *zReferer;
   478    493   
   479    494     login_check_credentials();
................................................................................
   487    502       cgi_redirectf("%s%s%s", g.zHttpsURL, P("PATH_INFO"), zQS);
   488    503       return;
   489    504     }
   490    505     sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
   491    506                     constant_time_cmp_function, 0, 0);
   492    507     zUsername = P("u");
   493    508     zPasswd = P("p");
   494         -  anonFlag = P("anon")!=0;
   495         -  if( P("out")!=0 ){
          509  +  anonFlag = g.zLogin==0 && PB("anon");
          510  +
          511  +  /* Handle log-out requests */
          512  +  if( P("out") ){
   496    513       login_clear_login_data();
   497    514       redirect_to_g();
          515  +    return;
   498    516     }
          517  +
          518  +  /* Deal with password-change requests */
   499    519     if( g.perm.Password && zPasswd
   500    520      && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
   501    521     ){
   502    522       /* The user requests a password change */
   503    523       zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
   504    524       if( db_int(1, "SELECT 0 FROM user"
   505    525                     " WHERE uid=%d"
................................................................................
   573    593         ** code prefix, and LOGIN is the user name.
   574    594         */
   575    595         login_set_user_cookie(zUsername, uid, NULL);
   576    596         redirect_to_g();
   577    597       }
   578    598     }
   579    599     style_header("Login/Logout");
          600  +  style_adunit_config(ADUNIT_OFF);
   580    601     @ %s(zErrMsg)
   581         -  if( zGoto && P("anon")==0 ){
   582         -    @ <p>A login is required for <a href="%h(zGoto)">%h(zGoto)</a>.</p>
          602  +  if( zGoto ){
          603  +    char *zAbbrev = fossil_strdup(zGoto);
          604  +    int i;
          605  +    for(i=0; zAbbrev[i] && zAbbrev[i]!='?'; i++){}
          606  +    zAbbrev[i] = 0;
          607  +    if( g.zLogin ){
          608  +      @ <p>Use a different login with greater privilege than <b>%h(g.zLogin)</b>
          609  +      @ to access <b>%h(zAbbrev)</b>.
          610  +    }else if( anonFlag ){
          611  +      @ <p>Login as <b>anonymous</b> or any named user
          612  +      @ to access page <b>%h(zAbbrev)</b>.
          613  +    }else{
          614  +      @ <p>Login as a named user to access page <b>%h(zAbbrev)</b>.
          615  +    }
   583    616     }
   584    617     form_begin(0, "%R/login");
   585    618     if( zGoto ){
   586    619       @ <input type="hidden" name="g" value="%h(zGoto)" />
   587    620     }else if( zReferer && strncmp(g.zBaseURL, zReferer, strlen(g.zBaseURL))==0 ){
   588    621       @ <input type="hidden" name="g" value="%h(zReferer)" />
   589    622     }
          623  +  if( anonFlag ){
          624  +    @ <input type="hidden" name="anon" value="1" />
          625  +  }
          626  +  if( g.zLogin ){
          627  +    @ <p>Currently logged in as <b>%h(g.zLogin)</b>.
          628  +    @ <input type="submit" name="out" value="Logout"></p>
          629  +    @ <hr />
          630  +    @ <p>Change user:
          631  +  }
   590    632     @ <table class="login_out">
   591    633     @ <tr>
   592    634     @   <td class="login_out_label">User ID:</td>
   593    635     if( anonFlag ){
   594    636       @ <td><input type="text" id="u" name="u" value="anonymous" size="30" /></td>
   595    637     }else{
   596    638       @ <td><input type="text" id="u" name="u" value="" size="30" /></td>
   597    639     }
   598    640     @ </tr>
   599    641     @ <tr>
   600    642     @  <td class="login_out_label">Password:</td>
   601    643     @   <td><input type="password" id="p" name="p" value="" size="30" /></td>
   602    644     @ </tr>
   603         -  if( g.zLogin==0 ){
          645  +  if( g.zLogin==0 && (anonFlag || zGoto==0) ){
   604    646       zAnonPw = db_text(0, "SELECT pw FROM user"
   605    647                            " WHERE login='anonymous'"
   606    648                            "   AND cap!=''");
   607    649     }
   608    650     @ <tr>
   609    651     @   <td></td>
   610    652     @   <td><input type="submit" name="in" value="Login"
................................................................................
   621    663        char *zSSL = mprintf("https:%s", &g.zBaseURL[5]);
   622    664        @  if( form.u.value!="anonymous" ){
   623    665        @     form.action = "%h(zSSL)/login";
   624    666        @  }
   625    667     }
   626    668     @ }
   627    669     @ </script>
   628         -  if( g.zLogin==0 ){
   629         -    @ <p>Enter
   630         -  }else{
   631         -    @ <p>You are currently logged in as <b>%h(g.zLogin)</b></p>
   632         -    @ <p>To change your login to a different user, enter
   633         -  }
   634         -  @ your user-id and password at the left and press the
   635         -  @ "Login" button.  Your user name will be stored in a browser cookie.
   636         -  @ You must configure your web browser to accept cookies in order for
   637         -  @ the login to take.</p>
          670  +  @ <p>Pressing the Login button grants permission to store a cookie.</p>
   638    671     if( db_get_boolean("self-register", 0) ){
   639    672       @ <p>If you do not have an account, you can
   640         -    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
          673  +    @ <a href="%R/register?g=%T(P("G"))">create one</a>.
   641    674     }
   642    675     if( zAnonPw ){
   643    676       unsigned int uSeed = captcha_seed();
   644    677       const char *zDecoded = captcha_decode(uSeed);
   645    678       int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
   646    679       char *zCaptcha = captcha_render(zDecoded);
   647    680   
................................................................................
   654    687       if( bAutoCaptcha ) {
   655    688           @ <input type="button" value="Fill out captcha"
   656    689           @  onclick="gebi('u').value='anonymous'; gebi('p').value='%s(zDecoded)';" />
   657    690       }
   658    691       @ </div>
   659    692       free(zCaptcha);
   660    693     }
   661         -  if( g.zLogin ){
   662         -    @ <hr />
   663         -    @ <p>To log off the system (and delete your login cookie)
   664         -    @  press the following button:<br />
   665         -    @ <input type="submit" name="out" value="Logout" /></p>
   666         -  }
   667    694     @ </form>
   668    695     if( g.perm.Password ){
   669    696       @ <hr />
   670         -    @ <p>To change your password, enter your old password and your
   671         -    @ new password twice below then press the "Change Password"
   672         -    @ button.</p>
          697  +    @ <p>Change Password for user <b>%h(g.zLogin)</b>:</p>
   673    698       form_begin(0, "%R/login");
   674    699       @ <table>
   675    700       @ <tr><td class="login_out_label">Old Password:</td>
   676    701       @ <td><input type="password" name="p" size="30" /></td></tr>
   677    702       @ <tr><td class="login_out_label">New Password:</td>
   678    703       @ <td><input type="password" name="n1" size="30" /></td></tr>
   679    704       @ <tr><td class="login_out_label">Repeat New Password:</td>
................................................................................
   808    833   ** This routine examines the login cookie to see if it exists and
   809    834   ** is valid.  If the login cookie checks out, it then sets global
   810    835   ** variables appropriately.
   811    836   **
   812    837   **    g.userUid      Database USER.UID value.  Might be -1 for "nobody"
   813    838   **    g.zLogin       Database USER.LOGIN value.  NULL for user "nobody"
   814    839   **    g.perm         Permissions granted to this user
          840  +**    g.anon         Permissions that would be available to anonymous
   815    841   **    g.isHuman      True if the user is human, not a spider or robot
   816    842   **
   817    843   */
   818    844   void login_check_credentials(void){
   819    845     int uid = 0;                  /* User id */
   820    846     const char *zCookie;          /* Text of the login cookie */
   821    847     const char *zIpAddr;          /* Raw IP address of the requestor */
................................................................................
   999   1025   
  1000   1026   /*
  1001   1027   ** Memory of settings
  1002   1028   */
  1003   1029   static int login_anon_once = 1;
  1004   1030   
  1005   1031   /*
  1006         -** Add the default privileges of users "nobody" and "anonymous" as appropriate
  1007         -** for the user g.zLogin.
         1032  +** Add to g.perm the default privileges of users "nobody" and/or "anonymous"
         1033  +** as appropriate for the user g.zLogin.
         1034  +**
         1035  +** This routine also sets up g.anon to be either a copy of g.perm for
         1036  +** all logged in uses, or the privileges that would be available to "anonymous"
         1037  +** if g.zLogin==0 (meaning that the user is "nobody").
  1008   1038   */
  1009   1039   void login_set_anon_nobody_capabilities(void){
  1010         -  if( g.zLogin && login_anon_once ){
         1040  +  if( login_anon_once ){
  1011   1041       const char *zCap;
  1012         -    /* All logged-in users inherit privileges from "nobody" */
         1042  +    /* All users get privileges from "nobody" */
  1013   1043       zCap = db_text("", "SELECT cap FROM user WHERE login = 'nobody'");
  1014   1044       login_set_capabilities(zCap, 0);
  1015         -    if( fossil_strcmp(g.zLogin, "nobody")!=0 ){
         1045  +    zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
         1046  +    if( g.zLogin && fossil_strcmp(g.zLogin, "nobody")!=0 ){
  1016   1047         /* All logged-in users inherit privileges from "anonymous" */
  1017         -      zCap = db_text("", "SELECT cap FROM user WHERE login = 'anonymous'");
  1018   1048         login_set_capabilities(zCap, 0);
         1049  +      g.anon = g.perm;
         1050  +    }else{
         1051  +      /* Record the privileges of anonymous in g.anon */
         1052  +      g.anon = g.perm;
         1053  +      login_set_capabilities(zCap, LOGIN_ANON);
  1019   1054       }
  1020   1055       login_anon_once = 0;
  1021   1056     }
  1022   1057   }
  1023   1058   
  1024   1059   /*
  1025   1060   ** Flags passed into the 2nd argument of login_set/replace_capabilities().
  1026   1061   */
  1027   1062   #if INTERFACE
  1028   1063   #define LOGIN_IGNORE_UV  0x01         /* Ignore "u" and "v" */
         1064  +#define LOGIN_ANON       0x02         /* Use g.anon instead of g.perm */
  1029   1065   #endif
  1030   1066   
  1031   1067   /*
  1032         -** Adds all capability flags in zCap to g.perm.
         1068  +** Adds all capability flags in zCap to g.perm or g.anon.
  1033   1069   */
  1034   1070   void login_set_capabilities(const char *zCap, unsigned flags){
  1035   1071     int i;
         1072  +  FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
  1036   1073     if(NULL==zCap){
  1037   1074       return;
  1038   1075     }
  1039   1076     for(i=0; zCap[i]; i++){
  1040   1077       switch( zCap[i] ){
  1041         -      case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
  1042         -      case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
  1043         -                           g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
  1044         -                           g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone =
  1045         -                           g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
  1046         -                           g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt =
  1047         -                           g.perm.ModWiki = g.perm.ModTkt = 1;
         1078  +      case 's':   p->Setup = 1;  /* Fall thru into Admin */
         1079  +      case 'a':   p->Admin = p->RdTkt = p->WrTkt = p->Zip =
         1080  +                           p->RdWiki = p->WrWiki = p->NewWiki =
         1081  +                           p->ApndWiki = p->Hyperlink = p->Clone =
         1082  +                           p->NewTkt = p->Password = p->RdAddr =
         1083  +                           p->TktFmt = p->Attach = p->ApndTkt =
         1084  +                           p->ModWiki = p->ModTkt = 1;
  1048   1085                              /* Fall thru into Read/Write */
  1049         -      case 'i':   g.perm.Read = g.perm.Write = 1;                     break;
  1050         -      case 'o':   g.perm.Read = 1;                                 break;
  1051         -      case 'z':   g.perm.Zip = 1;                                  break;
  1052         -
  1053         -      case 'd':   g.perm.Delete = 1;                               break;
  1054         -      case 'h':   g.perm.Hyperlink = 1;                            break;
  1055         -      case 'g':   g.perm.Clone = 1;                                break;
  1056         -      case 'p':   g.perm.Password = 1;                             break;
  1057         -
  1058         -      case 'j':   g.perm.RdWiki = 1;                               break;
  1059         -      case 'k':   g.perm.WrWiki = g.perm.RdWiki = g.perm.ApndWiki =1;    break;
  1060         -      case 'm':   g.perm.ApndWiki = 1;                             break;
  1061         -      case 'f':   g.perm.NewWiki = 1;                              break;
  1062         -      case 'l':   g.perm.ModWiki = 1;                              break;
  1063         -
  1064         -      case 'e':   g.perm.RdAddr = 1;                               break;
  1065         -      case 'r':   g.perm.RdTkt = 1;                                break;
  1066         -      case 'n':   g.perm.NewTkt = 1;                               break;
  1067         -      case 'w':   g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt =
  1068         -                  g.perm.ApndTkt = 1;                              break;
  1069         -      case 'c':   g.perm.ApndTkt = 1;                              break;
  1070         -      case 'q':   g.perm.ModTkt = 1;                               break;
  1071         -      case 't':   g.perm.TktFmt = 1;                               break;
  1072         -      case 'b':   g.perm.Attach = 1;                               break;
  1073         -      case 'x':   g.perm.Private = 1;                              break;
         1086  +      case 'i':   p->Read = p->Write = 1;                     break;
         1087  +      case 'o':   p->Read = 1;                                 break;
         1088  +      case 'z':   p->Zip = 1;                                  break;
         1089  +
         1090  +      case 'd':   p->Delete = 1;                               break;
         1091  +      case 'h':   p->Hyperlink = 1;                            break;
         1092  +      case 'g':   p->Clone = 1;                                break;
         1093  +      case 'p':   p->Password = 1;                             break;
         1094  +
         1095  +      case 'j':   p->RdWiki = 1;                               break;
         1096  +      case 'k':   p->WrWiki = p->RdWiki = p->ApndWiki =1;    break;
         1097  +      case 'm':   p->ApndWiki = 1;                             break;
         1098  +      case 'f':   p->NewWiki = 1;                              break;
         1099  +      case 'l':   p->ModWiki = 1;                              break;
         1100  +
         1101  +      case 'e':   p->RdAddr = 1;                               break;
         1102  +      case 'r':   p->RdTkt = 1;                                break;
         1103  +      case 'n':   p->NewTkt = 1;                               break;
         1104  +      case 'w':   p->WrTkt = p->RdTkt = p->NewTkt =
         1105  +                  p->ApndTkt = 1;                              break;
         1106  +      case 'c':   p->ApndTkt = 1;                              break;
         1107  +      case 'q':   p->ModTkt = 1;                               break;
         1108  +      case 't':   p->TktFmt = 1;                               break;
         1109  +      case 'b':   p->Attach = 1;                               break;
         1110  +      case 'x':   p->Private = 1;                              break;
  1074   1111   
  1075   1112         /* The "u" privileges is a little different.  It recursively
  1076   1113         ** inherits all privileges of the user named "reader" */
  1077   1114         case 'u': {
  1078   1115           if( (flags & LOGIN_IGNORE_UV)==0 ){
  1079   1116             const char *zUser;
  1080   1117             zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
................................................................................
  1107   1144   }
  1108   1145   
  1109   1146   /*
  1110   1147   ** If the current login lacks any of the capabilities listed in
  1111   1148   ** the input, then return 0.  If all capabilities are present, then
  1112   1149   ** return 1.
  1113   1150   */
  1114         -int login_has_capability(const char *zCap, int nCap){
         1151  +int login_has_capability(const char *zCap, int nCap, u32 flgs){
  1115   1152     int i;
  1116   1153     int rc = 1;
         1154  +  FossilUserPerms *p = (flgs & LOGIN_ANON) ? &g.anon : &g.perm;
  1117   1155     if( nCap<0 ) nCap = strlen(zCap);
  1118   1156     for(i=0; i<nCap && rc && zCap[i]; i++){
  1119   1157       switch( zCap[i] ){
  1120         -      case 'a':  rc = g.perm.Admin;     break;
  1121         -      case 'b':  rc = g.perm.Attach;    break;
  1122         -      case 'c':  rc = g.perm.ApndTkt;   break;
  1123         -      case 'd':  rc = g.perm.Delete;    break;
  1124         -      case 'e':  rc = g.perm.RdAddr;    break;
  1125         -      case 'f':  rc = g.perm.NewWiki;   break;
  1126         -      case 'g':  rc = g.perm.Clone;     break;
  1127         -      case 'h':  rc = g.perm.Hyperlink; break;
  1128         -      case 'i':  rc = g.perm.Write;     break;
  1129         -      case 'j':  rc = g.perm.RdWiki;    break;
  1130         -      case 'k':  rc = g.perm.WrWiki;    break;
  1131         -      case 'l':  rc = g.perm.ModWiki;   break;
  1132         -      case 'm':  rc = g.perm.ApndWiki;  break;
  1133         -      case 'n':  rc = g.perm.NewTkt;    break;
  1134         -      case 'o':  rc = g.perm.Read;      break;
  1135         -      case 'p':  rc = g.perm.Password;  break;
  1136         -      case 'q':  rc = g.perm.ModTkt;    break;
  1137         -      case 'r':  rc = g.perm.RdTkt;     break;
  1138         -      case 's':  rc = g.perm.Setup;     break;
  1139         -      case 't':  rc = g.perm.TktFmt;    break;
         1158  +      case 'a':  rc = p->Admin;     break;
         1159  +      case 'b':  rc = p->Attach;    break;
         1160  +      case 'c':  rc = p->ApndTkt;   break;
         1161  +      case 'd':  rc = p->Delete;    break;
         1162  +      case 'e':  rc = p->RdAddr;    break;
         1163  +      case 'f':  rc = p->NewWiki;   break;
         1164  +      case 'g':  rc = p->Clone;     break;
         1165  +      case 'h':  rc = p->Hyperlink; break;
         1166  +      case 'i':  rc = p->Write;     break;
         1167  +      case 'j':  rc = p->RdWiki;    break;
         1168  +      case 'k':  rc = p->WrWiki;    break;
         1169  +      case 'l':  rc = p->ModWiki;   break;
         1170  +      case 'm':  rc = p->ApndWiki;  break;
         1171  +      case 'n':  rc = p->NewTkt;    break;
         1172  +      case 'o':  rc = p->Read;      break;
         1173  +      case 'p':  rc = p->Password;  break;
         1174  +      case 'q':  rc = p->ModTkt;    break;
         1175  +      case 'r':  rc = p->RdTkt;     break;
         1176  +      case 's':  rc = p->Setup;     break;
         1177  +      case 't':  rc = p->TktFmt;    break;
  1140   1178         /* case 'u': READER    */
  1141   1179         /* case 'v': DEVELOPER */
  1142         -      case 'w':  rc = g.perm.WrTkt;     break;
  1143         -      case 'x':  rc = g.perm.Private;   break;
         1180  +      case 'w':  rc = p->WrTkt;     break;
         1181  +      case 'x':  rc = p->Private;   break;
  1144   1182         /* case 'y': */
  1145         -      case 'z':  rc = g.perm.Zip;       break;
         1183  +      case 'z':  rc = p->Zip;       break;
  1146   1184         default:   rc = 0;             break;
  1147   1185       }
  1148   1186     }
  1149   1187     return rc;
  1150   1188   }
  1151   1189   
  1152   1190   /*
................................................................................
  1192   1230     return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody";
  1193   1231   }
  1194   1232   
  1195   1233   /*
  1196   1234   ** Call this routine when the credential check fails.  It causes
  1197   1235   ** a redirect to the "login" page.
  1198   1236   */
  1199         -void login_needed(void){
         1237  +void login_needed(int anonOk){
  1200   1238   #ifdef FOSSIL_ENABLE_JSON
  1201   1239     if(g.json.isJsonMode){
  1202   1240       json_err( FSL_JSON_E_DENIED, NULL, 1 );
  1203   1241       fossil_exit(0);
  1204   1242       /* NOTREACHED */
  1205   1243       assert(0);
  1206   1244     }else
  1207   1245   #endif /* FOSSIL_ENABLE_JSON */
  1208   1246     {
  1209   1247       const char *zUrl = PD("REQUEST_URI", "index");
  1210         -    cgi_redirect(mprintf("login?g=%T", zUrl));
         1248  +    const char *zQS = P("QUERY_STRING");
         1249  +    Blob redir;
         1250  +    blob_init(&redir, 0, 0);
         1251  +    if( login_wants_https_redirect() ){
         1252  +      blob_appendf(&redir, "%s/login?g=%T", g.zHttpsURL, zUrl);
         1253  +    }else{
         1254  +      blob_appendf(&redir, "%R/login?g=%T", zUrl);
         1255  +    }
         1256  +    if( anonOk ) blob_append(&redir, "&anon", 5);
         1257  +    if( zQS && zQS[0] ){
         1258  +      blob_appendf(&redir, "&%s", zQS);
         1259  +    }
         1260  +    cgi_redirect(blob_str(&redir));
  1211   1261       /* NOTREACHED */
  1212   1262       assert(0);
  1213   1263     }
  1214   1264   }
  1215   1265   
  1216   1266   /*
  1217   1267   ** Call this routine if the user lacks g.perm.Hyperlink permission.  If
  1218   1268   ** the anonymous user has Hyperlink permission, then paint a mesage
  1219   1269   ** to inform the user that much more information is available by
  1220   1270   ** logging in as anonymous.
  1221   1271   */
  1222   1272   void login_anonymous_available(void){
  1223         -  if( !g.perm.Hyperlink &&
  1224         -      db_exists("SELECT 1 FROM user"
  1225         -                " WHERE login='anonymous'"
  1226         -                "   AND cap LIKE '%%h%%'") ){
         1273  +  if( !g.perm.Hyperlink && g.anon.Hyperlink ){
  1227   1274       const char *zUrl = PD("REQUEST_URI", "index");
  1228   1275       @ <p>Many <span class="disabled">hyperlinks are disabled.</span><br />
  1229         -    @ Use <a href="%s(g.zTop)/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
         1276  +    @ Use <a href="%R/login?anon=1&amp;g=%T(zUrl)">anonymous login</a>
  1230   1277       @ to enable hyperlinks.</p>
  1231   1278     }
  1232   1279   }
  1233   1280   
  1234   1281   /*
  1235   1282   ** While rendering a form, call this routine to add the Anti-CSRF token
  1236   1283   ** as a hidden element of the form.

Changes to src/main.c.

   141    141     char *zLocalRoot;       /* The directory holding the  local database */
   142    142     int minPrefix;          /* Number of digits needed for a distinct UUID */
   143    143     int fSqlTrace;          /* True if --sqltrace flag is present */
   144    144     int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
   145    145     int fSqlPrint;          /* True if -sqlprint flag is present */
   146    146     int fQuiet;             /* True if -quiet flag is present */
   147    147     int fHttpTrace;         /* Trace outbound HTTP requests */
          148  +  int fAnyTrace;          /* Any kind of tracing */
   148    149     char *zHttpAuth;        /* HTTP Authorization user:pass information */
   149    150     int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
   150    151     int fSshTrace;          /* Trace the SSH setup traffic */
   151    152     int fSshClient;         /* HTTP client flags for SSH client */
   152    153     char *zSshCmd;          /* SSH command string */
   153    154     int fNoSync;            /* Do not do an autosync ever.  --nosync */
          155  +  int fIPv4;              /* Use only IPv4, not IPv6. --ipv4 */
   154    156     char *zPath;            /* Name of webpage being served */
   155    157     char *zExtra;           /* Extra path information past the webpage name */
   156    158     char *zBaseURL;         /* Full text of the URL being served */
   157    159     char *zHttpsURL;        /* zBaseURL translated to https: */
   158    160     char *zTop;             /* Parent directory of zPath */
   159    161     const char *zContentType;  /* The content type of the input HTTP request */
   160    162     int iErrPriority;       /* Priority of current error message */
................................................................................
   189    191     int comFmtFlags;        /* Zero or more "COMMENT_PRINT_*" bit flags */
   190    192   
   191    193     /* Information used to populate the RCVFROM table */
   192    194     int rcvid;              /* The rcvid.  0 if not yet defined. */
   193    195     char *zIpAddr;          /* The remote IP address */
   194    196     char *zNonce;           /* The nonce used for login */
   195    197   
   196         -  /* permissions used by the server */
          198  +  /* permissions available to current user */
   197    199     struct FossilUserPerms perm;
          200  +
          201  +  /* permissions available to current user or to "anonymous".
          202  +  ** This is the logical union of perm permissions above with
          203  +  ** the value that perm would take if g.zLogin were "anonymous". */
          204  +  struct FossilUserPerms anon;
   198    205   
   199    206   #ifdef FOSSIL_ENABLE_TCL
   200    207     /* all Tcl related context necessary for integration */
   201    208     struct TclContext tcl;
   202    209   #endif
   203    210   
   204    211     /* For defense against Cross-site Request Forgery attacks */
................................................................................
   656    663       g.fSqlTrace = find_option("sqltrace", 0, 0)!=0;
   657    664       g.fSqlStats = find_option("sqlstats", 0, 0)!=0;
   658    665       g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
   659    666       g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
   660    667       g.fSshClient = 0;
   661    668       g.zSshCmd = 0;
   662    669       if( g.fSqlTrace ) g.fSqlStats = 1;
   663         -    g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
   664    670       g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
   665    671   #ifdef FOSSIL_ENABLE_TH1_HOOKS
   666    672       g.fNoThHook = find_option("no-th-hook", 0, 0)!=0;
   667    673   #endif
          674  +    g.fAnyTrace = g.fSqlTrace|g.fSystemTrace|g.fSshTrace|g.fHttpTrace;
   668    675       g.zHttpAuth = 0;
   669    676       g.zLogin = find_option("user", "U", 1);
   670    677       g.zSSLIdentity = find_option("ssl-identity", 0, 1);
   671    678       g.zErrlog = find_option("errorlog", 0, 1);
   672    679       fossil_init_flags_from_options();
   673    680       if( find_option("utc",0,0) ) g.fTimeFormat = 1;
   674    681       if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
................................................................................
   686    693         zNewArgv[1] = "help";
   687    694         g.argc++;
   688    695         g.argv = zNewArgv;
   689    696       }
   690    697       zCmdName = g.argv[1];
   691    698     }
   692    699   #ifndef _WIN32
   693         -  if( !is_valid_fd(2) ) fossil_panic("file descriptor 2 not open");
   694         -  /* if( is_valid_fd(3) ) fossil_warning("file descriptor 3 is open"); */
          700  +  /* There is a bug in stunnel4 in which it sometimes starts up client
          701  +  ** processes without first opening file descriptor 2 (standard error).
          702  +  ** If this happens, and a subsequent open() of a database returns file
          703  +  ** descriptor 2, and then an assert() fires and writes on fd 2, that
          704  +  ** can corrupt the data file.  To avoid this problem, make sure open()
          705  +  ** will never return file descriptor 2 or less. */
          706  +  if( !is_valid_fd(2) ){
          707  +    int nTry = 0;
          708  +    int fd = 0;
          709  +    int x = 0;
          710  +    do{
          711  +      fd = open("/dev/null",O_WRONLY);
          712  +      if( fd>=2 ) break;
          713  +      if( fd<0 ) x = errno;
          714  +    }while( nTry++ < 2 );
          715  +    if( fd<2 ){
          716  +      g.cgiOutput = 1;
          717  +      g.httpOut = stdout;
          718  +      g.fullHttpReply = !g.isHTTP;
          719  +      fossil_fatal("file descriptor 2 is not open. (fd=%d, errno=%d)",
          720  +                   fd, x);
          721  +    }
          722  +  }
   695    723   #endif
   696    724     rc = name_search(zCmdName, aCommand, count(aCommand), FOSSIL_FIRST_CMD, &idx);
   697    725     if( rc==1 ){
   698    726   #ifdef FOSSIL_ENABLE_TH1_HOOKS
   699    727       if( !g.isHTTP && !g.fNoThHook ){
   700    728         rc = Th_CommandHook(zCmdName, 0);
   701    729       }else{
................................................................................
  1149   1177       n = (j+6)/7;
  1150   1178       for(i=j=0; i<count(aCommand); i++){
  1151   1179         const char *z = aCommand[i].zName;
  1152   1180         if( '/'==*z || strncmp(z,"test",4)==0 ) continue;
  1153   1181         if( j==0 ){
  1154   1182           @ <td valign="top"><ul>
  1155   1183         }
  1156         -      @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
         1184  +      @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
  1157   1185         j++;
  1158   1186         if( j>=n ){
  1159   1187           @ </ul></td>
  1160   1188           j = 0;
  1161   1189         }
  1162   1190       }
  1163   1191       if( j>0 ){
................................................................................
  1177   1205       for(i=j=0; i<count(aCommand); i++){
  1178   1206         const char *z = aCommand[i].zName;
  1179   1207         if( '/'!=*z ) continue;
  1180   1208         if( j==0 ){
  1181   1209           @ <td valign="top"><ul>
  1182   1210         }
  1183   1211         if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
  1184         -        @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z+1)</a></li>
         1212  +        @ <li><a href="%R/help?cmd=%s(z)">%s(z+1)</a></li>
  1185   1213         }else{
  1186   1214           @ <li>%s(z+1)</li>
  1187   1215         }
  1188   1216         j++;
  1189   1217         if( j>=n ){
  1190   1218           @ </ul></td>
  1191   1219           j = 0;
................................................................................
  1207   1235       for(i=j=0; i<count(aCommand); i++){
  1208   1236         const char *z = aCommand[i].zName;
  1209   1237         if( strncmp(z,"test",4)!=0 ) continue;
  1210   1238         if( j==0 ){
  1211   1239           @ <td valign="top"><ul>
  1212   1240         }
  1213   1241         if( aCmdHelp[i].zText && *aCmdHelp[i].zText ){
  1214         -        @ <li><a href="%s(g.zTop)/help?cmd=%s(z)">%s(z)</a></li>
         1242  +        @ <li><a href="%R/help?cmd=%s(z)">%s(z)</a></li>
  1215   1243         }else{
  1216   1244           @ <li>%s(z)</li>
  1217   1245         }
  1218   1246         j++;
  1219   1247         if( j>=n ){
  1220   1248           @ </ul></td>
  1221   1249           j = 0;
................................................................................
  1328   1356   ** new repository name.
  1329   1357   **
  1330   1358   ** zRepo might be a directory itself.  In that case chroot into
  1331   1359   ** the directory zRepo.
  1332   1360   **
  1333   1361   ** Assume the user-id and group-id of the repository, or if zRepo
  1334   1362   ** is a directory, of that directory.
         1363  +**
         1364  +** The noJail flag means that the chroot jail is not entered.  But
         1365  +** privileges are still lowered to that of the the user-id and group-id.
  1335   1366   */
  1336         -static char *enter_chroot_jail(char *zRepo){
         1367  +static char *enter_chroot_jail(char *zRepo, int noJail){
  1337   1368   #if !defined(_WIN32)
  1338   1369     if( getuid()==0 ){
  1339   1370       int i;
  1340   1371       struct stat sStat;
  1341   1372       Blob dir;
  1342   1373       char *zDir;
  1343   1374       if( g.db!=0 ){
  1344   1375         db_close(1);
  1345   1376       }
  1346   1377   
  1347   1378       file_canonical_name(zRepo, &dir, 0);
  1348   1379       zDir = blob_str(&dir);
  1349         -    if( file_isdir(zDir)==1 ){
  1350         -      if( file_chdir(zDir, 1) ){
  1351         -        fossil_fatal("unable to chroot into %s", zDir);
  1352         -      }
  1353         -      zRepo = "/";
  1354         -    }else{
  1355         -      for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
  1356         -      if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
  1357         -      if( i>0 ){
  1358         -        zDir[i] = 0;
         1380  +    if( !noJail ){
         1381  +      if( file_isdir(zDir)==1 ){
  1359   1382           if( file_chdir(zDir, 1) ){
  1360   1383             fossil_fatal("unable to chroot into %s", zDir);
  1361   1384           }
  1362         -        zDir[i] = '/';
         1385  +        zRepo = "/";
         1386  +      }else{
         1387  +        for(i=strlen(zDir)-1; i>0 && zDir[i]!='/'; i--){}
         1388  +        if( zDir[i]!='/' ) fossil_fatal("bad repository name: %s", zRepo);
         1389  +        if( i>0 ){
         1390  +          zDir[i] = 0;
         1391  +          if( file_chdir(zDir, 1) ){
         1392  +            fossil_fatal("unable to chroot into %s", zDir);
         1393  +          }
         1394  +          zDir[i] = '/';
         1395  +        }
         1396  +        zRepo = &zDir[i];
  1363   1397         }
  1364         -      zRepo = &zDir[i];
  1365   1398       }
  1366   1399       if( stat(zRepo, &sStat)!=0 ){
  1367   1400         fossil_fatal("cannot stat() repository: %s", zRepo);
  1368   1401       }
  1369   1402       i = setgid(sStat.st_gid);
  1370   1403       i = i || setuid(sStat.st_uid);
  1371   1404       if(i){
................................................................................
  1374   1407       if( g.db==0 && file_isfile(zRepo) ){
  1375   1408         db_open_repository(zRepo);
  1376   1409       }
  1377   1410     }
  1378   1411   #endif
  1379   1412     return zRepo;
  1380   1413   }
         1414  +
         1415  +/*
         1416  +** Generate a web-page that lists all repositories located under the
         1417  +** g.zRepositoryName directory and return non-zero.
         1418  +**
         1419  +** Or, if no repositories can be located beneath g.zRepositoryName,
         1420  +** return 0.
         1421  +*/
         1422  +static int repo_list_page(void){
         1423  +  Blob base;
         1424  +  int n = 0;
         1425  +
         1426  +  assert( g.db==0 );
         1427  +  blob_init(&base, g.zRepositoryName, -1);
         1428  +  sqlite3_open(":memory:", &g.db);
         1429  +  db_multi_exec("CREATE TABLE sfile(x TEXT);");
         1430  +  db_multi_exec("CREATE TABLE vfile(pathname);");
         1431  +  vfile_scan(&base, blob_size(&base), 0, 0, 0);
         1432  +  db_multi_exec("DELETE FROM sfile WHERE x NOT GLOB '*.fossil'");
         1433  +  n = db_int(0, "SELECT count(*) FROM sfile");
         1434  +  if( n>0 ){
         1435  +    Stmt q;
         1436  +    @ <h1>Available Repositories:</h1>
         1437  +    @ <ol>
         1438  +    db_prepare(&q, "SELECT x, substr(x,-7,-100000)||'/home'"
         1439  +                   " FROM sfile ORDER BY x COLLATE nocase;");
         1440  +    while( db_step(&q)==SQLITE_ROW ){
         1441  +      const char *zName = db_column_text(&q, 0);
         1442  +      const char *zUrl = db_column_text(&q, 1);
         1443  +      @ <li><a href="%h(zUrl)">%h(zName)</a></li>
         1444  +    }
         1445  +    @ </ol>
         1446  +    cgi_reply();
         1447  +  }
         1448  +  sqlite3_close(g.db);
         1449  +  g.db = 0;
         1450  +  return n;
         1451  +}
  1381   1452   
  1382   1453   /*
  1383   1454   ** Preconditions:
  1384   1455   **
  1385   1456   **  * Environment variables are set up according to the CGI standard.
  1386   1457   **
  1387   1458   ** If the repository is known, it has already been opened.  If unknown,
................................................................................
  1397   1468   ** Or, if an ordinary file named $prefix is found, and $prefix matches
  1398   1469   ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
  1399   1470   ** $prefix can be determined from its suffix, then the file $prefix is
  1400   1471   ** returned as static text.
  1401   1472   **
  1402   1473   ** If no suitable webpage is found, try to redirect to zNotFound.
  1403   1474   */
  1404         -static void process_one_web_page(const char *zNotFound, Glob *pFileGlob){
         1475  +static void process_one_web_page(
         1476  +  const char *zNotFound,      /* Redirect here on a 404 if not NULL */
         1477  +  Glob *pFileGlob,            /* Deliver static files matching */
         1478  +  int allowRepoList           /* Send repo list for "/" URL */
         1479  +){
  1405   1480     const char *zPathInfo;
  1406   1481     char *zPath = NULL;
  1407   1482     int idx;
  1408   1483     int i;
  1409   1484   
  1410   1485     /* If the repository has not been opened already, then find the
  1411   1486     ** repository based on the first element of PATH_INFO and open it.
................................................................................
  1472   1547           zRepo[j] = '.';
  1473   1548         }
  1474   1549   
  1475   1550         if( szFile<1024 ){
  1476   1551           set_base_url(0);
  1477   1552           if( zNotFound ){
  1478   1553             cgi_redirect(zNotFound);
         1554  +        }else if( strcmp(zPathInfo,"/")==0
         1555  +                  && allowRepoList
         1556  +                  && repo_list_page() ){
         1557  +          /* Will return a list of repositories */
  1479   1558           }else{
  1480   1559   #ifdef FOSSIL_ENABLE_JSON
  1481   1560             if(g.json.isJsonMode){
  1482   1561               json_err(FSL_JSON_E_RESOURCE_NOT_FOUND,NULL,1);
  1483   1562               return;
  1484   1563             }
  1485   1564   #endif
................................................................................
  1771   1850   */
  1772   1851   void cmd_cgi(void){
  1773   1852     const char *zFile;
  1774   1853     const char *zNotFound = 0;
  1775   1854     char **azRedirect = 0;             /* List of repositories to redirect to */
  1776   1855     int nRedirect = 0;                 /* Number of entries in azRedirect */
  1777   1856     Glob *pFileGlob = 0;               /* Pattern for files */
         1857  +  int allowRepoList = 0;             /* Allow lists of repository files */
  1778   1858     Blob config, line, key, value, value2;
  1779   1859     if( g.argc==3 && fossil_strcmp(g.argv[1],"cgi")==0 ){
  1780   1860       zFile = g.argv[2];
  1781   1861     }else{
  1782   1862       zFile = g.argv[1];
  1783   1863     }
  1784   1864     g.httpOut = stdout;
................................................................................
  1786   1866     fossil_binary_mode(g.httpOut);
  1787   1867     fossil_binary_mode(g.httpIn);
  1788   1868     g.cgiOutput = 1;
  1789   1869     blob_read_from_file(&config, zFile);
  1790   1870     while( blob_line(&config, &line) ){
  1791   1871       if( !blob_token(&line, &key) ) continue;
  1792   1872       if( blob_buffer(&key)[0]=='#' ) continue;
  1793         -    if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
  1794         -      g.fDebug = fossil_fopen(blob_str(&value), "ab");
  1795         -      blob_reset(&value);
  1796         -      continue;
  1797         -    }
  1798         -    if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
  1799         -      g.zErrlog = mprintf("%s", blob_str(&value));
  1800         -      continue;
  1801         -    }
  1802         -    if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
  1803         -      cgi_setenv("HOME", blob_str(&value));
  1804         -      blob_reset(&value);
  1805         -      continue;
  1806         -    }
  1807   1873       if( blob_eq(&key, "repository:") && blob_tail(&line, &value) ){
         1874  +      /* repository: FILENAME
         1875  +      **
         1876  +      ** The name of the Fossil repository to be served via CGI.  Most
         1877  +      ** fossil CGI scripts have a single non-comment line that contains
         1878  +      ** this one entry.
         1879  +      */
  1808   1880         blob_trim(&value);
  1809   1881         db_open_repository(blob_str(&value));
  1810   1882         blob_reset(&value);
  1811   1883         continue;
  1812   1884       }
  1813   1885       if( blob_eq(&key, "directory:") && blob_token(&line, &value) ){
         1886  +      /* directory: DIRECTORY
         1887  +      **
         1888  +      ** If repository: is omitted, then terms of the PATH_INFO cgi parameter
         1889  +      ** are appended to DIRECTORY looking for a repository (whose name ends
         1890  +      ** in ".fossil") or a file in "files:".
         1891  +      */
  1814   1892         db_close(1);
  1815   1893         g.zRepositoryName = mprintf("%s", blob_str(&value));
  1816   1894         blob_reset(&value);
  1817   1895         continue;
  1818   1896       }
  1819   1897       if( blob_eq(&key, "notfound:") && blob_token(&line, &value) ){
         1898  +      /* notfound: URL
         1899  +      **
         1900  +      ** If using directory: and no suitable repository or file is found,
         1901  +      ** then redirect to URL.
         1902  +      */
  1820   1903         zNotFound = mprintf("%s", blob_str(&value));
  1821   1904         blob_reset(&value);
  1822   1905         continue;
  1823   1906       }
  1824   1907       if( blob_eq(&key, "localauth") ){
         1908  +      /* localauth
         1909  +      **
         1910  +      ** Grant "administrator" privileges to users connecting with HTTP
         1911  +      ** from IP address 127.0.0.1.  Do not bother checking credentials.
         1912  +      */
  1825   1913         g.useLocalauth = 1;
  1826   1914         continue;
         1915  +    }
         1916  +    if( blob_eq(&key, "repolist") ){
         1917  +      /* repolist
         1918  +      **
         1919  +      ** If using "directory:" and the URL is "/" then generate a page
         1920  +      ** showing a list of available repositories.
         1921  +      */
         1922  +      allowRepoList = 1;
         1923  +      continue;
  1827   1924       }
  1828   1925       if( blob_eq(&key, "redirect:") && blob_token(&line, &value)
  1829   1926               && blob_token(&line, &value2) ){
         1927  +      /* See the header comment on the redirect_web_page() function
         1928  +      ** above for details. */
  1830   1929         nRedirect++;
  1831   1930         azRedirect = fossil_realloc(azRedirect, 2*nRedirect*sizeof(char*));
  1832   1931         azRedirect[nRedirect*2-2] = mprintf("%s", blob_str(&value));
  1833   1932         azRedirect[nRedirect*2-1] = mprintf("%s", blob_str(&value2));
  1834   1933         blob_reset(&value);
  1835   1934         blob_reset(&value2);
  1836   1935         continue;
  1837   1936       }
  1838   1937       if( blob_eq(&key, "files:") && blob_token(&line, &value) ){
         1938  +      /* files: GLOBLIST
         1939  +      **
         1940  +      ** GLOBLIST is a comma-separated list of filename globs.  For
         1941  +      ** example:  *.html,*.css,*.js
         1942  +      **
         1943  +      ** If the repository: line is omitted and then PATH_INFO is searched
         1944  +      ** for files that match any of these GLOBs and if any such file is
         1945  +      ** found it is returned verbatim.  This feature allows "fossil server"
         1946  +      ** to function as a primitive web-server delivering arbitrary content.
         1947  +      */
  1839   1948         pFileGlob = glob_create(blob_str(&value));
         1949  +      blob_reset(&value);
         1950  +      continue;
         1951  +    }
         1952  +    if( blob_eq(&key, "setenv:") && blob_token(&line, &value)
         1953  +            && blob_token(&line, &value2) ){
         1954  +      /* setenv: NAME VALUE
         1955  +      **
         1956  +      ** Sets environment variable NAME to VALUE
         1957  +      */
         1958  +      fossil_setenv(blob_str(&value), blob_str(&value2));
         1959  +      blob_reset(&value);
         1960  +      blob_reset(&value2);
         1961  +      continue;
         1962  +    }
         1963  +    if( blob_eq(&key, "debug:") && blob_token(&line, &value) ){
         1964  +      /* debug: FILENAME
         1965  +      **
         1966  +      ** Causes output from cgi_debug() and CGIDEBUG(()) calls to go
         1967  +      ** into FILENAME.
         1968  +      */
         1969  +      g.fDebug = fossil_fopen(blob_str(&value), "ab");
         1970  +      blob_reset(&value);
         1971  +      continue;
         1972  +    }
         1973  +    if( blob_eq(&key, "errorlog:") && blob_token(&line, &value) ){
         1974  +      /* errorlog: FILENAME
         1975  +      **
         1976  +      ** Causes messages from warnings, errors, and panics to be appended
         1977  +      ** to FILENAME.
         1978  +      */
         1979  +      g.zErrlog = mprintf("%s", blob_str(&value));
         1980  +      blob_reset(&value);
         1981  +      continue;
         1982  +    }
         1983  +    if( blob_eq(&key, "HOME:") && blob_token(&line, &value) ){
         1984  +      /* HOME: VALUE
         1985  +      **
         1986  +      ** Set CGI parameter "HOME" to VALUE.  This is legacy.  Use
         1987  +      ** setenv: instead.
         1988  +      */
         1989  +      cgi_setenv("HOME", blob_str(&value));
         1990  +      blob_reset(&value);
         1991  +      continue;
         1992  +    }
         1993  +    if( blob_eq(&key, "skin:") && blob_token(&line, &value) ){
         1994  +      /* skin: LABEL
         1995  +      **
         1996  +      ** Use one of the built-in skins defined by LABEL.  LABEL is the
         1997  +      ** name of the subdirectory under the skins/ directory that holds
         1998  +      ** the elements of the built-in skin.  If LABEL does not match,
         1999  +      ** this directive is a silent no-op.
         2000  +      */
         2001  +      skin_use_alternative(blob_str(&value));
         2002  +      blob_reset(&value);
  1840   2003         continue;
  1841   2004       }
  1842   2005     }
  1843   2006     blob_reset(&config);
  1844   2007     if( g.db==0 && g.zRepositoryName==0 && nRedirect==0 ){
  1845   2008       cgi_panic("Unable to find or open the project repository");
  1846   2009     }
  1847   2010     cgi_init();
  1848   2011     if( nRedirect ){
  1849   2012       redirect_web_page(nRedirect, azRedirect);
  1850   2013     }else{
  1851         -    process_one_web_page(zNotFound, pFileGlob);
         2014  +    process_one_web_page(zNotFound, pFileGlob, allowRepoList);
  1852   2015     }
  1853   2016   }
  1854   2017   
  1855   2018   /*
  1856   2019   ** If g.argv[arg] exists then it is either the name of a repository
  1857   2020   ** that will be used by a server, or else it is a directory that
  1858   2021   ** contains multiple repositories that can be served.  If g.argv[arg]
................................................................................
  1862   2025   ** that check-out.
  1863   2026   **
  1864   2027   ** Open the repository to be served if it is known.  If g.argv[arg] is
  1865   2028   ** a directory full of repositories, then set g.zRepositoryName to
  1866   2029   ** the name of that directory and the specific repository will be
  1867   2030   ** opened later by process_one_web_page() based on the content of
  1868   2031   ** the PATH_INFO variable.
  1869         -**
  1870         -** If disallowDir is set, then the directory full of repositories method
  1871         -** is disallowed.
  1872   2032   */
  1873         -static void find_server_repository(int disallowDir, int arg){
         2033  +static void find_server_repository(int arg){
  1874   2034     if( g.argc<=arg ){
  1875   2035       db_must_be_within_tree();
  1876   2036     }else if( file_isdir(g.argv[arg])==1 ){
  1877         -    if( disallowDir ){
  1878         -      fossil_fatal("\"%s\" is a directory, not a repository file", g.argv[arg]);
  1879         -    }else{
  1880         -      g.zRepositoryName = mprintf("%s", g.argv[arg]);
  1881         -      file_simplify_name(g.zRepositoryName, -1, 0);
  1882         -    }
         2037  +    g.zRepositoryName = mprintf("%s", g.argv[arg]);
         2038  +    file_simplify_name(g.zRepositoryName, -1, 0);
  1883   2039     }else{
  1884   2040       db_open_repository(g.argv[arg]);
  1885   2041     }
  1886   2042   }
  1887   2043   
  1888   2044   /*
  1889   2045   ** undocumented format:
................................................................................
  1920   2076   ** thus also no redirecting from http: to https: will take place.
  1921   2077   **
  1922   2078   ** If the --localauth option is given, then automatic login is performed
  1923   2079   ** for requests coming from localhost, if the "localauth" setting is not
  1924   2080   ** enabled.
  1925   2081   **
  1926   2082   ** Options:
         2083  +**   --baseurl URL    base URL (useful with reverse proxies)
         2084  +**   --files GLOB     comma-separate glob patterns for static file to serve
  1927   2085   **   --localauth      enable automatic login for local connections
  1928   2086   **   --host NAME      specify hostname of the server
  1929   2087   **   --https          signal a request coming in via https
         2088  +**   --nojail         drop root privilege but do not enter the chroot jail
  1930   2089   **   --nossl          signal that no SSL connections are available
  1931   2090   **   --notfound URL   use URL as "HTTP 404, object not found" page.
  1932         -**   --files GLOB     comma-separate glob patterns for static file to serve
  1933         -**   --baseurl URL    base URL (useful with reverse proxies)
         2091  +**   --repolist       If REPOSITORY is directory, URL "/" lists all repos
  1934   2092   **   --scgi           Interpret input as SCGI rather than HTTP
         2093  +**   --skin LABEL     Use override skin LABEL
  1935   2094   **
  1936   2095   ** See also: cgi, server, winsrv
  1937   2096   */
  1938   2097   void cmd_http(void){
  1939   2098     const char *zIpAddr = 0;
  1940   2099     const char *zNotFound;
  1941   2100     const char *zHost;
  1942   2101     const char *zAltBase;
  1943   2102     const char *zFileGlob;
  1944   2103     int useSCGI;
         2104  +  int noJail;
         2105  +  int allowRepoList;
  1945   2106   
  1946   2107     /* The winhttp module passes the --files option as --files-urlenc with
  1947   2108     ** the argument being URL encoded, to avoid wildcard expansion in the
  1948   2109     ** shell.  This option is for internal use and is undocumented.
  1949   2110     */
  1950   2111     zFileGlob = find_option("files-urlenc",0,1);
  1951   2112     if( zFileGlob ){
  1952   2113       char *z = mprintf("%s", zFileGlob);
  1953   2114       dehttpize(z);
  1954   2115       zFileGlob = z;
  1955   2116     }else{
  1956   2117       zFileGlob = find_option("files",0,1);
  1957   2118     }
         2119  +  skin_override();
  1958   2120     zNotFound = find_option("notfound", 0, 1);
         2121  +  noJail = find_option("nojail",0,0)!=0;
         2122  +  allowRepoList = find_option("repolist",0,0)!=0;
  1959   2123     g.useLocalauth = find_option("localauth", 0, 0)!=0;
  1960   2124     g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
  1961   2125     useSCGI = find_option("scgi", 0, 0)!=0;
  1962   2126     zAltBase = find_option("baseurl", 0, 1);
  1963   2127     if( zAltBase ) set_base_url(zAltBase);
  1964   2128     if( find_option("https",0,0)!=0 ){
  1965   2129       zIpAddr = fossil_getenv("REMOTE_HOST"); /* From stunnel */
................................................................................
  1976   2140       fossil_fatal("no repository specified");
  1977   2141     }
  1978   2142     g.fullHttpReply = 1;
  1979   2143     if( g.argc>=5 ){
  1980   2144       g.httpIn = fossil_fopen(g.argv[2], "rb");
  1981   2145       g.httpOut = fossil_fopen(g.argv[3], "wb");
  1982   2146       zIpAddr = g.argv[4];
  1983         -    find_server_repository(0, 5);
         2147  +    find_server_repository(5);
  1984   2148     }else{
  1985   2149       g.httpIn = stdin;
  1986   2150       g.httpOut = stdout;
  1987         -    find_server_repository(0, 2);
         2151  +    find_server_repository(2);
  1988   2152     }
  1989   2153     if( zIpAddr==0 ){
  1990   2154       zIpAddr = cgi_ssh_remote_addr(0);
  1991   2155       if( zIpAddr && zIpAddr[0] ){
  1992   2156         g.fSshClient |= CGI_SSH_CLIENT;
  1993   2157       }
  1994   2158     }
  1995         -  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName);
         2159  +  g.zRepositoryName = enter_chroot_jail(g.zRepositoryName, noJail);
  1996   2160     if( useSCGI ){
  1997   2161       cgi_handle_scgi_request();
  1998   2162     }else if( g.fSshClient & CGI_SSH_CLIENT ){
  1999   2163       ssh_request_loop(zIpAddr, glob_create(zFileGlob));
  2000   2164     }else{
  2001   2165       cgi_handle_http_request(zIpAddr);
  2002   2166     }
  2003         -  process_one_web_page(zNotFound, glob_create(zFileGlob));
         2167  +  process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
  2004   2168   }
  2005   2169   
  2006   2170   /*
  2007   2171   ** Process all requests in a single SSH connection if possible.
  2008   2172   */
  2009   2173   void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
  2010   2174     blob_zero(&g.cgiIn);
  2011   2175     do{
  2012   2176       cgi_handle_ssh_http_request(zIpAddr);
  2013         -    process_one_web_page(0, FileGlob);
         2177  +    process_one_web_page(0, FileGlob, 0);
  2014   2178       blob_reset(&g.cgiIn);
  2015   2179     } while ( g.fSshClient & CGI_SSH_FOSSIL ||
  2016   2180             g.fSshClient & CGI_SSH_COMPAT );
  2017   2181   }
  2018   2182   
  2019   2183   /*
  2020   2184   ** Note that the following command is used by ssh:// processing.
................................................................................
  2027   2191     const char *zIpAddr;    /* IP address of remote client */
  2028   2192   
  2029   2193     Th_InitTraceLog();
  2030   2194     login_set_capabilities("sx", 0);
  2031   2195     g.useLocalauth = 1;
  2032   2196     g.httpIn = stdin;
  2033   2197     g.httpOut = stdout;
  2034         -  find_server_repository(0, 2);
         2198  +  find_server_repository(2);
  2035   2199     g.cgiOutput = 1;
  2036   2200     g.fullHttpReply = 1;
  2037   2201     zIpAddr = cgi_ssh_remote_addr(0);
  2038   2202     if( zIpAddr && zIpAddr[0] ){
  2039   2203       g.fSshClient |= CGI_SSH_CLIENT;
  2040   2204       ssh_request_loop(zIpAddr, 0);
  2041   2205     }else{
  2042   2206       cgi_set_parameter("REMOTE_ADDR", "127.0.0.1");
  2043   2207       cgi_handle_http_request(0);
  2044         -    process_one_web_page(0, 0);
         2208  +    process_one_web_page(0, 0, 0);
  2045   2209     }
  2046   2210   }
  2047   2211   
  2048   2212   #if !defined(_WIN32)
  2049   2213   #if !defined(__DARWIN__) && !defined(__APPLE__) && !defined(__HAIKU__)
  2050   2214   /*
  2051   2215   ** Search for an executable on the PATH environment variable.
................................................................................
  2098   2262   ** list of glob patterns given by --files and that have known suffixes
  2099   2263   ** such as ".txt" or ".html" or ".jpeg" and do not match the pattern
  2100   2264   ** "*.fossil*" will be served as static content.  With the "ui" command,
  2101   2265   ** the REPOSITORY can only be a directory if the --notfound option is
  2102   2266   ** also present.
  2103   2267   **
  2104   2268   ** By default, the "ui" command provides full administrative access without
  2105         -** having to log in.  This can be disabled by setting turning off the
  2106         -** "localauth" setting.  Automatic login for the "server" command is available
  2107         -** if the --localauth option is present and the "localauth" setting is off
  2108         -** and the connection is from localhost.  The optional REPOSITORY argument
  2109         -** to "ui" may be a directory and will function as "server" if and only if
  2110         -** the --notfound option is used.
         2269  +** having to log in.  This can be disabled by turning off the "localauth"
         2270  +** setting.  Automatic login for the "server" command is available if the
         2271  +** --localauth option is present and the "localauth" setting is off and the
         2272  +** connection is from localhost.  The "ui" command also enables --repolist
         2273  +** by default.
  2111   2274   **
  2112   2275   ** Options:
         2276  +**   --baseurl URL       Use URL as the base (useful for reverse proxies)
         2277  +**   --files GLOBLIST    Comma-separated list of glob patterns for static files
  2113   2278   **   --localauth         enable automatic login for requests from localhost
  2114   2279   **   --localhost         listen on 127.0.0.1 only (always true for "ui")
         2280  +**   --nojail            Drop root privileges but do not enter the chroot jail
         2281  +**   --notfound URL      Redirect
  2115   2282   **   -P|--port TCPPORT   listen to request on port TCPPORT
  2116   2283   **   --th-trace          trace TH1 execution (for debugging purposes)
  2117         -**   --baseurl URL       Use URL as the base (useful for reverse proxies)
  2118         -**   --notfound URL      Redirect
  2119         -**   --files GLOBLIST    Comma-separated list of glob patterns for static files
         2284  +**   --repolist          If REPOSITORY is dir, URL "/" lists repos.
  2120   2285   **   --scgi              Accept SCGI rather than HTTP
         2286  +**   --skin LABEL        Use override skin LABEL
         2287  +
  2121   2288   **
  2122   2289   ** See also: cgi, http, winsrv
  2123   2290   */
  2124   2291   void cmd_webserver(void){
  2125   2292     int iPort, mxPort;        /* Range of TCP ports allowed */
  2126   2293     const char *zPort;        /* Value of the --port option */
  2127   2294     const char *zBrowser;     /* Name of web browser program */
  2128   2295     char *zBrowserCmd = 0;    /* Command to launch the web browser */
  2129   2296     int isUiCmd;              /* True if command is "ui", not "server' */
  2130   2297     const char *zNotFound;    /* The --notfound option or NULL */
  2131   2298     int flags = 0;            /* Server flags */
         2299  +#if !defined(_WIN32)
         2300  +  int noJail;               /* Do not enter the chroot jail */
         2301  +#endif
         2302  +  int allowRepoList;        /* List repositories on URL "/" */
  2132   2303     const char *zAltBase;     /* Argument to the --baseurl option */
  2133   2304     const char *zFileGlob;    /* Static content must match this */
  2134   2305     char *zIpAddr = 0;        /* Bind to this IP address */
  2135   2306   
  2136   2307   #if defined(_WIN32)
  2137   2308     const char *zStopperFile;    /* Name of file used to terminate server */
  2138   2309     zStopperFile = find_option("stopper", 0, 1);
................................................................................
  2142   2313     if( zFileGlob ){
  2143   2314       char *z = mprintf("%s", zFileGlob);
  2144   2315       dehttpize(z);
  2145   2316       zFileGlob = z;
  2146   2317     }else{
  2147   2318       zFileGlob = find_option("files",0,1);
  2148   2319     }
         2320  +  skin_override();
         2321  +#if !defined(_WIN32)
         2322  +  noJail = find_option("nojail",0,0)!=0;
         2323  +#endif
  2149   2324     g.useLocalauth = find_option("localauth", 0, 0)!=0;
  2150   2325     Th_InitTraceLog();
  2151   2326     zPort = find_option("port", "P", 1);
  2152   2327     zNotFound = find_option("notfound", 0, 1);
         2328  +  allowRepoList = find_option("repolist",0,0)!=0;
  2153   2329     zAltBase = find_option("baseurl", 0, 1);
  2154   2330     if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
  2155   2331     if( zAltBase ){