Fossil

Check-in [abd131b8]
Login

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

Overview
Comment:Merge updates from trunk.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | mvHardDirFix
Files: files | file ages | folders
SHA1: abd131b83c2a37c583cbff17a298e03e39ab04f0
User & Date: mistachkin 2016-03-06 06:26:31
Context
2016-03-11
23:38
Fix compilation issues cause by the trunk merge. check-in: 12453740 user: mistachkin tags: mvHardDirFix
2016-03-06
06:26
Merge updates from trunk. check-in: abd131b8 user: mistachkin tags: mvHardDirFix
2016-03-05
20:01
Added add_content_sql_commands() to /admin_sql, as per ML discussion. check-in: 93f514ca user: stephan tags: trunk
2015-07-29
18:44
Candidate fix for directory renaming issue with the --hard option as reported via the mailing list. check-in: b86127e1 user: mistachkin tags: mvHardDirFix
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to Dockerfile.

     1      1   ###
     2      2   #   Dockerfile for Fossil
     3      3   ###
     4         -FROM fedora:21
            4  +FROM fedora:23
     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 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 dnf update -y && dnf install -y gcc make zlib-devel openssl-devel tar && dnf 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 "release", change the next line accordingly.
    10         -ENV FOSSIL_INSTALL_VERSION trunk
            9  +### If you want to build "trunk", change the next line accordingly.
           10  +ENV FOSSIL_INSTALL_VERSION release
    11     11   
    12     12   RUN curl "http://core.tcl.tk/tcl/tarball/tcl-src.tar.gz?name=tcl-src&uuid=release" | tar zx
    13         -RUN cd tcl-src/unix && ./configure --prefix=/usr --disable-shared --disable-threads --disable-load && make && make install
           13  +RUN cd tcl-src/unix && ./configure --prefix=/usr --disable-load && make && make install
    14     14   RUN curl "http://www.fossil-scm.org/index.html/tarball/fossil-src.tar.gz?name=fossil-src&uuid=${FOSSIL_INSTALL_VERSION}" | tar zx
    15         -RUN cd fossil-src && ./configure --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl
           15  +RUN cd fossil-src && ./configure --disable-fusefs --json --with-th1-docs --with-th1-hooks --with-tcl --with-tcl-stubs --with-tcl-private-stubs
           16  +RUN cd fossil-src/src && mv main.c main.c.orig && sed s/\"now\"/0/ <main.c.orig >main.c
    16     17   RUN cd fossil-src && make && strip fossil && cp fossil /usr/bin && cd .. && rm -rf fossil-src && chmod a+rx /usr/bin/fossil && mkdir -p /opt/fossil && chown fossil:fossil /opt/fossil
    17     18   
    18     19   ### Build is done, remove modules no longer needed
    19         -RUN yum remove -y gcc make zlib-devel openssl-devel tar && yum clean all
           20  +RUN dnf remove -y gcc make zlib-devel openssl-devel tar && dnf clean all
    20     21   
    21     22   USER fossil
    22     23   
    23     24   ENV HOME /opt/fossil
    24     25   
    25     26   EXPOSE 8080
    26     27   
    27     28   CMD ["/usr/bin/fossil", "server", "--create", "--user", "admin", "/opt/fossil/repository.fossil"]

Changes to Makefile.classic.

    50     50   TCC += -DFOSSIL_DYNAMIC_BUILD=1
    51     51   
    52     52   #### Extra arguments for linking the finished binary.  Fossil needs
    53     53   #    to link against the Z-Lib compression library unless the miniz
    54     54   #    library in the source tree is being used.  There are no other
    55     55   #    required dependencies.
    56     56   ZLIB_LIB.0 = -lz
    57         -ZLIB_LIB.1 = 
           57  +ZLIB_LIB.1 =
    58     58   ZLIB_LIB.  = $(ZLIB_LIB.0)
    59     59   
    60     60   # If using zlib:
    61     61   LIB += $(ZLIB_LIB.$(FOSSIL_ENABLE_MINIZ)) $(LDFLAGS)
    62     62   
    63     63   # If using HTTPS:
    64     64   LIB += -lcrypto -lssl

Changes to Makefile.in.

    38     38   #
    39     39   TCLSH = tclsh
    40     40   
    41     41   LIB =	@LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
    42     42   TCCFLAGS =	@EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H -D_HAVE_SQLITE_CONFIG_H
    43     43   INSTALLDIR = $(DESTDIR)@prefix@/bin
    44     44   USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
           45  +USE_LINENOISE = @USE_LINENOISE@
    45     46   FOSSIL_ENABLE_MINIZ = @FOSSIL_ENABLE_MINIZ@
    46     47   
    47     48   include $(SRCDIR)/main.mk
    48     49   
    49     50   distclean: clean
    50     51   	rm -f autoconfig.h config.log Makefile

Changes to VERSION.

     1         -1.33
            1  +1.35

Changes to ajax/i-test/rhino-test.js.

    40     40           if(!TestApp.verbose) return;
    41     41           print("ERROR: "+WhAjaj.stringify(opt));
    42     42       };
    43     43       cb.onResponse = function(resp,req){
    44     44           if(!TestApp.verbose) return;
    45     45           print("GOT RESPONSE: "+(('string'===typeof resp) ? resp : WhAjaj.stringify(resp)));
    46     46       };
    47         -    
           47  +
    48     48   })();
    49     49   
    50     50   /**
    51     51       Throws an exception of cond is a falsy value.
    52     52   */
    53     53   function assert(cond, descr){
    54     54       descr = descr || "Undescribed condition.";
................................................................................
   125    125   }
   126    126   testHAI.description = 'Get server version info.';
   127    127   
   128    128   function testIAmNobody(){
   129    129       TestApp.fossil.whoami('/json/whoami');
   130    130       assert('nobody' === TestApp.fossil.auth.name, 'User == nobody.' );
   131    131       assert(!TestApp.fossil.auth.authToken, 'authToken is not set.' );
   132         -   
          132  +
   133    133   }
   134    134   testIAmNobody.description = 'Ensure that current user is "nobody".';
   135    135   
   136    136   
   137    137   function testAnonymousLogin(){
   138    138       TestApp.fossil.login();
   139    139       assert('string' === typeof TestApp.fossil.auth.authToken, 'authToken = '+TestApp.fossil.auth.authToken);
................................................................................
   219    219       osb.write(json,0, json.length);
   220    220       osb.close();
   221    221       req = json = outs = osr = osb = undefined;
   222    222       var ins = p.getInputStream();
   223    223       var isr = new java.io.InputStreamReader(ins);
   224    224       var br = new java.io.BufferedReader(isr);
   225    225       var line;
   226         -    
          226  +
   227    227       while( null !== (line=br.readLine())){
   228    228           print(line);
   229    229       }
   230    230       br.close();
   231    231       isr.close();
   232    232       ins.close();
   233    233       p.waitFor();

Changes to ajax/index.html.

     1      1   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     2      2   	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     3      3   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     4      4   
     5      5   <head>
     6      6   	<title>Fossil/JSON raw request sending</title>
     7      7   	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
     8         -    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script> 
            8  +    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
     9      9       <script type="text/javascript" src="js/whajaj.js"></script>
    10     10       <script type="text/javascript" src="js/fossil-ajaj.js"></script>
    11     11   
    12     12   <style type='text/css'>
    13     13   th {
    14     14     text-align: left;
    15         -  background-color: #ececec;  
           15  +  background-color: #ececec;
    16     16   }
    17     17   
    18     18   .dangerWillRobinson {
    19     19       background-color: yellow;
    20     20   }
    21     21   </style>
    22     22   

Changes to ajax/wiki-editor.html.

     1      1   <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     2      2   	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
     3      3   <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
     4      4   
     5      5   <head>
     6      6   	<title>Fossil/JSON Wiki Editor Prototype</title>
     7      7   	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
     8         -    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script> 
            8  +    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
     9      9       <script type="text/javascript" src="js/whajaj.js"></script>
    10     10       <script type="text/javascript" src="js/fossil-ajaj.js"></script>
    11     11   
    12     12   <style type='text/css'>
    13     13   th {
    14     14     text-align: left;
    15         -  background-color: #ececec;  
           15  +  background-color: #ececec;
    16     16   }
    17     17   
    18     18   .dangerWillRobinson {
    19     19       background-color: yellow;
    20     20   }
    21     21   
    22     22   .wikiPageLink {
................................................................................
   213    213       TheApp.refreshPageListView = function(){
   214    214           var list = (function(){
   215    215               var k, v, li = [];
   216    216               for( k in TheApp.pages ){
   217    217                   if(!TheApp.pages.hasOwnProperty(k)) continue;
   218    218                   li.push(k);
   219    219               }
   220         -            return li;                
          220  +            return li;
   221    221           })();
   222    222           var i, p, a, tgt = TheApp.jqe.pageListArea;
   223    223           tgt.text('');
   224    224           function makeLink(name){
   225    225               var link = jQuery('<span></span>');
   226    226               link.text(name);
   227    227               link.addClass('wikiPageLink');
................................................................................
   320    320   
   321    321   See also: <a href='index.html'>main test page</a>.
   322    322   
   323    323   <br>
   324    324   <b>Login:</b>
   325    325   <br/>
   326    326   <input type='button' value='Anon. Login' onclick='TheApp.cgi.login()' />
   327         -or: 
          327  +or:
   328    328   name:<input type='text' id='textUser' value='json-demo' size='12'/>
   329    329   pw:<input type='password' id='textPassword' value='json-demo' size='12'/>
   330    330   <input type='button' value='login' onclick='TheApp.cgi.login(jQuery("#textUser").val(),jQuery("#textPassword").val(),{onResponse:TheApp.onLogin})' />
   331    331   <input type='button' value='logout' onclick='TheApp.cgi.logout()' />
   332    332   
   333    333   <br/>
   334    334   <span id='currentAuthToken' style='font-family:monospaced'></span>

Changes to auto.def.

     1      1   # System autoconfiguration. Try: ./configure --help
     2      2   
     3      3   use cc cc-lib
     4      4   
     5      5   options {
     6         -    with-openssl:path|auto|none
     7         -                         => {Look for OpenSSL in the given path, or auto or none}
            6  +    with-openssl:path|auto|tree|none
            7  +                         => {Look for OpenSSL in the given path, automatically, in the source tree, or none}
     8      8       with-miniz=0         => {Use miniz from the source tree}
     9         -    with-zlib:path       => {Look for zlib in the given path}
            9  +    with-zlib:path|auto|tree
           10  +                         => {Look for zlib in the given path, automatically, or in the source tree}
           11  +    with-exec-rel-paths=0
           12  +                         => {Enable relative paths for external diff/gdiff}
    10     13       with-legacy-mv-rm=0  => {Enable legacy behavior for mv/rm (skip checkout files)}
    11     14       with-th1-docs=0      => {Enable TH1 for embedded documentation pages}
    12     15       with-th1-hooks=0     => {Enable TH1 hooks for commands and web pages}
    13     16       with-tcl:path        => {Enable Tcl integration, with Tcl in the specified path}
    14     17       with-tcl-stubs=0     => {Enable Tcl integration via stubs library mechanism}
    15     18       with-tcl-private-stubs=0
    16     19                            => {Enable Tcl integration via private stubs mechanism}
................................................................................
    31     34   
    32     35   # Find tclsh for the test suite. Can't yet use jimsh for this.
    33     36   cc-check-progs tclsh
    34     37   
    35     38   define EXTRA_CFLAGS ""
    36     39   define EXTRA_LDFLAGS ""
    37     40   define USE_SYSTEM_SQLITE 0
           41  +define USE_LINENOISE 0
           42  +define FOSSIL_ENABLE_MINIZ 0
           43  +
           44  +# This procedure is a customized version of "cc-check-function-in-lib",
           45  +# that does not modify the LIBS variable.  Its use prevents prematurely
           46  +# pulling in libraries that will be added later anyhow (e.g. "-ldl").
           47  +proc check-function-in-lib {function libs {otherlibs {}}} {
           48  +    if {[string length $otherlibs]} {
           49  +        msg-checking "Checking for $function in $libs with $otherlibs..."
           50  +    } else {
           51  +        msg-checking "Checking for $function in $libs..."
           52  +    }
           53  +    set found 0
           54  +    cc-with [list -libs $otherlibs] {
           55  +        if {[cctest_function $function]} {
           56  +            msg-result "none needed"
           57  +            define lib_$function ""
           58  +            incr found
           59  +        } else {
           60  +            foreach lib $libs {
           61  +                cc-with [list -libs -l$lib] {
           62  +                    if {[cctest_function $function]} {
           63  +                        msg-result -l$lib
           64  +                        define lib_$function -l$lib
           65  +                        incr found
           66  +                        break
           67  +                    }
           68  +                }
           69  +            }
           70  +        }
           71  +    }
           72  +    if {$found} {
           73  +        define [feature-define-name $function]
           74  +    } else {
           75  +        msg-result "no"
           76  +    }
           77  +    return $found
           78  +}
    38     79   
    39     80   if {![opt-bool internal-sqlite]} {
    40     81     proc find_internal_sqlite {} {
    41     82   
    42     83       # On some systems (slackware), libsqlite3 requires -ldl to link. So
    43     84       # search for the system SQLite once with -ldl, and once without. If
    44     85       # the library can only be found with $extralibs set to -ldl, then
    45     86       # the code below will append -ldl to LIBS.
    46     87       #
    47     88       foreach extralibs {{} {-ldl}} {
    48     89   
    49     90         # Locate the system SQLite by searching for sqlite3_open(). Then check
    50         -      # if sqlite3_strglob() can be found as well. If we can find open() but
    51         -      # not strglob(), then the system SQLite is too old to link against
           91  +      # if sqlite3_strlike() can be found as well. If we can find open() but
           92  +      # not strlike(), then the system SQLite is too old to link against
    52     93         # fossil.
    53     94         #
    54         -      if {[cc-check-function-in-lib sqlite3_open sqlite3 $extralibs]} {
    55         -        if {![cc-check-function-in-lib sqlite3_malloc64 sqlite3 $extralibs]} {
    56         -          user-error "system sqlite3 too old (require >= 3.8.7)"
           95  +      if {[check-function-in-lib sqlite3_open sqlite3 $extralibs]} {
           96  +        if {![check-function-in-lib sqlite3_strlike sqlite3 $extralibs]} {
           97  +          user-error "system sqlite3 too old (require >= 3.10.0)"
    57     98           }
    58     99   
    59    100           # Success. Update symbols and return.
    60    101           #
    61    102           define USE_SYSTEM_SQLITE 1
          103  +        define-append LIBS -lsqlite3
    62    104           define-append LIBS $extralibs
    63    105           return
    64    106         }
    65    107       }
    66    108       user-error "system sqlite3 not found"
    67    109     }
    68    110   
    69    111     find_internal_sqlite
    70    112   }
          113  +
          114  +proc is_mingw {} {
          115  +    return [string match *mingw* [get-define host]]
          116  +}
          117  +
          118  +if {[is_mingw]} {
          119  +    define-append EXTRA_CFLAGS -DBROKEN_MINGW_CMDLINE
          120  +    define-append LIBS -lkernel32 -lws2_32
          121  +} else {
          122  +    #
          123  +    # NOTE: All platforms except MinGW should use the linenoise
          124  +    #       package.  It is currently unsupported on Win32.
          125  +    #
          126  +    define USE_LINENOISE 1
          127  +}
    71    128   
    72    129   if {[string match *-solaris* [get-define host]]} {
    73    130       define-append EXTRA_CFLAGS {-D_XOPEN_SOURCE=500 -D__EXTENSIONS__}
    74    131   }
    75    132   
    76    133   if {[opt-bool fossil-debug]} {
    77    134       define-append EXTRA_CFLAGS -DFOSSIL_DEBUG
................................................................................
    89    146   }
    90    147   
    91    148   if {[opt-bool with-legacy-mv-rm]} {
    92    149       define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_LEGACY_MV_RM
    93    150       define FOSSIL_ENABLE_LEGACY_MV_RM
    94    151       msg-result "Legacy mv/rm support enabled"
    95    152   }
          153  +
          154  +if {[opt-bool with-exec-rel-paths]} {
          155  +    define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_EXEC_REL_PATHS
          156  +    define FOSSIL_ENABLE_EXEC_REL_PATHS
          157  +    msg-result "Relative paths in external diff/gdiff enabled"
          158  +}
    96    159   
    97    160   if {[opt-bool with-th1-docs]} {
    98    161       define-append EXTRA_CFLAGS -DFOSSIL_ENABLE_TH1_DOCS
    99    162       define FOSSIL_ENABLE_TH1_DOCS
   100    163       msg-result "TH1 embedded documentation support enabled"
   101    164   }
   102    165   
................................................................................
   115    178       # XXX: This will not work on all systems.
   116    179       define-append EXTRA_LDFLAGS -static
   117    180       msg-result "Trying to link statically"
   118    181   } else {
   119    182       define-append EXTRA_CFLAGS -DFOSSIL_DYNAMIC_BUILD=1
   120    183       define FOSSIL_DYNAMIC_BUILD
   121    184   }
          185  +
          186  +# Helper for OpenSSL checking
          187  +proc check-for-openssl {msg {cflags {}} {libs {-lssl -lcrypto}}} {
          188  +    msg-checking "Checking for $msg..."
          189  +    set rc 0
          190  +    if {[is_mingw]} {
          191  +        lappend libs -lgdi32 -lwsock32
          192  +    }
          193  +    if {[info exists ::zlib_lib]} {
          194  +        lappend libs $::zlib_lib
          195  +    }
          196  +    msg-quiet cc-with [list -cflags $cflags -libs $libs] {
          197  +        if {[cc-check-includes openssl/ssl.h] && \
          198  +                [cc-check-functions SSL_new]} {
          199  +            incr rc
          200  +        }
          201  +    }
          202  +    if {!$rc && ![is_mingw]} {
          203  +        # On some systems, OpenSSL appears to require -ldl to link.
          204  +        lappend libs -ldl
          205  +        msg-quiet cc-with [list -cflags $cflags -libs $libs] {
          206  +            if {[cc-check-includes openssl/ssl.h] && \
          207  +                    [cc-check-functions SSL_new]} {
          208  +                incr rc
          209  +            }
          210  +        }
          211  +    }
          212  +    if {$rc} {
          213  +        msg-result "ok"
          214  +        return 1
          215  +    } else {
          216  +        msg-result "no"
          217  +        return 0
          218  +    }
          219  +}
          220  +
          221  +if {[opt-bool with-miniz]} {
          222  +    define FOSSIL_ENABLE_MINIZ 1
          223  +    msg-result "Using miniz for compression"
          224  +} else {
          225  +    # Check for zlib, using the given location if specified
          226  +    set zlibpath [opt-val with-zlib]
          227  +    if {$zlibpath eq "tree"} {
          228  +        set zlibdir [file dirname $autosetup(dir)]/compat/zlib
          229  +        if {![file isdirectory $zlibdir]} {
          230  +            user-error "The zlib in source tree directory does not exist"
          231  +        }
          232  +        cc-with [list -cflags "-I$zlibdir -L$zlibdir"]
          233  +        define-append EXTRA_CFLAGS -I$zlibdir
          234  +        define-append LIBS $zlibdir/libz.a
          235  +        set ::zlib_lib $zlibdir/libz.a
          236  +        msg-result "Using zlib in source tree"
          237  +    } else {
          238  +        if {$zlibpath ni {auto ""}} {
          239  +            cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
          240  +            define-append EXTRA_CFLAGS -I$zlibpath
          241  +            define-append EXTRA_LDFLAGS -L$zlibpath
          242  +            msg-result "Using zlib from $zlibpath"
          243  +        }
          244  +        if {![cc-check-includes zlib.h] || ![check-function-in-lib inflateEnd z]} {
          245  +            user-error "zlib not found please install it or specify the location with --with-zlib"
          246  +        }
          247  +        set ::zlib_lib -lz
          248  +    }
          249  +}
          250  +
          251  +set ssldirs [opt-val with-openssl]
          252  +if {$ssldirs ne "none"} {
          253  +    if {[opt-bool with-miniz]} {
          254  +        user-error "The --with-miniz option is incompatible with OpenSSL"
          255  +    }
          256  +    set found 0
          257  +    if {$ssldirs eq "tree"} {
          258  +        set ssldir [file dirname $autosetup(dir)]/compat/openssl
          259  +        if {![file isdirectory $ssldir]} {
          260  +            user-error "The OpenSSL in source tree directory does not exist"
          261  +        }
          262  +        set msg "ssl in $ssldir"
          263  +        set cflags "-I$ssldir/include"
          264  +        set ldflags "-L$ssldir"
          265  +        set ssllibs "$ssldir/libssl.a $ssldir/libcrypto.a"
          266  +        set found [check-for-openssl "ssl in source tree" "$cflags $ldflags" $ssllibs]
          267  +    } else {
          268  +        if {$ssldirs in {auto ""}} {
          269  +            catch {
          270  +                set cflags [exec pkg-config openssl --cflags-only-I]
          271  +                set ldflags [exec pkg-config openssl --libs-only-L]
          272  +                set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"]
          273  +            } msg
          274  +            if {!$found} {
          275  +                set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl \
          276  +                             /usr/pkg /usr/local /usr /usr/local/opt/openssl"
          277  +            }
          278  +        }
          279  +        if {!$found} {
          280  +            foreach dir $ssldirs {
          281  +                if {$dir eq ""} {
          282  +                    set msg "system ssl"
          283  +                    set cflags ""
          284  +                    set ldflags ""
          285  +                } else {
          286  +                    set msg "ssl in $dir"
          287  +                    set cflags "-I$dir/include"
          288  +                    set ldflags "-L$dir/lib"
          289  +                }
          290  +                if {[check-for-openssl $msg "$cflags $ldflags"]} {
          291  +                    incr found
          292  +                    break
          293  +                }
          294  +            }
          295  +        }
          296  +    }
          297  +    if {$found} {
          298  +        define FOSSIL_ENABLE_SSL
          299  +        define-append EXTRA_CFLAGS $cflags
          300  +        define-append EXTRA_LDFLAGS $ldflags
          301  +        if {[info exists ssllibs]} {
          302  +            define-append LIBS $ssllibs
          303  +        } else {
          304  +            define-append LIBS -lssl -lcrypto
          305  +        }
          306  +        if {[info exists ::zlib_lib]} {
          307  +            define-append LIBS $::zlib_lib
          308  +        }
          309  +        if {[is_mingw]} {
          310  +            define-append LIBS -lgdi32 -lwsock32
          311  +        }
          312  +        msg-result "HTTPS support enabled"
          313  +
          314  +        # Silence OpenSSL deprecation warnings on Mac OS X 10.7.
          315  +        if {[string match *-darwin* [get-define host]]} {
          316  +            if {[cctest -cflags {-Wdeprecated-declarations}]} {
          317  +                define-append EXTRA_CFLAGS -Wdeprecated-declarations
          318  +            }
          319  +        }
          320  +    } else {
          321  +        user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
          322  +    }
          323  +} else {
          324  +    if {[info exists ::zlib_lib]} {
          325  +        define-append LIBS $::zlib_lib
          326  +    }
          327  +}
   122    328   
   123    329   set tclpath [opt-val with-tcl]
   124    330   if {$tclpath ne ""} {
   125    331       set tclprivatestubs [opt-bool with-tcl-private-stubs]
   126    332       # Note parse-tclconfig-sh is in autosetup/local.tcl
   127    333       if {$tclpath eq "1"} {
          334  +        set tcldir [file dirname $autosetup(dir)]/compat/tcl-8.6
   128    335           if {$tclprivatestubs} {
   129         -            set tclconfig(TCL_INCLUDE_SPEC) -Icompat/tcl-8.6/generic
          336  +            set tclconfig(TCL_INCLUDE_SPEC) -I$tcldir/generic
   130    337               set tclconfig(TCL_VERSION) {Private Stubs}
   131    338               set tclconfig(TCL_PATCH_LEVEL) {}
   132         -            set tclconfig(TCL_PREFIX) {compat/tcl-8.6}
          339  +            set tclconfig(TCL_PREFIX) $tcldir
   133    340               set tclconfig(TCL_LD_FLAGS) { }
   134    341           } else {
   135    342               # Use the system Tcl. Look in some likely places.
   136    343               array set tclconfig [parse-tclconfig-sh \
   137         -                compat/tcl-8.6/unix compat/tcl-8.6/win \
          344  +                $tcldir/unix $tcldir/win \
   138    345                   /usr /usr/local /usr/share /opt/local]
   139    346               set msg "on your system"
   140    347           }
   141    348       } else {
   142    349           array set tclconfig [parse-tclconfig-sh $tclpath]
   143    350           set msg "at $tclpath"
   144    351       }
................................................................................
   154    361           define FOSSIL_ENABLE_TCL_STUBS
   155    362           define USE_TCL_STUBS
   156    363       } else {
   157    364           set libs "$tclconfig(TCL_LIB_SPEC) $tclconfig(TCL_LIBS)"
   158    365       }
   159    366       set cflags $tclconfig(TCL_INCLUDE_SPEC)
   160    367       if {!$tclprivatestubs} {
          368  +        set foundtcl 0; # Did we find a working Tcl library?
   161    369           cc-with [list -cflags $cflags -libs $libs] {
   162    370               if {$tclstubs} {
   163         -                if {![cc-check-functions Tcl_InitStubs]} {
   164         -                    user-error "Cannot find a usable Tcl stubs library $msg"
          371  +                if {[cc-check-functions Tcl_InitStubs]} {
          372  +                    set foundtcl 1
   165    373                   }
   166    374               } else {
   167         -                if {![cc-check-functions Tcl_CreateInterp]} {
   168         -                    user-error "Cannot find a usable Tcl library $msg"
          375  +                if {[cc-check-functions Tcl_CreateInterp]} {
          376  +                    set foundtcl 1
          377  +                }
          378  +            }
          379  +        }
          380  +        if {!$foundtcl && [string match *-lieee* $libs]} {
          381  +            # On some systems, using "-lieee" from TCL_LIB_SPEC appears
          382  +            # to cause issues.
          383  +            msg-result "Removing \"-lieee\" and retrying for Tcl..."
          384  +            set libs [string map [list -lieee ""] $libs]
          385  +            cc-with [list -cflags $cflags -libs $libs] {
          386  +                if {$tclstubs} {
          387  +                    if {[cc-check-functions Tcl_InitStubs]} {
          388  +                        set foundtcl 1
          389  +                    }
          390  +                } else {
          391  +                    if {[cc-check-functions Tcl_CreateInterp]} {
          392  +                        set foundtcl 1
          393  +                    }
          394  +                }
          395  +            }
          396  +        }
          397  +        if {!$foundtcl && ![string match *-lpthread* $libs]} {
          398  +            # On some systems, TCL_LIB_SPEC appears to be missing
          399  +            # "-lpthread".  Try adding it.
          400  +            msg-result "Adding \"-lpthread\" and retrying for Tcl..."
          401  +            set libs "$libs -lpthread"
          402  +            cc-with [list -cflags $cflags -libs $libs] {
          403  +                if {$tclstubs} {
          404  +                    if {[cc-check-functions Tcl_InitStubs]} {
          405  +                        set foundtcl 1
          406  +                    }
          407  +                } else {
          408  +                    if {[cc-check-functions Tcl_CreateInterp]} {
          409  +                        set foundtcl 1
          410  +                    }
   169    411                   }
   170    412               }
          413  +        }
          414  +        if {!$foundtcl} {
          415  +            if {$tclstubs} {
          416  +                user-error "Cannot find a usable Tcl stubs library $msg"
          417  +            } else {
          418  +                user-error "Cannot find a usable Tcl library $msg"
          419  +            }
   171    420           }
   172    421       }
   173    422       set version $tclconfig(TCL_VERSION)$tclconfig(TCL_PATCH_LEVEL)
   174    423       msg-result "Found Tcl $version at $tclconfig(TCL_PREFIX)"
   175    424       if {!$tclprivatestubs} {
   176    425           define-append LIBS $libs
   177    426       }
   178    427       define-append EXTRA_CFLAGS $cflags
          428  +    if {[info exists zlibpath] && $zlibpath eq "tree"} {
          429  +      #
          430  +      # NOTE: When using zlib in the source tree, prevent Tcl from
          431  +      #       pulling in the system one.
          432  +      #
          433  +      set tclconfig(TCL_LD_FLAGS) [string map [list -lz ""] \
          434  +          $tclconfig(TCL_LD_FLAGS)]
          435  +    }
          436  +    #
          437  +    # NOTE: Remove "-ldl" from the TCL_LD_FLAGS because it will be
          438  +    #       be checked for near the bottom of this file.
          439  +    #
          440  +    set tclconfig(TCL_LD_FLAGS) [string map [list -ldl ""] \
          441  +        $tclconfig(TCL_LD_FLAGS)]
   179    442       define-append EXTRA_LDFLAGS $tclconfig(TCL_LD_FLAGS)
   180    443       define FOSSIL_ENABLE_TCL
   181    444   }
   182    445   
   183         -# Helper for OpenSSL checking
   184         -proc check-for-openssl {msg {cflags {}}} {
   185         -    msg-checking "Checking for $msg..."
   186         -    set rc 0
   187         -    msg-quiet cc-with [list -cflags $cflags -libs {-lssl -lcrypto}] {
   188         -        if {[cc-check-includes openssl/ssl.h] && [cc-check-functions SSL_new]} {
   189         -            incr rc
   190         -        }
   191         -    }
   192         -    if {$rc} {
   193         -        msg-result "ok"
   194         -        return 1
   195         -    } else {
   196         -        msg-result "no"
   197         -        return 0
   198         -    }
   199         -}
   200         -
   201         -set ssldirs [opt-val with-openssl]
   202         -if {$ssldirs ne "none"} {
   203         -    set found 0
   204         -    if {$ssldirs in {auto ""}} {
   205         -        catch {
   206         -            set cflags [exec pkg-config openssl --cflags-only-I]
   207         -            set ldflags [exec pkg-config openssl --libs-only-L]
   208         -
   209         -            set found [check-for-openssl "ssl via pkg-config" "$cflags $ldflags"]
   210         -        } msg
   211         -        if {!$found} {
   212         -            set ssldirs "{} /usr/sfw /usr/local/ssl /usr/lib/ssl /usr/ssl /usr/pkg /usr/local /usr"
   213         -        }
   214         -    }
   215         -    if {!$found} {
   216         -        foreach dir $ssldirs {
   217         -            if {$dir eq ""} {
   218         -                set msg "system ssl"
   219         -                set cflags ""
   220         -                set ldflags ""
   221         -            } else {
   222         -                set msg "ssl in $dir"
   223         -                set cflags "-I$dir/include"
   224         -                set ldflags "-L$dir/lib"
   225         -            }
   226         -            if {[check-for-openssl $msg "$cflags $ldflags"]} {
   227         -                incr found
   228         -                break
   229         -            }
   230         -        }
   231         -    }
   232         -    if {$found} {
   233         -        define FOSSIL_ENABLE_SSL
   234         -        define-append EXTRA_CFLAGS $cflags
   235         -        define-append EXTRA_LDFLAGS $ldflags
   236         -        define-append LIBS -lssl -lcrypto
   237         -        msg-result "HTTPS support enabled"
   238         -
   239         -        # Silence OpenSSL deprecation warnings on Mac OS X 10.7.
   240         -        if {[string match *-darwin* [get-define host]]} {
   241         -            if {[cctest -cflags {-Wdeprecated-declarations}]} {
   242         -                define-append EXTRA_CFLAGS -Wdeprecated-declarations
   243         -            }
   244         -        }
   245         -    } else {
   246         -        user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
   247         -    }
   248         -}
   249         -
   250         -if {[opt-bool with-miniz]} {
   251         -  define FOSSIL_ENABLE_MINIZ 1
   252         -  msg-result "Using miniz for compression"
   253         -} else {
   254         -  # Check for zlib, using the given location if specified
   255         -  set zlibpath [opt-val with-zlib]
   256         -  if {$zlibpath ne ""} {
   257         -      cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
   258         -      define-append EXTRA_CFLAGS -I$zlibpath
   259         -      define-append EXTRA_LDFLAGS -L$zlibpath
   260         -      msg-result "Using zlib from $zlibpath"
   261         -  }
   262         -  if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
   263         -      user-error "zlib not found please install it or specify the location with --with-zlib"
   264         -  }
   265         -}
   266         -
   267    446   # Network functions require libraries on some systems
   268    447   cc-check-function-in-lib gethostbyname nsl
   269    448   if {![cc-check-function-in-lib socket {socket network}]} {
   270    449       # Last resort, may be Windows
   271         -    if {[string match *mingw* [get-define host]]} {
          450  +    if {[is_mingw]} {
   272    451           define-append LIBS -lwsock32
   273    452       }
   274    453   }
   275    454   cc-check-function-in-lib iconv iconv
   276    455   cc-check-functions utime
   277    456   cc-check-functions usleep
   278    457   cc-check-functions strchrnul

Changes to autosetup/autosetup.

   814    814   	return $pwd
   815    815   }
   816    816   
   817    817   # Follow symlinks until we get to something which is not a symlink
   818    818   proc realpath {path} {
   819    819   	while {1} {
   820    820   		if {[catch {
   821         -			set path [file link $path]
          821  +			set path [file readlink $path]
   822    822   		}]} {
   823    823   			# Not a link
   824    824   			break
   825    825   		}
   826    826   	}
   827    827   	return $path
   828    828   }
................................................................................
  1186   1186       exit 0
  1187   1187   }
  1188   1188   
  1189   1189   # If not already paged and stdout is a tty, pipe the output through the pager
  1190   1190   # This is done by reinvoking autosetup with --nopager added
  1191   1191   proc use_pager {} {
  1192   1192       if {![opt-bool nopager] && [getenv PAGER ""] ne "" && [isatty? stdin] && [isatty? stdout]} {
  1193         -        catch {
  1194         -            exec [info nameofexecutable] $::argv0 --nopager {*}$::argv |& [getenv PAGER] >@stdout <@stdin
         1193  +        if {[catch {
         1194  +            exec [info nameofexecutable] $::argv0 --nopager {*}$::argv |& {*}[getenv PAGER] >@stdout <@stdin 2>@stderr
         1195  +        } msg opts] == 1} {
         1196  +            if {[dict get $opts -errorcode] eq "NONE"} {
         1197  +                # an internal/exec error
         1198  +                puts stderr $msg
         1199  +                exit 1
         1200  +            }
  1195   1201           }
  1196   1202           exit 0
  1197   1203       }
  1198   1204   }
  1199   1205   
  1200   1206   # Outputs the autosetup references in one of several formats
  1201   1207   proc autosetup_reference {{type text}} {

Changes to autosetup/system.tcl.

   105    105   # Reads the input file <srcdir>/$template and writes the output file $outfile.
   106    106   # If $outfile is blank/omitted, $template should end with ".in" which
   107    107   # is removed to create the output file name.
   108    108   #
   109    109   # Each pattern of the form @define@ is replaced the the corresponding
   110    110   # define, if it exists, or left unchanged if not.
   111    111   # 
   112         -# The special value @srcdir@ is subsituted with the relative
          112  +# The special value @srcdir@ is substituted with the relative
   113    113   # path to the source directory from the directory where the output
   114         -# file is created. Use @top_srcdir@ for the absolute path.
          114  +# file is created, while the special value @top_srcdir@ is substituted
          115  +# with the relative path to the top level source directory.
   115    116   #
   116    117   # Conditional sections may be specified as follows:
   117    118   ## @if name == value
   118    119   ## lines
   119    120   ## @else
   120    121   ## lines
   121    122   ## @endif
................................................................................
   149    150   	}
   150    151   
   151    152   	set outdir [file dirname $out]
   152    153   
   153    154   	# Make sure the directory exists
   154    155   	file mkdir $outdir
   155    156   
   156         -	# Set up srcdir to be relative to the target dir
          157  +	# Set up srcdir and top_srcdir to be relative to the target dir
   157    158   	define srcdir [relative-path [file join $::autosetup(srcdir) $outdir] $outdir]
          159  +	define top_srcdir [relative-path $::autosetup(srcdir) $outdir]
   158    160   
   159    161   	set mapping {}
   160    162   	foreach {n v} [array get ::define] {
   161    163   		lappend mapping @$n@ $v
   162    164   	}
   163    165   	set result {}
   164    166   	foreach line [split [readfile $infile] \n] {

Changes to fossil.1.

    12     12   \fICOMMAND [OPTIONS]\fR
    13     13   .SH DESCRIPTION
    14     14   Fossil is a distributed version control system (DVCS) with built-in
    15     15   wiki, ticket tracker, CGI/http interface, and http server.
    16     16   
    17     17   .SH Common COMMANDs:
    18     18   
    19         -add            clean          import         pull           stash 
           19  +add            clean          import         pull           stash
    20     20   .br
    21     21   addremove      clone          info           purge          status
    22     22   .br
    23     23   all            commit         init           push           sync
    24     24   .br
    25     25   annotate       diff           json           rebuild        tag
    26     26   .br

Changes to setup/fossil.nsi.

    10     10   Name "Fossil"
    11     11   
    12     12   ; The file to write
    13     13   OutFile "fossil-setup.exe"
    14     14   
    15     15   ; The default installation directory
    16     16   InstallDir $PROGRAMFILES\Fossil
    17         -; Registry key to check for directory (so if you install again, it will 
           17  +; Registry key to check for directory (so if you install again, it will
    18     18   ; overwrite the old one automatically)
    19     19   InstallDirRegKey HKLM SOFTWARE\Fossil "Install_Dir"
    20     20   
    21     21   ; The text to prompt the user to enter a directory
    22     22   ComponentText "This will install fossil on your computer."
    23     23   ; The text to prompt the user to enter a directory
    24     24   DirText "Choose a directory to install in to:"

Changes to skins/README.md.

    27     27   
    28     28      5.   Type "make" to rebuild.
    29     29   
    30     30   Development Hints
    31     31   -----------------
    32     32   
    33     33   One way to develop a new skin is to copy the baseline files (css.txt,
    34         -details.txt, footer.txt, and header.txt) into a working directory $WORKDIR 
           34  +details.txt, footer.txt, and header.txt) into a working directory $WORKDIR
    35     35   then launch Fossil with a command-line option "--skin $WORKDIR".  Example:
    36     36   
    37     37           cp -r skins/default newskin
    38     38           fossil ui --skin ./newskin
    39     39   
    40     40   When the argument to --skin contains one or more '/' characters, the
    41     41   appropriate skin files are read from disk from the directory specified.
    42     42   So after launching fossil as shown above, you can edit the newskin/css.txt,
    43     43   newskin/details.txt, newskin/footer.txt, and newskin/header.txt files using
    44     44   your favorite text editor, then press Reload on your browser to see
    45     45   immediate results.

Changes to skins/black_and_white/footer.txt.

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

Changes to skins/blitz/css.txt.

  1060   1060     border-right: 1px solid #ddd;
  1061   1061   }
  1062   1062   
  1063   1063   tr.timelineSelected {
  1064   1064     border-left: 2px solid orange;
  1065   1065     background-color: #ffffe8;
  1066   1066     border-bottom: 1px solid #ddd;
  1067         -  border-right: 1px solid #ddd;  
         1067  +  border-right: 1px solid #ddd;
  1068   1068   }
  1069   1069   
  1070   1070   tr.timelineCurrent td.timelineTableCell {
  1071   1071   }
  1072   1072   
  1073   1073   tr.timelineSpacer {
  1074   1074   }

Changes to skins/blitz/footer.txt.

     1      1         </div> <!-- end div container -->
     2      2       </div> <!-- end div middle max-full-width -->
     3      3       <div class="footer">
     4      4         <div class="container">
     5      5           <div class="pull-right">
     6         -          <a href="http://fossil-scm.org">Fossil version $manifest_version $manifest_date</a>
            6  +          <a href="http://fossil-scm.org">Fossil $release_version $manifest_version $manifest_date</a>
     7      7           </div>
     8      8           This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
     9      9         </div>
    10     10       </div>
    11     11     </body>
    12     12   </html>

Changes to skins/blitz/ticket.txt.

    83     83                 username AS xusername
    84     84            FROM ticketchng
    85     85           WHERE tkt_id=$tkt_id AND length(icomment)>0} {
    86     86             if {$seenRow eq "0"} {
    87     87               html "<h5>User Comments</h5>\n"
    88     88               set seenRow 1
    89     89             }
    90         -  html "<div class='tktComment'>\n"          
           90  +  html "<div class='tktComment'>\n"
    91     91     html "<div class='tktCommentHeader'>\n"
    92     92     html "<div class='pull-right'>$xdate</div>\n"
    93     93     html "<span class='tktCommentLogin'>[htmlize $xlogin]</span>"
    94     94     if {$xlogin ne $xusername && [string length $xusername]>0} {
    95     95       html " (claiming to be <span class='tktCommentLogin'>[htmlize $xusername]</span>)"
    96     96     }
    97     97     html " commented</div>\n"

Changes to skins/blitz_no_logo/footer.txt.

     1      1         </div> <!-- end div container -->
     2      2       </div> <!-- end div middle max-full-width -->
     3      3       <div class="footer">
     4      4         <div class="container">
     5      5           <div class="pull-right">
     6         -          <a href="http://fossil-scm.org">Fossil version $manifest_version $manifest_date</a>
            6  +          <a href="http://fossil-scm.org">Fossil $release_version $manifest_version $manifest_date</a>
     7      7           </div>
     8      8           This page was generated in about <th1>puts [expr {([utime]+[stime]+1000)/1000*0.001}]</th1>s
     9      9         </div>
    10     10       </div>
    11     11     </body>
    12     12   </html>

Changes to skins/blitz_no_logo/ticket.txt.

    83     83                 username AS xusername
    84     84            FROM ticketchng
    85     85           WHERE tkt_id=$tkt_id AND length(icomment)>0} {
    86     86             if {$seenRow eq "0"} {
    87     87               html "<h5>User Comments</h5>\n"
    88     88               set seenRow 1
    89     89             }
    90         -  html "<div class='tktComment'>\n"          
           90  +  html "<div class='tktComment'>\n"
    91     91     html "<div class='tktCommentHeader'>\n"
    92     92     html "<div class='pull-right'>$xdate</div>\n"
    93     93     html "<span class='tktCommentLogin'>[htmlize $xlogin]</span>"
    94     94     if {$xlogin ne $xusername && [string length $xusername]>0} {
    95     95       html " (claiming to be <span class='tktCommentLogin'>[htmlize $xusername]</span>)"
    96     96     }
    97     97     html " commented</div>\n"

Changes to skins/default/footer.txt.

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

Changes to skins/khaki/footer.txt.

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

Changes to skins/original/footer.txt.

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

Changes to skins/plain_gray/footer.txt.

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

Changes to skins/rounded1/footer.txt.

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

Changes to skins/xekri/css.txt.

     1      1   /******************************************************************************
     2      2    * Xekri
     3      3    *
     4         - * To adjust the width of the contents for this skin, look for the "max-width" 
     5         - * property and change its value.  (It's in the "Main Area" section)  The value 
     6         - * determines how much of the browser window to use.  Some like 100%, so that 
     7         - * the entire window is used.  Others prefer 80%, which makes the contents 
            4  + * To adjust the width of the contents for this skin, look for the "max-width"
            5  + * property and change its value.  (It's in the "Main Area" section)  The value
            6  + * determines how much of the browser window to use.  Some like 100%, so that
            7  + * the entire window is used.  Others prefer 80%, which makes the contents
     8      8    * easier to read for them.
     9      9    */
    10     10   
    11     11   
    12     12   /**************************************
    13     13    * General HTML
    14     14    */
................................................................................
   929    929   span.wikiruleHead {
   930    930     font-weight: bold;
   931    931   }
   932    932   
   933    933   
   934    934   /* format for user color input on checkin edit page */
   935    935   input.checkinUserColor {
   936         -  /* no special definitions, class defined, to enable color pickers, 
          936  +  /* no special definitions, class defined, to enable color pickers,
   937    937     * f.e.:
   938         -  * ** add the color picker found at http:jscolor.com as java script 
          938  +  * ** add the color picker found at http:jscolor.com as java script
   939    939     * include
   940    940     * ** to the header and configure the java script file with
   941    941     * ** 1. use as bindClass :checkinUserColor
   942    942     * ** 2. change the default hash adding behaviour to ON
   943         -  * ** or change the class defition of element identified by 
          943  +  * ** or change the class defition of element identified by
   944    944     * id="clrcust"
   945         -  * ** to a standard jscolor definition with java script in the footer. 
          945  +  * ** to a standard jscolor definition with java script in the footer.
   946    946     * */
   947    947   }
   948    948   
   949    949   /* format for end of content area, to be used to clear page flow. */
   950    950   div.endContent {
   951    951     clear: both;
   952    952   }

Changes to src/add.c.

   166    166     if( !file_is_simple_pathname(zPath, 1) ){
   167    167       fossil_warning("filename contains illegal characters: %s", zPath);
   168    168       return 0;
   169    169     }
   170    170     if( db_exists("SELECT 1 FROM vfile"
   171    171                   " WHERE pathname=%Q %s", zPath, filename_collation()) ){
   172    172       db_multi_exec("UPDATE vfile SET deleted=0"
   173         -                  " WHERE pathname=%Q %s", zPath, filename_collation());
          173  +                  " WHERE pathname=%Q %s AND deleted",
          174  +                  zPath, filename_collation());
   174    175     }else{
   175    176       char *zFullname = mprintf("%s%s", g.zLocalRoot, zPath);
   176    177       int isExe = file_wd_isexe(zFullname);
   177    178       db_multi_exec(
   178    179         "INSERT INTO vfile(vid,deleted,rid,mrid,pathname,isexe,islink)"
   179    180         "VALUES(%d,0,0,0,%Q,%d,%d)",
   180    181         vid, zPath, isExe, file_wd_islink(0));
................................................................................
   711    712     const char *zNew,
   712    713     int dryRunFlag
   713    714   ){
   714    715     int x = db_int(-1, "SELECT deleted FROM vfile WHERE pathname=%Q %s",
   715    716                            zNew, filename_collation());
   716    717     if( x>=0 ){
   717    718       if( x==0 ){
   718         -      fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'"
   719         -                   " is currently under management", zOrig, zNew, zNew);
          719  +      if( !filenames_are_case_sensitive() && fossil_stricmp(zOrig,zNew)==0 ){
          720  +        /* Case change only */
          721  +      }else{
          722  +        fossil_fatal("cannot rename '%s' to '%s' since another file named '%s'"
          723  +                     " is currently under management", zOrig, zNew, zNew);
          724  +      }
   720    725       }else{
   721    726         fossil_fatal("cannot rename '%s' to '%s' since the delete of '%s' has "
   722    727                      "not yet been committed", zOrig, zNew, zNew);
   723    728       }
   724    729     }
   725    730     fossil_print("RENAME %s %s\n", zOrig, zNew);
   726    731     if( !dryRunFlag ){
................................................................................
   741    746   static void add_file_to_move(
   742    747     const char *zOldName, /* The old name of the file on disk. */
   743    748     const char *zNewName  /* The new name of the file on disk. */
   744    749   ){
   745    750     static int tableCreated = 0;
   746    751     Blob fullOldName;
   747    752     Blob fullNewName;
          753  +  char *zOld, *zNew;
   748    754     if( !tableCreated ){
   749    755       db_multi_exec("CREATE TEMP TABLE fmove(x TEXT PRIMARY KEY %s, y TEXT %s)",
   750    756                     filename_collation(), filename_collation());
   751    757       tableCreated = 1;
   752    758     }
   753    759     file_tree_name(zOldName, &fullOldName, 1, 1);
          760  +  zOld = blob_str(&fullOldName);
   754    761     file_tree_name(zNewName, &fullNewName, 1, 1);
   755         -  db_multi_exec("INSERT INTO fmove VALUES('%q','%q');",
   756         -                blob_str(&fullOldName), blob_str(&fullNewName));
          762  +  zNew = blob_str(&fullNewName);
          763  +  if( filenames_are_case_sensitive() || fossil_stricmp(zOld,zNew)!=0 ){
          764  +    db_multi_exec("INSERT INTO fmove VALUES('%q','%q');", zOld, zNew);
          765  +  }
   757    766     blob_reset(&fullNewName);
   758    767     blob_reset(&fullOldName);
   759    768   }
   760    769   
   761    770   /*
   762    771   ** This function moves files within the checkout, using the file names
   763    772   ** contained in the temporary table "fmove".  The temporary table is

Changes to src/allrepo.c.

   100    100   **                caution should be exercised with this command because its
   101    101   **                effects cannot be undone.  Use of the --dry-run option to
   102    102   **                carefully review the local checkouts to be operated upon
   103    103   **                and the --whatif option to carefully review the files to
   104    104   **                be deleted beforehand is highly recommended.  The command
   105    105   **                line options supported by the clean command itself, if any
   106    106   **                are present, are passed along verbatim.
          107  +**
          108  +**    config      Only the "config pull AREA" command works.
   107    109   **
   108    110   **    dbstat      Run the "dbstat" command on all repositories.
   109    111   **
   110    112   **    extras      Shows "extra" files from all local checkouts.  The command
   111    113   **                line options supported by the extra command itself, if any
   112    114   **                are present, are passed along verbatim.
   113    115   **
................................................................................
   181    183       dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
   182    184     }
   183    185   
   184    186     if( g.argc<3 ){
   185    187       usage("SUBCOMMAND ...");
   186    188     }
   187    189     n = strlen(g.argv[2]);
   188         -  db_open_config(1);
          190  +  db_open_config(1, 0);
   189    191     blob_zero(&extra);
   190    192     zCmd = g.argv[2];
   191    193     if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin);
   192    194     if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
   193    195       zCmd = "list";
   194    196       useCheckouts = find_option("ckout","c",0)!=0;
   195    197     }else if( strncmp(zCmd, "clean", n)==0 ){
................................................................................
   205    207       collect_argument_value(&extra, "ignore");
   206    208       collect_argument_value(&extra, "keep");
   207    209       collect_argument(&extra, "no-prompt",0);
   208    210       collect_argument(&extra, "temp",0);
   209    211       collect_argument(&extra, "verbose","v");
   210    212       collect_argument(&extra, "whatif",0);
   211    213       useCheckouts = 1;
          214  +  }else if( strncmp(zCmd, "config", n)==0 ){
          215  +    zCmd = "config -R";
          216  +    collect_argv(&extra, 3);
          217  +    (void)find_option("legacy",0,0);
          218  +    (void)find_option("overwrite",0,0);
          219  +    verify_all_options();
          220  +    if( g.argc!=5 || fossil_strcmp(g.argv[3],"pull")!=0 ){
          221  +      usage("configure pull AREA ?OPTIONS?");
          222  +    }
   212    223     }else if( strncmp(zCmd, "dbstat", n)==0 ){
   213    224       zCmd = "dbstat --omit-version-info -R";
   214    225       showLabel = 1;
   215    226       quiet = 1;
   216    227       collect_argument(&extra, "brief", "b");
   217    228       collect_argument(&extra, "db-check", 0);
   218    229     }else if( strncmp(zCmd, "extras", n)==0 ){
................................................................................
   279    290       int j;
   280    291       Blob fn = BLOB_INITIALIZER;
   281    292       Blob sql = BLOB_INITIALIZER;
   282    293       useCheckouts = find_option("ckout","c",0)!=0;
   283    294       verify_all_options();
   284    295       db_begin_transaction();
   285    296       for(j=3; j<g.argc; j++, blob_reset(&sql), blob_reset(&fn)){
   286         -      file_canonical_name(g.argv[j], &fn, 0);
          297  +      file_canonical_name(g.argv[j], &fn, useCheckouts?1:0);
   287    298         blob_append_sql(&sql,
   288    299            "DELETE FROM global_config WHERE name GLOB '%s:%q'",
   289    300            useCheckouts?"ckout":"repo", blob_str(&fn)
   290    301         );
   291    302         if( dryRunFlag ){
   292    303           fossil_print("%s\n", blob_sql_text(&sql));
   293    304         }else{

Changes to src/attach.c.

    24     24   /*
    25     25   ** WEBPAGE: attachlist
    26     26   ** List attachments.
    27     27   **
    28     28   **    tkt=TICKETUUID
    29     29   **    page=WIKIPAGE
    30     30   **
    31         -** Either one of tkt= or page= are supplied or neither but not both.
    32         -** If neither are given, all attachments are listed.  If one is given,
    33         -** only attachments for the designated ticket or wiki page are shown.
    34         -** TICKETUUID must be complete
           31  +** At most one of technote=, tkt= or page= are supplied. 
           32  +** If none is given, all attachments are listed.  If one is given,
           33  +** only attachments for the designated technote, ticket or wiki page
           34  +** are shown. TECHNOTEUUID and TICKETUUID may be just a prefix of the
           35  +** relevant tech note or ticket, in which case all attachments of all
           36  +** tech notes or tickets with the prefix will be listed.
    35     37   */
    36     38   void attachlist_page(void){
    37     39     const char *zPage = P("page");
    38     40     const char *zTkt = P("tkt");
           41  +  const char *zTechNote = P("technote");
    39     42     Blob sql;
    40     43     Stmt q;
    41     44   
    42     45     if( zPage && zTkt ) zTkt = 0;
    43     46     login_check_credentials();
    44     47     blob_zero(&sql);
    45     48     blob_append_sql(&sql,
    46         -     "SELECT datetime(mtime%s), src, target, filename,"
           49  +     "SELECT datetime(mtime,toLocal()), src, target, filename,"
    47     50        "       comment, user,"
    48         -     "       (SELECT uuid FROM blob WHERE rid=attachid), attachid"
    49         -     "  FROM attachment",
    50         -     timeline_utc()
           51  +     "       (SELECT uuid FROM blob WHERE rid=attachid), attachid,"
           52  +     "       (CASE WHEN 'tkt-'||target IN (SELECT tagname FROM tag)"
           53  +     "                  THEN 1"
           54  +     "             WHEN 'event-'||target IN (SELECT tagname FROM tag)"
           55  +     "                  THEN 2"
           56  +     "             ELSE 0 END)"
           57  +     "  FROM attachment"
    51     58     );
    52     59     if( zPage ){
    53     60       if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
    54     61       style_header("Attachments To %h", zPage);
    55     62       blob_append_sql(&sql, " WHERE target=%Q", zPage);
    56     63     }else if( zTkt ){
    57     64       if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
    58     65       style_header("Attachments To Ticket %S", zTkt);
    59     66       blob_append_sql(&sql, " WHERE target GLOB '%q*'", zTkt);
           67  +  }else if( zTechNote ){
           68  +    if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
           69  +    style_header("Attachments to Tech Note %S", zTechNote);
           70  +    blob_append_sql(&sql, " WHERE target GLOB '%q*'",
           71  +                    zTechNote);
    60     72     }else{
    61     73       if( g.perm.RdTkt==0 && g.perm.RdWiki==0 ){
    62     74         login_needed(g.anon.RdTkt || g.anon.RdWiki);
    63     75         return;
    64     76       }
    65     77       style_header("All Attachments");
    66     78     }
................................................................................
    72     84       const char *zSrc = db_column_text(&q, 1);
    73     85       const char *zTarget = db_column_text(&q, 2);
    74     86       const char *zFilename = db_column_text(&q, 3);
    75     87       const char *zComment = db_column_text(&q, 4);
    76     88       const char *zUser = db_column_text(&q, 5);
    77     89       const char *zUuid = db_column_text(&q, 6);
    78     90       int attachid = db_column_int(&q, 7);
           91  +    // type 0 is a wiki page, 1 is a ticket, 2 is a tech note
           92  +    int type = db_column_int(&q, 8);
    79     93       const char *zDispUser = zUser && zUser[0] ? zUser : "anonymous";
    80     94       int i;
    81     95       char *zUrlTail;
    82     96       for(i=0; zFilename[i]; i++){
    83     97         if( zFilename[i]=='/' && zFilename[i+1]!=0 ){
    84     98           zFilename = &zFilename[i+1];
    85     99           i = -1;
    86    100         }
    87    101       }
    88         -    if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
          102  +    if( type==1 ){
    89    103         zUrlTail = mprintf("tkt=%s&file=%t", zTarget, zFilename);
          104  +    }else if( type==2 ){
          105  +      zUrlTail = mprintf("technote=%s&file=%t", zTarget, zFilename);
    90    106       }else{
    91    107         zUrlTail = mprintf("page=%t&file=%t", zTarget, zFilename);
    92    108       }
    93    109       @ <li><p>
    94    110       @ Attachment %z(href("%R/ainfo/%!S",zUuid))%S(zUuid)</a>
    95    111       if( moderation_pending(attachid) ){
    96    112         @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
................................................................................
    97    113       }
    98    114       @ <br><a href="%R/attachview?%s(zUrlTail)">%h(zFilename)</a>
    99    115       @ [<a href="%R/attachdownload/%t(zFilename)?%s(zUrlTail)">download</a>]<br />
   100    116       if( zComment ) while( fossil_isspace(zComment[0]) ) zComment++;
   101    117       if( zComment && zComment[0] ){
   102    118         @ %!W(zComment)<br />
   103    119       }
   104         -    if( zPage==0 && zTkt==0 ){
          120  +    if( zPage==0 && zTkt==0 && zTechNote==0 ){
   105    121         if( zSrc==0 || zSrc[0]==0 ){
   106    122           zSrc = "Deleted from";
   107    123         }else {
   108    124           zSrc = "Added to";
   109    125         }
   110         -      if( strlen(zTarget)==UUID_SIZE && validate16(zTarget, UUID_SIZE) ){
          126  +      if( type==1 ){
   111    127           @ %s(zSrc) ticket <a href="%R/tktview?name=%s(zTarget)">
   112    128           @ %S(zTarget)</a>
          129  +      }else if( type==2 ){
          130  +        @ %s(zSrc) tech note <a href="%R/technote/%s(zTarget)">
          131  +        @ %S(zTarget)</a>
   113    132         }else{
   114    133           @ %s(zSrc) wiki page <a href="%R/wiki?name=%t(zTarget)">
   115    134           @ %h(zTarget)</a>
   116    135         }
   117    136       }else{
   118    137         if( zSrc==0 || zSrc[0]==0 ){
   119    138           @ Deleted
................................................................................
   137    156   ** WEBPAGE: attachview
   138    157   **
   139    158   ** Download or display an attachment.
   140    159   ** Query parameters:
   141    160   **
   142    161   **    tkt=TICKETUUID
   143    162   **    page=WIKIPAGE
          163  +**    technote=TECHNOTEUUID
   144    164   **    file=FILENAME
   145    165   **    attachid=ID
   146    166   **
   147    167   */
   148    168   void attachview_page(void){
   149    169     const char *zPage = P("page");
   150    170     const char *zTkt = P("tkt");
          171  +  const char *zTechNote = P("technote");
   151    172     const char *zFile = P("file");
   152    173     const char *zTarget = 0;
   153    174     int attachid = atoi(PD("attachid","0"));
   154    175     char *zUUID;
   155    176   
   156         -  if( zPage && zTkt ) zTkt = 0;
   157    177     if( zFile==0 ) fossil_redirect_home();
   158    178     login_check_credentials();
   159    179     if( zPage ){
   160    180       if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
   161    181       zTarget = zPage;
   162    182     }else if( zTkt ){
   163    183       if( g.perm.RdTkt==0 ){ login_needed(g.anon.RdTkt); return; }
   164    184       zTarget = zTkt;
          185  +  }else if( zTechNote ){
          186  +    if( g.perm.RdWiki==0 ){ login_needed(g.anon.RdWiki); return; }
          187  +    zTarget = zTechNote;
   165    188     }else{
   166    189       fossil_redirect_home();
   167    190     }
   168    191     if( attachid>0 ){
   169    192       zUUID = db_text(0,
   170    193          "SELECT coalesce(src,'x') FROM attachment"
   171    194          " WHERE target=%Q AND attachid=%d",
................................................................................
   185    208       style_footer();
   186    209       return;
   187    210     }else if( zUUID[0]=='x' ){
   188    211       style_header("Missing");
   189    212       @ Attachment has been deleted
   190    213       style_footer();
   191    214       return;
   192         -  }
   193         -  g.perm.Read = 1;
   194         -  cgi_replace_parameter("name",zUUID);
   195         -  if( fossil_strcmp(g.zPath,"attachview")==0 ){
   196         -    artifact_page();
   197    215     }else{
   198         -    cgi_replace_parameter("m", mimetype_from_name(zFile));
   199         -    rawartifact_page();
          216  +    g.perm.Read = 1;
          217  +    cgi_replace_parameter("name",zUUID);
          218  +    if( fossil_strcmp(g.zPath,"attachview")==0 ){
          219  +      artifact_page();
          220  +    }else{
          221  +      cgi_replace_parameter("m", mimetype_from_name(zFile));
          222  +      rawartifact_page();
          223  +    }
   200    224     }
   201    225   }
   202    226   
   203    227   /*
   204    228   ** Save an attachment control artifact into the repository
   205    229   */
   206    230   static void attach_put(
................................................................................
   227    251   
   228    252   /*
   229    253   ** WEBPAGE: attachadd
   230    254   ** Add a new attachment.
   231    255   **
   232    256   **    tkt=TICKETUUID
   233    257   **    page=WIKIPAGE
          258  +**    technote=TECHNOTEUUID
   234    259   **    from=URL
   235    260   **
   236    261   */
   237    262   void attachadd_page(void){
   238    263     const char *zPage = P("page");
   239    264     const char *zTkt = P("tkt");
          265  +  const char *zTechNote = P("technote");
   240    266     const char *zFrom = P("from");
   241    267     const char *aContent = P("f");
   242    268     const char *zName = PD("f:filename","unknown");
   243    269     const char *zTarget;
   244         -  const char *zTargetType;
          270  +  char *zTargetType;
   245    271     int szContent = atoi(PD("f:bytes","0"));
   246    272     int goodCaptcha = 1;
   247    273   
   248    274     if( P("cancel") ) cgi_redirect(zFrom);
   249         -  if( zPage && zTkt ) fossil_redirect_home();
   250         -  if( zPage==0 && zTkt==0 ) fossil_redirect_home();
          275  +  if( (zPage && zTkt)
          276  +   || (zPage && zTechNote)
          277  +   || (zTkt && zTechNote)
          278  +  ){
          279  +   fossil_redirect_home();
          280  +  }
          281  +  if( zPage==0 && zTkt==0 && zTechNote==0) fossil_redirect_home();
   251    282     login_check_credentials();
   252    283     if( zPage ){
   253    284       if( g.perm.ApndWiki==0 || g.perm.Attach==0 ){
   254    285         login_needed(g.anon.ApndWiki && g.anon.Attach);
   255    286         return;
   256    287       }
   257    288       if( !db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'", zPage) ){
   258    289         fossil_redirect_home();
   259    290       }
   260    291       zTarget = zPage;
   261    292       zTargetType = mprintf("Wiki Page <a href=\"%R/wiki?name=%h\">%h</a>",
   262    293                              zPage, zPage);
          294  +  }else if ( zTechNote ){
          295  +    if( g.perm.Write==0 || g.perm.ApndWiki==0 || g.perm.Attach==0 ){
          296  +      login_needed(g.anon.Write && g.anon.ApndWiki && g.anon.Attach);
          297  +      return;
          298  +    }
          299  +    if( !db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'", zTechNote) ){
          300  +      zTechNote = db_text(0, "SELECT substr(tagname,7) FROM tag"
          301  +                             " WHERE tagname GLOB 'event-%q*'", zTechNote);
          302  +      if( zTechNote==0) fossil_redirect_home();
          303  +    }
          304  +    zTarget = zTechNote;
          305  +    zTargetType = mprintf("Tech Note <a href=\"%R/technote/%h\">%h</a>",
          306  +                           zTechNote, zTechNote);
          307  +  
   263    308     }else{
   264    309       if( g.perm.ApndTkt==0 || g.perm.Attach==0 ){
   265    310         login_needed(g.anon.ApndTkt && g.anon.Attach);
   266    311         return;
   267    312       }
   268    313       if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
   269    314         zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag"
................................................................................
   339    384     @ <div>
   340    385     @ File to Attach:
   341    386     @ <input type="file" name="f" size="60" /><br />
   342    387     @ Description:<br />
   343    388     @ <textarea name="comment" cols="80" rows="5" wrap="virtual"></textarea><br />
   344    389     if( zTkt ){
   345    390       @ <input type="hidden" name="tkt" value="%h(zTkt)" />
          391  +  }else if( zTechNote ){
          392  +    @ <input type="hidden" name="technote" value="%h(zTechNote)" />
   346    393     }else{
   347    394       @ <input type="hidden" name="page" value="%h(zPage)" />
   348    395     }
   349    396     @ <input type="hidden" name="from" value="%h(zFrom)" />
   350    397     @ <input type="submit" name="ok" value="Add Attachment" />
   351    398     @ <input type="submit" name="cancel" value="Cancel" />
   352    399     @ </div>
   353    400     captcha_generate(0);
   354    401     @ </form>
   355    402     style_footer();
          403  +  fossil_free(zTargetType);
   356    404   }
   357    405   
   358    406   /*
   359    407   ** WEBPAGE: ainfo
   360    408   ** URL: /ainfo?name=ARTIFACTID
   361    409   **
   362    410   ** Show the details of an attachment artifact.
................................................................................
   363    411   */
   364    412   void ainfo_page(void){
   365    413     int rid;                       /* RID for the control artifact */
   366    414     int ridSrc;                    /* RID for the attached file */
   367    415     char *zDate;                   /* Date attached */
   368    416     const char *zUuid;             /* UUID of the control artifact */
   369    417     Manifest *pAttach;             /* Parse of the control artifact */
   370         -  const char *zTarget;           /* Wiki or ticket attached to */
          418  +  const char *zTarget;           /* Wiki, ticket or tech note attached to */
   371    419     const char *zSrc;              /* UUID of the attached file */
   372    420     const char *zName;             /* Name of the attached file */
   373    421     const char *zDesc;             /* Description of the attached file */
   374    422     const char *zWikiName = 0;     /* Wiki page name when attached to Wiki */
          423  +  const char *zTNUuid = 0;       /* Tech Note ID when attached to tech note */
   375    424     const char *zTktUuid = 0;      /* Ticket ID when attached to a ticket */
   376    425     int modPending;                /* True if awaiting moderation */
   377    426     const char *zModAction;        /* Moderation action or NULL */
   378    427     int isModerator;               /* TRUE if user is the moderator */
   379    428     const char *zMime;             /* MIME Type */
   380    429     Blob attach;                   /* Content of the attachment */
   381    430     int fShowContent = 0;
................................................................................
   421    470       }
   422    471     }else if( db_exists("SELECT 1 FROM tag WHERE tagname='wiki-%q'",zTarget) ){
   423    472       zWikiName = zTarget;
   424    473       if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
   425    474       if( g.perm.WrWiki ){
   426    475         style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
   427    476       }
          477  +  }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",zTarget) ){
          478  +    zTNUuid = zTarget;
          479  +    if( !g.perm.RdWiki ){ login_needed(g.anon.RdWiki); return; }
          480  +    if( g.perm.Write && g.perm.WrWiki ){
          481  +      style_submenu_element("Delete","Delete","%R/ainfo/%s?del", zUuid);
          482  +    }
   428    483     }
   429    484     zDate = db_text(0, "SELECT datetime(%.12f)", pAttach->rDate);
   430    485   
   431    486     if( P("confirm")
   432         -   && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
          487  +   && ((zTktUuid && g.perm.WrTkt) || 
          488  +       (zWikiName && g.perm.WrWiki) ||
          489  +       (zTNUuid && g.perm.Write && g.perm.WrWiki))
   433    490     ){
   434    491       int i, n, rid;
   435    492       char *zDate;
   436    493       Blob manifest;
   437    494       Blob cksum;
   438    495       const char *zFile = zName;
   439    496   
................................................................................
   453    510       rid = content_put(&manifest);
   454    511       manifest_crosslink(rid, &manifest, MC_NONE);
   455    512       db_end_transaction(0);
   456    513       @ <p>The attachment below has been deleted.</p>
   457    514     }
   458    515   
   459    516     if( P("del")
   460         -   && ((zTktUuid && g.perm.WrTkt) || (zWikiName && g.perm.WrWiki))
          517  +   && ((zTktUuid && g.perm.WrTkt) ||
          518  +       (zWikiName && g.perm.WrWiki) ||
          519  +       (zTNUuid && g.perm.Write && g.perm.WrWiki))
   461    520     ){
   462    521       form_begin(0, "%R/ainfo/%!S", zUuid);
   463    522       @ <p>Confirm you want to delete the attachment shown below.
   464    523       @ <input type="submit" name="confirm" value="Confirm">
   465    524       @ </form>
   466    525     }
   467    526   
................................................................................
   501    560     if( modPending ){
   502    561       @ <span class="modpending">*** Awaiting Moderator Approval ***</span>
   503    562     }
   504    563     if( zTktUuid ){
   505    564       @ <tr><th>Ticket:</th>
   506    565       @ <td>%z(href("%R/tktview/%s",zTktUuid))%s(zTktUuid)</a></td></tr>
   507    566     }
          567  +  if( zTNUuid ){
          568  +    @ <tr><th>Tech Note:</th>
          569  +    @ <td>%z(href("%R/technote/%s",zTNUuid))%s(zTNUuid)</a></td></tr>
          570  +  }
   508    571     if( zWikiName ){
   509    572       @ <tr><th>Wiki&nbsp;Page:</th>
   510    573       @ <td>%z(href("%R/wiki?name=%t",zWikiName))%h(zWikiName)</a></td></tr>
   511    574     }
   512    575     @ <tr><th>Date:</th><td>
   513    576     hyperlink_to_date(zDate, "</td></tr>");
   514    577     @ <tr><th>User:</th><td>
................................................................................
   550    613         output_text_with_line_numbers(z, zLn);
   551    614       }else{
   552    615         @ <pre>
   553    616         @ %h(z)
   554    617         @ </pre>
   555    618       }
   556    619     }else if( strncmp(zMime, "image/", 6)==0 ){
          620  +    int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
          621  +    @ <i>(file is %d(sz) bytes of image data)</i><br>
   557    622       @ <img src="%R/raw/%s(zSrc)?m=%s(zMime)"></img>
   558    623       style_submenu_element("Image", "Image", "%R/raw/%s?m=%s", zSrc, zMime);
   559    624     }else{
   560    625       int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
   561    626       @ <i>(file is %d(sz) bytes of binary data)</i>
   562    627     }
   563    628     @ </blockquote>
................................................................................
   572    637   void attachment_list(
   573    638     const char *zTarget,   /* Object that things are attached to */
   574    639     const char *zHeader    /* Header to display with attachments */
   575    640   ){
   576    641     int cnt = 0;
   577    642     Stmt q;
   578    643     db_prepare(&q,
   579         -     "SELECT datetime(mtime%s), filename, user,"
          644  +     "SELECT datetime(mtime,toLocal()), filename, user,"
   580    645        "       (SELECT uuid FROM blob WHERE rid=attachid), src"
   581    646        "  FROM attachment"
   582    647        " WHERE isLatest AND src!='' AND target=%Q"
   583    648        " ORDER BY mtime DESC",
   584         -     timeline_utc(), zTarget
          649  +     zTarget
   585    650     );
   586    651     while( db_step(&q)==SQLITE_ROW ){
   587    652       const char *zDate = db_column_text(&q, 0);
   588    653       const char *zFile = db_column_text(&q, 1);
   589    654       const char *zUser = db_column_text(&q, 2);
   590    655       const char *zUuid = db_column_text(&q, 3);
   591    656       const char *zSrc = db_column_text(&q, 4);

Changes to src/bisect.c.

   167    167     db_multi_exec(
   168    168        "REPLACE INTO vvar(name,value) VALUES('bisect-log',"
   169    169          "COALESCE((SELECT value||' ' FROM vvar WHERE name='bisect-log'),'')"
   170    170          " || '%d')", rid);
   171    171   }
   172    172   
   173    173   /*
   174         -** Show a chart of bisect "good" and "bad" versions.  The chart can be
   175         -** sorted either chronologically by bisect time, or by check-in time.
          174  +** Create a TEMP table named "bilog" that contains the complete history
          175  +** of the current bisect.
   176    176   */
   177         -static void bisect_chart(int sortByCkinTime){
          177  +void bisect_create_bilog_table(int iCurrent){
   178    178     char *zLog = db_lget("bisect-log","");
   179    179     Blob log, id;
   180    180     Stmt q;
   181    181     int cnt = 0;
   182    182     blob_init(&log, zLog, -1);
   183    183     db_multi_exec(
   184    184        "CREATE TEMP TABLE bilog("
   185    185        "  seq INTEGER PRIMARY KEY,"  /* Sequence of events */
   186    186        "  stat TEXT,"                /* Type of occurrence */
   187         -     "  rid INTEGER"               /* Check-in number */
          187  +     "  rid INTEGER UNIQUE"        /* Check-in number */
   188    188        ");"
   189    189     );
   190    190     db_prepare(&q, "INSERT OR IGNORE INTO bilog(seq,stat,rid)"
   191    191                    " VALUES(:seq,:stat,:rid)");
   192    192     while( blob_token(&log, &id) ){
   193    193       int rid = atoi(blob_str(&id));
   194    194       db_bind_int(&q, ":seq", ++cnt);
   195    195       db_bind_text(&q, ":stat", rid>0 ? "GOOD" : "BAD");
   196    196       db_bind_int(&q, ":rid", rid>=0 ? rid : -rid);
   197    197       db_step(&q);
   198    198       db_reset(&q);
   199    199     }
   200         -  db_bind_int(&q, ":seq", ++cnt);
   201         -  db_bind_text(&q, ":stat", "CURRENT");
   202         -  db_bind_int(&q, ":rid", db_lget_int("checkout", 0));
   203         -  db_step(&q);
          200  +  if( iCurrent>0 ){
          201  +    db_bind_int(&q, ":seq", ++cnt);
          202  +    db_bind_text(&q, ":stat", "CURRENT");
          203  +    db_bind_int(&q, ":rid", iCurrent);
          204  +    db_step(&q);
          205  +  }
   204    206     db_finalize(&q);
          207  +}
          208  +
          209  +/*
          210  +** Show a chart of bisect "good" and "bad" versions.  The chart can be
          211  +** sorted either chronologically by bisect time, or by check-in time.
          212  +*/
          213  +static void bisect_chart(int sortByCkinTime){
          214  +  Stmt q;
          215  +  int iCurrent = db_lget_int("checkout",0);
          216  +  bisect_create_bilog_table(iCurrent);
   205    217     db_prepare(&q,
   206    218       "SELECT bilog.seq, bilog.stat,"
   207         -    "       substr(blob.uuid,1,16), datetime(event.mtime)"
          219  +    "       substr(blob.uuid,1,16), datetime(event.mtime),"
          220  +    "       blob.rid==%d"
   208    221       "  FROM bilog, blob, event"
   209    222       " WHERE blob.rid=bilog.rid AND event.objid=bilog.rid"
   210    223       "   AND event.type='ci'"
   211    224       " ORDER BY %s bilog.rowid ASC",
   212         -    (sortByCkinTime ? "event.mtime DESC, " : "")
          225  +    iCurrent, (sortByCkinTime ? "event.mtime DESC, " : "")
   213    226     );
   214    227     while( db_step(&q)==SQLITE_ROW ){
   215         -    fossil_print("%3d %-7s %s %s\n",
          228  +    const char *zGoodBad = db_column_text(&q, 1);
          229  +    fossil_print("%3d %-7s %s %s%s\n",
   216    230           db_column_int(&q, 0),
   217         -        db_column_text(&q, 1),
          231  +        zGoodBad,
   218    232           db_column_text(&q, 3),
   219         -        db_column_text(&q, 2));
          233  +        db_column_text(&q, 2),
          234  +        (db_column_int(&q, 4) && zGoodBad[0]!='C') ? " CURRENT" : "");
   220    235     }
   221    236     db_finalize(&q);
   222    237   }
   223    238   
   224    239   /*
   225    240   ** COMMAND: bisect
   226    241   **
................................................................................
   260    275   **     Reinitialize a bisect session.  This cancels prior bisect history
   261    276   **     and allows a bisect session to start over from the beginning.
   262    277   **
   263    278   **   fossil bisect vlist|ls|status ?-a|--all?
   264    279   **
   265    280   **     List the versions in between "bad" and "good".
   266    281   **
          282  +**   fossil bisect ui
          283  +**
          284  +**     Like "fossil ui" except start on a timeline that shows only the
          285  +**     check-ins that are part of the current bisect.
          286  +**
   267    287   **   fossil bisect undo
   268    288   **
   269    289   **     Undo the most recent "good" or "bad" command.
   270    290   **
   271    291   ** Summary:
   272    292   **
   273    293   **   fossil bisect bad ?VERSION?
................................................................................
   274    294   **   fossil bisect good ?VERSION?
   275    295   **   fossil bisect log
   276    296   **   fossil bisect chart
   277    297   **   fossil bisect next
   278    298   **   fossil bisect options
   279    299   **   fossil bisect reset
   280    300   **   fossil bisect status
          301  +**   fossil bisect ui
   281    302   **   fossil bisect undo
   282    303   */
   283    304   void bisect_cmd(void){
   284    305     int n;
   285    306     const char *zCmd;
   286    307     int foundCmd = 0;
   287    308     db_must_be_within_tree();
................................................................................
   415    436         usage("bisect option ?NAME? ?VALUE?");
   416    437       }
   417    438     }else if( strncmp(zCmd, "reset", n)==0 ){
   418    439       db_multi_exec(
   419    440         "DELETE FROM vvar WHERE name IN "
   420    441         " ('bisect-good', 'bisect-bad', 'bisect-log')"
   421    442       );
          443  +  }else if( strcmp(zCmd, "ui")==0 ){
          444  +    char *newArgv[8];
          445  +    newArgv[0] = g.argv[0];
          446  +    newArgv[1] = "ui";
          447  +    newArgv[2] = "--page";
          448  +    newArgv[3] = "timeline?bisect";
          449  +    newArgv[4] = 0;
          450  +    g.argv = newArgv;
          451  +    g.argc = 4;
          452  +    cmd_webserver();
   422    453     }else if( strncmp(zCmd, "vlist", n)==0
   423    454            || strncmp(zCmd, "ls", n)==0
   424    455            || strncmp(zCmd, "status", n)==0
   425    456     ){
   426    457       int fAll = find_option("all", "a", 0)!=0;
   427    458       bisect_list(!fAll);
   428    459     }else if( !foundCmd ){
   429         -    usage("bad|good|log|next|options|reset|status|undo");
          460  +    usage("bad|good|log|next|options|reset|status|ui|undo");
   430    461     }
   431    462   }

Changes to src/blob.c.

   269    269     pBlob->xRealloc = blobReallocStatic;
   270    270   }
   271    271   
   272    272   /*
   273    273   ** Append text or data to the end of a blob.
   274    274   */
   275    275   void blob_append(Blob *pBlob, const char *aData, int nData){
          276  +  assert( aData!=0 || nData==0 );
   276    277     blob_is_init(pBlob);
   277    278     if( nData<0 ) nData = strlen(aData);
   278    279     if( nData==0 ) return;
   279    280     if( pBlob->nUsed + nData >= pBlob->nAlloc ){
   280    281       pBlob->xRealloc(pBlob, pBlob->nUsed + nData + pBlob->nAlloc + 100);
   281    282       if( pBlob->nUsed + nData >= pBlob->nAlloc ){
   282    283         blob_panic();

Changes to src/browse.c.

   597    597       pM = manifest_get_by_name(zCI, &rid);
   598    598       if( pM ){
   599    599         int trunkRid = symbolic_name_to_rid("tag:trunk", "ci");
   600    600         linkTrunk = trunkRid && rid != trunkRid;
   601    601         linkTip = rid != symbolic_name_to_rid("tip", "ci");
   602    602         zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   603    603         rNow = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
   604         -      zNow = db_text("", "SELECT datetime(mtime,'localtime')"
          604  +      zNow = db_text("", "SELECT datetime(mtime,toLocal())"
   605    605                            " FROM event WHERE objid=%d", rid);
   606    606       }else{
   607    607         zCI = 0;
   608    608       }
   609    609     }
   610    610     if( zCI==0 ){
   611    611       rNow = db_double(0.0, "SELECT max(mtime) FROM event");
   612         -    zNow = db_text("", "SELECT datetime(max(mtime),'localtime') FROM event");
          612  +    zNow = db_text("", "SELECT datetime(max(mtime),toLocal()) FROM event");
   613    613     }
   614    614   
   615    615     /* Compute the title of the page */
   616    616     blob_zero(&dirname);
   617    617     if( zD ){
   618    618       blob_append(&dirname, "within directory ", -1);
   619    619       hyperlinked_path(zD, &dirname, zCI, "tree", zREx);
................................................................................
  1027   1027     if( zName==0 ) zName = "tip";
  1028   1028     rid = symbolic_name_to_rid(zName, "ci");
  1029   1029     if( rid==0 ){
  1030   1030       fossil_fatal("not a valid check-in: %s", zName);
  1031   1031     }
  1032   1032     zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1033   1033     baseTime = db_double(0.0,"SELECT mtime FROM event WHERE objid=%d", rid);
  1034         -  zNow = db_text("", "SELECT datetime(mtime,'localtime') FROM event"
         1034  +  zNow = db_text("", "SELECT datetime(mtime,toLocal()) FROM event"
  1035   1035                        " WHERE objid=%d", rid);
  1036   1036     style_submenu_element("Tree-View", "Tree-View",
  1037   1037                           "%R/tree?ci=%T&mtime=1&type=tree",
  1038   1038                           zName);
  1039   1039     style_header("File Ages");
  1040   1040     zGlob = P("glob");
  1041   1041     compute_fileage(rid,zGlob);

Changes to src/cache.c.

   250    250   **
   251    251   **    list|ls      List the keys and content sizes and other stats for
   252    252   **                 all entries currently in the cache
   253    253   **
   254    254   **    status       Show a summary of cache status.
   255    255   **
   256    256   ** The cache is stored in a file that is distinct from the repository
   257         -** but that is held in the same directory as the repository.  To cache
          257  +** but that is held in the same directory as the repository.  The cache
   258    258   ** file can be deleted in order to completely disable the cache.
   259    259   */
   260    260   void cache_cmd(void){
   261    261     const char *zCmd;
   262    262     int nCmd;
   263    263     sqlite3 *db;
   264    264     sqlite3_stmt *pStmt;

Changes to src/cgi.c.

   794    794   */
   795    795   void cgi_parse_POST_JSON( FILE * zIn, unsigned int contentLen ){
   796    796     cson_value * jv = NULL;
   797    797     int rc;
   798    798     CgiPostReadState state;
   799    799     cson_parse_opt popt = cson_parse_opt_empty;
   800    800     cson_parse_info pinfo = cson_parse_info_empty;
          801  +  assert(g.json.gc.a && "json_main_bootstrap() was not called!");
   801    802     popt.maxDepth = 15;
   802    803     state.fh = zIn;
   803    804     state.len = contentLen;
   804    805     state.pos = 0;
   805    806     rc = cson_parse( &jv,
   806    807                      contentLen ? cson_data_source_FILE_n : cson_data_source_FILE,
   807    808                      contentLen ? (void *)&state : (void *)zIn, &popt, &pinfo );
................................................................................
  1048   1049       }
  1049   1050     }
  1050   1051   
  1051   1052     /* If no match is found and the name begins with an upper-case
  1052   1053     ** letter, then check to see if there is an environment variable
  1053   1054     ** with the given name.
  1054   1055     */
  1055         -  if( fossil_isupper(zName[0]) ){
         1056  +  if( zName && fossil_isupper(zName[0]) ){
  1056   1057       const char *zValue = fossil_getenv(zName);
  1057   1058       if( zValue ){
  1058   1059         cgi_set_parameter_nocopy(zName, zValue, 0);
  1059   1060         CGIDEBUG(("env-match [%s] = [%s]\n", zName, zValue));
  1060   1061         return zValue;
  1061   1062       }
  1062   1063     }

Changes to src/checkin.c.

     1      1   /*
     2      2   ** Copyright (c) 2007 D. Richard Hipp
     3      3   **
     4      4   ** This program is free software; you can redistribute it and/or
     5      5   ** modify it under the terms of the Simplified BSD License (also
     6      6   ** known as the "2-Clause License" or "FreeBSD License".)
     7         -
            7  +**
     8      8   ** This program is distributed in the hope that it will be useful,
     9      9   ** but without any warranty; without even the implied warranty of
    10     10   ** merchantability or fitness for a particular purpose.
    11     11   **
    12     12   ** Author contact information:
    13     13   **   drh@hwaci.com
    14     14   **   http://www.hwaci.com/drh/
................................................................................
    60     60         (blob_size(&where)>0) ? "OR" : "AND", zName,
    61     61         filename_collation(), zName, filename_collation(),
    62     62         zName, filename_collation()
    63     63       );
    64     64     }
    65     65   
    66     66     db_prepare(&q,
    67         -    "SELECT pathname, deleted, chnged, rid, coalesce(origname!=pathname,0)"
           67  +    "SELECT pathname, deleted, chnged,"
           68  +    "       rid, coalesce(origname!=pathname,0), islink"
    68     69       "  FROM vfile "
    69     70       " WHERE is_selected(id) %s"
    70     71       "   AND (chnged OR deleted OR rid=0 OR pathname!=origname)"
    71     72       " ORDER BY 1 /*scan*/",
    72     73       blob_sql_text(&where)
    73     74     );
    74     75     blob_zero(&rewrittenPathname);
................................................................................
    75     76     while( db_step(&q)==SQLITE_ROW ){
    76     77       const char *zPathname = db_column_text(&q,0);
    77     78       const char *zDisplayName = zPathname;
    78     79       int isDeleted = db_column_int(&q, 1);
    79     80       int isChnged = db_column_int(&q,2);
    80     81       int isNew = db_column_int(&q,3)==0;
    81     82       int isRenamed = db_column_int(&q,4);
           83  +    int isLink = db_column_int(&q,5);
    82     84       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
    83     85       if( cwdRelative ){
    84     86         file_relative_name(zFullName, &rewrittenPathname, 0);
    85     87         zDisplayName = blob_str(&rewrittenPathname);
    86     88         if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){
    87     89           zDisplayName += 2;  /* no unnecessary ./ prefix */
    88     90         }
................................................................................
   119    121           blob_appendf(report, "EXECUTABLE %s\n", zDisplayName);
   120    122         }else if( isChnged==7 ){
   121    123           blob_appendf(report, "SYMLINK    %s\n", zDisplayName);
   122    124         }else if( isChnged==8 ){
   123    125           blob_appendf(report, "UNEXEC     %s\n", zDisplayName);
   124    126         }else if( isChnged==9 ){
   125    127           blob_appendf(report, "UNLINK     %s\n", zDisplayName);
   126         -      }else if( file_contains_merge_marker(zFullName) ){
          128  +      }else if( !isLink && file_contains_merge_marker(zFullName) ){
   127    129           blob_appendf(report, "CONFLICT   %s\n", zDisplayName);
   128    130         }else{
   129    131           blob_appendf(report, "EDITED     %s\n", zDisplayName);
   130    132         }
   131    133       }else if( isRenamed ){
   132    134         blob_appendf(report, "RENAMED    %s\n", zDisplayName);
   133    135       }else{
................................................................................
   323    325   
   324    326     if( timeOrder ){
   325    327       zOrderBy = "mtime DESC";
   326    328     }
   327    329   
   328    330     compute_fileage(rid,0);
   329    331     db_prepare(&q,
   330         -    "SELECT datetime(fileage.mtime, 'localtime'), fileage.pathname,\n"
          332  +    "SELECT datetime(fileage.mtime, toLocal()), fileage.pathname,\n"
   331    333       "       blob.size\n"
   332    334       "  FROM fileage, blob\n"
   333    335       " WHERE blob.rid=fileage.fid %s\n"
   334    336       " ORDER BY %s;", blob_sql_text(&where), zOrderBy /*safe-for-%s*/
   335    337     );
   336    338     blob_reset(&where);
   337    339   
................................................................................
   392    394     timeOrder = find_option("t","t",0)!=0;
   393    395   
   394    396     if( zRev!=0 ){
   395    397       db_find_and_open_repository(0, 0);
   396    398       verify_all_options();
   397    399       ls_cmd_rev(zRev,verboseFlag,showAge,timeOrder);
   398    400       return;
          401  +  }else if( find_option("R",0,1)!=0 ){
          402  +    fossil_fatal("the -r is required in addition to -R");
   399    403     }
   400    404   
   401    405     db_must_be_within_tree();
   402    406     vid = db_lget_int("checkout", 0);
   403    407     if( timeOrder ){
   404    408       if( showAge ){
   405    409         zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid);
................................................................................
   425    429          zName, filename_collation()
   426    430       );
   427    431     }
   428    432     vfile_check_signature(vid, 0);
   429    433     if( showAge ){
   430    434       db_prepare(&q,
   431    435          "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0),"
   432         -       "       datetime(checkin_mtime(%d,rid),'unixepoch'%s)"
          436  +       "       datetime(checkin_mtime(%d,rid),'unixepoch',toLocal())"
   433    437          "  FROM vfile %s"
   434    438          " ORDER BY %s",
   435         -       vid, timeline_utc(), blob_sql_text(&where), zOrderBy /*safe-for-%s*/
          439  +       vid, blob_sql_text(&where), zOrderBy /*safe-for-%s*/
   436    440       );
   437    441     }else{
   438    442       db_prepare(&q,
   439         -       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
          443  +       "SELECT pathname, deleted, rid, chnged,"
          444  +       "       coalesce(origname!=pathname,0), islink"
   440    445          "  FROM vfile %s"
   441    446          " ORDER BY %s", blob_sql_text(&where), zOrderBy /*safe-for-%s*/
   442    447       );
   443    448     }
   444    449     blob_reset(&where);
   445    450     while( db_step(&q)==SQLITE_ROW ){
   446    451       const char *zPathname = db_column_text(&q,0);
   447    452       int isDeleted = db_column_int(&q, 1);
   448    453       int isNew = db_column_int(&q,2)==0;
   449    454       int chnged = db_column_int(&q,3);
   450    455       int renamed = db_column_int(&q,4);
          456  +    int isLink = db_column_int(&q,5);
   451    457       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
   452    458       const char *type = "";
   453    459       if( verboseFlag ){
   454    460         if( isNew ){
   455    461           type = "ADDED      ";
   456    462         }else if( isDeleted ){
   457    463           type = "DELETED    ";
................................................................................
   466    472             type = "UPDATED_BY_MERGE ";
   467    473           }else if( chnged==3 ){
   468    474             type = "ADDED_BY_MERGE ";
   469    475           }else if( chnged==4 ){
   470    476             type = "UPDATED_BY_INTEGRATE ";
   471    477           }else if( chnged==5 ){
   472    478             type = "ADDED_BY_INTEGRATE ";
   473         -        }else if( file_contains_merge_marker(zFullName) ){
          479  +        }else if( !isLink && file_contains_merge_marker(zFullName) ){
   474    480             type = "CONFLICT   ";
   475    481           }else{
   476    482             type = "EDITED     ";
   477    483           }
   478    484         }else if( renamed ){
   479    485           type = "RENAMED    ";
   480    486         }else{
................................................................................
   668    674   **                     explicitly exempted via the empty-dirs setting
   669    675   **                     or another applicable setting or command line
   670    676   **                     argument.  Matching files, if any, are removed
   671    677   **                     prior to checking for any empty directories;
   672    678   **                     therefore, directories that contain only files
   673    679   **                     that were removed will be removed as well.
   674    680   **    -f|--force       Remove files without prompting.
          681  +**    -i|--prompt      Prompt before removing each file.
   675    682   **    -x|--verily      WARNING: Removes everything that is not a managed
   676    683   **                     file or the repository itself.  This option
   677    684   **                     implies the --force, --emptydirs, --dotfiles, and
   678    685   **                     --disable-undo options.  Furthermore, it completely
   679    686   **                     disregards the keep-glob and ignore-glob settings.
   680    687   **                     However, it does honor the --ignore and --keep
   681    688   **                     options.
................................................................................
   696    703   **
   697    704   ** See also: addremove, extras, status
   698    705   */
   699    706   void clean_cmd(void){
   700    707     int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
   701    708     int emptyDirsFlag, dirsOnlyFlag;
   702    709     int disableUndo, noPrompt;
          710  +  int alwaysPrompt = 0;
   703    711     unsigned scanFlags = 0;
   704    712     int verilyFlag = 0;
   705    713     const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
   706    714     Glob *pIgnore, *pKeep, *pClean;
   707    715     int nRoot;
   708    716   
   709    717   #ifndef UNDO_SIZE_LIMIT  /* TODO: Setting? */
................................................................................
   716    724       dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
   717    725     }
   718    726     if( !dryRunFlag ){
   719    727       dryRunFlag = find_option("whatif",0,0)!=0;
   720    728     }
   721    729     disableUndo = find_option("disable-undo",0,0)!=0;
   722    730     noPrompt = find_option("no-prompt",0,0)!=0;
          731  +  alwaysPrompt = find_option("prompt","i",0)!=0;
   723    732     allFileFlag = allDirFlag = find_option("force","f",0)!=0;
   724    733     dirsOnlyFlag = find_option("dirsonly",0,0)!=0;
   725    734     emptyDirsFlag = find_option("emptydirs","d",0)!=0 || dirsOnlyFlag;
   726    735     if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
   727    736     if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
   728    737     if( find_option("allckouts",0,0)!=0 ) scanFlags |= SCAN_NESTED;
   729    738     zIgnoreFlag = find_option("ignore",0,1);
................................................................................
   775    784           if( verboseFlag ){
   776    785             fossil_print("KEPT file \"%s\" not removed (due to --keep"
   777    786                          " or \"keep-glob\")\n", zName+nRoot);
   778    787           }
   779    788           continue;
   780    789         }
   781    790         if( !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
          791  +        char *zPrompt = 0;
          792  +        char cReply;
          793  +        Blob ans = empty_blob;
   782    794           int undoRc = UNDO_NONE;
   783         -        if( !disableUndo ){
          795  +        if( alwaysPrompt ){
          796  +          zPrompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
          797  +                            zName+nRoot);
          798  +          prompt_user(zPrompt, &ans);
          799  +          fossil_free(zPrompt);
          800  +          cReply = fossil_toupper(blob_str(&ans)[0]);
          801  +          blob_reset(&ans);
          802  +          if( cReply=='N' ) continue;
          803  +          if( cReply=='A' ){
          804  +            allFileFlag = 1;
          805  +            alwaysPrompt = 0;
          806  +          }else{
          807  +            undoRc = UNDO_SAVED_OK;
          808  +          }
          809  +        }else if( !disableUndo ){
   784    810             undoRc = undo_maybe_save(zName+nRoot, UNDO_SIZE_LIMIT);
   785    811           }
   786    812           if( undoRc!=UNDO_SAVED_OK ){
   787         -          char cReply;
   788    813             if( allFileFlag ){
   789    814               cReply = 'Y';
   790    815             }else if( !noPrompt ){
   791    816               Blob ans;
   792         -            char *prompt = mprintf("\nWARNING: Deletion of this file will "
   793         -                                   "not be undoable via the 'undo'\n"
   794         -                                   "         command because %s.\n\n"
   795         -                                   "Remove unmanaged file \"%s\" (a=all/y/N)? ",
   796         -                                   undo_save_message(undoRc), zName+nRoot);
   797         -            prompt_user(prompt, &ans);
          817  +            zPrompt = mprintf("\nWARNING: Deletion of this file will "
          818  +                              "not be undoable via the 'undo'\n"
          819  +                              "         command because %s.\n\n"
          820  +                              "Remove unmanaged file \"%s\" (a=all/y/N)? ",
          821  +                              undo_save_message(undoRc), zName+nRoot);
          822  +            prompt_user(zPrompt, &ans);
          823  +            fossil_free(zPrompt);
   798    824               cReply = blob_str(&ans)[0];
   799    825               blob_reset(&ans);
   800    826             }else{
   801    827               cReply = 'N';
   802    828             }
   803    829             if( cReply=='a' || cReply=='A' ){
   804    830               allFileFlag = 1;
................................................................................
   807    833             }
   808    834           }
   809    835         }
   810    836         if( dryRunFlag || file_delete(zName)==0 ){
   811    837           if( verboseFlag || dryRunFlag ){
   812    838             fossil_print("Removed unmanaged file: %s\n", zName+nRoot);
   813    839           }
   814         -      }else if( verboseFlag ){
          840  +      }else{
   815    841           fossil_print("Could not remove file: %s\n", zName+nRoot);
   816    842         }
   817    843       }
   818    844       db_finalize(&q);
   819    845       if( !dryRunFlag && !disableUndo ) undo_finish();
   820    846     }
   821    847     if( emptyDirsFlag ){
................................................................................
   845    871           char cReply;
   846    872           if( !noPrompt ){
   847    873             Blob ans;
   848    874             char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ",
   849    875                                    zName+nRoot);
   850    876             prompt_user(prompt, &ans);
   851    877             cReply = blob_str(&ans)[0];
          878  +          fossil_free(prompt);
   852    879             blob_reset(&ans);
   853    880           }else{
   854    881             cReply = 'N';
   855    882           }
   856    883           if( cReply=='a' || cReply=='A' ){
   857    884             allDirFlag = 1;
   858    885           }else if( cReply!='y' && cReply!='Y' ){
................................................................................
   899    926     if( zEditor==0 ){
   900    927       zEditor = fossil_getenv("EDITOR");
   901    928     }
   902    929   #if defined(_WIN32) || defined(__CYGWIN__)
   903    930     if( zEditor==0 ){
   904    931       zEditor = mprintf("%s\\notepad.exe", fossil_getenv("SYSTEMROOT"));
   905    932   #if defined(__CYGWIN__)
   906         -    zEditor = fossil_utf8_to_filename(zEditor);
          933  +    zEditor = fossil_utf8_to_path(zEditor, 0);
   907    934       blob_add_cr(pPrompt);
   908    935   #endif
   909    936     }
   910    937   #endif
   911    938     if( zEditor==0 ){
   912    939       blob_append(pPrompt,
   913    940          "#\n"
................................................................................
  1648   1675   **    -n|--dry-run               If given, display instead of run actions
  1649   1676   **    --no-warnings              omit all warnings about file contents
  1650   1677   **    --nosign                   do not attempt to sign this commit with gpg
  1651   1678   **    --private                  do not sync changes and their descendants
  1652   1679   **    --sha1sum                  verify file status using SHA1 hashing rather
  1653   1680   **                               than relying on file mtimes
  1654   1681   **    --tag TAG-NAME             assign given tag TAG-NAME to the check-in
         1682  +**    --date-override DATE       DATE to use instead of 'now'
         1683  +**    --user-override USER       USER to use instead of the current default
  1655   1684   **
  1656   1685   ** See also: branch, changes, checkout, extras, sync
  1657   1686   */
  1658   1687   void commit_cmd(void){
  1659   1688     int hasChanges;        /* True if unsaved changes exist */
  1660   1689     int vid;               /* blob-id of parent version */
  1661   1690     int nrid;              /* blob-id of a modified file */

Changes to src/checkout.c.

   241    241       return;
   242    242     }
   243    243     if( !keepFlag ){
   244    244       uncheckout(prior);
   245    245     }
   246    246     db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
   247    247     if( !keepFlag ){
   248         -    vfile_to_disk(vid, 0, 1, promptFlag);
          248  +    vfile_to_disk(vid, 0, !g.fQuiet, promptFlag);
   249    249     }
   250    250     checkout_set_all_exe(vid);
   251    251     manifest_to_disk(vid);
   252    252     ensure_empty_dirs_created();
   253    253     db_lset_int("checkout", vid);
   254    254     undo_reset();
   255    255     db_multi_exec("DELETE FROM vmerge");

Changes to src/clone.c.

    78     78     );
    79     79   }
    80     80   
    81     81   
    82     82   /*
    83     83   ** COMMAND: clone
    84     84   **
    85         -** Usage: %fossil clone ?OPTIONS? URL FILENAME
           85  +** Usage: %fossil clone ?OPTIONS? URI FILENAME
    86     86   **
    87         -** Make a clone of a repository specified by URL in the local
           87  +** Make a clone of a repository specified by URI in the local
    88     88   ** file named FILENAME.
    89     89   **
    90         -** URL must be in one of the following form: ([...] mean optional)
           90  +** URI may be one of the following form: ([...] mean optional)
    91     91   **   HTTP/HTTPS protocol:
    92     92   **     http[s]://[userid[:password]@]host[:port][/path]
    93     93   **
    94     94   **   SSH protocol:
    95         -**     ssh://[userid[:password]@]host[:port]/path/to/repo.fossil\\
           95  +**     ssh://[userid@]host[:port]/path/to/repo.fossil\\
    96     96   **     [?fossil=path/to/fossil.exe]
    97     97   **
    98     98   **   Filesystem:
    99     99   **     [file://]path/to/repo.fossil
   100    100   **
   101         -**   Note: For ssh and filesystem, path must have an extra leading
          101  +** Note 1: For ssh and filesystem, path must have an extra leading
   102    102   **         '/' to use an absolute path.
          103  +**
          104  +** Note 2: Use %HH escapes for special characters in the userid and 
          105  +**         password.  For example "%40" in place of "@", "%2f" in place
          106  +**         of "/", and "%3a" in place of ":".
   103    107   **
   104    108   ** By default, your current login name is used to create the default
   105    109   ** admin user. This can be overridden using the -A|--admin-user
   106    110   ** parameter.
   107    111   **
   108    112   ** Options:
   109    113   **    --admin-user|-A USERNAME   Make USERNAME the administrator
   110         -**    --once                     Don't save url.
          114  +**    --once                     Don't remember the URI.
   111    115   **    --private                  Also clone private branches
   112         -**    --ssl-identity=filename    Use the SSL identity if requested by the server
   113         -**    --ssh-command|-c 'command' Use this SSH command
   114         -**    --httpauth|-B 'user:pass'  Add HTTP Basic Authorization to requests
          116  +**    --ssl-identity FILENAME    Use the SSL identity if requested by the server
          117  +**    --ssh-command|-c SSH       Use SSH as the "ssh" command
          118  +**    --httpauth|-B USER:PASS    Add HTTP Basic Authorization to requests
   115    119   **    --verbose                  Show more statistics in output
   116    120   **
   117    121   ** See also: init
   118    122   */
   119    123   void clone_cmd(void){
   120    124     char *zPassword;
   121    125     const char *zDefaultUser;   /* Optional name of the default user */
................................................................................
   135    139   
   136    140     /* We should be done with options.. */
   137    141     verify_all_options();
   138    142   
   139    143     if( g.argc < 4 ){
   140    144       usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
   141    145     }
   142         -  db_open_config(0);
          146  +  db_open_config(0, 0);
   143    147     if( -1 != file_size(g.argv[3]) ){
   144    148       fossil_fatal("file already exists: %s", g.argv[3]);
   145    149     }
   146    150   
   147    151     url_parse(g.argv[2], urlFlags);
   148    152     if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
   149    153     if( g.url.isFile ){

Changes to src/codecheck1.c.

   248    248   /*
   249    249   ** A list of functions that return strings that are safe to insert into
   250    250   ** SQL using %s.
   251    251   */
   252    252   static const char *azSafeFunc[] = {
   253    253     "filename_collation",
   254    254     "db_name",
   255         -  "timeline_utc",
   256    255     "leaf_is_closed_sql",
   257    256     "timeline_query_for_www",
   258    257     "timeline_query_for_tty",
   259    258     "blob_sql_text",
   260    259     "glob_expr",
   261    260     "fossil_all_reserved_names",
   262    261     "configure_inop_rhs",

Changes to src/configure.c.

   880    880   **    -R|--repository FILE       Extract info from repository FILE
   881    881   **
   882    882   ** See also: settings, unset
   883    883   */
   884    884   void configuration_cmd(void){
   885    885     int n;
   886    886     const char *zMethod;
          887  +  db_find_and_open_repository(0, 0);
          888  +  db_open_config(0, 0);
   887    889     if( g.argc<3 ){
   888         -    usage("export|import|merge|pull|reset ...");
          890  +    usage("SUBCOMMAND ...");
   889    891     }
   890         -  db_find_and_open_repository(0, 0);
   891         -  db_open_config(0);
   892    892     zMethod = g.argv[2];
   893    893     n = strlen(zMethod);
   894    894     if( strncmp(zMethod, "export", n)==0 ){
   895    895       int mask;
   896    896       const char *zSince = find_option("since",0,1);
   897    897       sqlite3_int64 iStart;
   898    898       if( g.argc!=5 ){

Changes to src/content.c.

   707    707   /*
   708    708   ** COMMAND:  test-content-undelta
   709    709   **
   710    710   ** Make sure the content at RECORDID is not a delta
   711    711   */
   712    712   void test_content_undelta_cmd(void){
   713    713     int rid;
   714         -  if( g.argc!=2 ) usage("RECORDID");
          714  +  if( g.argc!=3 ) usage("RECORDID");
   715    715     db_must_be_within_tree();
   716    716     rid = atoi(g.argv[2]);
   717    717     content_undelta(rid);
   718    718   }
   719    719   
   720    720   /*
   721    721   ** Return true if the given RID is marked as PRIVATE.
................................................................................
   903    903       if( blob_size(&content)!=size ){
   904    904         fossil_print("size mismatch on artifact %d: wanted %d but got %d\n",
   905    905                        rid, size, blob_size(&content));
   906    906         nErr++;
   907    907       }
   908    908       sha1sum_blob(&content, &cksum);
   909    909       if( fossil_strcmp(blob_str(&cksum), zUuid)!=0 ){
   910         -      fossil_print("checksum mismatch on artifact %d: wanted %s but got %s\n",
          910  +      fossil_print("wrong hash on artifact %d: wanted %s but got %s\n",
   911    911                      rid, zUuid, blob_str(&cksum));
   912    912         nErr++;
   913    913       }
   914    914       if( bParse && looks_like_control_artifact(&content) ){
   915    915         Blob err;
   916    916         int i, n;
   917    917         char *z;
................................................................................
   951    951           "control", "wiki", "ticket", "attachment", "event" };
   952    952       int i;
   953    953       fossil_print("%d total control artifacts\n", nCA);
   954    954       for(i=1; i<count(azType); i++){
   955    955         if( anCA[i] ) fossil_print("  %d %ss\n", anCA[i], azType[i]);
   956    956       }
   957    957     }
          958  +  fossil_print("low-level database integrity-check: ");
          959  +  fossil_print("%s\n", db_text(0, "PRAGMA integrity_check(10)"));
   958    960   }
   959    961   
   960    962   /*
   961    963   ** COMMAND: test-orphans
   962    964   **
   963    965   ** Search the repository for orphaned artifacts
   964    966   */
................................................................................
  1122   1124     }
  1123   1125     db_finalize(&q);
  1124   1126     if( nErr>0 || quietFlag==0 ){
  1125   1127       fossil_print("%d missing or shunned references in %d control artifacts\n",
  1126   1128                    nErr, nArtifact);
  1127   1129     }
  1128   1130   }
         1131  +
         1132  +/*
         1133  +** COMMAND: test-content-erase
         1134  +**
         1135  +** Usage: %fossil test-content-erase RID ....
         1136  +**
         1137  +** Remove all traces of one or more artifacts from the local repository.
         1138  +**
         1139  +** WARNING: This command destroys data and can cause you to lose work.
         1140  +** Make sure you have a backup copy before using this command!
         1141  +**
         1142  +** WARNING: You must run "fossil rebuild" after this command to rebuild
         1143  +** the metadata.
         1144  +**
         1145  +** Note that the arguments are the integer raw RID values from the BLOB table,
         1146  +** not SHA1 hashs or labels.
         1147  +*/
         1148  +void test_content_erase(void){
         1149  +  int i;
         1150  +  Blob x;
         1151  +  char c;
         1152  +  Stmt q;
         1153  +  prompt_user("This command erases information from the repository and\n"
         1154  +              "might irrecoverably damage the repository.  Make sure you\n"
         1155  +              "have a backup copy!\n"
         1156  +              "Continue? (y/N)? ", &x);
         1157  +  c = blob_str(&x)[0];
         1158  +  blob_reset(&x);
         1159  +  if( c!='y' && c!='Y' ) return;
         1160  +  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
         1161  +  db_begin_transaction();
         1162  +  db_prepare(&q, "SELECT rid FROM delta WHERE srcid=:rid");
         1163  +  for(i=2; i<g.argc; i++){
         1164  +    int rid = atoi(g.argv[i]);
         1165  +    fossil_print("Erasing artifact %d (%s)\n", 
         1166  +                 rid, db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid));
         1167  +    db_bind_int(&q, ":rid", rid);
         1168  +    while( db_step(&q)==SQLITE_ROW ){
         1169  +      content_undelta(db_column_int(&q,0));
         1170  +    }
         1171  +    db_reset(&q);
         1172  +    db_multi_exec("DELETE FROM blob WHERE rid=%d", rid);
         1173  +    db_multi_exec("DELETE FROM delta WHERE rid=%d", rid);
         1174  +  }
         1175  +  db_finalize(&q);
         1176  +  db_end_transaction(0);
         1177  +}

Changes to src/cson_amalgamation.c.

  1476   1476   #define cson_value_api_empty_m {           \
  1477   1477           CSON_TYPE_UNDEF/*typeID*/,         \
  1478   1478           NULL/*cleanup*/\
  1479   1479         }
  1480   1480   /**
  1481   1481      Empty-initialized cson_value_api object.
  1482   1482   */
  1483         -static const cson_value_api cson_value_api_empty = cson_value_api_empty_m;
         1483  +/*static const cson_value_api cson_value_api_empty = cson_value_api_empty_m;*/
  1484   1484   
  1485   1485   
  1486   1486   typedef unsigned int cson_counter_t;
  1487   1487   struct cson_value
  1488   1488   {
  1489   1489       /** The "vtbl" of type-specific operations. All instances
  1490   1490           of a given logical value type share a single api instance.
................................................................................
  1527   1527       cson_counter_t refcount;
  1528   1528   };
  1529   1529   
  1530   1530   
  1531   1531   /**
  1532   1532      Empty-initialized cson_value object.
  1533   1533   */
  1534         -#define cson_value_empty_m { &cson_value_api_empty/*api*/, NULL/*value*/, 0/*refcount*/ }
  1535         -/**
  1536         -   Empty-initialized cson_value object.
  1537         -*/
  1538         -static const cson_value cson_value_empty = cson_value_empty_m;
  1539   1534   const cson_parse_opt cson_parse_opt_empty = cson_parse_opt_empty_m;
  1540   1535   const cson_output_opt cson_output_opt_empty = cson_output_opt_empty_m;
  1541   1536   const cson_object_iterator cson_object_iterator_empty = cson_object_iterator_empty_m;
  1542   1537   const cson_buffer cson_buffer_empty = cson_buffer_empty_m;
  1543   1538   const cson_parse_info cson_parse_info_empty = cson_parse_info_empty_m;
  1544   1539   
  1545   1540   static void cson_value_destroy_zero_it( cson_value * self );
................................................................................
  1556   1551   static const cson_value_api cson_value_api_integer = { CSON_TYPE_INTEGER, cson_value_destroy_zero_it };
  1557   1552   static const cson_value_api cson_value_api_double = { CSON_TYPE_DOUBLE, cson_value_destroy_zero_it };
  1558   1553   static const cson_value_api cson_value_api_string = { CSON_TYPE_STRING, cson_value_destroy_zero_it };
  1559   1554   static const cson_value_api cson_value_api_array = { CSON_TYPE_ARRAY, cson_value_destroy_array };
  1560   1555   static const cson_value_api cson_value_api_object = { CSON_TYPE_OBJECT, cson_value_destroy_object };
  1561   1556   
  1562   1557   static const cson_value cson_value_undef = { &cson_value_api_undef, NULL, 0 };
  1563         -static const cson_value cson_value_null_empty = { &cson_value_api_null, NULL, 0 };
  1564         -static const cson_value cson_value_bool_empty = { &cson_value_api_bool, NULL, 0 };
  1565   1558   static const cson_value cson_value_integer_empty = { &cson_value_api_integer, NULL, 0 };
  1566   1559   static const cson_value cson_value_double_empty = { &cson_value_api_double, NULL, 0 };
  1567   1560   static const cson_value cson_value_string_empty = { &cson_value_api_string, NULL, 0 };
  1568   1561   static const cson_value cson_value_array_empty = { &cson_value_api_array, NULL, 0 };
  1569   1562   static const cson_value cson_value_object_empty = { &cson_value_api_object, NULL, 0 };
  1570   1563   
  1571   1564   /**
................................................................................
  2116   2109   {
  2117   2110       cson_kvp ** list;
  2118   2111       unsigned int count;
  2119   2112       unsigned int alloced;
  2120   2113   };
  2121   2114   typedef struct cson_kvp_list cson_kvp_list;
  2122   2115   #define cson_kvp_list_empty_m {NULL/*list*/,0/*count*/,0/*alloced*/}
  2123         -static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;
         2116  +/*static const cson_kvp_list cson_kvp_list_empty = cson_kvp_list_empty_m;*/
  2124   2117   
  2125   2118   struct cson_object
  2126   2119   {
  2127   2120       cson_kvp_list kvp;
  2128   2121   };
  2129   2122   /*typedef struct cson_object cson_object;*/
  2130   2123   #define cson_object_empty_m { cson_kvp_list_empty_m/*kvp*/ }
................................................................................
  2307   2300   {
  2308   2301       return ( !v || !v->api || (v->api==&cson_value_api_undef))
  2309   2302           ? 1 : 0;
  2310   2303   }
  2311   2304   #define ISA(T,TID) char cson_value_is_##T( cson_value const * v ) {       \
  2312   2305           /*return (v && v->api) ? cson_value_is_a(v,CSON_TYPE_##TID) : 0;*/ \
  2313   2306           return (v && (v->api == &cson_value_api_##T)) ? 1 : 0; \
  2314         -    } static const char bogusPlaceHolderForEmacsIndention##TID = CSON_TYPE_##TID
         2307  +    } extern char bogusPlaceHolderForEmacsIndention##TID
  2315   2308   ISA(null,NULL);
  2316   2309   ISA(bool,BOOL);
  2317   2310   ISA(integer,INTEGER);
  2318   2311   ISA(double,DOUBLE);
  2319   2312   ISA(string,STRING);
  2320   2313   ISA(array,ARRAY);
  2321   2314   ISA(object,OBJECT);
................................................................................
  3885   3878                       rc = sprintf(ubuf, "\\u%04x",ch);
  3886   3879                       if( rc != 6 )
  3887   3880                       {
  3888   3881                           rc = cson_rc.RangeError;
  3889   3882                           break;
  3890   3883                       }
  3891   3884                       rc = f( state, ubuf, 6 );
  3892         -                }else{ /* encode as a UTF16 surrugate pair */
         3885  +                }else{ /* encode as a UTF16 surrogate pair */
  3893   3886                       /* http://unicodebook.readthedocs.org/en/latest/unicode_encodings.html#surrogates */
  3894   3887                       ch -= 0x10000;
  3895   3888                       rc = sprintf(ubuf, "\\u%04x\\u%04x",
  3896   3889                                    (0xd800 | (ch>>10)),
  3897   3890                                    (0xdc00 | (ch & 0x3ff)));
  3898   3891                       if( rc != 12 )
  3899   3892                       {

Changes to src/db.c.

   789    789       }else if(0==rid){
   790    790         sqlite3_result_null(context);
   791    791       }else{
   792    792         sqlite3_result_int64(context, rid);
   793    793       }
   794    794     }
   795    795   }
          796  +
          797  +/*
          798  +** The toLocal() SQL function returns a string that is an argument to a
          799  +** date/time function that is appropriate for modifying the time for display.
          800  +** If UTC time display is selected, no modification occurs.  If local time
          801  +** display is selected, the time is adjusted appropriately.
          802  +**
          803  +** Example usage:
          804  +**
          805  +**         SELECT datetime('now',toLocal());
          806  +*/
          807  +void db_tolocal_function(
          808  +  sqlite3_context *context,
          809  +  int argc,
          810  +  sqlite3_value **argv
          811  +){
          812  +  if( g.fTimeFormat==0 ){
          813  +    if( db_get_int("timeline-utc", 1) ){
          814  +      g.fTimeFormat = 1;
          815  +    }else{
          816  +      g.fTimeFormat = 2;
          817  +    }
          818  +  }
          819  +  if( g.fTimeFormat==1 ){
          820  +    sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
          821  +  }else{
          822  +    sqlite3_result_text(context, "localtime", -1, SQLITE_STATIC);
          823  +  }
          824  +}
          825  +
          826  +/*
          827  +** The fromLocal() SQL function returns a string that is an argument to a
          828  +** date/time function that is appropriate to convert an input time to UTC.
          829  +** If UTC time display is selected, no modification occurs.  If local time
          830  +** display is selected, the time is adjusted from local to UTC.
          831  +**
          832  +** Example usage:
          833  +**
          834  +**         SELECT julianday(:user_input,fromLocal());
          835  +*/
          836  +void db_fromlocal_function(
          837  +  sqlite3_context *context,
          838  +  int argc,
          839  +  sqlite3_value **argv
          840  +){
          841  +  if( g.fTimeFormat==0 ){
          842  +    if( db_get_int("timeline-utc", 1) ){
          843  +      g.fTimeFormat = 1;
          844  +    }else{
          845  +      g.fTimeFormat = 2;
          846  +    }
          847  +  }
          848  +  if( g.fTimeFormat==1 ){
          849  +    sqlite3_result_text(context, "0 seconds", -1, SQLITE_STATIC);
          850  +  }else{
          851  +    sqlite3_result_text(context, "utc", -1, SQLITE_STATIC);
          852  +  }
          853  +}
          854  +
   796    855   
   797    856   /*
   798    857   ** Register the SQL functions that are useful both to the internal
   799    858   ** representation and to the "fossil sql" command.
   800    859   */
   801    860   void db_add_aux_functions(sqlite3 *db){
   802    861     sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_UTF8, 0,
   803    862                             db_checkin_mtime_function, 0, 0);
   804    863     sqlite3_create_function(db, "symbolic_name_to_rid", 1, SQLITE_UTF8, 0,
   805    864                             db_sym2rid_function, 0, 0);
   806    865     sqlite3_create_function(db, "symbolic_name_to_rid", 2, SQLITE_UTF8, 0,
   807    866                             db_sym2rid_function, 0, 0);
   808    867     sqlite3_create_function(db, "now", 0, SQLITE_UTF8, 0,
   809         -                                 db_now_function, 0, 0);
          868  +                          db_now_function, 0, 0);
          869  +  sqlite3_create_function(db, "toLocal", 0, SQLITE_UTF8, 0,
          870  +                          db_tolocal_function, 0, 0);
          871  +  sqlite3_create_function(db, "fromLocal", 0, SQLITE_UTF8, 0,
          872  +                          db_fromlocal_function, 0, 0);
   810    873   }
   811    874   
   812    875   
   813    876   /*
   814    877   ** Open a database file.  Return a pointer to the new database
   815    878   ** connection.  An error results in process abort.
   816    879   */
................................................................................
   916    979   ** opened on a separate database connection g.dbConfig.  This prevents
   917    980   ** the ~/.fossil database from becoming locked on long check-in or sync
   918    981   ** operations which hold an exclusive transaction.  In a few cases, though,
   919    982   ** it is convenient for the ~/.fossil to be attached to the main database
   920    983   ** connection so that we can join between the various databases.  In that
   921    984   ** case, invoke this routine with useAttach as 1.
   922    985   */
   923         -void db_open_config(int useAttach){
          986  +int db_open_config(int useAttach, int isOptional){
   924    987     char *zDbName;
   925    988     char *zHome;
   926    989     if( g.zConfigDbName ){
   927         -    if( useAttach==g.useAttach ) return;
          990  +    if( useAttach==g.useAttach ) return 1; /* Already open. */
   928    991       db_close_config();
   929    992     }
   930    993     zHome = fossil_getenv("FOSSIL_HOME");
   931    994   #if defined(_WIN32) || defined(__CYGWIN__)
   932    995     if( zHome==0 ){
   933    996       zHome = fossil_getenv("LOCALAPPDATA");
   934    997       if( zHome==0 ){
................................................................................
   937   1000           char *zDrive = fossil_getenv("HOMEDRIVE");
   938   1001           char *zPath = fossil_getenv("HOMEPATH");
   939   1002           if( zDrive && zPath ) zHome = mprintf("%s%s", zDrive, zPath);
   940   1003         }
   941   1004       }
   942   1005     }
   943   1006     if( zHome==0 ){
         1007  +    if( isOptional ) return 0;
   944   1008       fossil_fatal("cannot locate home directory - please set the "
   945   1009                    "FOSSIL_HOME, LOCALAPPDATA, APPDATA, or HOMEPATH "
   946   1010                    "environment variables");
   947   1011     }
   948   1012   #else
   949   1013     if( zHome==0 ){
   950   1014       zHome = fossil_getenv("HOME");
   951   1015     }
   952   1016     if( zHome==0 ){
         1017  +    if( isOptional ) return 0;
   953   1018       fossil_fatal("cannot locate home directory - please set the "
   954   1019                    "FOSSIL_HOME or HOME environment variables");
   955   1020     }
   956   1021   #endif
   957   1022     if( file_isdir(zHome)!=1 ){
         1023  +    if( isOptional ) return 0;
   958   1024       fossil_fatal("invalid home directory: %s", zHome);
   959   1025     }
   960   1026   #if defined(_WIN32) || defined(__CYGWIN__)
   961   1027     /* . filenames give some window systems problems and many apps problems */
   962   1028     zDbName = mprintf("%//_fossil", zHome);
   963   1029   #else
   964   1030     zDbName = mprintf("%s/.fossil", zHome);
   965   1031   #endif
   966   1032     if( file_size(zDbName)<1024*3 ){
   967   1033       if( file_access(zHome, W_OK) ){
         1034  +      if( isOptional ) return 0;
   968   1035         fossil_fatal("home directory %s must be writeable", zHome);
   969   1036       }
   970   1037       db_init_database(zDbName, zConfigSchema, (char*)0);
   971   1038     }
   972   1039     if( file_access(zDbName, W_OK) ){
         1040  +    if( isOptional ) return 0;
   973   1041       fossil_fatal("configuration file %s must be writeable", zDbName);
   974   1042     }
   975   1043     if( useAttach ){
   976   1044       db_open_or_attach(zDbName, "configdb", &g.useAttach);
   977   1045       g.dbConfig = 0;
   978   1046       g.zConfigDbType = 0;
   979   1047     }else{
   980   1048       g.useAttach = 0;
   981   1049       g.dbConfig = db_open(zDbName);
   982   1050       g.zConfigDbType = "configdb";
   983   1051     }
   984   1052     g.zConfigDbName = zDbName;
         1053  +  return 1;
   985   1054   }
   986   1055   
   987   1056   /*
   988   1057   ** Return TRUE if zTable exists.
   989   1058   */
   990   1059   int db_table_exists(
   991   1060     const char *zDb,      /* One of: NULL, "configdb", "localdb", "repository" */
................................................................................
  1091   1160     if( g.localOpen ) return 1;
  1092   1161     file_getcwd(zPwd, sizeof(zPwd)-20);
  1093   1162     n = strlen(zPwd);
  1094   1163     while( n>0 ){
  1095   1164       for(i=0; i<count(aDbName); i++){
  1096   1165         sqlite3_snprintf(sizeof(zPwd)-n, &zPwd[n], "/%s", aDbName[i]);
  1097   1166         if( isValidLocalDb(zPwd) ){
         1167  +        if( db_open_config(0, 1)==0 ){
         1168  +          return 0; /* Configuration could not be opened */
         1169  +        }
  1098   1170           /* Found a valid checkout database file */
  1099   1171           g.zLocalDbName = mprintf("%s", zPwd);
  1100   1172           zPwd[n] = 0;
  1101   1173           while( n>0 && zPwd[n-1]=='/' ){
  1102   1174             n--;
  1103   1175             zPwd[n] = 0;
  1104   1176           }
  1105   1177           g.zLocalRoot = mprintf("%s/", zPwd);
  1106   1178           g.localOpen = 1;
  1107         -        db_open_config(0);
  1108   1179           db_open_repository(zDbName);
  1109   1180           return 1;
  1110   1181         }
  1111   1182       }
  1112   1183       n--;
  1113   1184       while( n>1 && zPwd[n]!='/' ){ n--; }
  1114   1185       while( n>1 && zPwd[n-1]=='/' ){ n--; }
................................................................................
  1132   1203       zRepo = db_lget("repository", 0);
  1133   1204       if( zRepo && !file_is_absolute_path(zRepo) ){
  1134   1205         zRepo = mprintf("%s%s", g.zLocalRoot, zRepo);
  1135   1206       }
  1136   1207     }
  1137   1208     return zRepo;
  1138   1209   }
         1210  +
         1211  +/*
         1212  +** Returns non-zero if the default value for the "allow-symlinks" setting
         1213  +** is "on".
         1214  +*/
         1215  +int db_allow_symlinks_by_default(void){
         1216  +#if defined(_WIN32)
         1217  +  return 0;
         1218  +#else
         1219  +  return 1;
         1220  +#endif
         1221  +}
  1139   1222   
  1140   1223   /*
  1141   1224   ** Open the repository database given by zDbName.  If zDbName==NULL then
  1142   1225   ** get the name from the already open local database.
  1143   1226   */
  1144   1227   void db_open_repository(const char *zDbName){
  1145   1228     if( g.repositoryOpen ) return;
................................................................................
  1170   1253         fossil_panic("not a valid repository: %s", zDbName);
  1171   1254       }
  1172   1255     }
  1173   1256     g.zRepositoryName = mprintf("%s", zDbName);
  1174   1257     db_open_or_attach(g.zRepositoryName, "repository", 0);
  1175   1258     g.repositoryOpen = 1;
  1176   1259     /* Cache "allow-symlinks" option, because we'll need it on every stat call */
  1177         -  g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
         1260  +  g.allowSymlinks = db_get_boolean("allow-symlinks",
         1261  +                                   db_allow_symlinks_by_default());
  1178   1262     g.zAuxSchema = db_get("aux-schema","");
  1179   1263   
  1180   1264     /* Verify that the PLINK table has a new column added by the
  1181   1265     ** 2014-11-28 schema change.  Create it if necessary.  This code
  1182   1266     ** can be removed in the future, once all users have upgraded to the
  1183   1267     ** 2014-11-28 or later schema.
  1184   1268     */
................................................................................
  1652   1736   
  1653   1737     if( -1 != file_size(g.argv[2]) ){
  1654   1738       fossil_fatal("file already exists: %s", g.argv[2]);
  1655   1739     }
  1656   1740   
  1657   1741     db_create_repository(g.argv[2]);
  1658   1742     db_open_repository(g.argv[2]);
  1659         -  db_open_config(0);
         1743  +  db_open_config(0, 0);
  1660   1744     if( zTemplate ) db_attach(zTemplate, "settingSrc");
  1661   1745     db_begin_transaction();
  1662   1746     if( zDate==0 ) zDate = "now";
  1663   1747     db_initial_setup(zTemplate, zDate, zDefaultUser);
  1664   1748     db_end_transaction(0);
  1665   1749     if( zTemplate ) db_detach("settingSrc");
  1666   1750     fossil_print("project-id: %s\n", db_get("project-code", 0));
................................................................................
  1991   2075   ** repository and local databases.
  1992   2076   **
  1993   2077   ** If no such variable exists, return zDefault.  Or, if zName is the name
  1994   2078   ** of a setting, then the zDefault is ignored and the default value of the
  1995   2079   ** setting is returned instead.  If zName is a versioned setting, then
  1996   2080   ** versioned value takes priority.
  1997   2081   */
  1998         -char *db_get(const char *zName, char *zDefault){
         2082  +char *db_get(const char *zName, const char *zDefault){
  1999   2083     char *z = 0;
  2000   2084     const Setting *pSetting = db_find_setting(zName, 0);
  2001   2085     if( g.repositoryOpen ){
  2002   2086       z = db_text(0, "SELECT value FROM config WHERE name=%Q", zName);
  2003   2087     }
  2004   2088     if( z==0 && g.zConfigDbName ){
  2005   2089       db_swap_connections();
................................................................................
  2011   2095       ** checked out file */
  2012   2096       z = db_get_versioned(zName, z);
  2013   2097     }
  2014   2098     if( z==0 ){
  2015   2099       if( zDefault==0 && pSetting && pSetting->def[0] ){
  2016   2100         z = fossil_strdup(pSetting->def);
  2017   2101       }else{
  2018         -      z = zDefault;
         2102  +      z = fossil_strdup(zDefault);
  2019   2103       }
  2020   2104     }
  2021   2105     return z;
  2022   2106   }
  2023         -char *db_get_mtime(const char *zName, char *zFormat, char *zDefault){
         2107  +char *db_get_mtime(const char *zName, const char *zFormat, const char *zDefault){
  2024   2108     char *z = 0;
  2025   2109     if( g.repositoryOpen ){
  2026   2110       z = db_text(0, "SELECT mtime FROM config WHERE name=%Q", zName);
  2027   2111     }
  2028   2112     if( z==0 ){
  2029         -    z = zDefault;
         2113  +    z = fossil_strdup(zDefault);
  2030   2114     }else if( zFormat!=0 ){
  2031   2115       z = db_text(0, "SELECT strftime(%Q,%Q,'unixepoch');", zFormat, z);
  2032   2116     }
  2033   2117     return z;
  2034   2118   }
  2035   2119   void db_set(const char *zName, const char *zValue, int globalFlag){
  2036   2120     db_begin_transaction();
................................................................................
  2108   2192   }
  2109   2193   int db_get_boolean(const char *zName, int dflt){
  2110   2194     char *zVal = db_get(zName, dflt ? "on" : "off");
  2111   2195     if( is_truth(zVal) ) return 1;
  2112   2196     if( is_false(zVal) ) return 0;
  2113   2197     return dflt;
  2114   2198   }
  2115         -char *db_lget(const char *zName, char *zDefault){
  2116         -  return db_text((char*)zDefault,
         2199  +int db_get_versioned_boolean(const char *zName, int dflt){
         2200  +  char *zVal = db_get_versioned(zName, 0);
         2201  +  if( zVal==0 ) return dflt;
         2202  +  if( is_truth(zVal) ) return 1;
         2203  +  if( is_false(zVal) ) return 0;
         2204  +  return dflt;
         2205  +}
         2206  +char *db_lget(const char *zName, const char *zDefault){
         2207  +  return db_text(zDefault,
  2117   2208                    "SELECT value FROM vvar WHERE name=%Q", zName);
  2118   2209   }
  2119   2210   void db_lset(const char *zName, const char *zValue){
  2120   2211     db_multi_exec("REPLACE INTO vvar(name,value) VALUES(%Q,%Q)", zName, zValue);
  2121   2212   }
  2122   2213   int db_lget_int(const char *zName, int dflt){
  2123   2214     return db_int(dflt, "SELECT value FROM vvar WHERE name=%Q", zName);
................................................................................
  2217   2308   ** See also: close
  2218   2309   */
  2219   2310   void cmd_open(void){
  2220   2311     int emptyFlag;
  2221   2312     int keepFlag;
  2222   2313     int forceMissingFlag;
  2223   2314     int allowNested;
         2315  +  int allowSymlinks;
  2224   2316     static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0, 0 };
  2225   2317   
  2226   2318     url_proxy_options();
  2227   2319     emptyFlag = find_option("empty",0,0)!=0;
  2228   2320     keepFlag = find_option("keep",0,0)!=0;
  2229   2321     forceMissingFlag = find_option("force-missing",0,0)!=0;
  2230   2322     allowNested = find_option("nested",0,0)!=0;
................................................................................
  2247   2339       }else if( db_exists("SELECT 1 FROM event WHERE type='ci'") ){
  2248   2340         g.zOpenRevision = db_get("main-branch", "trunk");
  2249   2341       }
  2250   2342     }
  2251   2343   
  2252   2344     if( g.zOpenRevision ){
  2253   2345       /* Since the repository is open and we know the revision now,
  2254         -    ** refresh the allow-symlinks flag. */
  2255         -    g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
         2346  +    ** refresh the allow-symlinks flag.  Since neither the local
         2347  +    ** checkout nor the configuration database are open at this
         2348  +    ** point, this should always return the versioned setting,
         2349  +    ** if any, or the default value, which is negative one.  The
         2350  +    ** value negative one, in this context, means that the code
         2351  +    ** below should fallback to using the setting value from the
         2352  +    ** repository or global configuration databases only. */
         2353  +    allowSymlinks = db_get_versioned_boolean("allow-symlinks", -1);
         2354  +  }else{
         2355  +    allowSymlinks = -1; /* Use non-versioned settings only. */
  2256   2356     }
  2257   2357   
  2258   2358   #if defined(_WIN32) || defined(__CYGWIN__)
  2259   2359   # define LOCALDB_NAME "./_FOSSIL_"
  2260   2360   #else
  2261   2361   # define LOCALDB_NAME "./.fslckout"
  2262   2362   #endif
................................................................................
  2263   2363     db_init_database(LOCALDB_NAME, zLocalSchema,
  2264   2364   #ifdef FOSSIL_LOCAL_WAL
  2265   2365                      "COMMIT; PRAGMA journal_mode=WAL; BEGIN;",
  2266   2366   #endif
  2267   2367                      (char*)0);
  2268   2368     db_delete_on_failure(LOCALDB_NAME);
  2269   2369     db_open_local(0);
         2370  +  if( allowSymlinks>=0 ){
         2371  +    /* Use the value from the versioned setting, which was read
         2372  +    ** prior to opening the local checkout (i.e. which is most
         2373  +    ** likely empty and does not actually contain any versioned
         2374  +    ** setting files yet).  Normally, this value would be given
         2375  +    ** first priority within db_get_boolean(); however, this is
         2376  +    ** a special case because we know the on-disk files may not
         2377  +    ** exist yet. */
         2378  +    g.allowSymlinks = allowSymlinks;
         2379  +  }else{
         2380  +    /* Since the local checkout may not have any files at this
         2381  +    ** point, this will probably be the setting value from the
         2382  +    ** repository or global configuration databases. */
         2383  +    g.allowSymlinks = db_get_boolean("allow-symlinks",
         2384  +                                     db_allow_symlinks_by_default());
         2385  +  }
  2270   2386     db_lset("repository", g.argv[2]);
  2271   2387     db_record_repository_filename(g.argv[2]);
  2272   2388     db_lset_int("checkout", 0);
  2273   2389     azNewArgv[0] = g.argv[0];
  2274   2390     g.argv = azNewArgv;
  2275   2391     if( !emptyFlag ){
  2276   2392       g.argc = 3;
................................................................................
  2354   2470     const char *def;      /* Default value */
  2355   2471   };
  2356   2472   #endif /* INTERFACE */
  2357   2473   
  2358   2474   const Setting aSetting[] = {
  2359   2475     { "access-log",       0,              0, 0, 0, "off"                 },
  2360   2476     { "admin-log",        0,              0, 0, 0, "off"                 },
         2477  +#if defined(_WIN32)
  2361   2478     { "allow-symlinks",   0,              0, 1, 0, "off"                 },
         2479  +#else
         2480  +  { "allow-symlinks",   0,              0, 1, 0, "on"                  },
         2481  +#endif
  2362   2482     { "auto-captcha",     "autocaptcha",  0, 0, 0, "on"                  },
  2363   2483     { "auto-hyperlink",   0,              0, 0, 0, "on",                 },
  2364   2484     { "auto-shun",        0,              0, 0, 0, "on"                  },
  2365   2485     { "autosync",         0,              0, 0, 0, "on"                  },
  2366   2486     { "autosync-tries",   0,             16, 0, 0, "1"                   },
  2367   2487     { "binary-glob",      0,             40, 1, 0, ""                    },
  2368   2488   #if defined(_WIN32) || defined(__CYGWIN__) || defined(__DARWIN__) || \
................................................................................
  2378   2498     { "diff-binary",      0,              0, 0, 0, "on"                  },
  2379   2499     { "diff-command",     0,             40, 0, 0, ""                    },
  2380   2500     { "dont-push",        0,              0, 0, 0, "off"                 },
  2381   2501     { "dotfiles",         0,              0, 1, 0, "off"                 },
  2382   2502     { "editor",           0,             32, 0, 0, ""                    },
  2383   2503     { "empty-dirs",       0,             40, 1, 0, ""                    },
  2384   2504     { "encoding-glob",    0,             40, 1, 0, ""                    },
         2505  +#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
         2506  +  { "exec-rel-paths",   0,              0, 0, 0, "on"                  },
         2507  +#else
         2508  +  { "exec-rel-paths",   0,              0, 0, 0, "off"                 },
         2509  +#endif
  2385   2510     { "gdiff-command",    0,             40, 0, 0, "gdiff"               },
  2386   2511     { "gmerge-command",   0,             40, 0, 0, ""                    },
  2387   2512     { "hash-digits",      0,              5, 0, 0, "10"                  },
  2388   2513     { "http-port",        0,             16, 0, 0, "8080"                },
  2389   2514     { "https-login",      0,              0, 0, 0, "off"                 },
  2390   2515     { "ignore-glob",      0,             40, 1, 0, ""                    },
  2391   2516     { "keep-glob",        0,             40, 1, 0, ""                    },
................................................................................
  2553   2678   **                     created.
  2554   2679   **
  2555   2680   **    encoding-glob    The VALUE is a comma or newline-separated list of GLOB
  2556   2681   **     (versionable)   patterns specifying files that the "commit" command will
  2557   2682   **                     ignore when issuing warnings about text files that may
  2558   2683   **                     use another encoding than ASCII or UTF-8. Set to "*"
  2559   2684   **                     to disable encoding checking.
         2685  +**
         2686  +**    exec-rel-paths   When executing certain external commands (e.g. diff and
         2687  +**                     gdiff), use relative paths.
  2560   2688   **
  2561   2689   **    gdiff-command    External command to run when performing a graphical
  2562   2690   **                     diff. If undefined, text diff will be used.
  2563   2691   **
  2564   2692   **    gmerge-command   A graphical merge conflict resolver command operating
  2565   2693   **                     on four files.
  2566   2694   **                     Ex: kdiff3 "%baseline" "%original" "%merge" -o "%output"
................................................................................
  2701   2829   **
  2702   2830   ** See also: configuration
  2703   2831   */
  2704   2832   void setting_cmd(void){
  2705   2833     int i;
  2706   2834     int globalFlag = find_option("global","g",0)!=0;
  2707   2835     int unsetFlag = g.argv[1][0]=='u';
  2708         -  db_open_config(1);
         2836  +  db_open_config(1, 0);
  2709   2837     if( !globalFlag ){
  2710   2838       db_find_and_open_repository(OPEN_ANY_SCHEMA | OPEN_OK_NOT_FOUND, 0);
  2711   2839     }
  2712   2840     if( !g.repositoryOpen ){
  2713   2841       globalFlag = 1;
  2714   2842     }
  2715   2843     if( unsetFlag && g.argc!=3 ){

Changes to src/delta.c.

   100    100   };
   101    101   
   102    102   /*
   103    103   ** Initialize the rolling hash using the first NHASH characters of z[]
   104    104   */
   105    105   static void hash_init(hash *pHash, const char *z){
   106    106     u16 a, b, i;
   107         -  a = b = 0;
   108         -  for(i=0; i<NHASH; i++){
          107  +  a = b = z[0];
          108  +  for(i=1; i<NHASH; i++){
   109    109       a += z[i];
   110         -    b += (NHASH-i)*z[i];
   111         -    pHash->z[i] = z[i];
          110  +    b += a;
   112    111     }
          112  +  memcpy(pHash->z, z, NHASH);
   113    113     pHash->a = a & 0xffff;
   114    114     pHash->b = b & 0xffff;
   115    115     pHash->i = 0;
   116    116   }
   117    117   
   118    118   /*
   119    119   ** Advance the rolling hash by a single character "c"
................................................................................
   128    128   
   129    129   /*
   130    130   ** Return a 32-bit hash value
   131    131   */
   132    132   static u32 hash_32bit(hash *pHash){
   133    133     return (pHash->a & 0xffff) | (((u32)(pHash->b & 0xffff))<<16);
   134    134   }
          135  +
          136  +/*
          137  +** Compute a hash on NHASH bytes.
          138  +**
          139  +** This routine is intended to be equivalent to:
          140  +**    hash h;
          141  +**    hash_init(&h, zInput);
          142  +**    return hash_32bit(&h);
          143  +*/
          144  +static u32 hash_once(const char *z){
          145  +  u16 a, b, i;
          146  +  a = b = z[0];
          147  +  for(i=1; i<NHASH; i++){
          148  +    a += z[i];
          149  +    b += a;
          150  +  }
          151  +  return a | (((u32)b)<<16);
          152  +}
   135    153   
   136    154   /*
   137    155   ** Write an base-64 integer into the given buffer.
   138    156   */
   139    157   static void putInt(unsigned int v, char **pz){
   140    158     static const char zDigits[] =
   141    159       "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
................................................................................
   189    207   */
   190    208   static int digit_count(int v){
   191    209     unsigned int i, x;
   192    210     for(i=1, x=64; v>=x; i++, x <<= 6){}
   193    211     return i;
   194    212   }
   195    213   
          214  +#ifdef __GNUC__
          215  +# define GCC_VERSION (__GNUC__*1000000+__GNUC_MINOR__*1000+__GNUC_PATCHLEVEL__)
          216  +#else
          217  +# define GCC_VERSION 0
          218  +#endif
          219  +
   196    220   /*
   197         -** Compute a 32-bit checksum on the N-byte buffer.  Return the result.
          221  +** Compute a 32-bit big-endian checksum on the N-byte buffer.  If the
          222  +** buffer is not a multiple of 4 bytes length, compute the sum that would
          223  +** have occurred if the buffer was padded with zeros to the next multiple
          224  +** of four bytes.
   198    225   */
   199    226   static unsigned int checksum(const char *zIn, size_t N){
          227  +  static const int byteOrderTest = 1;
   200    228     const unsigned char *z = (const unsigned char *)zIn;
   201         -  unsigned sum0 = 0;
   202         -  unsigned sum1 = 0;
   203         -  unsigned sum2 = 0;
   204         -  unsigned sum3 = 0;
   205         -  while(N >= 16){
   206         -    sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
   207         -    sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
   208         -    sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
   209         -    sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
   210         -    z += 16;
   211         -    N -= 16;
   212         -  }
   213         -  while(N >= 4){
   214         -    sum0 += z[0];
   215         -    sum1 += z[1];
   216         -    sum2 += z[2];
   217         -    sum3 += z[3];
   218         -    z += 4;
   219         -    N -= 4;
          229  +  const unsigned char *zEnd = (const unsigned char*)&zIn[N&~3];
          230  +  unsigned sum = 0;
          231  +  assert( (z - (const unsigned char*)0)%4==0 );  /* Four-byte alignment */
          232  +  if( 0==*(char*)&byteOrderTest ){
          233  +    /* This is a big-endian machine */
          234  +    while( z<zEnd ){
          235  +      sum += *(unsigned*)z;
          236  +      z += 4;
          237  +    }
          238  +  }else{
          239  +    /* A little-endian machine */
          240  +#if GCC_VERSION>=4003000
          241  +    while( z<zEnd ){
          242  +      sum += __builtin_bswap32(*(unsigned*)z);
          243  +      z += 4;
          244  +    }
          245  +#elif defined(_MSC_VER) && _MSC_VER>=1300
          246  +    while( z<zEnd ){
          247  +      sum += _byteswap_ulong(*(unsigned*)z);
          248  +      z += 4;
          249  +    }
          250  +#else    
          251  +    unsigned sum0 = 0;
          252  +    unsigned sum1 = 0;
          253  +    unsigned sum2 = 0;
          254  +    while(N >= 16){
          255  +      sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
          256  +      sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
          257  +      sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
          258  +      sum  += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
          259  +      z += 16;
          260  +      N -= 16;
          261  +    }
          262  +    while(N >= 4){
          263  +      sum0 += z[0];
          264  +      sum1 += z[1];
          265  +      sum2 += z[2];
          266  +      sum  += z[3];
          267  +      z += 4;
          268  +      N -= 4;
          269  +    }
          270  +    sum += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
          271  +#endif
   220    272     }
   221         -  sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
   222         -  switch(N){
   223         -    case 3:   sum3 += (z[2] << 8);
   224         -    case 2:   sum3 += (z[1] << 16);
   225         -    case 1:   sum3 += (z[0] << 24);
          273  +  switch(N&3){
          274  +    case 3:   sum += (z[2] << 8);
          275  +    case 2:   sum += (z[1] << 16);
          276  +    case 1:   sum += (z[0] << 24);
   226    277       default:  ;
   227    278     }
   228         -  return sum3;
          279  +  return sum;
   229    280   }
   230    281   
   231    282   /*
   232    283   ** Create a new delta.
   233    284   **
   234    285   ** The delta is written into a preallocated buffer, zDelta, which
   235    286   ** should be at least 60 bytes longer than the target file, zOut.
................................................................................
   328    379     */
   329    380     nHash = lenSrc/NHASH;
   330    381     collide = fossil_malloc( nHash*2*sizeof(int) );
   331    382     landmark = &collide[nHash];
   332    383     memset(landmark, -1, nHash*sizeof(int));
   333    384     memset(collide, -1, nHash*sizeof(int));
   334    385     for(i=0; i<lenSrc-NHASH; i+=NHASH){
   335         -    int hv;
   336         -    hash_init(&h, &zSrc[i]);
   337         -    hv = hash_32bit(&h) % nHash;
          386  +    int hv = hash_once(&zSrc[i]) % nHash;
   338    387       collide[i/NHASH] = landmark[hv];
   339    388       landmark[hv] = i/NHASH;
   340    389     }
   341    390   
   342    391     /* Begin scanning the target file and generating copy commands and
   343    392     ** literal sections of the delta.
   344    393     */
................................................................................
   371    420           ** sz will be the overhead (in bytes) needed to encode the copy
   372    421           ** command.  Only generate copy command if the overhead of the
   373    422           ** copy command is less than the amount of literal text to be copied.
   374    423           */
   375    424           int cnt, ofst, litsz;
   376    425           int j, k, x, y;
   377    426           int sz;
          427  +        int limitX;
   378    428   
   379    429           /* Beginning at iSrc, match forwards as far as we can.  j counts
   380    430           ** the number of characters that match */
   381    431           iSrc = iBlock*NHASH;
   382         -        for(j=0, x=iSrc, y=base+i; x<lenSrc && y<lenOut; j++, x++, y++){
          432  +        y = base+i;
          433  +        limitX = ( lenSrc-iSrc <= lenOut-y ) ? lenSrc : iSrc + lenOut - y;
          434  +        for(x=iSrc; x<limitX; x++, y++){
   383    435             if( zSrc[x]!=zOut[y] ) break;
   384    436           }
   385         -        j--;
          437  +        j = x - iSrc - 1;
   386    438   
   387    439           /* Beginning at iSrc-1, match backwards as far as we can.  k counts
   388    440           ** the number of characters that match */
   389    441           for(k=1; k<iSrc && k<=i; k++){
   390    442             if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
   391    443           }
   392    444           k--;
................................................................................
   618    670     (void)getInt(&zDelta, &lenDelta);
   619    671     if( *zDelta!='\n' ){
   620    672       /* ERROR: size integer not terminated by "\n" */
   621    673       return -1;
   622    674     }
   623    675     zDelta++; lenDelta--;
   624    676     while( *zDelta && lenDelta>0 ){
   625         -    unsigned int cnt, ofst;
          677  +    unsigned int cnt;
   626    678       cnt = getInt(&zDelta, &lenDelta);
   627    679       switch( zDelta[0] ){
   628    680         case '@': {
   629    681           zDelta++; lenDelta--;
   630         -        ofst = getInt(&zDelta, &lenDelta);
          682  +        (void)getInt(&zDelta, &lenDelta);
   631    683           if( lenDelta>0 && zDelta[0]!=',' ){
   632    684             /* ERROR: copy command not terminated by ',' */
   633    685             return -1;
   634    686           }
   635    687           zDelta++; lenDelta--;
   636    688           nCopy += cnt;
   637    689           break;

Changes to src/deltacmd.c.

    78     78   ** Create and a delta that carries FILE1 into FILE2.  Print the
    79     79   ** number bytes copied and the number of bytes inserted.
    80     80   */
    81     81   void delta_analyze_cmd(void){
    82     82     Blob orig, target, delta;
    83     83     int nCopy = 0;
    84     84     int nInsert = 0;
    85         -  int sz1, sz2;
           85  +  int sz1, sz2, sz3;
    86     86     if( g.argc!=4 ){
    87     87       usage("ORIGIN TARGET");
    88     88     }
    89     89     if( blob_read_from_file(&orig, g.argv[2])<0 ){
    90     90       fossil_fatal("cannot read %s\n", g.argv[2]);
    91     91     }
    92     92     if( blob_read_from_file(&target, g.argv[3])<0 ){
    93     93       fossil_fatal("cannot read %s\n", g.argv[3]);
    94     94     }
    95     95     blob_delta_create(&orig, &target, &delta);
    96     96     delta_analyze(blob_buffer(&delta), blob_size(&delta), &nCopy, &nInsert);
    97     97     sz1 = blob_size(&orig);
    98     98     sz2 = blob_size(&target);
           99  +  sz3 = blob_size(&delta);
    99    100     blob_reset(&orig);
   100    101     blob_reset(&target);
   101    102     blob_reset(&delta);
   102    103     fossil_print("original size:  %8d\n", sz1);
   103         -  fossil_print("bytes copied:   %8d (%.1f%% of target)\n",
          104  +  fossil_print("bytes copied:   %8d (%.2f%% of target)\n",
   104    105                  nCopy, (100.0*nCopy)/sz2);
   105         -  fossil_print("bytes inserted: %8d (%.1f%% of target)\n",
          106  +  fossil_print("bytes inserted: %8d (%.2f%% of target)\n",
   106    107                  nInsert, (100.0*nInsert)/sz2);
   107    108     fossil_print("final size:     %8d\n", sz2);
          109  +  fossil_print("delta size:     %8d\n", sz3);
   108    110   }
   109    111   
   110    112   /*
   111    113   ** Apply the delta in pDelta to the original file pOriginal to generate
   112    114   ** the target file pTarget.  The pTarget blob is initialized by this
   113    115   ** routine.
   114    116   **

Changes to src/descendants.c.

   178    178       "INSERT INTO ok"
   179    179       "  SELECT rid FROM ancestor;",
   180    180       rid, rid, directOnly ? "AND plink.isPrim" : "", N
   181    181     );
   182    182   }
   183    183   
   184    184   /*
   185         -** Compute up to N direct ancestors (merge ancestors do not count)
          185  +** Compute all direct ancestors (merge ancestors do not count)
   186    186   ** for the check-in rid and put them in a table named "ancestor".
   187    187   ** Label each generation with consecutive integers going backwards
   188    188   ** in time such that rid has the smallest generation number and the oldest
   189    189   ** direct ancestor as the largest generation number.
   190    190   */
   191         -void compute_direct_ancestors(int rid, int N){
   192         -  Stmt ins;
   193         -  Stmt q;
   194         -  int gen = 0;
          191  +void compute_direct_ancestors(int rid){
   195    192     db_multi_exec(
   196    193       "CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER UNIQUE NOT NULL,"
   197    194                                               " generation INTEGER PRIMARY KEY);"
   198    195       "DELETE FROM ancestor;"
   199         -    "INSERT INTO ancestor VALUES(%d, 0);", rid
          196  +    "WITH RECURSIVE g(x,i) AS ("
          197  +    "  VALUES(%d,1)"
          198  +    "  UNION ALL"
          199  +    "  SELECT plink.pid, g.i+1 FROM plink, g"
          200  +    "   WHERE plink.cid=g.x AND plink.isprim)"
          201  +    "INSERT INTO ancestor(rid,generation) SELECT x,i FROM g;", 
          202  +    rid
   200    203     );
   201         -  db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
   202         -  db_prepare(&q,
   203         -    "SELECT pid FROM plink"
   204         -    " WHERE cid=:rid AND isprim"
   205         -  );
   206         -  while( (N--)>0 ){
   207         -    db_bind_int(&q, ":rid", rid);
   208         -    if( db_step(&q)!=SQLITE_ROW ) break;
   209         -    rid = db_column_int(&q, 0);
   210         -    db_reset(&q);
   211         -    gen++;
   212         -    db_bind_int(&ins, ":rid", rid);
   213         -    db_bind_int(&ins, ":gen", gen);
   214         -    db_step(&ins);
   215         -    db_reset(&ins);
   216         -  }
   217         -  db_finalize(&ins);
   218         -  db_finalize(&q);
   219    204   }
   220    205   
   221    206   /*
   222    207   ** Compute the "mtime" of the file given whose blob.rid is "fid" that
   223    208   ** is part of check-in "vid".  The mtime will be the mtime on vid or
   224    209   ** some ancestor of vid where fid first appears.
   225    210   */

Changes to src/diff.c.

   787    787         nSuffix++;
   788    788       }
   789    789       if( nSuffix<nShort ){
   790    790         while( nSuffix>0 && (zLeft[nLeft-nSuffix]&0xc0)==0x80 ) nSuffix--;
   791    791       }
   792    792       if( nSuffix==nLeft || nSuffix==nRight ) nPrefix = 0;
   793    793     }
   794         -  if( nPrefix+nSuffix > nShort ) nPrefix = nShort - nSuffix;
          794  +
          795  +  /* If the prefix and suffix overlap, that means that we are dealing with
          796  +  ** a pure insertion or deletion of text that can have multiple alignments.
          797  +  ** Try to find an alignment to begins and ends on whitespace, or on
          798  +  ** punctuation, rather than in the middle of a name or number.
          799  +  */
          800  +  if( nPrefix+nSuffix > nShort ){
          801  +    int iBest = -1;
          802  +    int iBestVal = -1;
          803  +    int i;
          804  +    int nLong = nLeft<nRight ? nRight : nLeft;
          805  +    int nGap = nLong - nShort;
          806  +    for(i=nShort-nSuffix; i<=nPrefix; i++){
          807  +       int iVal = 0;
          808  +       char c = zLeft[i];
          809  +       if( fossil_isspace(c) ){
          810  +         iVal += 5;
          811  +       }else if( !fossil_isalnum(c) ){
          812  +         iVal += 2;
          813  +       }
          814  +       c = zLeft[i+nGap-1];
          815  +       if( fossil_isspace(c) ){
          816  +         iVal += 5;
          817  +       }else if( !fossil_isalnum(c) ){
          818  +         iVal += 2;
          819  +       }
          820  +       if( iVal>iBestVal ){
          821  +         iBestVal = iVal;
          822  +         iBest = i;
          823  +       }
          824  +    }
          825  +    nPrefix = iBest;
          826  +    nSuffix = nShort - nPrefix;
          827  +  }
   795    828   
   796    829     /* A single chunk of text inserted on the right */
   797    830     if( nPrefix+nSuffix==nLeft ){
   798    831       sbsWriteLineno(p, lnLeft, SBS_LNA);
   799    832       p->iStart2 = p->iEnd2 = 0;
   800    833       p->iStart = p->iEnd = -1;
   801    834       sbsWriteText(p, pLeft, SBS_TXTA);
................................................................................
  2053   2086     p->nOrig = p->c.nTo;
  2054   2087     return 0;
  2055   2088   }
  2056   2089   
  2057   2090   /*
  2058   2091   ** The input pParent is the next most recent ancestor of the file
  2059   2092   ** being annotated.  Do another step of the annotation.  Return true
  2060         -** if additional annotation is required.  zPName is the tag to insert
  2061         -** on each line of the file being annotated that was contributed by
  2062         -** pParent.  Memory to hold zPName is leaked.
         2093  +** if additional annotation is required.
  2063   2094   */
  2064   2095   static int annotation_step(Annotator *p, Blob *pParent, int iVers, u64 diffFlags){
  2065   2096     int i, j;
  2066   2097     int lnTo;
  2067   2098   
  2068   2099     /* Prepare the parent file to be diffed */
  2069   2100     p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent),
................................................................................
  2106   2137   
  2107   2138   /* Annotation flags (any DIFF flag can be used as Annotation flag as well) */
  2108   2139   #define ANN_FILE_VERS   (((u64)0x20)<<32) /* Show file vers rather than commit vers */
  2109   2140   #define ANN_FILE_ANCEST (((u64)0x40)<<32) /* Prefer check-ins in the ANCESTOR table */
  2110   2141   
  2111   2142   /*
  2112   2143   ** Compute a complete annotation on a file.  The file is identified
  2113         -** by its filename number (filename.fnid) and the baseline in which
  2114         -** it was checked in (mlink.mid).
         2144  +** by its filename number (filename.fnid) and check-in (mlink.mid).
  2115   2145   */
  2116   2146   static void annotate_file(
  2117   2147     Annotator *p,        /* The annotator */
  2118   2148     int fnid,            /* The name of the file to be annotated */
  2119   2149     int mid,             /* Use the version of the file in this check-in */
  2120   2150     int iLimit,          /* Limit the number of levels if greater than zero */
  2121   2151     u64 annFlags         /* Flags to alter the annotation */
................................................................................
  2264   2294     ignoreWs = P("w")!=0;
  2265   2295     if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS;
  2266   2296     if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
  2267   2297       fossil_redirect_home();
  2268   2298     }
  2269   2299   
  2270   2300     /* compute the annotation */
  2271         -  compute_direct_ancestors(mid, 10000000);
         2301  +  compute_direct_ancestors(mid);
  2272   2302     annotate_file(&ann, fnid, mid, iLimit, annFlags);
  2273   2303     zCI = ann.aVers[0].zMUuid;
  2274   2304   
  2275   2305     /* generate the web page */
  2276   2306     style_header("Annotation For %h", zFilename);
  2277   2307     if( bBlame ){
  2278   2308       url_initialize(&url, "blame");
................................................................................
  2470   2500       fossil_fatal("not part of current checkout: %s", zFilename);
  2471   2501     }
  2472   2502     cid = db_lget_int("checkout", 0);
  2473   2503     if( cid == 0 ){
  2474   2504       fossil_fatal("Not in a checkout");
  2475   2505     }
  2476   2506     if( iLimit<=0 ) iLimit = 1000000000;
  2477         -  compute_direct_ancestors(cid, 1000000);
         2507  +  compute_direct_ancestors(cid);
  2478   2508     mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
  2479   2509             " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
  2480   2510             " ORDER BY ancestor.generation ASC LIMIT 1",
  2481   2511             fid, fnid);
  2482   2512     if( mid==0 ){
  2483   2513       fossil_fatal("unable to find manifest");
  2484   2514     }

Changes to src/diffcmd.c.

    30     30   #  define NULL_DEVICE "/dev/null"
    31     31   #endif
    32     32   
    33     33   /*
    34     34   ** Used when the name for the diff is unknown.
    35     35   */
    36     36   #define DIFF_NO_NAME  "(unknown)"
           37  +
           38  +/*
           39  +** Use the "exec-rel-paths" setting and the --exec-abs-paths and
           40  +** --exec-rel-paths command line options to determine whether
           41  +** certain external commands are executed using relative paths.
           42  +*/
           43  +static int determine_exec_relative_option(int force){
           44  +  static int relativePaths = -1;
           45  +  if( force || relativePaths==-1 ){
           46  +    int relPathOption = find_option("exec-rel-paths", 0, 0)!=0;
           47  +    int absPathOption = find_option("exec-abs-paths", 0, 0)!=0;
           48  +#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
           49  +    relativePaths = db_get_boolean("exec-rel-paths", 1);
           50  +#else
           51  +    relativePaths = db_get_boolean("exec-rel-paths", 0);
           52  +#endif
           53  +    if( relPathOption ){ relativePaths = 1; }
           54  +    if( absPathOption ){ relativePaths = 0; }
           55  +  }
           56  +  return relativePaths;
           57  +}
           58  +
           59  +#if INTERFACE
           60  +/*
           61  +** An array of FileDirList objects describe the files and directories listed
           62  +** on the command line of a "diff" command.  Only those objects listed are
           63  +** actually diffed.
           64  +*/
           65  +struct FileDirList {
           66  +  int nUsed;       /* Number of times each entry is used */
           67  +  int nName;       /* Length of the entry */
           68  +  char *zName;     /* Text of the entry */
           69  +};
           70  +#endif
           71  +
           72  +/*
           73  +** Return true if zFile is a file named on the azInclude[] list or is
           74  +** a file in a directory named on the azInclude[] list.
           75  +**
           76  +** if azInclude is NULL, then always include zFile.
           77  +*/
           78  +static int file_dir_match(FileDirList *p, const char *zFile){
           79  +  if( p==0 || strcmp(p->zName,".")==0 ) return 1;
           80  +  if( filenames_are_case_sensitive() ){
           81  +    while( p->zName ){
           82  +      if( strcmp(zFile, p->zName)==0
           83  +       || (strncmp(zFile, p->zName, p->nName)==0
           84  +           && zFile[p->nName]=='/')
           85  +      ){
           86  +        break;
           87  +      }
           88  +      p++;
           89  +    }
           90  +  }else{
           91  +    while( p->zName ){
           92  +      if( fossil_stricmp(zFile, p->zName)==0
           93  +       || (fossil_strnicmp(zFile, p->zName, p->nName)==0
           94  +           && zFile[p->nName]=='/')
           95  +      ){
           96  +        break;
           97  +      }
           98  +      p++;
           99  +    }
          100  +  }
          101  +  if( p->zName ){
          102  +    p->nUsed++;
          103  +    return 1;
          104  +  }
          105  +  return 0;
          106  +}
    37    107   
    38    108   /*
    39    109   ** Print the "Index:" message that patches wants to see at the top of a diff.
    40    110   */
    41    111   void diff_print_index(const char *zFile, u64 diffFlags){
    42    112     if( (diffFlags & (DIFF_SIDEBYSIDE|DIFF_BRIEF))==0 ){
    43    113       char *z = mprintf("Index: %s\n%.66c\n", zFile, '=');
................................................................................
   273    343       /* Delete the temporary file and clean up memory used */
   274    344       file_delete(zTemp1);
   275    345       file_delete(zTemp2);
   276    346       blob_reset(&cmd);
   277    347     }
   278    348   }
   279    349   
   280         -/*
   281         -** Do a diff against a single file named in zFileTreeName from version zFrom
   282         -** against the same file on disk.
   283         -**
   284         -** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
   285         -** command zDiffCmd to do the diffing.
   286         -**
   287         -** When using an external diff program, zBinGlob contains the GLOB patterns
   288         -** for file names to treat as binary.  If fIncludeBinary is zero, these files
   289         -** will be skipped in addition to files that may contain binary content.
   290         -*/
   291         -static void diff_one_against_disk(
   292         -  const char *zFrom,        /* Name of file */
   293         -  const char *zDiffCmd,     /* Use this "diff" command */
   294         -  const char *zBinGlob,     /* Treat file names matching this as binary */
   295         -  int fIncludeBinary,       /* Include binary files for external diff */
   296         -  u64 diffFlags,            /* Diff control flags */
   297         -  const char *zFileTreeName
   298         -){
   299         -  Blob fname;
   300         -  Blob content;
   301         -  int isLink;
   302         -  int isBin;
   303         -  file_tree_name(zFileTreeName, &fname, 0, 1);
   304         -  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0,
   305         -                             fIncludeBinary ? 0 : &isBin, 0);
   306         -  if( !isLink != !file_wd_islink(zFrom) ){
   307         -    fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK);
   308         -  }else{
   309         -    diff_file(&content, isBin, zFileTreeName, zFileTreeName,
   310         -              zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   311         -  }
   312         -  blob_reset(&content);
   313         -  blob_reset(&fname);
   314         -}
   315         -
   316    350   /*
   317    351   ** Run a diff between the version zFrom and files on disk.  zFrom might
   318    352   ** be NULL which means to simply show the difference between the edited
   319    353   ** files on disk and the check-out on which they are based.
   320    354   **
   321    355   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
   322    356   ** command zDiffCmd to do the diffing.
   323    357   **
   324    358   ** When using an external diff program, zBinGlob contains the GLOB patterns
   325    359   ** for file names to treat as binary.  If fIncludeBinary is zero, these files
   326    360   ** will be skipped in addition to files that may contain binary content.
   327    361   */
   328         -static void diff_all_against_disk(
          362  +static void diff_against_disk(
   329    363     const char *zFrom,        /* Version to difference from */
   330    364     const char *zDiffCmd,     /* Use this diff command.  NULL for built-in */
   331    365     const char *zBinGlob,     /* Treat file names matching this as binary */
   332    366     int fIncludeBinary,       /* Treat file names matching this as binary */
   333         -  u64 diffFlags             /* Flags controlling diff output */
          367  +  u64 diffFlags,            /* Flags controlling diff output */
          368  +  FileDirList *pFileDir     /* Which files to diff */
   334    369   ){
   335    370     int vid;
   336    371     Blob sql;
   337    372     Stmt q;
   338    373     int asNewFile;            /* Treat non-existant files as empty files */
   339    374   
   340    375     asNewFile = (diffFlags & DIFF_VERBOSE)!=0;
................................................................................
   382    417     while( db_step(&q)==SQLITE_ROW ){
   383    418       const char *zPathname = db_column_text(&q,0);
   384    419       int isDeleted = db_column_int(&q, 1);
   385    420       int isChnged = db_column_int(&q,2);
   386    421       int isNew = db_column_int(&q,3);
   387    422       int srcid = db_column_int(&q, 4);
   388    423       int isLink = db_column_int(&q, 5);
   389         -    char *zToFree = mprintf("%s%s", g.zLocalRoot, zPathname);
   390         -    const char *zFullName = zToFree;
          424  +    const char *zFullName;
   391    425       int showDiff = 1;
          426  +    Blob fname;
          427  +
          428  +    if( !file_dir_match(pFileDir, zPathname) ) continue;
          429  +    if( determine_exec_relative_option(0) ){
          430  +      blob_zero(&fname);
          431  +      file_relative_name(zPathname, &fname, 1);
          432  +    }else{
          433  +      blob_set(&fname, g.zLocalRoot);
          434  +      blob_append(&fname, zPathname, -1);
          435  +    }
          436  +    zFullName = blob_str(&fname);
   392    437       if( isDeleted ){
   393    438         fossil_print("DELETED  %s\n", zPathname);
   394    439         if( !asNewFile ){ showDiff = 0; zFullName = NULL_DEVICE; }
   395    440       }else if( file_access(zFullName, F_OK) ){
   396    441         fossil_print("MISSING  %s\n", zPathname);
   397    442         if( !asNewFile ){ showDiff = 0; }
   398    443       }else if( isNew ){
   399    444         fossil_print("ADDED    %s\n", zPathname);
   400    445         srcid = 0;
   401    446         if( !asNewFile ){ showDiff = 0; }
   402    447       }else if( isChnged==3 ){
   403    448         fossil_print("ADDED_BY_MERGE %s\n", zPathname);
   404    449         srcid = 0;
          450  +      if( !asNewFile ){ showDiff = 0; }
          451  +    }else if( isChnged==5 ){
          452  +      fossil_print("ADDED_BY_INTEGRATE %s\n", zPathname);
          453  +      srcid = 0;
   405    454         if( !asNewFile ){ showDiff = 0; }
   406    455       }
   407    456       if( showDiff ){
   408    457         Blob content;
   409    458         int isBin;
   410    459         if( !isLink != !file_wd_islink(zFullName) ){
   411    460           diff_print_index(zPathname, diffFlags);
................................................................................
   420    469         }
   421    470         isBin = fIncludeBinary ? 0 : looks_like_binary(&content);
   422    471         diff_print_index(zPathname, diffFlags);
   423    472         diff_file(&content, isBin, zFullName, zPathname, zDiffCmd,
   424    473                   zBinGlob, fIncludeBinary, diffFlags);
   425    474         blob_reset(&content);
   426    475       }
   427         -    free(zToFree);
          476  +    blob_reset(&fname);
   428    477     }
   429    478     db_finalize(&q);
   430    479     db_end_transaction(1);  /* ROLLBACK */
   431    480   }
   432    481   
   433    482   /*
   434         -** Output the differences between two versions of a single file.
   435         -** zFrom and zTo are the check-ins containing the two file versions.
          483  +** Run a diff between the undo buffer and files on disk.
   436    484   **
   437    485   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
   438    486   ** command zDiffCmd to do the diffing.
   439    487   **
   440    488   ** When using an external diff program, zBinGlob contains the GLOB patterns
   441    489   ** for file names to treat as binary.  If fIncludeBinary is zero, these files
   442    490   ** will be skipped in addition to files that may contain binary content.
   443    491   */
   444         -static void diff_one_two_versions(
   445         -  const char *zFrom,
   446         -  const char *zTo,
   447         -  const char *zDiffCmd,
   448         -  const char *zBinGlob,
   449         -  int fIncludeBinary,
   450         -  u64 diffFlags,
   451         -  const char *zFileTreeName
          492  +static void diff_against_undo(
          493  +  const char *zDiffCmd,     /* Use this diff command.  NULL for built-in */
          494  +  const char *zBinGlob,     /* Treat file names matching this as binary */
          495  +  int fIncludeBinary,       /* Treat file names matching this as binary */
          496  +  u64 diffFlags,            /* Flags controlling diff output */
          497  +  FileDirList *pFileDir     /* List of files and directories to diff */
   452    498   ){
   453         -  char *zName;
   454         -  Blob fname;
   455         -  Blob v1, v2;
   456         -  int isLink1, isLink2;
   457         -  int isBin1, isBin2;
   458         -  if( diffFlags & DIFF_BRIEF ) return;
   459         -  file_tree_name(zFileTreeName, &fname, 0, 1);
   460         -  zName = blob_str(&fname);
   461         -  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0,
   462         -                             fIncludeBinary ? 0 : &isBin1, 0);
   463         -  historical_version_of_file(zTo, zName, &v2, &isLink2, 0,
   464         -                             fIncludeBinary ? 0 : &isBin2, 0);
   465         -  if( isLink1 != isLink2 ){
   466         -    diff_print_filenames(zName, zName, diffFlags);
   467         -    fossil_print("%s",DIFF_CANNOT_COMPUTE_SYMLINK);
   468         -  }else{
   469         -    diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd,
   470         -                  zBinGlob, fIncludeBinary, diffFlags);
          499  +  Stmt q;
          500  +  Blob content;
          501  +  db_prepare(&q, "SELECT pathname, content FROM undo");
          502  +  blob_init(&content, 0, 0);
          503  +  while( db_step(&q)==SQLITE_ROW ){
          504  +    char *zFullName;
          505  +    const char *zFile = (const char*)db_column_text(&q, 0);
          506  +    if( !file_dir_match(pFileDir, zFile) ) continue;
          507  +    zFullName = mprintf("%s%s", g.zLocalRoot, zFile);
          508  +    db_column_blob(&q, 1, &content);
          509  +    diff_file(&content, 0, zFullName, zFile,
          510  +              zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
          511  +    fossil_free(zFullName);
          512  +    blob_reset(&content);
   471    513     }
   472         -  blob_reset(&v1);
   473         -  blob_reset(&v2);
   474         -  blob_reset(&fname);
          514  +  db_finalize(&q);
   475    515   }
   476    516   
   477    517   /*
   478    518   ** Show the difference between two files identified by ManifestFile
   479    519   ** entries.
   480    520   **
   481    521   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
................................................................................
   532    572   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
   533    573   ** command zDiffCmd to do the diffing.
   534    574   **
   535    575   ** When using an external diff program, zBinGlob contains the GLOB patterns
   536    576   ** for file names to treat as binary.  If fIncludeBinary is zero, these files
   537    577   ** will be skipped in addition to files that may contain binary content.
   538    578   */
   539         -static void diff_all_two_versions(
          579  +static void diff_two_versions(
   540    580     const char *zFrom,
   541    581     const char *zTo,
   542    582     const char *zDiffCmd,
   543    583     const char *zBinGlob,
   544    584     int fIncludeBinary,
   545         -  u64 diffFlags
          585  +  u64 diffFlags,
          586  +  FileDirList *pFileDir
   546    587   ){
   547    588     Manifest *pFrom, *pTo;
   548    589     ManifestFile *pFromFile, *pToFile;
   549    590     int asNewFlag = (diffFlags & DIFF_VERBOSE)!=0 ? 1 : 0;
   550    591   
   551    592     pFrom = manifest_get_by_name(zFrom, 0);
   552    593     manifest_file_rewind(pFrom);
................................................................................
   561    602         cmp = +1;
   562    603       }else if( pToFile==0 ){
   563    604         cmp = -1;
   564    605       }else{
   565    606         cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
   566    607       }
   567    608       if( cmp<0 ){
   568         -      fossil_print("DELETED %s\n", pFromFile->zName);
   569         -      if( asNewFlag ){
   570         -        diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
   571         -                            fIncludeBinary, diffFlags);
          609  +      if( file_dir_match(pFileDir, pFromFile->zName) ){
          610  +        fossil_print("DELETED %s\n", pFromFile->zName);
          611  +        if( asNewFlag ){
          612  +          diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
          613  +                              fIncludeBinary, diffFlags);
          614  +        }
   572    615         }
   573    616         pFromFile = manifest_file_next(pFrom,0);
   574    617       }else if( cmp>0 ){
   575         -      fossil_print("ADDED   %s\n", pToFile->zName);
   576         -      if( asNewFlag ){
   577         -        diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
   578         -                            fIncludeBinary, diffFlags);
          618  +      if( file_dir_match(pFileDir, pToFile->zName) ){
          619  +        fossil_print("ADDED   %s\n", pToFile->zName);
          620  +        if( asNewFlag ){
          621  +          diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
          622  +                              fIncludeBinary, diffFlags);
          623  +        }
   579    624         }
   580    625         pToFile = manifest_file_next(pTo,0);
   581    626       }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
   582    627         /* No changes */
          628  +      (void)file_dir_match(pFileDir, pFromFile->zName); /* Record name usage */
   583    629         pFromFile = manifest_file_next(pFrom,0);
   584    630         pToFile = manifest_file_next(pTo,0);
   585    631       }else{
   586         -      if( diffFlags & DIFF_BRIEF ){
   587         -        fossil_print("CHANGED %s\n", pFromFile->zName);
   588         -      }else{
   589         -        diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob,
   590         -                            fIncludeBinary, diffFlags);
          632  +      if( file_dir_match(pFileDir, pToFile->zName) ){
          633  +        if( diffFlags & DIFF_BRIEF ){
          634  +          fossil_print("CHANGED %s\n", pFromFile->zName);
          635  +        }else{
          636  +          diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob,
          637  +                              fIncludeBinary, diffFlags);
          638  +        }
   591    639         }
   592    640         pFromFile = manifest_file_next(pFrom,0);
   593    641         pToFile = manifest_file_next(pTo,0);
   594    642       }
   595    643     }
   596    644     manifest_destroy(pFrom);
   597    645     manifest_destroy(pTo);
................................................................................
   598    646   }
   599    647   
   600    648   /*
   601    649   ** Return the name of the external diff command, or return NULL if
   602    650   ** no external diff command is defined.
   603    651   */
   604    652   const char *diff_command_external(int guiDiff){
   605         -  char *zDefault;
          653  +  const char *zDefault;
   606    654     const char *zName;
   607    655   
   608    656     if( guiDiff ){
   609    657   #if defined(_WIN32)
   610    658       zDefault = "WinDiff.exe";
   611    659   #else
   612    660       zDefault = 0;
................................................................................
   745    793   **
   746    794   ** Options:
   747    795   **   --binary PATTERN           Treat files that match the glob PATTERN as binary
   748    796   **   --branch BRANCH            Show diff of all changes on BRANCH
   749    797   **   --brief                    Show filenames only
   750    798   **   --context|-c N             Use N lines of context
   751    799   **   --diff-binary BOOL         Include binary files when using external commands
          800  +**   --exec-abs-paths           Force absolute path names with external commands.
          801  +**   --exec-rel-paths           Force relative path names with external commands.
   752    802   **   --from|-r VERSION          select VERSION as source for the diff
   753    803   **   --internal|-i              use internal diff logic
   754    804   **   --side-by-side|-y          side-by-side diff
   755    805   **   --strip-trailing-cr        Strip trailing CR
   756    806   **   --tk                       Launch a Tcl/Tk GUI for display
   757    807   **   --to VERSION               select VERSION as target for the diff
          808  +**   --undo                     Diff against the "undo" buffer
   758    809   **   --unified                  unified diff
   759    810   **   -v|--verbose               output complete text of added or deleted files
   760    811   **   -w|--ignore-all-space      Ignore white space when comparing lines
   761    812   **   -W|--width <num>           Width of lines in side-by-side diff
   762    813   **   -Z|--ignore-trailing-space Ignore changes to end-of-line whitespace
   763    814   */
   764    815   void diff_cmd(void){
................................................................................
   767    818     int verboseFlag;           /* True if -v or --verbose flag is used */
   768    819     const char *zFrom;         /* Source version number */
   769    820     const char *zTo;           /* Target version number */
   770    821     const char *zBranch;       /* Branch to diff */
   771    822     const char *zDiffCmd = 0;  /* External diff command. NULL for internal diff */
   772    823     const char *zBinGlob = 0;  /* Treat file names matching this as binary */
   773    824     int fIncludeBinary = 0;    /* Include binary files for external diff */
          825  +  int againstUndo = 0;       /* Diff against files in the undo buffer */
   774    826     u64 diffFlags = 0;         /* Flags to control the DIFF */
   775         -  int f;
          827  +  FileDirList *pFileDir = 0; /* Restrict the diff to these files */
   776    828   
   777    829     if( find_option("tk",0,0)!=0 ){
   778    830       diff_tk("diff", 2);
   779    831       return;
   780    832     }
   781    833     isGDiff = g.argv[1][0]=='g';
   782    834     isInternDiff = find_option("internal","i",0)!=0;
   783    835     zFrom = find_option("from", "r", 1);
   784    836     zTo = find_option("to", 0, 1);
   785    837     zBranch = find_option("branch", 0, 1);
          838  +  againstUndo = find_option("undo",0,0)!=0;
   786    839     diffFlags = diff_options();
   787    840     verboseFlag = find_option("verbose","v",0)!=0;
   788    841     if( !verboseFlag ){
   789    842       verboseFlag = find_option("new-file","N",0)!=0; /* deprecated */
   790    843     }
   791    844     if( verboseFlag ) diffFlags |= DIFF_VERBOSE;
          845  +  if( againstUndo && (zFrom!=0 || zTo!=0 || zBranch!=0) ){
          846  +    fossil_fatal("cannot use --undo together with --from or --to or --branch");
          847  +  }
   792    848     if( zBranch ){
   793    849       if( zTo || zFrom ){
   794    850         fossil_fatal("cannot use --from or --to with --branch");
   795    851       }
   796    852       zTo = zBranch;
   797    853       zFrom = mprintf("root:%s", zBranch);
   798    854     }
   799         -  if( zTo==0 ){
          855  +  if( zTo==0 || againstUndo ){
   800    856       db_must_be_within_tree();
   801         -    if( !isInternDiff ){
   802         -      zDiffCmd = diff_command_external(isGDiff);
   803         -    }
   804         -    zBinGlob = diff_get_binary_glob();
   805         -    fIncludeBinary = diff_include_binary_files();
   806         -    verify_all_options();
   807         -    if( g.argc>=3 ){
   808         -      for(f=2; f<g.argc; ++f){
   809         -        diff_one_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
   810         -                              diffFlags, g.argv[f]);
   811         -      }
   812         -    }else{
   813         -      diff_all_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
   814         -                            diffFlags);
   815         -    }
   816    857     }else if( zFrom==0 ){
   817    858       fossil_fatal("must use --from if --to is present");
   818    859     }else{
   819    860       db_find_and_open_repository(0, 0);
   820         -    if( !isInternDiff ){
   821         -      zDiffCmd = diff_command_external(isGDiff);
          861  +  }
          862  +  if( !isInternDiff ){
          863  +    zDiffCmd = diff_command_external(isGDiff);
          864  +  }
          865  +  zBinGlob = diff_get_binary_glob();
          866  +  fIncludeBinary = diff_include_binary_files();
          867  +  determine_exec_relative_option(1);
          868  +  verify_all_options();
          869  +  if( g.argc>=3 ){
          870  +    int i;
          871  +    Blob fname;
          872  +    pFileDir = fossil_malloc( sizeof(*pFileDir) * (g.argc-1) );
          873  +    memset(pFileDir, 0, sizeof(*pFileDir) * (g.argc-1));
          874  +    for(i=2; i<g.argc; i++){
          875  +      file_tree_name(g.argv[i], &fname, 0, 1);
          876  +      pFileDir[i-2].zName = fossil_strdup(blob_str(&fname));
          877  +      if( strcmp(pFileDir[i-2].zName,".")==0 ){
          878  +        pFileDir[0].zName[0] = '.';
          879  +        pFileDir[0].zName[1] = 0;
          880  +        break;
          881  +      }
          882  +      pFileDir[i-2].nName = blob_size(&fname);
          883  +      pFileDir[i-2].nUsed = 0;
          884  +      blob_reset(&fname);
          885  +    }
          886  +  }
          887  +  if( againstUndo ){
          888  +    if( db_lget_int("undo_available",0)==0 ){
          889  +      fossil_print("No undo or redo is available\n");
          890  +      return;
   822    891       }
   823         -    zBinGlob = diff_get_binary_glob();
   824         -    fIncludeBinary = diff_include_binary_files();
   825         -    verify_all_options();
   826         -    if( g.argc>=3 ){
   827         -      for(f=2; f<g.argc; ++f){
   828         -        diff_one_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
   829         -                              diffFlags, g.argv[f]);
          892  +    diff_against_undo(zDiffCmd, zBinGlob, fIncludeBinary,
          893  +                      diffFlags, pFileDir);
          894  +  }else if( zTo==0 ){
          895  +    diff_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
          896  +                      diffFlags, pFileDir);
          897  +  }else{
          898  +    diff_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
          899  +                      diffFlags, pFileDir);
          900  +  }
          901  +  if( pFileDir ){
          902  +    int i;
          903  +    for(i=0; pFileDir[i].zName; i++){
          904  +      if( pFileDir[i].nUsed==0
          905  +       && strcmp(pFileDir[0].zName,".")!=0
          906  +       && !file_isdir(g.argv[i+2])
          907  +      ){
          908  +        fossil_fatal("not found: '%s'", g.argv[i+2]);
   830    909         }
   831         -    }else{
   832         -      diff_all_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
   833         -                            diffFlags);
          910  +      fossil_free(pFileDir[i].zName);
   834    911       }
          912  +    fossil_free(pFileDir);
   835    913     }
   836    914   }
   837    915   
   838    916   /*
   839    917   ** WEBPAGE: vpatch
   840    918   ** URL: /vpatch?from=FROM&to=TO
   841    919   **
................................................................................
   845    923     const char *zFrom = P("from");
   846    924     const char *zTo = P("to");
   847    925     login_check_credentials();
   848    926     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   849    927     if( zFrom==0 || zTo==0 ) fossil_redirect_home();
   850    928   
   851    929     cgi_set_content_type("text/plain");
   852         -  diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE);
          930  +  diff_two_versions(zFrom, zTo, 0, 0, 0, DIFF_VERBOSE, 0);
   853    931   }

Changes to src/doc.c.

    33     33   ** For any other binary type, return "unknown/unknown".
    34     34   */
    35     35   const char *mimetype_from_content(Blob *pBlob){
    36     36     int i;
    37     37     int n;
    38     38     const unsigned char *x;
    39     39   
    40         -  static const char isBinary[256] = {
    41         -     1, 1, 1, 1,  1, 1, 1, 1,    1, 0, 0, 0,  0, 0, 1, 1,
    42         -     1, 1, 1, 1,  1, 1, 1, 1,    1, 1, 0, 0,  1, 1, 1, 1
    43         -  };
    44         -
    45     40     /* A table of mimetypes based on file content prefixes
    46     41     */
    47     42     static const struct {
    48     43       const char *zPrefix;       /* The file prefix */
    49     44       int size;                  /* Length of the prefix */
    50     45       const char *zMimetype;     /* The corresponding mimetype */
    51     46     } aMime[] = {
................................................................................
    52     47       { "GIF87a",                  6, "image/gif"  },
    53     48       { "GIF89a",                  6, "image/gif"  },
    54     49       { "\211PNG\r\n\032\n",       8, "image/png"  },
    55     50       { "\377\332\377",            3, "image/jpeg" },
    56     51       { "\377\330\377",            3, "image/jpeg" },
    57     52     };
    58     53   
           54  +  if( !looks_like_binary(pBlob) ) {
           55  +    return 0;   /* Plain text */
           56  +  }
    59     57     x = (const unsigned char*)blob_buffer(pBlob);
    60     58     n = blob_size(pBlob);
    61         -  for(i=0; i<n; i++){
    62         -    unsigned char c = x[i];
    63         -    if( isBinary[c] ){
    64         -      break;
    65         -    }
    66         -  }
    67         -  if( i>=n ){
    68         -    return 0;   /* Plain text */
    69         -  }
    70     59     for(i=0; i<ArraySize(aMime); i++){
    71     60       if( n>=aMime[i].size && memcmp(x, aMime[i].zPrefix, aMime[i].size)==0 ){
    72     61         return aMime[i].zMimetype;
    73     62       }
    74     63     }
    75     64     return "unknown/unknown";
    76     65   }
................................................................................
   102     91     { "ccad",       4, "application/clariscad"             },
   103     92     { "cdf",        3, "application/x-netcdf"              },
   104     93     { "class",      5, "application/octet-stream"          },
   105     94     { "cod",        3, "application/vnd.rim.cod"           },
   106     95     { "com",        3, "application/x-msdos-program"       },
   107     96     { "cpio",       4, "application/x-cpio"                },
   108     97     { "cpt",        3, "application/mac-compactpro"        },
           98  +  { "cs",         2, "text/plain"                        },
   109     99     { "csh",        3, "application/x-csh"                 },
   110    100     { "css",        3, "text/css"                          },
   111    101     { "csv",        3, "text/csv"                          },
   112    102     { "dcr",        3, "application/x-director"            },
   113    103     { "deb",        3, "application/x-debian-package"      },
   114    104     { "dir",        3, "application/x-director"            },
   115    105     { "dl",         2, "video/dl"                          },
................................................................................
   158    148     { "kar",        3, "audio/midi"                        },
   159    149     { "latex",      5, "application/x-latex"               },
   160    150     { "lha",        3, "application/octet-stream"          },
   161    151     { "lsp",        3, "application/x-lisp"                },
   162    152     { "lzh",        3, "application/octet-stream"          },
   163    153     { "m",          1, "text/plain"                        },
   164    154     { "m3u",        3, "audio/x-mpegurl"                   },
   165         -  { "man",        3, "application/x-troff-man"           },
          155  +  { "man",        3, "text/plain"                        },
   166    156     { "markdown",   8, "text/x-markdown"                   },
   167    157     { "md",         2, "text/x-markdown"                   },
   168    158     { "me",         2, "application/x-troff-me"            },
   169    159     { "mesh",       4, "model/mesh"                        },
   170    160     { "mid",        3, "audio/midi"                        },
   171    161     { "midi",       4, "audio/midi"                        },
   172    162     { "mif",        3, "application/x-mif"                 },
................................................................................
   179    169     { "mp4",        3, "video/mp4"                         },
   180    170     { "mpe",        3, "video/mpeg"                        },
   181    171     { "mpeg",       4, "video/mpeg"                        },
   182    172     { "mpg",        3, "video/mpeg"                        },
   183    173     { "mpga",       4, "audio/mpeg"                        },
   184    174     { "ms",         2, "application/x-troff-ms"            },
   185    175     { "msh",        3, "model/mesh"                        },
          176  +  { "n",          1, "text/plain"                        },
   186    177     { "nc",         2, "application/x-netcdf"              },
   187    178     { "oda",        3, "application/oda"                   },
   188    179     { "odp",        3, "application/vnd.oasis.opendocument.presentation" },
   189    180     { "ods",        3, "application/vnd.oasis.opendocument.spreadsheet" },
   190    181     { "odt",        3, "application/vnd.oasis.opendocument.text" },
   191    182     { "ogg",        3, "application/ogg"                   },
   192    183     { "ogm",        3, "application/ogg"                   },
................................................................................
   263    254     { "tr",         2, "application/x-troff"               },
   264    255     { "tsi",        3, "audio/TSP-audio"                   },
   265    256     { "tsp",        3, "application/dsptype"               },
   266    257     { "tsv",        3, "text/tab-separated-values"         },
   267    258     { "txt",        3, "text/plain"                        },
   268    259     { "unv",        3, "application/i-deas"                },
   269    260     { "ustar",      5, "application/x-ustar"               },
          261  +  { "vb",         2, "text/plain"                        },
   270    262     { "vcd",        3, "application/x-cdlink"              },
   271    263     { "vda",        3, "application/vda"                   },
   272    264     { "viv",        3, "video/vnd.vivo"                    },
   273    265     { "vivo",       4, "video/vnd.vivo"                    },
   274    266     { "vrml",       4, "model/vrml"                        },
   275    267     { "wav",        3, "audio/x-wav"                       },
   276    268     { "wax",        3, "audio/x-ms-wax"                    },
................................................................................
   494    486     rid = db_int(0, "SELECT rid FROM vcache"
   495    487                     " WHERE vid=%d AND fname=%Q", vid, zName);
   496    488     if( rid && content_get(rid, pContent)==0 ){
   497    489       rid = 0;
   498    490     }
   499    491     return rid;
   500    492   }
          493  +
          494  +/*
          495  +** Transfer content to the output.  During the transfer, when text of
          496  +** the followign form is seen:
          497  +**
          498  +**       href="$ROOT/
          499  +**       action="$ROOT/
          500  +**
          501  +** Convert $ROOT to the root URI of the repository.  Allow ' in place of "
          502  +** and any case for href.
          503  +*/
          504  +static void convert_href_and_output(Blob *pIn){
          505  +  int i, base;
          506  +  int n = blob_size(pIn);
          507  +  char *z = blob_buffer(pIn);
          508  +  for(base=0, i=7; i<n; i++){
          509  +    if( z[i]=='$' 
          510  +     && strncmp(&z[i],"$ROOT/", 6)==0
          511  +     && (z[i-1]=='\'' || z[i-1]=='"')
          512  +     && i-base>=9
          513  +     && (fossil_strnicmp(&z[i-7]," href=", 6)==0 ||
          514  +           fossil_strnicmp(&z[i-9]," action=", 8)==0)
          515  +    ){
          516  +      blob_append(cgi_output_blob(), &z[base], i-base);
          517  +      blob_appendf(cgi_output_blob(), "%R");
          518  +      base = i+5;
          519  +    }
          520  +  }
          521  +  blob_append(cgi_output_blob(), &z[base], i-base);
          522  +}
   501    523   
   502    524   /*
   503    525   ** WEBPAGE: doc
   504    526   ** URL: /doc?name=CHECKIN/FILE
   505    527   ** URL: /doc/CHECKIN/FILE
   506    528   **
   507    529   ** CHECKIN can be either tag or SHA1 hash or timestamp identifying a
................................................................................
   520    542   **
   521    543   ** The "ckout" CHECKIN is intended for development - to provide a mechanism
   522    544   ** for looking at what a file will look like using the /doc webpage after
   523    545   ** it gets checked in.
   524    546   **
   525    547   ** The file extension is used to decide how to render the file.
   526    548   **
   527         -** If FILE ends in "/" then names "FILE/index.html", "FILE/index.wiki",
   528         -** and "FILE/index.md" are tried 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.
          549  +** If FILE ends in "/" then the names "FILE/index.html", "FILE/index.wiki",
          550  +** and "FILE/index.md" are tried in that order.  If the binary was compiled
          551  +** with TH1 embedded documentation support and the "th1-docs" setting is
          552  +** enabled, the name "FILE/index.th1" is also tried.  If none of those are
          553  +** found, then FILE is completely replaced by "404.md" and tried.  If that
          554  +** is not found, then a default 404 screen is generated.
          555  +**
          556  +** Headers and footers are added for text/x-fossil-wiki and text/md
          557  +** If the document has mimetype text/html then headers and footers are
          558  +** usually not added.  However, a text/html document begins with the
          559  +** following div:
          560  +**
          561  +**       <div class='fossil-doc' data-title='TEXT'>
          562  +**
          563  +** then headers and footers are supplied.  The optional data-title field
          564  +** specifies the title of the document in that case.
          565  +**
          566  +** For fossil-doc documents and for markdown documents, text of the
          567  +** form:  "href='$ROOT/" or "action='$ROOT" has the $ROOT name expanded
          568  +** to the top-level of the repository.
   531    569   */
   532    570   void doc_page(void){
   533    571     const char *zName;                /* Argument to the /doc page */
   534    572     const char *zOrigName = "?";      /* Original document name */
   535    573     const char *zMime;                /* Document MIME type */
   536    574     char *zCheckin = "tip";           /* The check-in holding the document */
   537    575     int vid = 0;                      /* Artifact of check-in */
................................................................................
   538    576     int rid = 0;                      /* Artifact of file */
   539    577     int i;                            /* Loop counter */
   540    578     Blob filebody;                    /* Content of the documentation file */
   541    579     Blob title;                       /* Document title */
   542    580     int nMiss = (-1);                 /* Failed attempts to find the document */
   543    581     static const char *const azSuffix[] = {
   544    582        "index.html", "index.wiki", "index.md"
          583  +#ifdef FOSSIL_ENABLE_TH1_DOCS
          584  +      , "index.th1"
          585  +#endif
   545    586     };
   546    587   
   547    588     login_check_credentials();
   548    589     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   549    590     blob_init(&title, 0, 0);
   550    591     db_begin_transaction();
   551    592     while( rid==0 && (++nMiss)<=ArraySize(azSuffix) ){
................................................................................
   624    665       markdown_to_html(&filebody, &title, &tail);
   625    666       if( blob_size(&title)>0 ){
   626    667         style_header("%s", blob_str(&title));
   627    668       }else{
   628    669         style_header("%s", nMiss>=ArraySize(azSuffix)?
   629    670                           "Not Found" : "Documentation");
   630    671       }
   631         -    blob_append(cgi_output_blob(), blob_buffer(&tail), blob_size(&tail));
          672  +    convert_href_and_output(&tail);
   632    673       style_footer();
   633    674     }else if( fossil_strcmp(zMime, "text/plain")==0 ){
   634    675       style_header("Documentation");
   635    676       @ <blockquote><pre>
   636    677       @ %h(blob_str(&filebody))
   637    678       @ </pre></blockquote>
   638    679       style_footer();
   639    680     }else if( fossil_strcmp(zMime, "text/html")==0
   640    681               && doc_is_embedded_html(&filebody, &title) ){
   641    682       if( blob_size(&title)==0 ) blob_append(&title,zName,-1);
   642    683       style_header("%s", blob_str(&title));
   643         -    blob_append(cgi_output_blob(), blob_buffer(&filebody),blob_size(&filebody));
          684  +    convert_href_and_output(&filebody);
   644    685       style_footer();
   645    686   #ifdef FOSSIL_ENABLE_TH1_DOCS
   646    687     }else if( Th_AreDocsEnabled() &&
   647    688               fossil_strcmp(zMime, "application/x-th1")==0 ){
   648    689       style_header("%h", zName);
   649    690       Th_Render(blob_str(&filebody));
   650    691       style_footer();

Changes to src/event.c.

    60     60   **  v=BOOLEAN        // Show details if TRUE.  Default is FALSE.  Optional.
    61     61   **
    62     62   ** Display an existing event identified by EVENTID
    63     63   */
    64     64   void event_page(void){
    65     65     int rid = 0;             /* rid of the event artifact */
    66     66     char *zUuid;             /* UUID corresponding to rid */
    67         -  const char *zId;    /* Event identifier */
           67  +  const char *zId;         /* Event identifier */
    68     68     const char *zVerbose;    /* Value of verbose option */
    69     69     char *zETime;            /* Time of the tech-note */
    70     70     char *zATime;            /* Time the artifact was created */
    71     71     int specRid;             /* rid specified by aid= parameter */
    72     72     int prevRid, nextRid;    /* Previous or next edits of this tech-note */
    73     73     Manifest *pTNote;        /* Parsed technote artifact */
    74     74     Blob fullbody;           /* Complete content of the technote body */
    75     75     Blob title;              /* Title extracted from the technote body */
    76     76     Blob tail;               /* Event body that comes after the title */
    77     77     Stmt q1;                 /* Query to search for the technote */
    78     78     int verboseFlag;         /* True to show details */
    79     79     const char *zMimetype = 0;  /* Mimetype of the document */
           80  +  const char *zFullId;     /* Full event identifier */
    80     81   
    81     82   
    82     83     /* wiki-read privilege is needed in order to read tech-notes.
    83     84     */
    84     85     login_check_credentials();
    85     86     if( !g.perm.RdWiki ){
    86     87       login_needed(g.anon.RdWiki);
................................................................................
   148    149     }else{
   149    150       blob_appendf(&title, "Tech-note %S", zId);
   150    151       tail = fullbody;
   151    152     }
   152    153     style_header("%s", blob_str(&title));
   153    154     if( g.perm.WrWiki && g.perm.Write && nextRid==0 ){
   154    155       style_submenu_element("Edit", 0, "%R/technoteedit?name=%!S", zId);
          156  +    if( g.perm.Attach ){
          157  +      style_submenu_element("Attach", "Add an attachment",
          158  +           "%R/attachadd?technote=%!S&from=%R/technote/%!S", 
          159  +           zId, zId);
          160  +    }
   155    161     }
   156    162     zETime = db_text(0, "SELECT datetime(%.17g)", pTNote->rEventDate);
   157    163     style_submenu_element("Context", 0, "%R/timeline?c=%.20s", zId);
   158    164     if( g.perm.Hyperlink ){
   159    165       if( verboseFlag ){
   160    166         style_submenu_element("Plain", 0,
   161    167                               "%R/technote?name=%!S&aid=%s&mimetype=text/plain",
................................................................................
   214    220     }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
   215    221       cgi_append_content(blob_buffer(&tail), blob_size(&tail));
   216    222     }else{
   217    223       @ <pre>
   218    224       @ %h(blob_str(&fullbody))
   219    225       @ </pre>
   220    226     }
          227  +  zFullId = db_text(0, "SELECT SUBSTR(tagname,7)"
          228  +                       "  FROM tag"
          229  +                       " WHERE tagname GLOB 'event-%q*'",
          230  +                    zId);
          231  +  attachment_list(zFullId, "<hr /><h2>Attachments:</h2><ul>");
   221    232     style_footer();
   222    233     manifest_destroy(pTNote);
   223    234   }
          235  +
          236  +/*
          237  +** Add or update a new tech note to the repository.  rid is id of
          238  +** the prior version of this technote, if any. 
          239  +**
          240  +** returns 1 if the tech note was added or updated, 0 if the
          241  +** update failed making an invalid artifact
          242  +*/
          243  +int event_commit_common(
          244  +  int rid,                 /* id of the prior version of the technote */
          245  +  const char *zId,         /* hash label for the technote */
          246  +  const char *zBody,       /* content of the technote */
          247  +  char *zETime,            /* timestamp for the technote */
          248  +  const char *zMimetype,   /* mimetype for the technote N-card */
          249  +  const char *zComment,    /* comment shown on the timeline */
          250  +  const char *zTags,       /* tags associated with this technote */
          251  +  const char *zClr         /* Background color */
          252  +){
          253  +  Blob event;
          254  +  char *zDate;
          255  +  Blob cksum;
          256  +  int nrid, n;
          257  +
          258  +  blob_init(&event, 0, 0);
          259  +  db_begin_transaction();
          260  +  while( fossil_isspace(zComment[0]) ) zComment++;
          261  +  n = strlen(zComment);
          262  +  while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
          263  +  if( n>0 ){
          264  +    blob_appendf(&event, "C %#F\n", n, zComment);
          265  +  }
          266  +  zDate = date_in_standard_format("now");
          267  +  blob_appendf(&event, "D %s\n", zDate);
          268  +  free(zDate);
          269  +  
          270  +  zETime[10] = 'T';
          271  +  blob_appendf(&event, "E %s %s\n", zETime, zId);
          272  +  zETime[10] = ' ';
          273  +  if( rid ){
          274  +    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
          275  +    blob_appendf(&event, "P %s\n", zUuid);
          276  +    free(zUuid);
          277  +  }
          278  +  if( zMimetype && zMimetype[0] ){
          279  +    blob_appendf(&event, "N %s\n", zMimetype);
          280  +  }
          281  +  if( zClr && zClr[0] ){
          282  +    blob_appendf(&event, "T +bgcolor * %F\n", zClr);
          283  +  }
          284  +  if( zTags && zTags[0] ){
          285  +    Blob tags, one;
          286  +    int i, j;
          287  +    Stmt q;
          288  +    char *zBlob;
          289  +
          290  +    /* Load the tags string into a blob */
          291  +    blob_zero(&tags);
          292  +    blob_append(&tags, zTags, -1);
          293  +
          294  +    /* Collapse all sequences of whitespace and "," characters into
          295  +    ** a single space character */
          296  +    zBlob = blob_str(&tags);
          297  +    for(i=j=0; zBlob[i]; i++, j++){
          298  +      if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
          299  +        while( fossil_isspace(zBlob[i+1]) ){ i++; }
          300  +        zBlob[j] = ' ';
          301  +      }else{
          302  +        zBlob[j] = zBlob[i];
          303  +      }
          304  +    }
          305  +    blob_resize(&tags, j);
          306  +
          307  +    /* Parse out each tag and load it into a temporary table for sorting */
          308  +    db_multi_exec("CREATE TEMP TABLE newtags(x);");
          309  +    while( blob_token(&tags, &one) ){
          310  +      db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
          311  +    }
          312  +    blob_reset(&tags);
          313  +
          314  +    /* Extract the tags in sorted order and make an entry in the
          315  +    ** artifact for each. */
          316  +    db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
          317  +    while( db_step(&q)==SQLITE_ROW ){
          318  +      blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
          319  +    }
          320  +    db_finalize(&q);
          321  +  }
          322  +  if( !login_is_nobody() ){
          323  +    blob_appendf(&event, "U %F\n", login_name());
          324  +  }
          325  +  blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
          326  +  md5sum_blob(&event, &cksum);
          327  +  blob_appendf(&event, "Z %b\n", &cksum);
          328  +  blob_reset(&cksum);
          329  +  nrid = content_put(&event);
          330  +  db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
          331  +  if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
          332  +    db_end_transaction(1);
          333  +    return 0;
          334  +  }
          335  +  assert( blob_is_reset(&event) );
          336  +  content_deltify(rid, nrid, 0);
          337  +  db_end_transaction(0);
          338  +  return 1;
          339  +}
   224    340   
   225    341   /*
   226    342   ** WEBPAGE: technoteedit
   227    343   ** WEBPAGE: eventedit
   228    344   **
   229    345   ** Revise or create a technical note (formerly called an 'event').
   230    346   **
................................................................................
   321    437           "   AND tag.tagname GLOB 'sym-*'",
   322    438           rid
   323    439         );
   324    440       }
   325    441     }
   326    442     zETime = db_text(0, "SELECT coalesce(datetime(%Q),datetime('now'))", zETime);
   327    443     if( P("submit")!=0 && (zBody!=0 && zComment!=0) ){
   328         -    char *zDate;
   329         -    Blob cksum;
   330         -    int nrid, n;
   331         -    blob_init(&event, 0, 0);
   332         -    db_begin_transaction();
   333    444       login_verify_csrf_secret();
   334         -    while( fossil_isspace(zComment[0]) ) zComment++;
   335         -    n = strlen(zComment);
   336         -    while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
   337         -    if( n>0 ){
   338         -      blob_appendf(&event, "C %#F\n", n, zComment);
   339         -    }
   340         -    zDate = date_in_standard_format("now");
   341         -    blob_appendf(&event, "D %s\n", zDate);
   342         -    free(zDate);
   343         -    zETime[10] = 'T';
   344         -    blob_appendf(&event, "E %s %s\n", zETime, zId);
   345         -    zETime[10] = ' ';
   346         -    if( rid ){
   347         -      char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
   348         -      blob_appendf(&event, "P %s\n", zUuid);
   349         -      free(zUuid);
   350         -    }
   351         -    if( zMimetype && zMimetype[0] ){
   352         -      blob_appendf(&event, "N %s\n", zMimetype);
   353         -    }
   354         -    if( zClr && zClr[0] ){
   355         -      blob_appendf(&event, "T +bgcolor * %F\n", zClr);
   356         -    }
   357         -    if( zTags && zTags[0] ){
   358         -      Blob tags, one;
   359         -      int i, j;
   360         -      Stmt q;
   361         -      char *zBlob;
   362         -
   363         -      /* Load the tags string into a blob */
   364         -      blob_zero(&tags);
   365         -      blob_append(&tags, zTags, -1);
   366         -
   367         -      /* Collapse all sequences of whitespace and "," characters into
   368         -      ** a single space character */
   369         -      zBlob = blob_str(&tags);
   370         -      for(i=j=0; zBlob[i]; i++, j++){
   371         -        if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
   372         -          while( fossil_isspace(zBlob[i+1]) ){ i++; }
   373         -          zBlob[j] = ' ';
   374         -        }else{
   375         -          zBlob[j] = zBlob[i];
   376         -        }
   377         -      }
   378         -      blob_resize(&tags, j);
   379         -
   380         -      /* Parse out each tag and load it into a temporary table for sorting */
   381         -      db_multi_exec("CREATE TEMP TABLE newtags(x);");
   382         -      while( blob_token(&tags, &one) ){
   383         -        db_multi_exec("INSERT INTO newtags VALUES(%B)", &one);
   384         -      }
   385         -      blob_reset(&tags);
   386         -
   387         -      /* Extract the tags in sorted order and make an entry in the
   388         -      ** artifact for each. */
   389         -      db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
   390         -      while( db_step(&q)==SQLITE_ROW ){
   391         -        blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
   392         -      }
   393         -      db_finalize(&q);
   394         -    }
   395         -    if( !login_is_nobody() ){
   396         -      blob_appendf(&event, "U %F\n", login_name());
   397         -    }
   398         -    blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
   399         -    md5sum_blob(&event, &cksum);
   400         -    blob_appendf(&event, "Z %b\n", &cksum);
   401         -    blob_reset(&cksum);
   402         -    nrid = content_put(&event);
   403         -    db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
   404         -    if( manifest_crosslink(nrid, &event, MC_NONE)==0 ){
   405         -      db_end_transaction(1);
          445  +    if ( !event_commit_common(rid, zId, zBody, zETime,
          446  +                              zMimetype, zComment, zTags, zClr) ){
   406    447         style_header("Error");
   407    448         @ Internal error:  Fossil tried to make an invalid artifact for
   408         -      @ the edited technode.
          449  +      @ the edited technote.
   409    450         style_footer();
   410    451         return;
   411    452       }
   412         -    assert( blob_is_reset(&event) );
   413         -    content_deltify(rid, nrid, 0);
   414         -    db_end_transaction(0);
   415    453       cgi_redirectf("technote?name=%T", zId);
   416    454     }
   417    455     if( P("cancel")!=0 ){
   418    456       cgi_redirectf("technote?name=%T", zId);
   419    457       return;
   420    458     }
   421    459     if( zBody==0 ){
................................................................................
   495    533     @ <input type="submit" name="preview" value="Preview Your Changes" />
   496    534     @ <input type="submit" name="submit" value="Apply These Changes" />
   497    535     @ <input type="submit" name="cancel" value="Cancel" />
   498    536     @ </td></tr></table>
   499    537     @ </div></form>
   500    538     style_footer();
   501    539   }
          540  +
          541  +/*
          542  +** Add a new tech note to the repository.  The timestamp is
          543  +** given by the zETime parameter.  isNew must be true to create
          544  +** a new page.  If no previous page with the name zPageName exists
          545  +** and isNew is false, then this routine throws an error.
          546  +*/
          547  +void event_cmd_commit(
          548  +  char *zETime,             /* timestamp */
          549  +  int isNew,                /* true to create a new page */
          550  +  Blob *pContent,           /* content of the new page */
          551  +  const char *zMimeType,    /* mimetype of the content */
          552  +  const char *zComment,     /* comment to go on the timeline */
          553  +  const char *zTags,        /* tags */
          554  +  const char *zClr          /* background color */
          555  +){
          556  +  int rid;                /* Artifact id of the tech note */
          557  +  const char *zId;        /* id of the tech note */
          558  +  rid = db_int(0, "SELECT objid FROM event"
          559  +        " WHERE datetime(mtime)=datetime('%q') AND type = 'e'"
          560  +        " LIMIT 1",
          561  +        zETime
          562  +  );
          563  +  if( rid==0 && !isNew ){
          564  +#ifdef FOSSIL_ENABLE_JSON
          565  +    g.json.resultCode = FSL_JSON_E_RESOURCE_NOT_FOUND;
          566  +#endif
          567  +    fossil_fatal("no such tech note: %s", zETime);
          568  +  }
          569  +  if( rid!=0 && isNew ){
          570  +#ifdef FOSSIL_ENABLE_JSON
          571  +    g.json.resultCode = FSL_JSON_E_RESOURCE_ALREADY_EXISTS;
          572  +#endif
          573  +    fossil_fatal("tech note %s already exists", zETime);
          574  +  }
          575  +
          576  +  if ( isNew ){
          577  +    zId = db_text(0, "SELECT lower(hex(randomblob(20)))");
          578  +  }else{
          579  +    zId = db_text(0,
          580  +      "SELECT substr(tagname,7) FROM tag"
          581  +      " WHERE tagid=(SELECT tagid FROM event WHERE objid='%d')",
          582  +      rid
          583  +    );
          584  +  }
          585  +
          586  +  user_select();
          587  +  if (event_commit_common(rid, zId, blob_str(pContent), zETime,
          588  +      zMimeType, zComment, zTags, zClr)==0 ){
          589  +#ifdef FOSSIL_ENABLE_JSON
          590  +    g.json.resultCode = FSL_JSON_E_ASSERT;
          591  +#endif
          592  +    fossil_fatal("Internal error: Fossil tried to make an "
          593  +                 "invalid artifact for the technote.");
          594  +  }
          595  +}

Changes to src/file.c.

    85     85   /*
    86     86   ** Fill stat buf with information received from stat() or lstat().
    87     87   ** lstat() is called on Unix if isWd is TRUE and allow-symlinks setting is on.
    88     88   **
    89     89   */
    90     90   static int fossil_stat(const char *zFilename, struct fossilStat *buf, int isWd){
    91     91     int rc;
    92         -  void *zMbcs = fossil_utf8_to_filename(zFilename);
           92  +  void *zMbcs = fossil_utf8_to_path(zFilename, 0);
    93     93   #if !defined(_WIN32)
    94     94     if( isWd && g.allowSymlinks ){
    95     95       rc = lstat(zMbcs, buf);
    96     96     }else{
    97     97       rc = stat(zMbcs, buf);
    98     98     }
    99     99   #else
   100    100     rc = win32_stat(zMbcs, buf, isWd);
   101    101   #endif
   102         -  fossil_filename_free(zMbcs);
          102  +  fossil_path_free(zMbcs);
   103    103     return rc;
   104    104   }
   105    105   
   106    106   /*
   107    107   ** Fill in the fileStat variable for the file named zFilename.
   108    108   ** If zFilename==0, then use the previous value of fileStat if
   109    109   ** there is a previous value.
................................................................................
   313    313   
   314    314   
   315    315   /*
   316    316   ** Wrapper around the access() system call.
   317    317   */
   318    318   int file_access(const char *zFilename, int flags){
   319    319     int rc;
   320         -  void *zMbcs = fossil_utf8_to_filename(zFilename);
          320  +  void *zMbcs = fossil_utf8_to_path(zFilename, 0);
   321    321   #ifdef _WIN32
   322    322     rc = win32_access(zMbcs, flags);
   323    323   #else
   324    324     rc = access(zMbcs, flags);
   325    325   #endif
   326         -  fossil_filename_free(zMbcs);
          326  +  fossil_path_free(zMbcs);
   327    327     return rc;
   328    328   }
   329    329   
   330    330   /*
   331    331   ** Wrapper around the chdir() system call.
   332    332   ** If bChroot=1, do a chroot to this dir as well
   333    333   ** (UNIX only)
   334    334   */
   335    335   int file_chdir(const char *zChDir, int bChroot){
   336    336     int rc;
   337         -  void *zPath = fossil_utf8_to_filename(zChDir);
          337  +  void *zPath = fossil_utf8_to_path(zChDir, 1);
   338    338   #ifdef _WIN32
   339    339     rc = win32_chdir(zPath, bChroot);
   340    340   #else
   341    341     rc = chdir(zPath);
   342    342     if( !rc && bChroot ){
   343    343       rc = chroot(zPath);
   344    344       if( !rc ) rc = chdir("/");
   345    345     }
   346    346   #endif
   347         -  fossil_filename_free(zPath);
          347  +  fossil_path_free(zPath);
   348    348     return rc;
   349    349   }
   350    350   
   351    351   /*
   352    352   ** Find an unused filename similar to zBase with zSuffix appended.
   353    353   **
   354    354   ** Make the name relative to the working directory if relFlag is true.
................................................................................
   488    488   void file_set_mtime(const char *zFilename, i64 newMTime){
   489    489   #if !defined(_WIN32)
   490    490     char *zMbcs;
   491    491     struct timeval tv[2];
   492    492     memset(tv, 0, sizeof(tv[0])*2);
   493    493     tv[0].tv_sec = newMTime;
   494    494     tv[1].tv_sec = newMTime;
   495         -  zMbcs = fossil_utf8_to_filename(zFilename);
          495  +  zMbcs = fossil_utf8_to_path(zFilename, 0);
   496    496     utimes(zMbcs, tv);
   497    497   #else
   498    498     struct _utimbuf tb;
   499         -  wchar_t *zMbcs = fossil_utf8_to_filename(zFilename);
          499  +  wchar_t *zMbcs = fossil_utf8_to_path(zFilename, 0);
   500    500     tb.actime = newMTime;
   501    501     tb.modtime = newMTime;
   502    502     _wutime(zMbcs, &tb);
   503    503   #endif
   504         -  fossil_filename_free(zMbcs);
          504  +  fossil_path_free(zMbcs);
   505    505   }
   506    506   
   507    507   /*
   508    508   ** COMMAND: test-set-mtime
   509    509   **
   510    510   ** Usage: %fossil test-set-mtime FILENAME DATE/TIME
   511    511   **
................................................................................
   531    531   ** Delete a file.
   532    532   **
   533    533   ** Returns zero upon success.
   534    534   */
   535    535   int file_delete(const char *zFilename){
   536    536     int rc;
   537    537   #ifdef _WIN32
   538         -  wchar_t *z = fossil_utf8_to_filename(zFilename);
          538  +  wchar_t *z = fossil_utf8_to_path(zFilename, 0);
   539    539     rc = _wunlink(z);
   540    540   #else
   541         -  char *z = fossil_utf8_to_filename(zFilename);
          541  +  char *z = fossil_utf8_to_path(zFilename, 0);
   542    542     rc = unlink(zFilename);
   543    543   #endif
   544         -  fossil_filename_free(z);
          544  +  fossil_path_free(z);
   545    545     return rc;
   546    546   }
   547    547   
   548    548   /*
   549    549   ** Create the directory named in the argument, if it does not already
   550    550   ** exist.  If forceFlag is 1, delete any prior non-directory object
   551    551   ** with the same name.
................................................................................
   556    556     int rc = file_wd_isdir(zName);
   557    557     if( rc==2 ){
   558    558       if( !forceFlag ) return 1;
   559    559       file_delete(zName);
   560    560     }
   561    561     if( rc!=1 ){
   562    562   #if defined(_WIN32)
   563         -    wchar_t *zMbcs = fossil_utf8_to_filename(zName);
          563  +    wchar_t *zMbcs = fossil_utf8_to_path(zName, 1);
   564    564       rc = _wmkdir(zMbcs);
   565    565   #else
   566         -    char *zMbcs = fossil_utf8_to_filename(zName);
          566  +    char *zMbcs = fossil_utf8_to_path(zName, 1);
   567    567       rc = mkdir(zName, 0755);
   568    568   #endif
   569         -    fossil_filename_free(zMbcs);
          569  +    fossil_path_free(zMbcs);
   570    570       return rc;
   571    571     }
   572    572     return 0;
   573    573   }
   574    574   
   575    575   /*
   576    576   ** Create the tree of directories in which zFilename belongs, if that sequence
................................................................................
   621    621   ** Returns zero upon success.
   622    622   */
   623    623   int file_rmdir(const char *zName){
   624    624     int rc = file_wd_isdir(zName);
   625    625     if( rc==2 ) return 1; /* cannot remove normal file */
   626    626     if( rc==1 ){
   627    627   #if defined(_WIN32)
   628         -    wchar_t *zMbcs = fossil_utf8_to_filename(zName);
          628  +    wchar_t *zMbcs = fossil_utf8_to_path(zName, 1);
   629    629       rc = _wrmdir(zMbcs);
   630    630   #else
   631         -    char *zMbcs = fossil_utf8_to_filename(zName);
          631  +    char *zMbcs = fossil_utf8_to_path(zName, 1);
   632    632       rc = rmdir(zName);
   633    633   #endif
   634         -    fossil_filename_free(zMbcs);
          634  +    fossil_path_free(zMbcs);
   635    635       return rc;
   636    636     }
   637    637     return 0;
   638    638   }
   639    639   
   640    640   /*
   641    641   ** Return true if the filename given is a valid filename for
................................................................................
  1262   1262     const char *zDir = ".";
  1263   1263     int cnt = 0;
  1264   1264   
  1265   1265   #if defined(_WIN32)
  1266   1266     wchar_t zTmpPath[MAX_PATH];
  1267   1267   
  1268   1268     if( GetTempPathW(MAX_PATH, zTmpPath) ){
  1269         -    azDirs[0] = fossil_filename_to_utf8(zTmpPath);
         1269  +    azDirs[0] = fossil_path_to_utf8(zTmpPath);
  1270   1270     }
  1271   1271   
  1272   1272     azDirs[1] = fossil_getenv("TEMP");
  1273   1273     azDirs[2] = fossil_getenv("TMP");
  1274   1274   #endif
  1275   1275   
  1276   1276   
................................................................................
  1296   1296       for(i=0; i<15; i++, j++){
  1297   1297         zBuf[j] = (char)zChars[ ((unsigned char)zBuf[j])%(sizeof(zChars)-1) ];
  1298   1298       }
  1299   1299       zBuf[j] = 0;
  1300   1300     }while( file_size(zBuf)>=0 );
  1301   1301   
  1302   1302   #if defined(_WIN32)
  1303         -  fossil_filename_free((char *)azDirs[0]);
  1304         -  fossil_filename_free((char *)azDirs[1]);
  1305         -  fossil_filename_free((char *)azDirs[2]);
         1303  +  fossil_path_free((char *)azDirs[0]);
         1304  +  fossil_path_free((char *)azDirs[1]);
         1305  +  fossil_path_free((char *)azDirs[2]);
  1306   1306   #endif
  1307   1307   }
  1308   1308   
  1309   1309   
  1310   1310   /*
  1311   1311   ** Return true if a file named zName exists and has identical content
  1312   1312   ** to the blob pContent.  If zName does not exist or if the content is
................................................................................
  1328   1328     rc = blob_compare(&onDisk, pContent);
  1329   1329     blob_reset(&onDisk);
  1330   1330     return rc==0;
  1331   1331   }
  1332   1332   
  1333   1333   /*
  1334   1334   ** Return the value of an environment variable as UTF8.
  1335         -** Use fossil_filename_free() to release resources.
         1335  +** Use fossil_path_free() to release resources.
  1336   1336   */
  1337   1337   char *fossil_getenv(const char *zName){
  1338   1338   #ifdef _WIN32
  1339   1339     wchar_t *uName = fossil_utf8_to_unicode(zName);
  1340   1340     void *zValue = _wgetenv(uName);
  1341   1341     fossil_unicode_free(uName);
  1342   1342   #else
  1343   1343     char *zValue = getenv(zName);
  1344   1344   #endif
  1345         -  if( zValue ) zValue = fossil_filename_to_utf8(zValue);
         1345  +  if( zValue ) zValue = fossil_path_to_utf8(zValue);
  1346   1346     return zValue;
  1347   1347   }
  1348   1348   
  1349   1349   /*
  1350   1350   ** Sets the value of an environment variable as UTF8.
  1351   1351   */
  1352   1352   int fossil_setenv(const char *zName, const char *zValue){
................................................................................
  1367   1367   
  1368   1368   /*
  1369   1369   ** Like fopen() but always takes a UTF8 argument.
  1370   1370   */
  1371   1371   FILE *fossil_fopen(const char *zName, const char *zMode){
  1372   1372   #ifdef _WIN32
  1373   1373     wchar_t *uMode = fossil_utf8_to_unicode(zMode);
  1374         -  wchar_t *uName = fossil_utf8_to_filename(zName);
         1374  +  wchar_t *uName = fossil_utf8_to_path(zName, 0);
  1375   1375     FILE *f = _wfopen(uName, uMode);
  1376         -  fossil_filename_free(uName);
         1376  +  fossil_path_free(uName);
  1377   1377     fossil_unicode_free(uMode);
  1378   1378   #else
  1379   1379     FILE *f = fopen(zName, zMode);
  1380   1380   #endif
  1381   1381     return f;
  1382   1382   }

Changes to src/finfo.c.

   180    180       rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
   181    181                    &fname, filename_collation());
   182    182       if( rid==0 ){
   183    183         fossil_fatal("no history for file: %b", &fname);
   184    184       }
   185    185       zFilename = blob_str(&fname);
   186    186       db_prepare(&q,
   187         -        "SELECT DISTINCT b.uuid, ci.uuid, date(event.mtime%s),"
          187  +        "SELECT DISTINCT b.uuid, ci.uuid, date(event.mtime,toLocal()),"
   188    188           "       coalesce(event.ecomment, event.comment),"
   189    189           "       coalesce(event.euser, event.user),"
   190    190           "       (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
   191    191                                   " AND tagxref.rid=mlink.mid)" /* Tags */
   192    192           "  FROM mlink, blob b, event, blob ci, filename"
   193    193           " WHERE filename.name=%Q %s"
   194    194           "   AND mlink.fnid=filename.fnid"
   195    195           "   AND b.rid=mlink.fid"
   196    196           "   AND event.objid=mlink.mid"
   197    197           "   AND event.objid=ci.rid"
   198    198           " ORDER BY event.mtime DESC LIMIT %d OFFSET %d",
   199         -        timeline_utc(), TAG_BRANCH, zFilename, filename_collation(),
          199  +        TAG_BRANCH, zFilename, filename_collation(),
   200    200           iLimit, iOffset
   201    201       );
   202    202       blob_zero(&line);
   203    203       if( iBrief ){
   204    204         fossil_print("History of %s\n", blob_str(&fname));
   205    205       }
   206    206       while( db_step(&q)==SQLITE_ROW ){
................................................................................
   297    297     const char *zFilename;
   298    298     char zPrevDate[20];
   299    299     const char *zA;
   300    300     const char *zB;
   301    301     int n;
   302    302     int baseCheckin;
   303    303     int fnid;
   304         -  Bag ancestor;
   305    304     Blob title;
   306    305     Blob sql;
   307    306     HQuery url;
   308    307     GraphContext *pGraph;
   309    308     int brBg = P("brbg")!=0;
   310    309     int uBg = P("ubg")!=0;
   311    310     int fDebug = atoi(PD("debug","0"));
   312    311     int fShowId = P("showid")!=0;
          312  +  Stmt qparent;
   313    313   
   314    314     login_check_credentials();
   315    315     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   316    316     style_header("File History");
   317    317     login_anonymous_available();
   318    318     url_initialize(&url, "finfo");
   319    319     if( brBg ) url_add_parameter(&url, "brbg", 0);
................................................................................
   323    323     zFilename = PD("name","");
   324    324     fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
   325    325     if( fnid==0 ){
   326    326       @ No such file: %h(zFilename)
   327    327       style_footer();
   328    328       return;
   329    329     }
          330  +  if( g.perm.Admin ){
          331  +    style_submenu_element("MLink Table", "mtab", "%R/mlink?name=%t", zFilename);
          332  +  }
   330    333     if( baseCheckin ){
   331         -    int baseFid = db_int(0,
   332         -      "SELECT fid FROM mlink WHERE fnid=%d AND mid=%d",
   333         -      fnid, baseCheckin
   334         -    );
   335         -    bag_init(&ancestor);
   336         -    if( baseFid ) bag_insert(&ancestor, baseFid);
          334  +    compute_direct_ancestors(baseCheckin);
   337    335     }
   338    336     url_add_parameter(&url, "name", zFilename);
   339    337     blob_zero(&sql);
   340    338     blob_append_sql(&sql,
   341    339       "SELECT"
   342         -    " datetime(min(event.mtime)%s),"                 /* Date of change */
          340  +    " datetime(min(event.mtime),toLocal()),"         /* Date of change */
   343    341       " coalesce(event.ecomment, event.comment),"      /* Check-in comment */
   344    342       " coalesce(event.euser, event.user),"            /* User who made chng */
   345    343       " mlink.pid,"                                    /* Parent file rid */
   346    344       " mlink.fid,"                                    /* File rid */
   347    345       " (SELECT uuid FROM blob WHERE rid=mlink.pid),"  /* Parent file uuid */
   348    346       " (SELECT uuid FROM blob WHERE rid=mlink.fid),"  /* Current file uuid */
   349    347       " (SELECT uuid FROM blob WHERE rid=mlink.mid),"  /* Check-in uuid */
................................................................................
   351    349       " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0"
   352    350                                   " AND tagxref.rid=mlink.mid)," /* Branchname */
   353    351       " mlink.mid,"                                    /* check-in ID */
   354    352       " mlink.pfnid"                                   /* Previous filename */
   355    353       "  FROM mlink, event"
   356    354       " WHERE mlink.fnid=%d"
   357    355       "   AND event.objid=mlink.mid",
   358         -    timeline_utc(), TAG_BRANCH, fnid
          356  +    TAG_BRANCH, fnid
   359    357     );
   360    358     if( (zA = P("a"))!=0 ){
   361    359       blob_append_sql(&sql, " AND event.mtime>=julianday('%q')", zA);
   362    360       url_add_parameter(&url, "a", zA);
   363    361     }
   364    362     if( (zB = P("b"))!=0 ){
   365    363       blob_append_sql(&sql, " AND event.mtime<=julianday('%q')", zB);
   366    364       url_add_parameter(&url, "b", zB);
   367    365     }
   368         -  /* We only want each version of a file to appear on the graph once,
   369         -  ** at its earliest appearance.  All the other times that it gets merged
   370         -  ** into this or that branch can be ignored.  An exception is for when
   371         -  ** files are deleted (when they have mlink.fid==0).  If the same file
   372         -  ** is deleted in multiple places, we want to show each deletion, so
   373         -  ** use a "fake fid" which is derived from the parent-fid for grouping.
   374         -  ** The same fake-fid must be used on the graph.
   375         -  */
   376         -  blob_append_sql(&sql,
   377         -    " GROUP BY"
   378         -    "   CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END"
   379         -    " ORDER BY event.mtime DESC /*sort*/"
   380         -  );
          366  +  if( baseCheckin ){
          367  +    blob_append_sql(&sql,
          368  +      " AND mlink.mid IN (SELECT rid FROM ancestor)"
          369  +      " GROUP BY mlink.fid"
          370  +    );
          371  +  }else{
          372  +    /* We only want each version of a file to appear on the graph once,
          373  +    ** at its earliest appearance.  All the other times that it gets merged
          374  +    ** into this or that branch can be ignored.  An exception is for when
          375  +    ** files are deleted (when they have mlink.fid==0).  If the same file
          376  +    ** is deleted in multiple places, we want to show each deletion, so
          377  +    ** use a "fake fid" which is derived from the parent-fid for grouping.
          378  +    ** The same fake-fid must be used on the graph.
          379  +    */
          380  +    blob_append_sql(&sql,
          381  +      " GROUP BY"
          382  +      "   CASE WHEN mlink.fid>0 THEN mlink.fid ELSE mlink.pid+1000000000 END"
          383  +    );
          384  +  }
          385  +  blob_append_sql(&sql, " ORDER BY event.mtime DESC /*sort*/");
   381    386     if( (n = atoi(PD("n","0")))>0 ){
   382    387       blob_append_sql(&sql, " LIMIT %d", n);
   383    388       url_add_parameter(&url, "n", P("n"));
   384    389     }
   385    390     db_prepare(&q, "%s", blob_sql_text(&sql));
   386    391     if( P("showsql")!=0 ){
   387    392       @ <p>SQL: %h(blob_str(&sql))</p>
   388    393     }
   389    394     blob_reset(&sql);
   390    395     blob_zero(&title);
   391    396     if( baseCheckin ){
   392    397       char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
   393    398       char *zLink = 	href("%R/info/%!S", zUuid);
   394         -    blob_appendf(&title, "Ancestors of file ");
   395         -    hyperlinked_path(zFilename, &title, zUuid, "tree", "");
          399  +    if( n>0 ){
          400  +      blob_appendf(&title, "First %d ancestors of file ", n);
          401  +    }else{
          402  +      blob_appendf(&title, "Ancestors of file ");
          403  +    }
          404  +    blob_appendf(&title,"<a href='%R/finfo?name=%T'>%h</a>",
          405  +                 zFilename, zFilename);
   396    406       if( fShowId ) blob_appendf(&title, " (%d)", fnid);
   397    407       blob_appendf(&title, " from check-in %z%S</a>", zLink, zUuid);
   398    408       if( fShowId ) blob_appendf(&title, " (%d)", baseCheckin);
   399    409       fossil_free(zUuid);
   400    410     }else{
   401    411       blob_appendf(&title, "History of files named ");
   402    412       hyperlinked_path(zFilename, &title, 0, "tree", "");
   403    413       if( fShowId ) blob_appendf(&title, " (%d)", fnid);
   404    414     }
   405    415     @ <h2>%b(&title)</h2>
   406    416     blob_reset(&title);
   407    417     pGraph = graph_init();
   408    418     @ <table id="timelineTable" class="timelineTable">
          419  +  if( baseCheckin ){
          420  +    db_prepare(&qparent,
          421  +      "SELECT DISTINCT pid FROM mlink"
          422  +      " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
          423  +      "   AND pmid IN (SELECT rid FROM ancestor)"
          424  +      " ORDER BY isaux /*sort*/"
          425  +    );
          426  +  }else{
          427  +    db_prepare(&qparent,
          428  +      "SELECT DISTINCT pid FROM mlink"
          429  +      " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
          430  +      " ORDER BY isaux /*sort*/"
          431  +    );
          432  +  }
   409    433     while( db_step(&q)==SQLITE_ROW ){
   410    434       const char *zDate = db_column_text(&q, 0);
   411    435       const char *zCom = db_column_text(&q, 1);
   412    436       const char *zUser = db_column_text(&q, 2);
   413    437       int fpid = db_column_int(&q, 3);
   414    438       int frid = db_column_int(&q, 4);
   415    439       const char *zPUuid = db_column_text(&q, 5);
................................................................................
   419    443       const char *zBr = db_column_text(&q, 9);
   420    444       int fmid = db_column_int(&q, 10);
   421    445       int pfnid = db_column_int(&q, 11);
   422    446       int gidx;
   423    447       char zTime[10];
   424    448       int nParent = 0;
   425    449       int aParent[GR_MAX_RAIL];
   426         -    static Stmt qparent;
   427    450   
   428         -    if( baseCheckin && frid && !bag_find(&ancestor, frid) ) continue;
   429         -    db_static_prepare(&qparent,
   430         -      "SELECT DISTINCT pid FROM mlink"
   431         -      " WHERE fid=:fid AND mid=:mid AND pid>0 AND fnid=:fnid"
   432         -      " ORDER BY isaux /*sort*/"
   433         -    );
   434    451       db_bind_int(&qparent, ":fid", frid);
   435    452       db_bind_int(&qparent, ":mid", fmid);
   436    453       db_bind_int(&qparent, ":fnid", fnid);
   437    454       while( db_step(&qparent)==SQLITE_ROW && nParent<ArraySize(aParent) ){
   438    455         aParent[nParent] = db_column_int(&qparent, 0);
   439         -      if( baseCheckin ) bag_insert(&ancestor, aParent[nParent]);
   440    456         nParent++;
   441    457       }
   442    458       db_reset(&qparent);
   443    459       if( zBr==0 ) zBr = "trunk";
   444    460       if( uBg ){
   445    461         zBgClr = hash_color(zUser);
   446    462       }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
................................................................................
   455    471         @   <div class="divider timelineDate">%s(zPrevDate)</div>
   456    472         @ </td><td></td><td></td></tr>
   457    473       }
   458    474       memcpy(zTime, &zDate[11], 5);
   459    475       zTime[5] = 0;
   460    476       @ <tr><td class="timelineTime">
   461    477       @ %z(href("%R/timeline?c=%t",zDate))%s(zTime)</a></td>
   462         -    @ <td class="timelineGraph"><div id="m%d(gidx)" class="tl-nodemark"></div></td>
          478  +    @ <td class="timelineGraph"><div id="m%d(gidx)" class="tl-nodemark"></div>
          479  +    @ </td>
   463    480       if( zBgClr && zBgClr[0] ){
   464    481         @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
   465    482       }else{
   466    483         @ <td class="timelineTableCell">
   467    484       }
   468    485       if( zUuid ){
   469    486         if( nParent==0 ){
................................................................................
   526    543         zAncLink = href("%R/finfo?name=%T&ci=%!S&debug=1",zFilename,zCkin);
   527    544         @ %z(zAncLink)[ancestry]</a>
   528    545       }
   529    546       tag_private_status(frid);
   530    547       @ </td></tr>
   531    548     }
   532    549     db_finalize(&q);
          550  +  db_finalize(&qparent);
   533    551     if( pGraph ){
   534    552       graph_finish(pGraph, 1);
   535    553       if( pGraph->nErr ){
   536    554         graph_free(pGraph);
   537    555         pGraph = 0;
   538    556       }else{
   539    557         @ <tr class="timelineBottom"><td></td><td></td><td></td></tr>
   540    558       }
   541    559     }
   542    560     @ </table>
   543    561     timeline_output_graph_javascript(pGraph, 0, 1);
   544    562     style_footer();
   545    563   }
          564  +
          565  +/*
          566  +** WEBPAGE: mlink
          567  +** URL: /mlink?name=FILENAME
          568  +** URL: /mlink?ci=NAME
          569  +**
          570  +** Show all MLINK table entries for a particular file, or for
          571  +** a particular check-in.  This screen is intended for use by developers
          572  +** in debugging Fossil.
          573  +*/
          574  +void mlink_page(void){
          575  +  const char *zFName = P("name");
          576  +  const char *zCI = P("ci");
          577  +  Stmt q;
          578  +  
          579  +  login_check_credentials();
          580  +  if( !g.perm.Admin ){ login_needed(g.anon.Admin); return; }
          581  +  style_header("MLINK Table");
          582  +  if( zFName==0 && zCI==0 ){
          583  +    @ <span class='generalError'>
          584  +    @ Requires either a name= or ci= query parameter
          585  +    @ </span>
          586  +  }else if( zFName ){
          587  +    int fnid = db_int(0,"SELECT fnid FROM filename WHERE name=%Q",zFName);
          588  +    if( fnid<=0 ) fossil_fatal("no such file: \"%s\"", zFName);
          589  +    db_prepare(&q,
          590  +       "SELECT"
          591  +       /* 0 */ "  datetime(event.mtime,toLocal()),"
          592  +       /* 1 */ "  (SELECT uuid FROM blob WHERE rid=mlink.mid),"
          593  +       /* 2 */ "  (SELECT uuid FROM blob WHERE rid=mlink.pmid),"
          594  +       /* 3 */ "  isaux,"
          595  +       /* 4 */ "  (SELECT uuid FROM blob WHERE rid=mlink.fid),"
          596  +       /* 5 */ "  (SELECT uuid FROM blob WHERE rid=mlink.pid),"
          597  +       /* 6 */ "  mlink.pid,"
          598  +       /* 7 */ "  mperm,"
          599  +       /* 8 */ "  (SELECT name FROM filename WHERE fnid=mlink.pfnid)"
          600  +       "  FROM mlink, event"
          601  +       " WHERE mlink.fnid=%d"
          602  +       "   AND event.objid=mlink.mid"
          603  +       " ORDER BY 1 DESC",
          604  +       fnid
          605  +    );
          606  +    @ <h1>MLINK table for file
          607  +    @ <a href='%R/finfo?name=%t(zFName)'>%h(zFName)</a></h1>
          608  +    @ <div class='brlist'>
          609  +    @ <table id='mlinktable'>
          610  +    @ <thead><tr>
          611  +    @ <th>Date</th>
          612  +    @ <th>Check-in</th>
          613  +    @ <th>Parent Check-in</th>
          614  +    @ <th>Merge?</th>
          615  +    @ <th>New</th>
          616  +    @ <th>Old</th>
          617  +    @ <th>Exe Bit?</th>
          618  +    @ <th>Prior Name</th>
          619  +    @ </tr></thead>
          620  +    @ <tbody>
          621  +    while( db_step(&q)==SQLITE_ROW ){
          622  +      const char *zDate = db_column_text(&q,0);
          623  +      const char *zCkin = db_column_text(&q,1);
          624  +      const char *zParent = db_column_text(&q,2);
          625  +      int isMerge = db_column_int(&q,3);
          626  +      const char *zFid = db_column_text(&q,4);
          627  +      const char *zPid = db_column_text(&q,5);
          628  +      int isExe = db_column_int(&q,7);
          629  +      const char *zPrior = db_column_text(&q,8);
          630  +      @ <tr>
          631  +      @ <td><a href='%R/timeline?c=%!S(zCkin)'>%s(zDate)</a></td>
          632  +      @ <td><a href='%R/info/%!S(zCkin)'>%S(zCkin)</a></td>
          633  +      if( zParent ){
          634  +        @ <td><a href='%R/info/%!S(zPid)'>%S(zParent)</a></td>
          635  +      }else{
          636  +        @ <td><i>(New)</i></td>
          637  +      }
          638  +      @ <td align='center'>%s(isMerge?"&#x2713;":"")</td>
          639  +      if( zFid ){
          640  +        @ <td><a href='%R/info/%!S(zFid)'>%S(zFid)</a></td>
          641  +      }else{
          642  +        @ <td><i>(Deleted)</i></td>
          643  +      }
          644  +      if( zPid ){
          645  +        @ <td><a href='%R/info/%!S(zPid)'>%S(zPid)</a>
          646  +      }else if( db_column_int(&q,6)<0 ){
          647  +        @ <td><i>(Added by merge)</i></td>
          648  +      }else{
          649  +        @ <td><i>(New)</i></td>
          650  +      }
          651  +      @ <td align='center'>%s(isExe?"&#x2713;":"")</td>
          652  +      if( zPrior ){
          653  +        @ <td><a href='%R/finfo?name=%t(zPrior)'>%h(zPrior)</a></td>
          654  +      }else{
          655  +        @ <td></td>
          656  +      }
          657  +      @ </tr>
          658  +    }
          659  +    db_finalize(&q);
          660  +    @ </tbody>
          661  +    @ </table>
          662  +    @ </div>
          663  +    output_table_sorting_javascript("mlinktable","tttxtttt",1);
          664  +  }else{
          665  +    int mid = name_to_rid_www("ci");
          666  +    db_prepare(&q,
          667  +       "SELECT"
          668  +       /* 0 */ "  (SELECT name FROM filename WHERE fnid=mlink.fnid),"
          669  +       /* 1 */ "  (SELECT uuid FROM blob WHERE rid=mlink.fid),"
          670  +       /* 2 */ "  pid,"
          671  +       /* 3 */ "  (SELECT uuid FROM blob WHERE rid=mlink.pid),"
          672  +       /* 4 */ "  (SELECT name FROM filename WHERE fnid=mlink.pfnid),"
          673  +       /* 5 */ "  (SELECT uuid FROM blob WHERE rid=mlink.pmid),"
          674  +       /* 6 */ "  mperm,"
          675  +       /* 7 */ "  isaux"
          676  +       "  FROM mlink WHERE mid=%d ORDER BY 1",
          677  +       mid
          678  +    );
          679  +    @ <h1>MLINK table for check-in %h(zCI)</h1>
          680  +    render_checkin_context(mid, 1);
          681  +    @ <hr>
          682  +    @ <div class='brlist'>
          683  +    @ <table id='mlinktable'>
          684  +    @ <thead><tr>
          685  +    @ <th>File</th>
          686  +    @ <th>From</th>
          687  +    @ <th>Merge?</th>
          688  +    @ <th>New</th>
          689  +    @ <th>Old</th>
          690  +    @ <th>Exe Bit?</th>
          691  +    @ <th>Prior Name</th>
          692  +    @ </tr></thead>
          693  +    @ <tbody>
          694  +    while( db_step(&q)==SQLITE_ROW ){
          695  +      const char *zName = db_column_text(&q,0);
          696  +      const char *zFid = db_column_text(&q,1);
          697  +      const char *zPid = db_column_text(&q,3);
          698  +      const char *zPrior = db_column_text(&q,4);
          699  +      const char *zParent = db_column_text(&q,5);
          700  +      int isExec = db_column_int(&q,6);
          701  +      int isAux = db_column_int(&q,7);
          702  +      @ <tr>
          703  +      @ <td><a href='%R/finfo?name=%t(zName)'>%h(zName)</a></td>
          704  +      if( zParent ){
          705  +        @ <td><a href='%R/info/%!S(zPid)'>%S(zParent)</a></td>
          706  +      }else{
          707  +        @ <td><i>(New)</i></td>
          708  +      }
          709  +      @ <td align='center'>%s(isAux?"&#x2713;":"")</td>
          710  +      if( zFid ){
          711  +        @ <td><a href='%R/info/%!S(zFid)'>%S(zFid)</a></td>
          712  +      }else{
          713  +        @ <td><i>(Deleted)</i></td>
          714  +      }
          715  +      if( zPid ){
          716  +        @ <td><a href='%R/info/%!S(zPid)'>%S(zPid)</a>
          717  +      }else if( db_column_int(&q,2)<0 ){
          718  +        @ <td><i>(Added by merge)</i></td>
          719  +      }else{
          720  +        @ <td><i>(New)</i></td>
          721  +      }
          722  +      @ <td align='center'>%s(isExec?"&#x2713;":"")</td>
          723  +      if( zPrior ){
          724  +        @ <td><a href='%R/finfo?name=%t(zPrior)'>%h(zPrior)</a></td>
          725  +      }else{
          726  +        @ <td></td>
          727  +      }
          728  +      @ </tr>
          729  +    }
          730  +    db_finalize(&q);
          731  +    @ </tbody>
          732  +    @ </table>
          733  +    @ </div>
          734  +    output_table_sorting_javascript("mlinktable","ttxtttt",1);
          735  +  }
          736  +  style_footer();
          737  +}

Changes to src/http_transport.c.

    76     76     }
    77     77   }
    78     78   
    79     79   /*
    80     80   ** Default SSH command
    81     81   */
    82     82   #ifdef _WIN32
    83         -static char zDefaultSshCmd[] = "plink -ssh -T";
           83  +static const char zDefaultSshCmd[] = "plink -ssh -T";
    84     84   #else
    85         -static char zDefaultSshCmd[] = "ssh -e none -T";
           85  +static const 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     92     /* For SSH we need to create and run SSH fossil http
    93     93     ** to talk to the remote machine.
    94     94     */
    95         -  const char *zSsh;  /* The base SSH command */
           95  +  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   
   100    100     socket_ssh_resolve_addr(pUrlData);
   101    101     zSsh = db_get("ssh-command", zDefaultSshCmd);
   102    102     blob_init(&zCmd, zSsh, -1);

Changes to src/import.c.

   126    126   ** Insert an artifact into the BLOB table if it isn't there already.
   127    127   ** If zMark is not zero, create a cross-reference from that mark back
   128    128   ** to the newly inserted artifact.
   129    129   **
   130    130   ** If saveUuid is true, then pContent is a commit record.  Record its
   131    131   ** UUID in gg.zPrevCheckin.
   132    132   */
   133         -static int fast_insert_content(Blob *pContent, const char *zMark, int saveUuid){
          133  +static int fast_insert_content(
          134  +  Blob *pContent,          /* Content to insert */
          135  +  const char *zMark,       /* Label using this mark, if not NULL */
          136  +  int saveUuid,            /* Save SHA1 hash in gg.zPrevCheckin */
          137  +  int doParse              /* Invoke manifest_crosslink() */
          138  +){
   134    139     Blob hash;
   135    140     Blob cmpr;
   136    141     int rid;
   137    142   
   138    143     sha1sum_blob(pContent, &hash);
   139    144     rid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &hash);
   140    145     if( rid==0 ){
................................................................................
   146    151       db_bind_int(&ins, ":size", gg.nData);
   147    152       blob_compress(pContent, &cmpr);
   148    153       db_bind_blob(&ins, ":content", &cmpr);
   149    154       db_step(&ins);
   150    155       db_reset(&ins);
   151    156       blob_reset(&cmpr);
   152    157       rid = db_last_insert_rowid();
          158  +    if( doParse ){
          159  +      manifest_crosslink(rid, pContent, MC_NONE);
          160  +    }
   153    161     }
   154    162     if( zMark ){
   155    163       db_multi_exec(
   156    164           "INSERT OR IGNORE INTO xmark(tname, trid, tuuid)"
   157    165           "VALUES(%Q,%d,%B)",
   158    166           zMark, rid, &hash
   159    167       );
................................................................................
   174    182   /*
   175    183   ** Use data accumulated in gg from a "blob" record to add a new file
   176    184   ** to the BLOB table.
   177    185   */
   178    186   static void finish_blob(void){
   179    187     Blob content;
   180    188     blob_init(&content, gg.aData, gg.nData);
   181         -  fast_insert_content(&content, gg.zMark, 0);
          189  +  fast_insert_content(&content, gg.zMark, 0, 0);
   182    190     blob_reset(&content);
   183    191     import_reset(0);
   184    192   }
   185    193   
   186    194   /*
   187    195   ** Use data accumulated in gg from a "tag" record to add a new
   188    196   ** control artifact to the BLOB table.
................................................................................
   192    200     if( gg.zDate && gg.zTag && gg.zFrom && gg.zUser ){
   193    201       blob_zero(&record);
   194    202       blob_appendf(&record, "D %s\n", gg.zDate);
   195    203       blob_appendf(&record, "T +%F %s\n", gg.zTag, gg.zFrom);
   196    204       blob_appendf(&record, "U %F\n", gg.zUser);
   197    205       md5sum_blob(&record, &cksum);
   198    206       blob_appendf(&record, "Z %b\n", &cksum);
   199         -    fast_insert_content(&record, 0, 0);
   200         -    blob_reset(&record);
          207  +    fast_insert_content(&record, 0, 0, 1);
   201    208       blob_reset(&cksum);
   202    209     }
   203    210     import_reset(0);
   204    211   }
   205    212   
   206    213   /*
   207    214   ** Compare two ImportFile objects for sorting
................................................................................
   236    243     Blob record, cksum;
   237    244   
   238    245     import_prior_files();
   239    246     qsort(gg.aFile, gg.nFile, sizeof(gg.aFile[0]), mfile_cmp);
   240    247     blob_zero(&record);
   241    248     blob_appendf(&record, "C %F\n", gg.zComment);
   242    249     blob_appendf(&record, "D %s\n", gg.zDate);
          250  +  if( !g.fQuiet ) fossil_print("%.10s\r", gg.zDate);
   243    251     for(i=0; i<gg.nFile; i++){
   244    252       const char *zUuid = gg.aFile[i].zUuid;
   245    253       if( zUuid==0 ) continue;
   246    254       blob_appendf(&record, "F %F %s", gg.aFile[i].zName, zUuid);
   247    255       if( gg.aFile[i].isExe ){
   248    256         blob_append(&record, " x\n", 3);
   249    257       }else if( gg.aFile[i].isLink ){
................................................................................
   288    296   
   289    297     free(zFromBranch);
   290    298     db_multi_exec("INSERT INTO xbranch(tname, brnm) VALUES(%Q,%Q)",
   291    299                   gg.zMark, gg.zBranch);
   292    300     blob_appendf(&record, "U %F\n", gg.zUser);
   293    301     md5sum_blob(&record, &cksum);
   294    302     blob_appendf(&record, "Z %b\n", &cksum);
   295         -  fast_insert_content(&record, gg.zMark, 1);
   296         -  blob_reset(&record);
          303  +  fast_insert_content(&record, gg.zMark, 1, 1);
   297    304     blob_reset(&cksum);
   298    305   
   299    306     /* The "git fast-export" command might output multiple "commit" lines
   300    307     ** that reference a tag using "refs/tags/TAGNAME".  The tag should only
   301    308     ** be applied to the last commit that is output.  The problem is we do not
   302    309     ** know at this time if the current commit is the last one to hold this
   303    310     ** tag or not.  So make an entry in the XTAG table to record this tag
................................................................................
   461    468   static void dequote_git_filename(char *zName){
   462    469     int n, i, j;
   463    470     if( zName==0 || zName[0]!='"' ) return;
   464    471     n = (int)strlen(zName);
   465    472     if( zName[n-1]!='"' ) return;
   466    473     for(i=0, j=1; j<n-1; j++){
   467    474       char c = zName[j];
   468         -    if( c=='\\' ) c = zName[++j];
          475  +    int x;
          476  +    if( c=='\\' ){
          477  +      if( j+3 <= n-1
          478  +       && zName[j+1]>='0' && zName[j+1]<='3'
          479  +       && zName[j+2]>='0' && zName[j+2]<='7'
          480  +       && zName[j+3]>='0' && zName[j+3]<='7'
          481  +       && (x = 64*(zName[j+1]-'0') + 8*(zName[j+2]-'0') + zName[j+3]-'0')!=0
          482  +      ){
          483  +        c = (unsigned char)x;
          484  +        j += 3;
          485  +      }else{
          486  +        c = zName[++j];
          487  +      }
          488  +    }
   469    489       zName[i++] = c;
   470    490     }
   471    491     zName[i] = 0;
   472    492   }
   473    493   
   474    494   
   475    495   /*
................................................................................
   527    547       }else
   528    548       if( strncmp(zLine, "tag ", 4)==0 ){
   529    549         gg.xFinish();
   530    550         gg.xFinish = finish_tag;
   531    551         trim_newline(&zLine[4]);
   532    552         gg.zTag = fossil_strdup(&zLine[4]);
   533    553       }else
   534         -    if( strncmp(zLine, "reset ", 4)==0 ){
          554  +    if( strncmp(zLine, "reset ", 6)==0 ){
   535    555         gg.xFinish();
   536    556       }else
   537    557       if( strncmp(zLine, "checkpoint", 10)==0 ){
   538    558         gg.xFinish();
   539    559       }else
   540    560       if( strncmp(zLine, "feature", 7)==0 ){
   541    561         gg.xFinish();
................................................................................
  1417   1437             fossil_fatal("Missing Node-kind");
  1418   1438           }
  1419   1439           if( strncmp(zKind, "dir", 3)!=0 ){
  1420   1440             if( deltaFlag ){
  1421   1441               Blob deltaSrc;
  1422   1442               Blob target;
  1423   1443               rid = db_int(0, "SELECT rid FROM blob WHERE uuid=("
  1424         -                            " SELECT uuid FROM xfiles"
         1444  +                            " SELECT tuuid FROM xfiles"
  1425   1445                               "  WHERE tpath=%Q AND tbranch=%d"
  1426   1446                               ")", zFile, branchId);
  1427   1447               content_get(rid, &deltaSrc);
  1428   1448               svn_apply_svndiff(&rec.content, &deltaSrc, &target);
  1429   1449               rid = content_put(&target);
  1430   1450             }else{
  1431   1451               rid = content_put(&rec.content);
................................................................................
  1488   1508   **                  --tags FOLDER      Name of tags folder
  1489   1509   **                  --base PATH        Path to project root in repository
  1490   1510   **                  --flat             The whole dump is a single branch
  1491   1511   **
  1492   1512   ** Common Options:
  1493   1513   **   -i|--incremental   allow importing into an existing repository
  1494   1514   **   -f|--force         overwrite repository if already exist
         1515  +**   -q|--quiet         omit progress output
         1516  +**   --no-rebuild       skip the "rebuilding metadata" step
         1517  +**   --no-vacuum        skip the final VACUUM of the database file
  1495   1518   **
  1496   1519   ** The --incremental option allows an existing repository to be extended
  1497   1520   ** with new content.
  1498         -**
  1499   1521   **
  1500   1522   ** See also: export
  1501   1523   */
  1502   1524   void import_cmd(void){
  1503   1525     char *zPassword;
  1504   1526     FILE *pIn;
  1505   1527     Stmt q;
  1506   1528     int forceFlag = find_option("force", "f", 0)!=0;
  1507   1529     int svnFlag = find_option("svn", 0, 0)!=0;
         1530  +  int omitRebuild = find_option("no-rebuild",0,0)!=0;
         1531  +  int omitVacuum = find_option("no-vacuum",0,0)!=0;
  1508   1532   
  1509   1533     /* Options common to all input formats */
  1510   1534     int incrFlag = find_option("incremental", "i", 0)!=0;
  1511   1535   
  1512   1536     /* Options for --svn only */
  1513   1537     const char *zBase="";
  1514   1538     int flatFlag=0;
................................................................................
  1538   1562       fossil_binary_mode(pIn);
  1539   1563     }
  1540   1564     if( !incrFlag ){
  1541   1565       if( forceFlag ) file_delete(g.argv[2]);
  1542   1566       db_create_repository(g.argv[2]);
  1543   1567     }
  1544   1568     db_open_repository(g.argv[2]);
  1545         -  db_open_config(0);
         1569  +  db_open_config(0, 0);
  1546   1570   
  1547   1571     db_begin_transaction();
  1548   1572     if( !incrFlag ) db_initial_setup(0, 0, 0);
  1549   1573   
  1550   1574     if( svnFlag ){
  1551   1575       db_multi_exec(
  1552   1576          "CREATE TEMP TABLE xrevisions("
................................................................................
  1623   1647       */
  1624   1648       db_multi_exec(
  1625   1649          "CREATE TEMP TABLE xmark(tname TEXT UNIQUE, trid INT, tuuid TEXT);"
  1626   1650          "CREATE TEMP TABLE xbranch(tname TEXT UNIQUE, brnm TEXT);"
  1627   1651          "CREATE TEMP TABLE xtag(tname TEXT UNIQUE, tcontent TEXT);"
  1628   1652       );
  1629   1653   
         1654  +    manifest_crosslink_begin();
  1630   1655       git_fast_import(pIn);
  1631   1656       db_prepare(&q, "SELECT tcontent FROM xtag");
  1632   1657       while( db_step(&q)==SQLITE_ROW ){
  1633   1658         Blob record;
  1634   1659         db_ephemeral_blob(&q, 0, &record);
  1635         -      fast_insert_content(&record, 0, 0);
         1660  +      fast_insert_content(&record, 0, 0, 1);
  1636   1661         import_reset(0);
  1637   1662       }
  1638   1663       db_finalize(&q);
         1664  +    manifest_crosslink_end(MC_NONE);
  1639   1665     }
  1640   1666   
  1641   1667     verify_cancel();
  1642   1668     db_end_transaction(0);
  1643         -  db_begin_transaction();
  1644         -  fossil_print("Rebuilding repository meta-data...\n");
  1645         -  rebuild_db(0, 1, !incrFlag);
  1646         -  verify_cancel();
  1647         -  db_end_transaction(0);
  1648         -  fossil_print("Vacuuming..."); fflush(stdout);
  1649         -  db_multi_exec("VACUUM");
         1669  +  fossil_print("                               \r");
         1670  +  if( omitRebuild ){
         1671  +    omitVacuum = 1;
         1672  +  }else{
         1673  +    db_begin_transaction();
         1674  +    fossil_print("Rebuilding repository meta-data...\n");
         1675  +    rebuild_db(0, 1, !incrFlag);
         1676  +    verify_cancel();
         1677  +    db_end_transaction(0);
         1678  +  }
         1679  +  if( !omitVacuum ){
         1680  +    fossil_print("Vacuuming..."); fflush(stdout);
         1681  +    db_multi_exec("VACUUM");
         1682  +  }
  1650   1683     fossil_print(" ok\n");
  1651   1684     if( !incrFlag ){
  1652   1685       fossil_print("project-id: %s\n", db_get("project-code", 0));
  1653   1686       fossil_print("server-id:  %s\n", db_get("server-code", 0));
  1654   1687       zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  1655   1688       fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
  1656   1689     }
  1657   1690   }

Changes to src/info.c.

   182    182     i64 fsize;
   183    183     int verboseFlag = find_option("verbose","v",0)!=0;
   184    184     if( !verboseFlag ){
   185    185       verboseFlag = find_option("detail","l",0)!=0; /* deprecated */
   186    186     }
   187    187   
   188    188     if( g.argc==3 && (fsize = file_size(g.argv[2]))>0 && (fsize&0x1ff)==0 ){
   189         -    db_open_config(0);
          189  +    db_open_config(0, 0);
   190    190       db_open_repository(g.argv[2]);
   191    191       db_record_repository_filename(g.argv[2]);
   192    192       fossil_print("project-name: %s\n", db_get("project-name", "<unnamed>"));
   193    193       fossil_print("project-code: %s\n", db_get("project-code", "<none>"));
   194    194       extraRepoInfo();
   195    195       return;
   196    196     }
................................................................................
   211    211       }
   212    212       fossil_print("project-code: %s\n", db_get("project-code", ""));
   213    213       vid = g.localOpen ? db_lget_int("checkout", 0) : 0;
   214    214       if( vid ){
   215    215         show_common_info(vid, "checkout:", 1, 1);
   216    216       }
   217    217       fossil_print("check-ins:    %d\n",
   218         -                 db_int(-1, "SELECT count(*) FROM event WHERE type='ci' /*scan*/"));
          218  +             db_int(-1, "SELECT count(*) FROM event WHERE type='ci' /*scan*/"));
   219    219     }else{
   220    220       int rid;
   221    221       rid = name_to_rid(g.argv[2]);
   222    222       if( rid==0 ){
   223    223         fossil_fatal("no such object: %s\n", g.argv[2]);
   224    224       }
   225    225       show_common_info(rid, "uuid:", 1, 1);
................................................................................
   231    231   */
   232    232   static void showTags(int rid){
   233    233     Stmt q;
   234    234     int cnt = 0;
   235    235     db_prepare(&q,
   236    236       "SELECT tag.tagid, tagname, "
   237    237       "       (SELECT uuid FROM blob WHERE rid=tagxref.srcid AND rid!=%d),"
   238         -    "       value, datetime(tagxref.mtime%s), tagtype,"
          238  +    "       value, datetime(tagxref.mtime,toLocal()), tagtype,"
   239    239       "       (SELECT uuid FROM blob WHERE rid=tagxref.origid AND rid!=%d)"
   240    240       "  FROM tagxref JOIN tag ON tagxref.tagid=tag.tagid"
   241    241       " WHERE tagxref.rid=%d"
   242         -    " ORDER BY tagname /*sort*/", rid, timeline_utc(), rid, rid
          242  +    " ORDER BY tagname /*sort*/", rid, rid, rid
   243    243     );
   244    244     while( db_step(&q)==SQLITE_ROW ){
   245    245       const char *zTagname = db_column_text(&q, 1);
   246    246       const char *zSrcUuid = db_column_text(&q, 2);
   247    247       const char *zValue = db_column_text(&q, 3);
   248    248       const char *zDate = db_column_text(&q, 4);
   249    249       int tagtype = db_column_int(&q, 5);
................................................................................
   293    293     }
   294    294   }
   295    295   
   296    296   /*
   297    297   ** Show the context graph (immediate parents and children) for
   298    298   ** check-in rid.
   299    299   */
   300         -static void showContext(int rid){
          300  +void render_checkin_context(int rid, int parentsOnly){
   301    301     Blob sql;
   302    302     Stmt q;
   303         -  @ <div class="section">Context</div>
   304    303     blob_zero(&sql);
   305    304     blob_append(&sql, timeline_query_for_www(), -1);
   306    305     db_multi_exec(
   307    306        "CREATE TEMP TABLE IF NOT EXISTS ok(rid INTEGER PRIMARY KEY);"
   308    307        "INSERT INTO ok VALUES(%d);"
   309         -     "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;"
   310         -     "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;",
   311         -     rid, rid, rid
          308  +     "INSERT OR IGNORE INTO ok SELECT pid FROM plink WHERE cid=%d;",
          309  +     rid, rid
   312    310     );
          311  +  if( !parentsOnly ){
          312  +    db_multi_exec(
          313  +      "INSERT OR IGNORE INTO ok SELECT cid FROM plink WHERE pid=%d;", rid
          314  +    );
          315  +  }
   313    316     blob_append_sql(&sql, " AND event.objid IN ok ORDER BY mtime DESC");
   314    317     db_prepare(&q, "%s", blob_sql_text(&sql));
   315    318     www_print_timeline(&q, TIMELINE_DISJOINT|TIMELINE_GRAPH, 0, 0, rid, 0);
   316    319     db_finalize(&q);
   317    320   }
   318    321   
   319    322   
................................................................................
   544    547     zParent = db_text(0,
   545    548       "SELECT uuid FROM plink, blob"
   546    549       " WHERE plink.cid=%d AND blob.rid=plink.pid AND plink.isprim",
   547    550       rid
   548    551     );
   549    552     isLeaf = is_a_leaf(rid);
   550    553     db_prepare(&q1,
   551         -     "SELECT uuid, datetime(mtime%s), user, comment,"
   552         -     "       datetime(omtime%s), mtime"
          554  +     "SELECT uuid, datetime(mtime,toLocal()), user, comment,"
          555  +     "       datetime(omtime,toLocal()), mtime"
   553    556        "  FROM blob, event"
   554    557        " WHERE blob.rid=%d"
   555    558        "   AND event.objid=%d",
   556         -     timeline_utc(), timeline_utc(), rid, rid
          559  +     rid, rid
   557    560     );
   558    561     sideBySide = !is_false(PD("sbs","1"));
   559    562     if( db_step(&q1)==SQLITE_ROW ){
   560    563       const char *zUuid = db_column_text(&q1, 0);
   561    564       char *zEUser, *zEComment;
   562    565       const char *zUser;
   563    566       const char *zComment;
................................................................................
   596    599         @ <tr><th>Original&nbsp;User:</th><td>
   597    600         hyperlink_to_user(zUser,zDate,"</td></tr>");
   598    601       }else{
   599    602         @ <tr><th>User:</th><td>
   600    603         hyperlink_to_user(zUser,zDate,"</td></tr>");
   601    604       }
   602    605       if( zEComment ){
   603         -      @ <tr><th>Edited&nbsp;Comment:</th><td class="infoComment">%!W(zEComment)</td></tr>
   604         -      @ <tr><th>Original&nbsp;Comment:</th><td class="infoComment">%!W(zComment)</td></tr>
          606  +      @ <tr><th>Edited&nbsp;Comment:</th>
          607  +      @     <td class="infoComment">%!W(zEComment)</td></tr>
          608  +      @ <tr><th>Original&nbsp;Comment:</th>
          609  +      @     <td class="infoComment">%!W(zComment)</td></tr>
   605    610       }else{
   606    611         @ <tr><th>Comment:</th><td class="infoComment">%!W(zComment)</td></tr>
   607    612       }
   608    613       if( g.perm.Admin ){
   609    614         db_prepare(&q2,
   610    615            "SELECT rcvfrom.ipaddr, user.login, datetime(rcvfrom.mtime)"
   611    616            "  FROM blob JOIN rcvfrom USING(rcvid) LEFT JOIN user USING(uid)"
................................................................................
   686    691       @ </table>
   687    692     }else{
   688    693       style_header("Check-in Information");
   689    694       login_anonymous_available();
   690    695     }
   691    696     db_finalize(&q1);
   692    697     showTags(rid);
   693         -  showContext(rid);
          698  +  @ <div class="section">Context</div>
          699  +  render_checkin_context(rid, 0);
   694    700     @ <div class="section">Changes</div>
   695    701     @ <div class="sectionmenu">
   696    702     verboseFlag = g.zPath[0]!='c';
   697    703     if( db_get_boolean("show-version-diffs", 0)==0 ){
   698    704       verboseFlag = !verboseFlag;
   699    705       zPage = "ci";
   700    706       zPageHide = "vinfo";
................................................................................
   724    730       @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
   725    731       @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
   726    732     }
   727    733     if( zParent ){
   728    734       @ %z(xhref("class='button'","%R/vpatch?from=%!S&to=%!S",zParent,zUuid))
   729    735       @ Patch</a>
   730    736     }
          737  +  if( g.perm.Admin ){
          738  +    @ %z(xhref("class='button'","%R/mlink?ci=%!S",zUuid))MLink Table</a>
          739  +  }
   731    740     @</div>
   732    741     if( pRe ){
   733    742       @ <p><b>Only differences that match regular expression "%h(zRe)"
   734    743       @ are shown.</b></p>
   735    744     }
   736    745     db_prepare(&q3,
   737    746       "SELECT name,"
................................................................................
   977    986   **   to=TAG          Right side of the comparison
   978    987   **   branch=TAG      Show all changes on a particular branch
   979    988   **   v=BOOLEAN       Default true.  If false, only list files that have changed
   980    989   **   sbs=BOOLEAN     Side-by-side diff if true.  Unified diff if false
   981    990   **   glob=STRING     only diff files matching this glob
   982    991   **   dc=N            show N lines of context around each diff
   983    992   **   w               ignore whitespace when computing diffs
          993  +**   nohdr           omit the description at the top of the page
   984    994   **
   985    995   **
   986    996   ** Show all differences between two check-ins.
   987    997   */
   988    998   void vdiff_page(void){
   989    999     int ridFrom, ridTo;
   990   1000     int verboseFlag = 0;
................................................................................
  1074   1084         style_submenu_element("Ignore Whitespace", "ignorews",
  1075   1085                               "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T&w", zFrom, zTo,
  1076   1086                               sideBySide, (verboseFlag && !sideBySide)?"&v":"",
  1077   1087                               zGlob ? "&glob=" : "", zGlob ? zGlob : "");
  1078   1088       }
  1079   1089     }
  1080   1090     style_header("Check-in Differences");
  1081         -  @ <h2>Difference From:</h2><blockquote>
  1082         -  checkin_description(ridFrom);
  1083         -  @ </blockquote><h2>To:</h2><blockquote>
  1084         -  checkin_description(ridTo);
  1085         -  @ </blockquote>
  1086         -  if( pRe ){
  1087         -    @ <p><b>Only differences that match regular expression "%h(zRe)"
  1088         -    @ are shown.</b></p>
         1091  +  if( P("nohdr")==0 ){
         1092  +    @ <h2>Difference From:</h2><blockquote>
         1093  +    checkin_description(ridFrom);
         1094  +    @ </blockquote><h2>To:</h2><blockquote>
         1095  +    checkin_description(ridTo);
         1096  +    @ </blockquote>
         1097  +    if( pRe ){
         1098  +      @ <p><b>Only differences that match regular expression "%h(zRe)"
         1099  +      @ are shown.</b></p>
         1100  +    }
         1101  +    if( zGlob ){
         1102  +      @ <p><b>Only files matching the glob "%h(zGlob)" are shown.</b></p>
         1103  +    }
         1104  +    @<hr /><p>
  1089   1105     }
  1090         -  if( zGlob ){
  1091         -    @ <p><b>Only files matching the glob "%h(zGlob)" are shown.</b></p>
  1092         -  }
  1093         -  @<hr /><p>
  1094   1106   
  1095   1107     manifest_file_rewind(pFrom);
  1096   1108     pFileFrom = manifest_file_next(pFrom, 0);
  1097   1109     manifest_file_rewind(pTo);
  1098   1110     pFileTo = manifest_file_next(pTo, 0);
  1099   1111     while( pFileFrom || pFileTo ){
  1100   1112       int cmp;
................................................................................
  1329   1341         }else if( zType[0]=='t' ){
  1330   1342           @ Ticket change
  1331   1343           objType |= OBJTYPE_TICKET;
  1332   1344         }else if( zType[0]=='c' ){
  1333   1345           @ Manifest of check-in
  1334   1346           objType |= OBJTYPE_CHECKIN;
  1335   1347         }else if( zType[0]=='e' ){
  1336         -        @ Instance of event
         1348  +        @ Instance of technote
  1337   1349           objType |= OBJTYPE_EVENT;
  1338   1350           hyperlink_to_event_tagid(db_column_int(&q, 5));
  1339   1351         }else{
  1340         -        @ Control file referencing
         1352  +        @ Tag referencing
  1341   1353         }
  1342   1354         if( zType[0]!='e' ){
  1343   1355           hyperlink_to_uuid(zUuid);
  1344   1356         }
  1345   1357         @ - %!W(zCom) by
  1346   1358         hyperlink_to_user(zUser,zDate," on");
  1347   1359         hyperlink_to_date(zDate, ".");
................................................................................
  1391   1403       cnt++;
  1392   1404       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1393   1405         blob_append(pDownloadName, zFilename, -1);
  1394   1406       }
  1395   1407       tag_private_status(rid);
  1396   1408     }
  1397   1409     db_finalize(&q);
         1410  +  if( db_exists("SELECT 1 FROM tagxref WHERE rid=%d AND tagid=%d",
         1411  +                rid, TAG_CLUSTER) ){
         1412  +    @ Cluster
         1413  +    cnt++;
         1414  +  }
  1398   1415     if( cnt==0 ){
  1399         -    @ Control artifact.
         1416  +    @ Unrecognized artifact
  1400   1417       if( pDownloadName && blob_size(pDownloadName)==0 ){
  1401   1418         blob_appendf(pDownloadName, "%S.txt", zUuid);
  1402   1419       }
  1403   1420       tag_private_status(rid);
  1404   1421     }
  1405   1422     return objType;
  1406   1423   }
................................................................................
  1616   1633     rid = name_to_rid_www("name");
  1617   1634     login_check_credentials();
  1618   1635     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1619   1636     if( rid==0 ) fossil_redirect_home();
  1620   1637     if( g.perm.Admin ){
  1621   1638       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1622   1639       if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
  1623         -      style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#delshun",
         1640  +      style_submenu_element("Unshun","Unshun","%s/shun?accept=%s&sub=1#delshun",
  1624   1641               g.zTop, zUuid);
  1625   1642       }else{
  1626   1643         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1627   1644               g.zTop, zUuid);
  1628   1645       }
  1629   1646     }
  1630   1647     style_header("Hex Artifact Content");
................................................................................
  1798   1815   ** Additional query parameters:
  1799   1816   **
  1800   1817   **   ln              - show line numbers
  1801   1818   **   ln=N            - highlight line number N
  1802   1819   **   ln=M-N          - highlight lines M through N inclusive
  1803   1820   **   ln=M-N+Y-Z      - higllight lines M through N and Y through Z (inclusive)
  1804   1821   **   verbose         - show more detail in the description
         1822  +**   download        - redirect to the download (artifact page only)
  1805   1823   **
  1806   1824   ** The /artifact page show the complete content of a file
  1807   1825   ** identified by SHA1HASH as preformatted text.  The
  1808   1826   ** /whatis page shows only a description of the file.
  1809   1827   */
  1810   1828   void artifact_page(void){
  1811   1829     int rid = 0;
................................................................................
  1827   1845     if( rid==0 ){
  1828   1846       rid = name_to_rid_www("name");
  1829   1847     }
  1830   1848   
  1831   1849     login_check_credentials();
  1832   1850     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
  1833   1851     if( rid==0 ) fossil_redirect_home();
         1852  +  if( descOnly || P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
         1853  +  blob_zero(&downloadName);
         1854  +  objType = object_description(rid, objdescFlags, &downloadName);
         1855  +  if( !descOnly && P("download")!=0 ){
         1856  +    cgi_redirectf("%R/raw/%T?name=%s", blob_str(&downloadName),
         1857  +          db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid));
         1858  +    /*NOTREACHED*/
         1859  +  }
  1834   1860     if( g.perm.Admin ){
  1835   1861       const char *zUuid = db_text("", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1836   1862       if( db_exists("SELECT 1 FROM shun WHERE uuid=%Q", zUuid) ){
  1837         -      style_submenu_element("Unshun","Unshun", "%s/shun?accept=%s&sub=1#accshun",
         1863  +      style_submenu_element("Unshun","Unshun","%s/shun?accept=%s&sub=1#accshun",
  1838   1864               g.zTop, zUuid);
  1839   1865       }else{
  1840   1866         style_submenu_element("Shun","Shun", "%s/shun?shun=%s#addshun",
  1841   1867               g.zTop, zUuid);
  1842   1868       }
  1843   1869     }
  1844         -  if( descOnly || P("verbose")!=0 ) objdescFlags |= OBJDESC_DETAIL;
  1845   1870     style_header("%s", descOnly ? "Artifact Description" : "Artifact Content");
  1846   1871     zUuid = db_text("?", "SELECT uuid FROM blob WHERE rid=%d", rid);
  1847   1872     if( g.perm.Setup ){
  1848   1873       @ <h2>Artifact %s(zUuid) (%d(rid)):</h2>
  1849   1874     }else{
  1850   1875       @ <h2>Artifact %s(zUuid):</h2>
  1851   1876     }
  1852         -  blob_zero(&downloadName);
  1853         -  objType = object_description(rid, objdescFlags, &downloadName);
         1877  +  if( g.perm.Admin ){
         1878  +    Stmt q;
         1879  +    db_prepare(&q,
         1880  +      "SELECT coalesce(user.login,rcvfrom.uid),"
         1881  +      "       datetime(rcvfrom.mtime), rcvfrom.ipaddr"
         1882  +      "  FROM blob, rcvfrom LEFT JOIN user ON user.uid=rcvfrom.uid"
         1883  +      " WHERE blob.rid=%d"
         1884  +      "   AND rcvfrom.rcvid=blob.rcvid;", rid);
         1885  +    while( db_step(&q)==SQLITE_ROW ){
         1886  +      const char *zUser = db_column_text(&q,0);
         1887  +      const char *zDate = db_column_text(&q,1);
         1888  +      const char *zIp = db_column_text(&q,2);
         1889  +      @ <p>Received on %s(zDate) from %h(zUser) at %h(zIp).</p>
         1890  +    }
         1891  +    db_finalize(&q);
         1892  +  }
  1854   1893     style_submenu_element("Download", "Download",
  1855   1894             "%R/raw/%T?name=%s", blob_str(&downloadName), zUuid);
  1856   1895     if( db_exists("SELECT 1 FROM mlink WHERE fid=%d", rid) ){
  1857   1896       style_submenu_element("Check-ins Using", "Check-ins Using",
  1858   1897             "%R/timeline?n=200&uf=%s",zUuid);
  1859   1898     }
  1860   1899     asText = P("txt")!=0;
................................................................................
  1894   1933       content_get(rid, &content);
  1895   1934       if( renderAsWiki ){
  1896   1935         wiki_render_by_mimetype(&content, zMime);
  1897   1936       }else if( renderAsHtml ){
  1898   1937         @ <iframe src="%R/raw/%T(blob_str(&downloadName))?name=%s(zUuid)"
  1899   1938         @   width="100%%" frameborder="0" marginwidth="0" marginheight="0"
  1900   1939         @   sandbox="allow-same-origin"
  1901         -      @   onload="this.height = this.contentDocument.documentElement.scrollHeight;">
         1940  +      @   onload="this.height=this.contentDocument.documentElement.scrollHeight;">
  1902   1941         @ </iframe>
  1903   1942       }else{
  1904   1943         style_submenu_element("Hex","Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
  1905   1944         blob_to_utf8_no_bom(&content, 0);
  1906   1945         zMime = mimetype_from_content(&content);
  1907   1946         @ <blockquote>
  1908   1947         if( zMime==0 ){
................................................................................
  1912   1951             output_text_with_line_numbers(z, zLn);
  1913   1952           }else{
  1914   1953             @ <pre>
  1915   1954             @ %h(z)
  1916   1955             @ </pre>
  1917   1956           }
  1918   1957         }else if( strncmp(zMime, "image/", 6)==0 ){
         1958  +        @ <i>(file is %d(blob_size(&content)) bytes of image data)</i><br>
  1919   1959           @ <img src="%R/raw/%s(zUuid)?m=%s(zMime)" />
  1920   1960           style_submenu_element("Image", "Image",
  1921   1961                                 "%R/raw/%s?m=%s", zUuid, zMime);
  1922   1962         }else{
  1923   1963           @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
  1924   1964         }
  1925   1965         @ </blockquote>
................................................................................
  2274   2314       }
  2275   2315       return 0;
  2276   2316     }
  2277   2317     while( fossil_isspace(zB[0]) ) zB++;
  2278   2318     while( fossil_isspace(zA[0]) ) zA++;
  2279   2319     return zA[0]==0 && zB[0]==0;
  2280   2320   }
         2321  +
         2322  +/*
         2323  +** The following methods operate on the newtags temporary table
         2324  +** that is used to collect various changes to be added to a control
         2325  +** artifact for a check-in edit.
         2326  +*/
         2327  +static void init_newtags(void){
         2328  +  db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)");
         2329  +}
         2330  +
         2331  +static void change_special(
         2332  +  const char *zName,    /* Name of the special tag */
         2333  +  const char *zOp,      /* Operation prefix (e.g. +,-,*) */
         2334  +  const char *zValue    /* Value of the tag */
         2335  +){
         2336  +  db_multi_exec("REPLACE INTO newtags VALUES(%Q,'%q',%Q)", zName, zOp, zValue);
         2337  +}
         2338  +
         2339  +static void change_sym_tag(const char *zTag, const char *zOp){
         2340  +  db_multi_exec("REPLACE INTO newtags VALUES('sym-%q',%Q,NULL)", zTag, zOp);
         2341  +}
         2342  +
         2343  +static void cancel_special(const char *zTag){
         2344  +  change_special(zTag,"-",0);
         2345  +}
         2346  +
         2347  +static void add_color(const char *zNewColor, int fPropagateColor){
         2348  +  change_special("bgcolor",fPropagateColor ? "*" : "+", zNewColor);
         2349  +}
         2350  +
         2351  +static void cancel_color(void){
         2352  +  change_special("bgcolor","-",0);
         2353  +}
         2354  +
         2355  +static void add_comment(const char *zNewComment){
         2356  +  change_special("comment","+",zNewComment);
         2357  +}
         2358  +
         2359  +static void add_date(const char *zNewDate){
         2360  +  change_special("date","+",zNewDate);
         2361  +}
         2362  +
         2363  +static void add_user(const char *zNewUser){
         2364  +  change_special("user","+",zNewUser);
         2365  +}
         2366  +
         2367  +static void add_tag(const char *zNewTag){
         2368  +  change_sym_tag(zNewTag,"+");
         2369  +}
         2370  +
         2371  +static void cancel_tag(int rid, const char *zCancelTag){
         2372  +  if( db_exists("SELECT 1 FROM tagxref, tag"
         2373  +                " WHERE tagxref.rid=%d AND tagtype>0"
         2374  +                "   AND tagxref.tagid=tag.tagid AND tagname='sym-%q'",
         2375  +                rid, zCancelTag)
         2376  +  ) change_sym_tag(zCancelTag,"-");
         2377  +}
         2378  +
         2379  +static void hide_branch(void){
         2380  +  change_special("hidden","*",0);
         2381  +}
         2382  +
         2383  +static void close_leaf(int rid){
         2384  +  change_special("closed",is_a_leaf(rid)?"+":"*",0);
         2385  +}
         2386  +
         2387  +static void change_branch(int rid, const char *zNewBranch){
         2388  +  db_multi_exec(
         2389  +    "REPLACE INTO newtags "
         2390  +    " SELECT tagname, '-', NULL FROM tagxref, tag"
         2391  +    "  WHERE tagxref.rid=%d AND tagtype==2"
         2392  +    "    AND tagname GLOB 'sym-*'"
         2393  +    "    AND tag.tagid=tagxref.tagid",
         2394  +    rid
         2395  +  );
         2396  +  change_special("branch","*",zNewBranch);
         2397  +  change_sym_tag(zNewBranch,"*");
         2398  +}
         2399  +
         2400  +/*
         2401  +** The apply_newtags method is called after all newtags have been added
         2402  +** and the control artifact is completed and then written to the DB.
         2403  +*/
         2404  +static void apply_newtags(Blob *ctrl, int rid, const char *zUuid){
         2405  +  Stmt q;
         2406  +  int nChng = 0;
         2407  +
         2408  +  db_prepare(&q, "SELECT tag, prefix, value FROM newtags"
         2409  +                 " ORDER BY prefix || tag");
         2410  +  while( db_step(&q)==SQLITE_ROW ){
         2411  +    const char *zTag = db_column_text(&q, 0);
         2412  +    const char *zPrefix = db_column_text(&q, 1);
         2413  +    const char *zValue = db_column_text(&q, 2);
         2414  +    nChng++;
         2415  +    if( zValue ){
         2416  +      blob_appendf(ctrl, "T %s%F %s %F\n", zPrefix, zTag, zUuid, zValue);
         2417  +    }else{
         2418  +      blob_appendf(ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid);
         2419  +    }
         2420  +  }
         2421  +  db_finalize(&q);
         2422  +  if( nChng>0 ){
         2423  +    int nrid;
         2424  +    Blob cksum;
         2425  +    blob_appendf(ctrl, "U %F\n", login_name());
         2426  +    md5sum_blob(ctrl, &cksum);
         2427  +    blob_appendf(ctrl, "Z %b\n", &cksum);
         2428  +    db_begin_transaction();
         2429  +    g.markPrivate = content_is_private(rid);
         2430  +    nrid = content_put(ctrl);
         2431  +    manifest_crosslink(nrid, ctrl, MC_PERMIT_HOOKS);
         2432  +    assert( blob_is_reset(ctrl) );
         2433  +    db_end_transaction(0);
         2434  +  }
         2435  +}
         2436  +
         2437  +/*
         2438  +** This method checks that the date can be parsed.
         2439  +** Returns 1 if datetime() can validate, 0 otherwise.
         2440  +*/
         2441  +int is_datetime(const char* zDate){
         2442  +  return db_int(0, "SELECT datetime(%Q) NOT NULL", zDate);
         2443  +}
  2281   2444   
  2282   2445   /*
  2283   2446   ** WEBPAGE: ci_edit
  2284   2447   ** URL:  /ci_edit?r=RID&c=NEWCOMMENT&u=NEWUSER
  2285   2448   **
  2286   2449   ** Present a dialog for updating properties of a check-in.
  2287   2450   **
................................................................................
  2352   2515     zNewBrFlag = P("newbr") ? " checked" : "";
  2353   2516     zNewBranch = PDT("brname","");
  2354   2517     zCloseFlag = P("close") ? " checked" : "";
  2355   2518     zHideFlag = P("hide") ? " checked" : "";
  2356   2519     if( P("apply") ){
  2357   2520       Blob ctrl;
  2358   2521       char *zNow;
  2359         -    int nChng = 0;
  2360   2522   
  2361   2523       login_verify_csrf_secret();
  2362   2524       blob_zero(&ctrl);
  2363   2525       zNow = date_in_standard_format(zChngTime ? zChngTime : "now");
  2364   2526       blob_appendf(&ctrl, "D %s\n", zNow);
  2365         -    db_multi_exec("CREATE TEMP TABLE newtags(tag UNIQUE, prefix, value)");
         2527  +    init_newtags();
  2366   2528       if( zNewColor[0]
  2367   2529        && (fPropagateColor!=fNewPropagateColor
  2368   2530                || fossil_strcmp(zColor,zNewColor)!=0)
  2369         -    ){
  2370         -      char *zPrefix = "+";
  2371         -      if( fNewPropagateColor ){
  2372         -        zPrefix = "*";
  2373         -      }
  2374         -      db_multi_exec("REPLACE INTO newtags VALUES('bgcolor',%Q,%Q)",
  2375         -                    zPrefix, zNewColor);
  2376         -    }
  2377         -    if( zNewColor[0]==0 && zColor[0]!=0 ){
  2378         -      db_multi_exec("REPLACE INTO newtags VALUES('bgcolor','-',NULL)");
  2379         -    }
  2380         -    if( comment_compare(zComment,zNewComment)==0 ){
  2381         -      db_multi_exec("REPLACE INTO newtags VALUES('comment','+',%Q)",
  2382         -                    zNewComment);
  2383         -    }
  2384         -    if( fossil_strcmp(zDate,zNewDate)!=0 ){
  2385         -      db_multi_exec("REPLACE INTO newtags VALUES('date','+',%Q)",
  2386         -                    zNewDate);
  2387         -    }
  2388         -    if( fossil_strcmp(zUser,zNewUser)!=0 ){
  2389         -      db_multi_exec("REPLACE INTO newtags VALUES('user','+',%Q)", zNewUser);
  2390         -    }
         2531  +    ) add_color(zNewColor,fNewPropagateColor);
         2532  +    if( zNewColor[0]==0 && zColor[0]!=0 ) cancel_color();
         2533  +    if( comment_compare(zComment,zNewComment)==0 ) add_comment(zNewComment);
         2534  +    if( fossil_strcmp(zDate,zNewDate)!=0 ) add_date(zNewDate);
         2535  +    if( fossil_strcmp(zUser,zNewUser)!=0 ) add_user(zNewUser);
  2391   2536       db_prepare(&q,
  2392   2537          "SELECT tag.tagid, tagname FROM tagxref, tag"
  2393   2538          " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid",
  2394   2539          rid
  2395   2540       );
  2396   2541       while( db_step(&q)==SQLITE_ROW ){
  2397   2542         int tagid = db_column_int(&q, 0);
  2398   2543         const char *zTag = db_column_text(&q, 1);
  2399   2544         char zLabel[30];
  2400   2545         sqlite3_snprintf(sizeof(zLabel), zLabel, "c%d", tagid);
  2401         -      if( P(zLabel) ){
  2402         -        db_multi_exec("REPLACE INTO newtags VALUES(%Q,'-',NULL)", zTag);
  2403         -      }
         2546  +      if( P(zLabel) ) cancel_special(zTag);
  2404   2547       }
  2405   2548       db_finalize(&q);
  2406         -    if( zHideFlag[0] ){
  2407         -      db_multi_exec("REPLACE INTO newtags VALUES('hidden','*',NULL)");
  2408         -    }
  2409         -    if( zCloseFlag[0] ){
  2410         -      db_multi_exec("REPLACE INTO newtags VALUES('closed','%s',NULL)",
  2411         -          is_a_leaf(rid)?"+":"*");
  2412         -    }
  2413         -    if( zNewTagFlag[0] && zNewTag[0] ){
  2414         -      db_multi_exec("REPLACE INTO newtags VALUES('sym-%q','+',NULL)", zNewTag);
  2415         -    }
  2416         -    if( zNewBrFlag[0] && zNewBranch[0] ){
  2417         -      db_multi_exec(
  2418         -        "REPLACE INTO newtags "
  2419         -        " SELECT tagname, '-', NULL FROM tagxref, tag"
  2420         -        "  WHERE tagxref.rid=%d AND tagtype==2"
  2421         -        "    AND tagname GLOB 'sym-*'"
  2422         -        "    AND tag.tagid=tagxref.tagid",
  2423         -        rid
  2424         -      );
  2425         -      db_multi_exec("REPLACE INTO newtags VALUES('branch','*',%Q)", zNewBranch);
  2426         -      db_multi_exec("REPLACE INTO newtags VALUES('sym-%q','*',NULL)",
  2427         -                    zNewBranch);
  2428         -    }
  2429         -    db_prepare(&q, "SELECT tag, prefix, value FROM newtags"
  2430         -                   " ORDER BY prefix || tag");
  2431         -    while( db_step(&q)==SQLITE_ROW ){
  2432         -      const char *zTag = db_column_text(&q, 0);
  2433         -      const char *zPrefix = db_column_text(&q, 1);
  2434         -      const char *zValue = db_column_text(&q, 2);
  2435         -      nChng++;
  2436         -      if( zValue ){
  2437         -        blob_appendf(&ctrl, "T %s%F %s %F\n", zPrefix, zTag, zUuid, zValue);
  2438         -      }else{
  2439         -        blob_appendf(&ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid);
  2440         -      }
  2441         -    }
  2442         -    db_finalize(&q);
  2443         -    if( nChng>0 ){
  2444         -      int nrid;
  2445         -      Blob cksum;
  2446         -      blob_appendf(&ctrl, "U %F\n", login_name());
  2447         -      md5sum_blob(&ctrl, &cksum);
  2448         -      blob_appendf(&ctrl, "Z %b\n", &cksum);
  2449         -      db_begin_transaction();
  2450         -      g.markPrivate = content_is_private(rid);
  2451         -      nrid = content_put(&ctrl);
  2452         -      manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
  2453         -      assert( blob_is_reset(&ctrl) );
  2454         -      db_end_transaction(0);
  2455         -    }
         2549  +    if( zHideFlag[0] ) hide_branch();
         2550  +    if( zCloseFlag[0] ) close_leaf(rid);
         2551  +    if( zNewTagFlag[0] && zNewTag[0] ) add_tag(zNewTag);
         2552  +    if( zNewBrFlag[0] && zNewBranch[0] ) change_branch(rid,zNewBranch);
         2553  +    apply_newtags(&ctrl, rid, zUuid);
  2456   2554       cgi_redirectf("ci?name=%s", zUuid);
  2457   2555     }
  2458   2556     blob_zero(&comment);
  2459   2557     blob_append(&comment, zNewComment, -1);
  2460   2558     zUuid[10] = 0;
  2461   2559     style_header("Edit Check-in [%s]", zUuid);
  2462   2560     /*
................................................................................
  2650   2748     @ <input type="submit" name="apply" value="Apply Changes" />
  2651   2749     @ <input type="submit" name="cancel" value="Cancel" />
  2652   2750     @ </td></tr>
  2653   2751     @ </table>
  2654   2752     @ </div></form>
  2655   2753     style_footer();
  2656   2754   }
         2755  +
         2756  +/*
         2757  +** Prepare an ammended commit comment.  Let the user modify it using the
         2758  +** editor specified in the global_config table or either
         2759  +** the VISUAL or EDITOR environment variable.
         2760  +**
         2761  +** Store the final commit comment in pComment.  pComment is assumed
         2762  +** to be uninitialized - any prior content is overwritten.
         2763  +**
         2764  +** Use zInit to initialize the check-in comment so that the user does
         2765  +** not have to retype.
         2766  +*/
         2767  +static void prepare_amend_comment(
         2768  +  Blob *pComment,
         2769  +  const char *zInit,
         2770  +  const char *zUuid
         2771  +){
         2772  +  Blob prompt;
         2773  +#if defined(_WIN32) || defined(__CYGWIN__)
         2774  +  int bomSize;
         2775  +  const unsigned char *bom = get_utf8_bom(&bomSize);
         2776  +  blob_init(&prompt, (const char *) bom, bomSize);
         2777  +  if( zInit && zInit[0]){
         2778  +    blob_append(&prompt, zInit, -1);
         2779  +  }
         2780  +#else
         2781  +  blob_init(&prompt, zInit, -1);
         2782  +#endif
         2783  +  blob_append(&prompt, "\n# Enter a new comment for check-in ", -1);
         2784  +  if( zUuid && zUuid[0] ){
         2785  +    blob_append(&prompt, zUuid, -1);
         2786  +  }
         2787  +  blob_append(&prompt, ".\n# Lines beginning with a # are ignored.\n", -1);
         2788  +  prompt_for_user_comment(pComment, &prompt);
         2789  +  blob_reset(&prompt);
         2790  +}
         2791  +
         2792  +#define AMEND_USAGE_STMT "UUID OPTION ?OPTION ...?"
         2793  +/*
         2794  +** COMMAND: amend
         2795  +**
         2796  +** Usage: %fossil amend UUID OPTION ?OPTION ...?
         2797  +**
         2798  +** Amend the tags on check-in UUID to change how it displays in the timeline.
         2799  +**
         2800  +** Options:
         2801  +**
         2802  +**    --author USER           Make USER the author for check-in
         2803  +**    -m|--comment COMMENT    Make COMMENT the check-in comment
         2804  +**    -M|--message-file FILE  Read the amended comment from FILE
         2805  +**    -e|--edit-comment       Launch editor to revise comment
         2806  +**    --date DATE             Make DATE the check-in time
         2807  +**    --bgcolor COLOR         Apply COLOR to this check-in
         2808  +**    --branchcolor COLOR     Apply and propagate COLOR to the branch
         2809  +**    --tag TAG               Add new TAG to this check-in
         2810  +**    --cancel TAG            Cancel TAG from this check-in
         2811  +**    --branch NAME           Make this check-in the start of branch NAME
         2812  +**    --hide                  Hide branch starting from this check-in
         2813  +**    --close                 Mark this "leaf" as closed
         2814  +*/
         2815  +void ci_amend_cmd(void){
         2816  +  int rid;
         2817  +  const char *zComment;         /* Current comment on the check-in */
         2818  +  const char *zNewComment;      /* Revised check-in comment */
         2819  +  const char *zComFile;         /* Filename from which to read comment */
         2820  +  const char *zUser;            /* Current user for the check-in */
         2821  +  const char *zNewUser;         /* Revised user */
         2822  +  const char *zDate;            /* Current date of the check-in */
         2823  +  const char *zNewDate;         /* Revised check-in date */
         2824  +  const char *zColor;
         2825  +  const char *zNewColor;
         2826  +  const char *zNewBrColor;
         2827  +  const char *zNewBranch;
         2828  +  const char **pzNewTags = 0;
         2829  +  const char **pzCancelTags = 0;
         2830  +  int fClose;                   /* True if leaf should be closed */
         2831  +  int fHide;                    /* True if branch should be hidden */
         2832  +  int fPropagateColor;          /* True if color propagates before amend */
         2833  +  int fNewPropagateColor = 0;   /* True if color propagates after amend */
         2834  +  int fHasHidden = 0;           /* True if hidden tag already set */
         2835  +  int fHasClosed = 0;           /* True if closed tag already set */
         2836  +  int fEditComment;             /* True if editor to be used for comment */
         2837  +  const char *zChngTime;        /* The change time on the control artifact */
         2838  +  const char *zUuid;
         2839  +  Blob ctrl;
         2840  +  Blob comment;
         2841  +  char *zNow;
         2842  +  int nTags, nCancels;
         2843  +  int i;
         2844  +  Stmt q;
         2845  +
         2846  +  if( g.argc==3 ) usage(AMEND_USAGE_STMT);
         2847  +  fEditComment = find_option("edit-comment","e",0)!=0;
         2848  +  zNewComment = find_option("comment","m",1);
         2849  +  zComFile = find_option("message-file","M",1);
         2850  +  zNewBranch = find_option("branch",0,1);
         2851  +  zNewColor = find_option("bgcolor",0,1);
         2852  +  zNewBrColor = find_option("branchcolor",0,1);
         2853  +  if( zNewBrColor ){
         2854  +    zNewColor = zNewBrColor;
         2855  +    fNewPropagateColor = 1;
         2856  +  }
         2857  +  zNewDate = find_option("date",0,1);
         2858  +  zNewUser = find_option("author",0,1);
         2859  +  pzNewTags = find_repeatable_option("tag",0,&nTags);
         2860  +  pzCancelTags = find_repeatable_option("cancel",0,&nCancels);
         2861  +  fClose = find_option("close",0,0)!=0;
         2862  +  fHide = find_option("hide",0,0)!=0;
         2863  +  zChngTime = find_option("chngtime",0,1);
         2864  +  db_find_and_open_repository(0,0);
         2865  +  user_select();
         2866  +  verify_all_options();
         2867  +  if( g.argc<3 || g.argc>=4 ) usage(AMEND_USAGE_STMT);
         2868  +  rid = name_to_typed_rid(g.argv[2], "ci");
         2869  +  if( rid==0 && !is_a_version(rid) ) fossil_fatal("no such check-in");
         2870  +  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
         2871  +  if( zUuid==0 ) fossil_fatal("Unable to find UUID");
         2872  +  zComment = db_text(0, "SELECT coalesce(ecomment,comment)"
         2873  +                        "  FROM event WHERE objid=%d", rid);
         2874  +  zUser = db_text(0, "SELECT coalesce(euser,user)"
         2875  +                     "  FROM event WHERE objid=%d", rid);
         2876  +  zDate = db_text(0, "SELECT datetime(mtime)"
         2877  +                     "  FROM event WHERE objid=%d", rid);
         2878  +  zColor = db_text("", "SELECT bgcolor"
         2879  +                        "  FROM event WHERE objid=%d", rid);
         2880  +  fPropagateColor = db_int(0, "SELECT tagtype FROM tagxref"
         2881  +                              " WHERE rid=%d AND tagid=%d",
         2882  +                              rid, TAG_BGCOLOR)==2;
         2883  +  fNewPropagateColor = zNewColor && zNewColor[0]
         2884  +                        ? fNewPropagateColor : fPropagateColor;
         2885  +  db_prepare(&q,
         2886  +     "SELECT tag.tagid FROM tagxref, tag"
         2887  +     " WHERE tagxref.rid=%d AND tagtype>0 AND tagxref.tagid=tag.tagid",
         2888  +     rid
         2889  +  );
         2890  +  while( db_step(&q)==SQLITE_ROW ){
         2891  +    int tagid = db_column_int(&q, 0);
         2892  +
         2893  +    if( tagid == TAG_CLOSED ){
         2894  +      fHasClosed = 1;
         2895  +    }else if( tagid==TAG_HIDDEN ){
         2896  +      fHasHidden = 1;
         2897  +    }else{
         2898  +      continue;
         2899  +    }
         2900  +  }
         2901  +  db_finalize(&q);
         2902  +  blob_zero(&ctrl);
         2903  +  zNow = date_in_standard_format(zChngTime && zChngTime[0] ? zChngTime : "now");
         2904  +  blob_appendf(&ctrl, "D %s\n", zNow);
         2905  +  init_newtags();
         2906  +  if( zNewColor && zNewColor[0]
         2907  +      && (fPropagateColor!=fNewPropagateColor
         2908  +            || fossil_strcmp(zColor,zNewColor)!=0)
         2909  +  ){
         2910  +    add_color(
         2911  +      mprintf("%s%s", (zNewColor[0]!='#' &&
         2912  +        validate16(zNewColor,strlen(zNewColor)) &&
         2913  +        (strlen(zNewColor)==6 || strlen(zNewColor)==3)) ? "#" : "",
         2914  +        zNewColor
         2915  +      ),
         2916  +      fNewPropagateColor
         2917  +    );
         2918  +  }
         2919  +  if( (zNewColor!=0 && zNewColor[0]==0) && (zColor && zColor[0] ) ){
         2920  +    cancel_color();
         2921  +  }
         2922  +  if( fEditComment ){
         2923  +    prepare_amend_comment(&comment, zComment, zUuid);
         2924  +    zNewComment = blob_str(&comment);
         2925  +  }else if( zComFile ){
         2926  +    blob_zero(&comment);
         2927  +    blob_read_from_file(&comment, zComFile);
         2928  +    blob_to_utf8_no_bom(&comment, 1);
         2929  +    zNewComment = blob_str(&comment);
         2930  +  }
         2931  +  if( zNewComment && zNewComment[0]
         2932  +      && comment_compare(zComment,zNewComment)==0 ) add_comment(zNewComment);
         2933  +  if( zNewDate && zNewDate[0] && fossil_strcmp(zDate,zNewDate)!=0 ){
         2934  +    if( is_datetime(zNewDate) ){
         2935  +      add_date(zNewDate);
         2936  +    }else{
         2937  +      fossil_fatal("Unsupported date format, use YYYY-MM-DD HH:MM:SS");
         2938  +    }
         2939  +  }
         2940  +  if( zNewUser && zNewUser[0] && fossil_strcmp(zUser,zNewUser)!=0 ){
         2941  +    add_user(zNewUser);
         2942  +  }
         2943  +  if( pzNewTags!=0 ){
         2944  +    for(i=0; i<nTags; i++){
         2945  +      if( pzNewTags[i] && pzNewTags[i][0] ) add_tag(pzNewTags[i]);
         2946  +    }
         2947  +    fossil_free((void *)pzNewTags);
         2948  +  }
         2949  +  if( pzCancelTags!=0 ){
         2950  +    for(i=0; i<nCancels; i++){
         2951  +      if( pzCancelTags[i] && pzCancelTags[i][0] )
         2952  +        cancel_tag(rid,pzCancelTags[i]);
         2953  +    }
         2954  +    fossil_free((void *)pzCancelTags);
         2955  +  }
         2956  +  if( fHide && !fHasHidden ) hide_branch();
         2957  +  if( fClose && !fHasClosed ) close_leaf(rid);
         2958  +  if( zNewBranch && zNewBranch[0] ) change_branch(rid,zNewBranch);
         2959  +  apply_newtags(&ctrl, rid, zUuid);
         2960  +  show_common_info(rid, "uuid:", 1, 0);
         2961  +}

Changes to src/json.c.

   220    220   ** incorrectly removes it from the gc (which we never do). If this
   221    221   ** function fails, it is fatal to the app (as it indicates an
   222    222   ** allocation error (more likely than not) or a serious internal error
   223    223   ** such as numeric overflow).
   224    224   */
   225    225   void json_gc_add( char const * key, cson_value * v ){
   226    226     int const rc = cson_array_append( g.json.gc.a, v );
          227  +
   227    228     assert( NULL != g.json.gc.a );
   228    229     if( 0 != rc ){
   229    230       cson_value_free( v );
   230    231     }
   231    232     assert( (0==rc) && "Adding item to GC failed." );
   232    233     if(0!=rc){
   233    234       fprintf(stderr,"%s: FATAL: alloc error.\n", g.argv[0])
................................................................................
   476    477   /*
   477    478   ** The boolean equivalent of json_find_option_cstr().
   478    479   ** If the option is not found, dftl is returned.
   479    480   */
   480    481   int json_find_option_bool(char const * zKey,
   481    482                             char const * zCLILong,
   482    483                             char const * zCLIShort,
   483         -                          char dflt ){
          484  +                          int dflt ){
   484    485     int rc = -1;
   485    486     if(!g.isHTTP){
   486    487       if(NULL != find_option(zCLILong ? zCLILong : zKey,
   487    488                              zCLIShort, 0)){
   488    489         rc = 1;
   489    490       }
   490    491     }
................................................................................
   629    630   ** we will not be able to replace fossil's internal idea of the auth
   630    631   ** info in time (and future changes to that state may cause unexpected
   631    632   ** results).
   632    633   **
   633    634   ** The result of this call are cached for future calls.
   634    635   */
   635    636   cson_value * json_auth_token(){
   636         -  if( !g.json.authToken ){
          637  +    assert(g.json.gc.a && "json_main_bootstrap() was not called!");
          638  +    if( !g.json.authToken ){
   637    639       /* Try to get an authorization token from GET parameter, POSTed
   638    640          JSON, or fossil cookie (in that order). */
   639    641       g.json.authToken = json_getenv(FossilJsonKeys.authToken);
   640    642       if(g.json.authToken
   641    643          && cson_value_is_string(g.json.authToken)
   642    644          && !PD(login_cookie_name(),NULL)){
   643    645         /* tell fossil to use this login info.
................................................................................
   695    697   */
   696    698   void json_main_bootstrap(){
   697    699     cson_value * v;
   698    700     assert( (NULL == g.json.gc.v) &&
   699    701             "json_main_bootstrap() was called twice!" );
   700    702   
   701    703     g.json.timerId = fossil_timer_start();
   702         -  
          704  +
   703    705     /* g.json.gc is our "garbage collector" - where we put JSON values
   704    706        which need a long lifetime but don't have a logical parent to put
   705    707        them in.
   706    708     */
   707    709     v = cson_value_new_array();
   708    710     g.json.gc.v = v;
          711  +  assert(0 != g.json.gc.v);
   709    712     g.json.gc.a = cson_value_get_array(v);
          713  +  assert(0 != g.json.gc.a);
   710    714     cson_value_add_reference(v)
   711    715       /* Needed to allow us to include this value in other JSON
   712    716          containers without transferring ownership to those containers.
   713    717          All other persistent g.json.XXX.v values get appended to
   714    718          g.json.gc.a, and therefore already have a live reference
   715    719          for this purpose.
   716    720       */
................................................................................
   753    757   ** for consistency with how json_err() works.
   754    758   */
   755    759   void json_warn( int code, char const * fmt, ... ){
   756    760     cson_object * obj = NULL;
   757    761     assert( (code>FSL_JSON_W_START)
   758    762             && (code<FSL_JSON_W_END)
   759    763             && "Invalid warning code.");
          764  +  assert(g.json.gc.a && "json_main_bootstrap() was not called!");
   760    765     if(!g.json.warnings){
   761    766       g.json.warnings = cson_new_array();
   762    767       assert((NULL != g.json.warnings) && "Alloc error.");
   763    768       json_gc_add("$WARNINGS",cson_array_value(g.json.warnings));
   764    769     }
   765    770     obj = cson_new_object();
   766    771     cson_array_append(g.json.warnings, cson_object_value(obj));
................................................................................
   799    804   ** Achtung: leading and trailing whitespace of elements are elided.
   800    805   **
   801    806   ** Achtung: empty elements will be skipped, meaning consecutive empty
   802    807   ** elements are collapsed.
   803    808   */
   804    809   int json_string_split( char const * zStr,
   805    810                          char separator,
   806         -                       char doDeHttp,
          811  +                       int doDeHttp,
   807    812                          cson_array * target ){
   808    813     char const * p = zStr /* current byte */;
   809    814     char const * head  /* current start-of-token */;
   810    815     unsigned int len = 0   /* current token's length */;
   811    816     int rc = 0   /* return code (number of added elements)*/;
   812    817     assert( zStr && target );
   813    818     while( fossil_isspace(*p) ){
................................................................................
   874    879   ** in any way or produced no tokens).
   875    880   **
   876    881   ** The returned value is owned by the caller. If not NULL then it
   877    882   ** _will_ have a JSON type of Array.
   878    883   */
   879    884   cson_value * json_string_split2( char const * zStr,
   880    885                                    char separator,
   881         -                                 char doDeHttp ){
          886  +                                 int doDeHttp ){
   882    887     cson_array * a = cson_new_array();
   883    888     int rc = json_string_split( zStr, separator, doDeHttp, a );
   884    889     if( 0>=rc ){
   885    890       cson_free_array(a);
   886    891       a = NULL;
   887    892     }
   888    893     return a ? cson_array_value(a) : NULL;
................................................................................
   902    907   ** before they do any work.
   903    908   **
   904    909   ** This must only be called once, or an assertion may be triggered.
   905    910   */
   906    911   static void json_mode_bootstrap(){
   907    912     static char once = 0  /* guard against multiple runs */;
   908    913     char const * zPath = P("PATH_INFO");
          914  +  assert(g.json.gc.a && "json_main_bootstrap() was not called!");
   909    915     assert( (0==once) && "json_mode_bootstrap() called too many times!");
   910    916     if( once ){
   911    917       return;
   912    918     }else{
   913    919       once = 1;
   914    920     }
   915    921     g.json.isJsonMode = 1;
................................................................................
  1080   1086   ** invalidated if that object is modified (depending on how it is
  1081   1087   ** modified).
  1082   1088   **
  1083   1089   ** Note that CLI options are not included in the command path. Use
  1084   1090   ** find_option() to get those.
  1085   1091   **
  1086   1092   */
  1087         -char const * json_command_arg(unsigned char ndx){
         1093  +char const * json_command_arg(unsigned short ndx){
  1088   1094     cson_array * ar = g.json.cmd.a;
  1089   1095     assert((NULL!=ar) && "Internal error. Was json_mode_bootstrap() called?");
  1090   1096     assert((g.argc>1) && "Internal error - we never should have gotten this far.");
  1091   1097     if( g.json.cmd.offset < 0 ){
  1092   1098       /* first-time setup. */
  1093   1099       short i = 0;
  1094   1100   #define NEXT cson_string_cstr(          \
................................................................................
  1495   1501   ** If !g.isHTTP then alsoOutput is ignored and all output is sent to
  1496   1502   ** stdout immediately.
  1497   1503   **
  1498   1504   ** For generating the resultText property: if msg is not NULL then it
  1499   1505   ** is used as-is. If it is NULL then g.zErrMsg is checked, and if that
  1500   1506   ** is NULL then json_err_cstr(code) is used.
  1501   1507   */
  1502         -void json_err( int code, char const * msg, char alsoOutput ){
         1508  +void json_err( int code, char const * msg, int alsoOutput ){
  1503   1509     int rc = code ? code : (g.json.resultCode
  1504   1510                             ? g.json.resultCode
  1505   1511                             : FSL_JSON_E_UNKNOWN);
  1506   1512     cson_value * resp = NULL;
  1507   1513     rc = json_dumbdown_rc(rc);
  1508   1514     if( rc && !msg ){
  1509   1515       msg = g.zErrMsg;
................................................................................
  1660   1666   ** pTgt has the same semantics as described for
  1661   1667   ** json_stmt_to_array_of_obj().
  1662   1668   **
  1663   1669   ** FIXME: change this to take a (char const *) instead of a blob,
  1664   1670   ** to simplify the trivial use-cases (which don't need a Blob).
  1665   1671   */
  1666   1672   cson_value * json_sql_to_array_of_obj(Blob * pSql, cson_array * pTgt,
  1667         -                                      char resetBlob){
         1673  +                                      int resetBlob){
  1668   1674     Stmt q = empty_Stmt;
  1669   1675     cson_value * pay = NULL;
  1670   1676     assert( blob_size(pSql) > 0 );
  1671   1677     db_prepare(&q, "%s", blob_str(pSql) /*safe-for-%s*/);
  1672   1678     if(resetBlob){
  1673   1679       blob_reset(pSql);
  1674   1680     }
................................................................................
  1685   1691   **
  1686   1692   ** See info_tags_of_checkin() for more details (this is simply a JSON
  1687   1693   ** wrapper for that function).
  1688   1694   **
  1689   1695   ** If there are no tags then this function returns NULL, not an empty
  1690   1696   ** Array.
  1691   1697   */
  1692         -cson_value * json_tags_for_checkin_rid(int rid, char propagatingOnly){
         1698  +cson_value * json_tags_for_checkin_rid(int rid, int propagatingOnly){
  1693   1699     cson_value * v = NULL;
  1694   1700     char * tags = info_tags_of_checkin(rid, propagatingOnly);
  1695   1701     if(tags){
  1696   1702       if(*tags){
  1697   1703         v = json_string_split2(tags,',',0);
  1698   1704       }
  1699   1705       free(tags);
................................................................................
  2219   2225   **
  2220   2226   ** Pages under /json/... must be entered into JsonPageDefs.
  2221   2227   ** This function dispatches them, and is the HTTP equivalent of
  2222   2228   ** json_cmd_top().
  2223   2229   */
  2224   2230   void json_page_top(void){
  2225   2231     char const * zCommand;
         2232  +  assert(g.json.gc.a && "json_main_bootstrap() was not called!");
  2226   2233     json_mode_bootstrap();
  2227   2234     zCommand = json_command_arg(1);
  2228   2235     if(!zCommand || !*zCommand){
  2229   2236       json_dispatch_missing_args_err( JsonPageDefs,
  2230   2237                                       "No command (sub-path) specified."
  2231   2238                                       " Try one of: ");
  2232   2239       return;

Changes to src/json_detail.h.

   182    182     **
   183    183     ** <0 = CLI only, >0 = HTTP only, 0==both
   184    184     **
   185    185     ** Now that we can simulate POST in CLI mode, the distinction
   186    186     ** between them has disappeared in most (or all) cases, so 0 is
   187    187     ** the standard value.
   188    188     */
   189         -  char runMode;
          189  +  int runMode;
   190    190   } JsonPageDef;
   191    191   
   192    192   /*
   193    193   ** Holds common keys used for various JSON API properties.
   194    194   */
   195    195   typedef struct FossilJsonKeys_{
   196    196     /** maintainers: please keep alpha sorted (case-insensitive) */

Changes to src/json_timeline.c.

   226    226     int rc = 0;
   227    227     zAfter = json_find_option_cstr("after",NULL,"a");
   228    228     zBefore = zAfter ? NULL : json_find_option_cstr("before",NULL,"b");
   229    229   
   230    230     if(zAfter&&*zAfter){
   231    231       while( fossil_isspace(*zAfter) ) ++zAfter;
   232    232       blob_appendf(pSql,
   233         -                 " AND event.mtime>=(SELECT julianday(%Q,'utc')) "
          233  +                 " AND event.mtime>=(SELECT julianday(%Q,fromLocal())) "
   234    234                    " ORDER BY event.mtime ASC ",
   235    235                    zAfter);
   236    236       rc = 1;
   237    237     }else if(zBefore && *zBefore){
   238    238       while( fossil_isspace(*zBefore) ) ++zBefore;
   239    239       blob_appendf(pSql,
   240         -                 " AND event.mtime<=(SELECT julianday(%Q,'utc')) "
          240  +                 " AND event.mtime<=(SELECT julianday(%Q,fromLocal())) "
   241    241                    " ORDER BY event.mtime DESC ",
   242    242                    zBefore);
   243    243       rc = -1;
   244    244     }else{
   245    245       blob_append(pSql, " ORDER BY event.mtime DESC ", -1);
   246    246       rc = 0;
   247    247     }
................................................................................
   324    324              "       (fid==0) AS isdel,"
   325    325              "       (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
   326    326              "       blob.uuid as uuid,"
   327    327              "       (SELECT uuid FROM blob WHERE rid=pid) as parent,"
   328    328              "       blob.size as size"
   329    329              "  FROM mlink, blob"
   330    330              " WHERE mid=%d AND pid!=fid"
   331         -           " AND blob.rid=fid "
          331  +           " AND blob.rid=fid AND NOT mlink.isaux"
   332    332              " ORDER BY name /*sort*/",
   333    333                rid
   334    334                );
   335    335     while( (SQLITE_ROW == db_step(&q)) ){
   336    336       cson_value * rowV = cson_value_new_object();
   337    337       cson_object * row = cson_value_get_object(rowV);
   338    338       int const isNew = db_column_int(&q,0);

Changes to src/json_wiki.c.

   544    544     }
   545    545   
   546    546     blob_init(&w1, pW1->zWiki, -1);
   547    547     blob_zero(&w2);
   548    548     blob_init(&w2, pW2->zWiki, -1);
   549    549     blob_zero(&d);
   550    550     diffFlags = DIFF_IGNORE_EOLWS | DIFF_STRIP_EOLCR;
   551         -  text_diff(&w2, &w1, &d, 0, diffFlags);
          551  +  text_diff(&w1, &w2, &d, 0, diffFlags);
   552    552     blob_reset(&w1);
   553    553     blob_reset(&w2);
   554    554   
   555    555     pay = cson_new_object();
   556    556     
   557    557     zUuid = json_wiki_get_uuid_for_rid( pW1->rid );
   558    558     cson_object_set(pay, "v1", json_new_string(zUuid) );

Changes to src/leaf.c.

   222    222                   TAG_BRANCH, rid);
   223    223     if( zBr==0 ) zBr = fossil_strdup("trunk");
   224    224     blob_init(&msg, 0, 0);
   225    225     blob_appendf(&msg, "WARNING: multiple open leaf check-ins on %s:", zBr);
   226    226     db_prepare(&q,
   227    227       "SELECT"
   228    228       "  (SELECT uuid FROM blob WHERE rid=leaf.rid),"
   229         -    "  (SELECT datetime(mtime%s) FROM event WHERE objid=leaf.rid),"
          229  +    "  (SELECT datetime(mtime,toLocal()) FROM event WHERE objid=leaf.rid),"
   230    230       "  leaf.rid"
   231    231       "  FROM leaf"
   232    232       " WHERE (SELECT value FROM tagxref WHERE tagid=%d AND rid=leaf.rid)=%Q"
   233    233       "   AND NOT %z"
   234    234       " ORDER BY 2 DESC",
   235         -    timeline_utc(), TAG_BRANCH, zBr, leaf_is_closed_sql("leaf.rid")
          235  +    TAG_BRANCH, zBr, leaf_is_closed_sql("leaf.rid")
   236    236     );
   237    237     while( db_step(&q)==SQLITE_ROW ){
   238    238       blob_appendf(&msg, "\n  (%d) %s [%S]%s",
   239    239             ++n, db_column_text(&q,1), db_column_text(&q,0),
   240    240             db_column_int(&q,2)==currentCkout ? " (current)" : "");
   241    241     }
   242    242     db_finalize(&q);

Changes to src/linenoise.c.

   116    116   #include <sys/types.h>
   117    117   #include <sys/ioctl.h>
   118    118   #include <unistd.h>
   119    119   #include "linenoise.h"
   120    120   
   121    121   #define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
   122    122   #define LINENOISE_MAX_LINE 4096
   123         -static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
          123  +static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
   124    124   static linenoiseCompletionCallback *completionCallback = NULL;
   125    125   
   126    126   static struct termios orig_termios; /* In order to restore at exit.*/
   127    127   static int rawmode = 0; /* For atexit() function to check if restore is needed*/
   128    128   static int mlmode = 0;  /* Multi line mode. Default is single line. */
   129    129   static int atexit_registered = 0; /* Register atexit just 1 time. */
   130    130   static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
................................................................................
   918    918           nread = read(STDIN_FILENO,&c,1);
   919    919           if (nread <= 0) continue;
   920    920           memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
   921    921           quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
   922    922           if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
   923    923   
   924    924           printf("'%c' %02x (%d) (type quit to exit)\n",
   925         -            isprint(c) ? c : '?', (int)c, (int)c);
          925  +            isprint((int)c) ? c : '?', (int)c, (int)c);
   926    926           printf("\r"); /* Go left edge manually, we are in raw mode. */
   927    927           fflush(stdout);
   928    928       }
   929    929       disableRawMode(STDIN_FILENO);
   930    930   }
   931    931   
   932    932   /* This function calls the line editing function linenoiseEdit() using

Changes to src/login.c.

   214    214     char *zSha1Pw = sha1_shared_secret(zPasswd, zUsername, 0);
   215    215     int const uid =
   216    216         db_int(0,
   217    217                "SELECT uid FROM user"
   218    218                " WHERE login=%Q"
   219    219                "   AND length(cap)>0 AND length(pw)>0"
   220    220                "   AND login NOT IN ('anonymous','nobody','developer','reader')"
   221         -             "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))",
          221  +             "   AND (pw=%Q OR (length(pw)<>40 AND pw=%Q))"
          222  +             "   AND (info NOT LIKE '%%expires 20%%'"
          223  +             "      OR substr(info,instr(lower(info),'expires')+8,10)>datetime('now'))",
   222    224                zUsername, zSha1Pw, zPasswd
   223    225                );
   224    226     free(zSha1Pw);
   225    227     return uid;
   226    228   }
   227    229   
   228    230   /*
................................................................................
  1071   1073     int i;
  1072   1074     FossilUserPerms *p = (flags & LOGIN_ANON) ? &g.anon : &g.perm;
  1073   1075     if(NULL==zCap){
  1074   1076       return;
  1075   1077     }
  1076   1078     for(i=0; zCap[i]; i++){
  1077   1079       switch( zCap[i] ){
  1078         -      case 's':   p->Setup = 1;  /* Fall thru into Admin */
         1080  +      case 's':   p->Setup = 1; /* Fall thru into Admin */
  1079   1081         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;
  1085         -                           /* Fall thru into Read/Write */
  1086         -      case 'i':   p->Read = p->Write = 1;                     break;
         1082  +                             p->RdWiki = p->WrWiki = p->NewWiki =
         1083  +                             p->ApndWiki = p->Hyperlink = p->Clone =
         1084  +                             p->NewTkt = p->Password = p->RdAddr =
         1085  +                             p->TktFmt = p->Attach = p->ApndTkt =
         1086  +                             p->ModWiki = p->ModTkt = p->Delete =
         1087  +                             p->Private = 1;
         1088  +                             /* Fall thru into Read/Write */
         1089  +      case 'i':   p->Read = p->Write = 1;                      break;
  1087   1090         case 'o':   p->Read = 1;                                 break;
  1088   1091         case 'z':   p->Zip = 1;                                  break;
  1089   1092   
  1090   1093         case 'd':   p->Delete = 1;                               break;
  1091   1094         case 'h':   p->Hyperlink = 1;                            break;
  1092   1095         case 'g':   p->Clone = 1;                                break;
  1093   1096         case 'p':   p->Password = 1;                             break;
  1094   1097   
  1095   1098         case 'j':   p->RdWiki = 1;                               break;
  1096         -      case 'k':   p->WrWiki = p->RdWiki = p->ApndWiki =1;    break;
         1099  +      case 'k':   p->WrWiki = p->RdWiki = p->ApndWiki =1;      break;
  1097   1100         case 'm':   p->ApndWiki = 1;                             break;
  1098   1101         case 'f':   p->NewWiki = 1;                              break;
  1099   1102         case 'l':   p->ModWiki = 1;                              break;
  1100   1103   
  1101   1104         case 'e':   p->RdAddr = 1;                               break;
  1102   1105         case 'r':   p->RdTkt = 1;                                break;
  1103   1106         case 'n':   p->NewTkt = 1;                               break;
................................................................................
  1177   1180         case 't':  rc = p->TktFmt;    break;
  1178   1181         /* case 'u': READER    */
  1179   1182         /* case 'v': DEVELOPER */
  1180   1183         case 'w':  rc = p->WrTkt;     break;
  1181   1184         case 'x':  rc = p->Private;   break;
  1182   1185         /* case 'y': */
  1183   1186         case 'z':  rc = p->Zip;       break;
  1184         -      default:   rc = 0;             break;
         1187  +      default:   rc = 0;            break;
  1185   1188       }
  1186   1189     }
  1187   1190     return rc;
  1188   1191   }
  1189   1192   
  1190   1193   /*
  1191   1194   ** Change the login to zUser.
................................................................................
  1285   1288   void login_insert_csrf_secret(void){
  1286   1289     @ <input type="hidden" name="csrf" value="%s(g.zCsrfToken)" />
  1287   1290   }
  1288   1291   
  1289   1292   /*
  1290   1293   ** Before using the results of a form, first call this routine to verify
  1291   1294   ** that this Anti-CSRF token is present and is valid.  If the Anti-CSRF token
  1292         -** is missing or is incorrect, that indicates a cross-site scripting attach
  1293         -** so emits an error message and abort.
         1295  +** is missing or is incorrect, that indicates a cross-site scripting attack.
         1296  +** If the event of an attack is detected, an error message is generated and
         1297  +** all further processing is aborted.
  1294   1298   */
  1295   1299   void login_verify_csrf_secret(void){
  1296   1300     if( g.okCsrf ) return;
  1297   1301     if( fossil_strcmp(P("csrf"), g.zCsrfToken)==0 ){
  1298   1302       g.okCsrf = 1;
  1299   1303       return;
  1300   1304     }

Changes to src/lookslike.c.

    93     93     if( n==0 ) return flags;  /* Empty file -> text */
    94     94     c = *z;
    95     95     if( c==0 ){
    96     96       flags |= LOOK_NUL;  /* NUL character in a file -> binary */
    97     97     }else if( c=='\r' ){
    98     98       flags |= LOOK_CR;
    99     99       if( n<=1 || z[1]!='\n' ){
   100         -      flags |= LOOK_LONE_CR;  /* More chars, next char is not LF */
          100  +      flags |= LOOK_LONE_CR;  /* Not enough chars or next char not LF */
   101    101       }
   102    102     }
   103    103     j = (c!='\n');
   104    104     if( !j ) flags |= (LOOK_LF | LOOK_LONE_LF);  /* Found LF as first char */
   105    105     while( !(flags&stopFlags) && --n>0 ){
   106    106       int c2 = c;
   107    107       c = *++z; ++j;
................................................................................
   117    117         if( j>LENGTH_MASK ){
   118    118           flags |= LOOK_LONG;  /* Very long line -> binary */
   119    119         }
   120    120         j = 0;
   121    121       }else if( c=='\r' ){
   122    122         flags |= LOOK_CR;
   123    123         if( n<=1 || z[1]!='\n' ){
   124         -        flags |= LOOK_LONE_CR;  /* More chars, next char is not LF */
          124  +        flags |= LOOK_LONE_CR;  /* Not enough chars or next char not LF */
   125    125         }
   126    126       }
   127    127     }
   128    128     if( n ){
   129    129       flags |= LOOK_SHORT;  /* The whole blob was not examined */
   130    130     }
   131    131     if( j>LENGTH_MASK ){
................................................................................
   242    242       c = UTF16_SWAP(c);
   243    243     }
   244    244     if( c==0 ){
   245    245       flags |= LOOK_NUL;  /* NUL character in a file -> binary */
   246    246     }else if( c=='\r' ){
   247    247       flags |= LOOK_CR;
   248    248       if( n<(2*sizeof(WCHAR_T)) || UTF16_SWAP_IF(bReverse, z[1])!='\n' ){
   249         -      flags |= LOOK_LONE_CR;  /* More chars, next char is not LF */
          249  +      flags |= LOOK_LONE_CR;  /* Not enough chars or next char not LF */
   250    250       }
   251    251     }
   252    252     j = (c!='\n');
   253    253     if( !j ) flags |= (LOOK_LF | LOOK_LONE_LF);  /* Found LF as first char */
   254    254     while( !(flags&stopFlags) && ((n-=sizeof(WCHAR_T))>=sizeof(WCHAR_T)) ){
   255    255       int c2 = c;
   256    256       c = *++z;
................................................................................
   270    270         if( j>UTF16_LENGTH_MASK ){
   271    271           flags |= LOOK_LONG;  /* Very long line -> binary */
   272    272         }
   273    273         j = 0;
   274    274       }else if( c=='\r' ){
   275    275         flags |= LOOK_CR;
   276    276         if( n<(2*sizeof(WCHAR_T)) || UTF16_SWAP_IF(bReverse, z[1])!='\n' ){
   277         -        flags |= LOOK_LONE_CR;  /* More chars, next char is not LF */
          277  +        flags |= LOOK_LONE_CR;  /* Not enough chars or next char not LF */
   278    278         }
   279    279       }
   280    280     }
   281    281     if( n ){
   282    282       flags |= LOOK_SHORT;  /* The whole blob was not examined */
   283    283     }
   284    284     if( j>UTF16_LENGTH_MASK ){

Changes to src/main.c.

   418    418   
   419    419     g.argc = argc;
   420    420     g.argv = argv;
   421    421     sqlite3_initialize();
   422    422   #if defined(_WIN32) && defined(BROKEN_MINGW_CMDLINE)
   423    423     for(i=0; i<g.argc; i++) g.argv[i] = fossil_mbcs_to_utf8(g.argv[i]);
   424    424   #else
   425         -  for(i=0; i<g.argc; i++) g.argv[i] = fossil_filename_to_utf8(g.argv[i]);
          425  +  for(i=0; i<g.argc; i++) g.argv[i] = fossil_path_to_utf8(g.argv[i]);
   426    426   #endif
   427    427   #if defined(_WIN32)
   428    428     GetModuleFileNameW(NULL, buf, MAX_PATH);
   429         -  g.nameOfExe = fossil_filename_to_utf8(buf);
          429  +  g.nameOfExe = fossil_path_to_utf8(buf);
   430    430   #else
   431    431     g.nameOfExe = g.argv[0];
   432    432   #endif
   433    433     for(i=1; i<g.argc-1; i++){
   434    434       z = g.argv[i];
   435    435       if( z[0]!='-' ) continue;
   436    436       z++;
................................................................................
   590    590   #endif
   591    591   int main(int argc, char **argv)
   592    592   #endif
   593    593   {
   594    594     const char *zCmdName = "unknown";
   595    595     int idx;
   596    596     int rc;
   597         -  if( sqlite3_libversion_number()<3008007 ){
   598         -    fossil_fatal("Unsuitable SQLite version %s, must be at least 3.8.7",
          597  +  if( sqlite3_libversion_number()<3010000 ){
          598  +    fossil_fatal("Unsuitable SQLite version %s, must be at least 3.10.0",
   599    599                    sqlite3_libversion());
   600    600     }
   601    601     sqlite3_config(SQLITE_CONFIG_MULTITHREAD);
   602    602     sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
   603    603     memset(&g, 0, sizeof(g));
   604    604     g.now = time(0);
   605    605     g.httpHeader = empty_blob;
................................................................................
   874    874         zReturn = g.argv[i+hasArg];
   875    875         remove_from_argv(i, 1+hasArg);
   876    876         break;
   877    877       }
   878    878     }
   879    879     return zReturn;
   880    880   }
          881  +
          882  +/*
          883  +** Look for multiple occurrences of a command-line option with the
          884  +** corresponding argument.
          885  +**
          886  +** Return a malloc allocated array of pointers to the arguments.
          887  +**
          888  +** pnUsedArgs is used to store the number of matched arguments.
          889  +**
          890  +** Caller is responsible to free allocated memory.
          891  +*/
          892  +const char **find_repeatable_option(
          893  +  const char *zLong,
          894  +  const char *zShort,
          895  +  int *pnUsedArgs
          896  +){
          897  +  const char *zOption;
          898  +  const char **pzArgs = 0;
          899  +  int nAllocArgs = 0;
          900  +  int nUsedArgs = 0;
          901  +
          902  +  while( (zOption = find_option(zLong, zShort, 1))!=0 ){
          903  +    if( pzArgs==0 && nAllocArgs==0 ){
          904  +      nAllocArgs = 1;
          905  +      pzArgs = fossil_malloc( nAllocArgs*sizeof(pzArgs[0]) );
          906  +    }else if( nAllocArgs<=nUsedArgs ){
          907  +      nAllocArgs = nAllocArgs*2;
          908  +      pzArgs = fossil_realloc( (void *)pzArgs, nAllocArgs*sizeof(pzArgs[0]) );
          909  +    }
          910  +    pzArgs[nUsedArgs++] = zOption;
          911  +  }
          912  +  *pnUsedArgs = nUsedArgs;
          913  +  return pzArgs;
          914  +}
   881    915   
   882    916   /*
   883    917   ** Look for a repository command-line option.  If present, [re-]cache it in
   884    918   ** the global state and return the new pointer, freeing any previous value.
   885    919   ** If absent and there is no cached value, return NULL.
   886    920   */
   887    921   const char *find_repository_option(){
................................................................................
   973   1007   ** This function returns a human readable version string.
   974   1008   */
   975   1009   const char *get_version(){
   976   1010     static const char version[] = RELEASE_VERSION " " MANIFEST_VERSION " "
   977   1011                                   MANIFEST_DATE " UTC";
   978   1012     return version;
   979   1013   }
         1014  +
         1015  +/*
         1016  +** This function populates a blob with version information.  It is used by
         1017  +** the "version" command and "test-version" web page.  It assumes the blob
         1018  +** passed to it is uninitialized; otherwise, it will leak memory.
         1019  +*/
         1020  +static void get_version_blob(
         1021  +  Blob *pOut,                 /* Write the manifest here */
         1022  +  int bVerbose                /* Non-zero for full information. */
         1023  +){
         1024  +#if defined(FOSSIL_ENABLE_TCL)
         1025  +  int rc;
         1026  +  const char *zRc;
         1027  +#endif
         1028  +  blob_zero(pOut);
         1029  +  blob_appendf(pOut, "This is fossil version %s\n", get_version());
         1030  +  if( !bVerbose ) return;
         1031  +  blob_appendf(pOut, "Compiled on %s %s using %s (%d-bit)\n",
         1032  +               __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
         1033  +  blob_appendf(pOut, "SQLite %s %.30s\n", sqlite3_libversion(),
         1034  +               sqlite3_sourceid());
         1035  +  blob_appendf(pOut, "Schema version %s\n", AUX_SCHEMA_MAX);
         1036  +#if defined(FOSSIL_ENABLE_MINIZ)
         1037  +  blob_appendf(pOut, "miniz %s, loaded %s\n", MZ_VERSION, mz_version());
         1038  +#else
         1039  +  blob_appendf(pOut, "zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
         1040  +#endif
         1041  +#if defined(FOSSIL_ENABLE_SSL)
         1042  +  blob_appendf(pOut, "SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
         1043  +#endif
         1044  +#if defined(FOSSIL_ENABLE_LEGACY_MV_RM)
         1045  +  blob_append(pOut, "LEGACY_MV_RM\n", -1);
         1046  +#endif
         1047  +#if defined(FOSSIL_ENABLE_EXEC_REL_PATHS)
         1048  +  blob_append(pOut, "EXEC_REL_PATHS\n", -1);
         1049  +#endif
         1050  +#if defined(FOSSIL_ENABLE_TH1_DOCS)
         1051  +  blob_append(pOut, "TH1_DOCS\n", -1);
         1052  +#endif
         1053  +#if defined(FOSSIL_ENABLE_TH1_HOOKS)
         1054  +  blob_append(pOut, "TH1_HOOKS\n", -1);
         1055  +#endif
         1056  +#if defined(FOSSIL_ENABLE_TCL)
         1057  +  Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
         1058  +  rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
         1059  +  zRc = Th_ReturnCodeName(rc, 0);
         1060  +  blob_appendf(pOut, "TCL (Tcl %s, loaded %s: %s)\n",
         1061  +    TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
         1062  +  );
         1063  +#endif
         1064  +#if defined(USE_TCL_STUBS)
         1065  +  blob_append(pOut, "USE_TCL_STUBS\n", -1);
         1066  +#endif
         1067  +#if defined(FOSSIL_ENABLE_TCL_STUBS)
         1068  +  blob_append(pOut, "TCL_STUBS\n", -1);
         1069  +#endif
         1070  +#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
         1071  +  blob_append(pOut, "TCL_PRIVATE_STUBS\n", -1);
         1072  +#endif
         1073  +#if defined(FOSSIL_ENABLE_JSON)
         1074  +  blob_appendf(pOut, "JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
         1075  +#endif
         1076  +#if defined(BROKEN_MINGW_CMDLINE)
         1077  +  blob_append(pOut, "MBCS_COMMAND_LINE\n", -1);
         1078  +#else
         1079  +  blob_append(pOut, "UNICODE_COMMAND_LINE\n", -1);
         1080  +#endif
         1081  +#if defined(FOSSIL_DYNAMIC_BUILD)
         1082  +  blob_append(pOut, "DYNAMIC_BUILD\n", -1);
         1083  +#else
         1084  +  blob_append(pOut, "STATIC_BUILD\n", -1);
         1085  +#endif
         1086  +}
   980   1087   
   981   1088   /*
   982   1089   ** This function returns the user-agent string for Fossil, for
   983   1090   ** use in HTTP(S) requests.
   984   1091   */
   985   1092   const char *get_user_agent(){
   986   1093     static const char version[] = "Fossil/" RELEASE_VERSION " (" MANIFEST_DATE
   987   1094                                   " " MANIFEST_VERSION ")";
   988   1095     return version;
   989   1096   }
         1097  +
   990   1098   
   991   1099   /*
   992   1100   ** COMMAND: version
   993   1101   **
   994   1102   ** Usage: %fossil version ?-verbose|-v?
   995   1103   **
   996   1104   ** Print the source code version number for the fossil executable.
   997   1105   ** If the verbose option is specified, additional details will
   998   1106   ** be output about what optional features this binary was compiled
   999   1107   ** with
  1000   1108   */
  1001   1109   void version_cmd(void){
  1002         -  int verboseFlag = 0;
  1003         -
  1004         -  fossil_print("This is fossil version %s\n", get_version());
  1005         -  verboseFlag = find_option("verbose","v",0)!=0;
         1110  +  Blob versionInfo;
         1111  +  int verboseFlag = find_option("verbose","v",0)!=0;
  1006   1112   
  1007   1113     /* We should be done with options.. */
  1008   1114     verify_all_options();
  1009         -
  1010         -  if(!verboseFlag){
  1011         -    return;
  1012         -  }else{
  1013         -#if defined(FOSSIL_ENABLE_TCL)
  1014         -    int rc;
  1015         -    const char *zRc;
  1016         -#endif
  1017         -    fossil_print("Compiled on %s %s using %s (%d-bit)\n",
  1018         -                 __DATE__, __TIME__, COMPILER_NAME, sizeof(void*)*8);
  1019         -    fossil_print("SQLite %s %.30s\n", sqlite3_libversion(), sqlite3_sourceid());
  1020         -    fossil_print("Schema version %s\n", AUX_SCHEMA_MAX);
  1021         -#if defined(FOSSIL_ENABLE_MINIZ)
  1022         -    fossil_print("miniz %s, loaded %s\n", MZ_VERSION, mz_version());
  1023         -#else
  1024         -    fossil_print("zlib %s, loaded %s\n", ZLIB_VERSION, zlibVersion());
  1025         -#endif
  1026         -#if defined(FOSSIL_ENABLE_SSL)
  1027         -    fossil_print("SSL (%s)\n", SSLeay_version(SSLEAY_VERSION));
  1028         -#endif
  1029         -#if defined(FOSSIL_ENABLE_LEGACY_MV_RM)
  1030         -    fossil_print("LEGACY_MV_RM\n");
  1031         -#endif
  1032         -#if defined(FOSSIL_ENABLE_TH1_DOCS)
  1033         -    fossil_print("TH1_DOCS\n");
  1034         -#endif
  1035         -#if defined(FOSSIL_ENABLE_TH1_HOOKS)
  1036         -    fossil_print("TH1_HOOKS\n");
  1037         -#endif
  1038         -#if defined(FOSSIL_ENABLE_TCL)
  1039         -    Th_FossilInit(TH_INIT_DEFAULT | TH_INIT_FORCE_TCL);
  1040         -    rc = Th_Eval(g.interp, 0, "tclInvoke info patchlevel", -1);
  1041         -    zRc = Th_ReturnCodeName(rc, 0);
  1042         -    fossil_print("TCL (Tcl %s, loaded %s: %s)\n",
  1043         -      TCL_PATCH_LEVEL, zRc, Th_GetResult(g.interp, 0)
  1044         -    );
  1045         -#endif
  1046         -#if defined(USE_TCL_STUBS)
  1047         -    fossil_print("USE_TCL_STUBS\n");
  1048         -#endif
  1049         -#if defined(FOSSIL_ENABLE_TCL_STUBS)
  1050         -    fossil_print("TCL_STUBS\n");
  1051         -#endif
  1052         -#if defined(FOSSIL_ENABLE_TCL_PRIVATE_STUBS)
  1053         -    fossil_print("TCL_PRIVATE_STUBS\n");
  1054         -#endif
  1055         -#if defined(FOSSIL_ENABLE_JSON)
  1056         -    fossil_print("JSON (API %s)\n", FOSSIL_JSON_API_VERSION);
  1057         -#endif
  1058         -#if defined(BROKEN_MINGW_CMDLINE)
  1059         -    fossil_print("MBCS_COMMAND_LINE\n");
  1060         -#else
  1061         -    fossil_print("UNICODE_COMMAND_LINE\n");
  1062         -#endif
  1063         -#if defined(FOSSIL_DYNAMIC_BUILD)
  1064         -    fossil_print("DYNAMIC_BUILD\n");
  1065         -#else
  1066         -    fossil_print("STATIC_BUILD\n");
  1067         -#endif
  1068         -  }
         1115  +  get_version_blob(&versionInfo, verboseFlag);
         1116  +  fossil_print("%s", blob_str(&versionInfo));
         1117  +}
         1118  +
         1119  +
         1120  +/*
         1121  +** WEBPAGE: test-version
         1122  +**
         1123  +** Show the version information for Fossil.
         1124  +**
         1125  +** Query parameters:
         1126  +**
         1127  +**    verbose       Show all available details.
         1128  +*/
         1129  +void test_version_page(void){
         1130  +  Blob versionInfo;
         1131  +  int verboseFlag;
         1132  +
         1133  +  login_check_credentials();
         1134  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
         1135  +  verboseFlag = P("verbose")!=0;
         1136  +  style_header("Version Information");
         1137  +  get_version_blob(&versionInfo, verboseFlag);
         1138  +  @ <blockquote><pre>
         1139  +  @ %h(blob_str(&versionInfo))
         1140  +  @ </pre></blockquote>
         1141  +  style_footer();
  1069   1142   }
  1070   1143   
  1071   1144   
  1072   1145   /*
  1073   1146   ** COMMAND: help
  1074   1147   **
  1075   1148   ** Usage: %fossil help COMMAND
  1076   1149   **    or: %fossil COMMAND --help
  1077   1150   **
  1078   1151   ** Display information on how to use COMMAND.  To display a list of
  1079         -** available commands one of:
         1152  +** available commands use one of:
  1080   1153   **
  1081   1154   **    %fossil help              Show common commands
  1082   1155   **    %fossil help -a|--all     Show both common and auxiliary commands
  1083   1156   **    %fossil help -t|--test    Show test commands only
  1084   1157   **    %fossil help -x|--aux     Show auxiliary commands only
  1085   1158   **    %fossil help -w|--www     Show list of WWW pages
  1086   1159   */
................................................................................
  1322   1395     const char *zMode;
  1323   1396     const char *zCur;
  1324   1397   
  1325   1398     if( g.zBaseURL!=0 ) return;
  1326   1399     if( zAltBase ){
  1327   1400       int i, n, c;
  1328   1401       g.zTop = g.zBaseURL = mprintf("%s", zAltBase);
  1329         -    if( memcmp(g.zTop, "http://", 7)!=0 && memcmp(g.zTop,"https://",8)!=0 ){
         1402  +    if( strncmp(g.zTop, "http://", 7)==0 ){
         1403  +      /* it is HTTP, replace prefix with HTTPS. */
         1404  +      g.zHttpsURL = mprintf("https://%s", &g.zTop[7]);
         1405  +    }else if( strncmp(g.zTop, "https://", 8)==0 ){
         1406  +      /* it is already HTTPS, use it. */
         1407  +      g.zHttpsURL = mprintf("%s", g.zTop);
         1408  +    }else{
  1330   1409         fossil_fatal("argument to --baseurl should be 'http://host/path'"
  1331   1410                      " or 'https://host/path'");
  1332   1411       }
  1333   1412       for(i=n=0; (c = g.zTop[i])!=0; i++){
  1334   1413         if( c=='/' ){
  1335   1414           n++;
  1336   1415           if( n==3 ){
................................................................................
  1388   1467   ** zRepo might be a directory itself.  In that case chroot into
  1389   1468   ** the directory zRepo.
  1390   1469   **
  1391   1470   ** Assume the user-id and group-id of the repository, or if zRepo
  1392   1471   ** is a directory, of that directory.
  1393   1472   **
  1394   1473   ** The noJail flag means that the chroot jail is not entered.  But
  1395         -** privileges are still lowered to that of the the user-id and group-id.
         1474  +** privileges are still lowered to that of the user-id and group-id
         1475  +** of the repository file.
  1396   1476   */
  1397   1477   static char *enter_chroot_jail(char *zRepo, int noJail){
  1398   1478   #if !defined(_WIN32)
  1399   1479     if( getuid()==0 ){
  1400   1480       int i;
  1401   1481       struct stat sStat;
  1402   1482       Blob dir;
................................................................................
  1495   1575   ** If the repository is known, it has already been opened.  If unknown,
  1496   1576   ** then g.zRepositoryName holds the directory that contains the repository
  1497   1577   ** and the actual repository is taken from the first element of PATH_INFO.
  1498   1578   **
  1499   1579   ** Process the webpage specified by the PATH_INFO or REQUEST_URI
  1500   1580   ** environment variable.
  1501   1581   **
  1502         -** If the repository is not known, the a search is done through the
         1582  +** If the repository is not known, then a search is done through the
  1503   1583   ** file hierarchy rooted at g.zRepositoryName for a suitable repository
  1504   1584   ** with a name of $prefix.fossil, where $prefix is any prefix of PATH_INFO.
  1505   1585   ** Or, if an ordinary file named $prefix is found, and $prefix matches
  1506   1586   ** pFileGlob and $prefix does not match "*.fossil*" and the mimetype of
  1507   1587   ** $prefix can be determined from its suffix, then the file $prefix is
  1508   1588   ** returned as static text.
  1509   1589   **
................................................................................
  1514   1594     Glob *pFileGlob,            /* Deliver static files matching */
  1515   1595     int allowRepoList           /* Send repo list for "/" URL */
  1516   1596   ){
  1517   1597     const char *zPathInfo;
  1518   1598     char *zPath = NULL;
  1519   1599     int idx;
  1520   1600     int i;
         1601  +
         1602  +  /* Handle universal query parameters */
         1603  +  if( PB("utc") ){
         1604  +    g.fTimeFormat = 1;
         1605  +  }else if( PB("localtime") ){
         1606  +    g.fTimeFormat = 2;
         1607  +  }
  1521   1608   
  1522   1609     /* If the repository has not been opened already, then find the
  1523   1610     ** repository based on the first element of PATH_INFO and open it.
  1524   1611     */
  1525   1612     zPathInfo = PD("PATH_INFO","");
  1526   1613     if( !g.repositoryOpen ){
  1527   1614       char *zRepo, *zToFree;
................................................................................
  2059   2146   }
  2060   2147   
  2061   2148   /*
  2062   2149   ** If g.argv[arg] exists then it is either the name of a repository
  2063   2150   ** that will be used by a server, or else it is a directory that
  2064   2151   ** contains multiple repositories that can be served.  If g.argv[arg]
  2065   2152   ** is a directory, the repositories it contains must be named
  2066         -** "*.fossil".  If g.argv[arg] does not exists, then we must be within
  2067         -** a check-out and the repository to be served is the repository of
         2153  +** "*.fossil".  If g.argv[arg] does not exist, then we must be within
         2154  +** an open check-out and the repository serve is the repository of
  2068   2155   ** that check-out.
  2069   2156   **
  2070   2157   ** Open the repository to be served if it is known.  If g.argv[arg] is
  2071   2158   ** a directory full of repositories, then set g.zRepositoryName to
  2072   2159   ** the name of that directory and the specific repository will be
  2073   2160   ** opened later by process_one_web_page() based on the content of
  2074   2161   ** the PATH_INFO variable.
................................................................................
  2109   2196   }
  2110   2197   
  2111   2198   /*
  2112   2199   ** undocumented format:
  2113   2200   **
  2114   2201   **        fossil http INFILE OUTFILE IPADDR ?REPOSITORY?
  2115   2202   **
  2116         -** The argv==6 form is used by the win32 server only.
         2203  +** The argv==6 form (with no options) is used by the win32 server only.
  2117   2204   **
  2118   2205   ** COMMAND: http*
  2119   2206   **
  2120   2207   ** Usage: %fossil http ?REPOSITORY? ?OPTIONS?
  2121   2208   **
  2122   2209   ** Handle a single HTTP request appearing on stdin.  The resulting webpage
  2123   2210   ** is delivered on stdout.  This method is used to launch an HTTP request
  2124   2211   ** handler from inetd, for example.  The argument is the name of the
  2125   2212   ** repository.
  2126   2213   **
  2127   2214   ** If REPOSITORY is a directory that contains one or more repositories,
  2128         -** either directly in REPOSITORY itself, or in subdirectories, and
  2129         -** with names of the form "*.fossil" then the a prefix of the URL pathname
         2215  +** either directly in REPOSITORY itself or in subdirectories, and
         2216  +** with names of the form "*.fossil" then a prefix of the URL pathname
  2130   2217   ** selects from among the various repositories.  If the pathname does
  2131   2218   ** not select a valid repository and the --notfound option is available,
  2132   2219   ** then the server redirects (HTTP code 302) to the URL of --notfound.
  2133   2220   ** When REPOSITORY is a directory, the pathname must contain only
  2134   2221   ** alphanumerics, "_", "/", "-" and "." and no "-" may occur after a "/"
  2135   2222   ** and every "." must be surrounded on both sides by alphanumerics or else
  2136   2223   ** a 404 error is returned.  Static content files in the directory are
................................................................................
  2247   2334             g.fSshClient & CGI_SSH_COMPAT );
  2248   2335   }
  2249   2336   
  2250   2337   /*
  2251   2338   ** Note that the following command is used by ssh:// processing.
  2252   2339   **
  2253   2340   ** COMMAND: test-http
         2341  +**
  2254   2342   ** Works like the http command but gives setup permission to all users.
  2255   2343   **
         2344  +** Options:
         2345  +**   --th-trace          trace TH1 execution (for debugging purposes)
         2346  +**
  2256   2347   */
  2257   2348   void cmd_test_http(void){
  2258   2349     const char *zIpAddr;    /* IP address of remote client */
  2259   2350   
  2260   2351     Th_InitTraceLog();
  2261   2352     login_set_capabilities("sx", 0);
  2262   2353     g.useLocalauth = 1;
................................................................................
  2338   2429   ** --localauth option is present and the "localauth" setting is off and the
  2339   2430   ** connection is from localhost.  The "ui" command also enables --repolist
  2340   2431   ** by default.
  2341   2432   **
  2342   2433   ** Options:
  2343   2434   **   --baseurl URL       Use URL as the base (useful for reverse proxies)
  2344   2435   **   --create            Create a new REPOSITORY if it does not already exist
         2436  +**   --page PAGE         Start "ui" on PAGE.  ex: --page "timeline?y=ci"
  2345   2437   **   --files GLOBLIST    Comma-separated list of glob patterns for static files
  2346   2438   **   --localauth         enable automatic login for requests from localhost
  2347   2439   **   --localhost         listen on 127.0.0.1 only (always true for "ui")
         2440  +**   --https             signal a request coming in via https
  2348   2441   **   --nojail            Drop root privileges but do not enter the chroot jail
         2442  +**   --nossl             signal that no SSL connections are available
  2349   2443   **   --notfound URL      Redirect
  2350   2444   **   -P|--port TCPPORT   listen to request on port TCPPORT
  2351   2445   **   --th-trace          trace TH1 execution (for debugging purposes)
  2352   2446   **   --repolist          If REPOSITORY is dir, URL "/" lists repos.
  2353   2447   **   --scgi              Accept SCGI rather than HTTP
  2354   2448   **   --skin LABEL        Use override skin LABEL
  2355   2449   
................................................................................
  2363   2457     char *zBrowserCmd = 0;    /* Command to launch the web browser */
  2364   2458     int isUiCmd;              /* True if command is "ui", not "server' */
  2365   2459     const char *zNotFound;    /* The --notfound option or NULL */
  2366   2460     int flags = 0;            /* Server flags */
  2367   2461   #if !defined(_WIN32)
  2368   2462     int noJail;               /* Do not enter the chroot jail */
  2369   2463   #endif
  2370         -  int allowRepoList;        /* List repositories on URL "/" */
  2371         -  const char *zAltBase;     /* Argument to the --baseurl option */
  2372         -  const char *zFileGlob;    /* Static content must match this */
  2373         -  char *zIpAddr = 0;        /* Bind to this IP address */
  2374         -  int fCreate = 0;
         2464  +  int allowRepoList;         /* List repositories on URL "/" */
         2465  +  const char *zAltBase;      /* Argument to the --baseurl option */
         2466  +  const char *zFileGlob;     /* Static content must match this */
         2467  +  char *zIpAddr = 0;         /* Bind to this IP address */
         2468  +  int fCreate = 0;           /* The --create flag */
         2469  +  const char *zInitPage = 0; /* Start on this page.  --page option */
  2375   2470   
  2376   2471   #if defined(_WIN32)
  2377   2472     const char *zStopperFile;    /* Name of file used to terminate server */
  2378   2473     zStopperFile = find_option("stopper", 0, 1);
  2379   2474   #endif
  2380   2475   
  2381   2476     zFileGlob = find_option("files-urlenc",0,1);
................................................................................
  2389   2484     skin_override();
  2390   2485   #if !defined(_WIN32)
  2391   2486     noJail = find_option("nojail",0,0)!=0;
  2392   2487   #endif
  2393   2488     g.useLocalauth = find_option("localauth", 0, 0)!=0;
  2394   2489     Th_InitTraceLog();
  2395   2490     zPort = find_option("port", "P", 1);
         2491  +  isUiCmd = g.argv[1][0]=='u';
         2492  +  if( isUiCmd ){
         2493  +    zInitPage = find_option("page", 0, 1);
         2494  +  }
         2495  +  if( zInitPage==0 ) zInitPage = "";
  2396   2496     zNotFound = find_option("notfound", 0, 1);
  2397   2497     allowRepoList = find_option("repolist",0,0)!=0;
  2398   2498     zAltBase = find_option("baseurl", 0, 1);
  2399   2499     fCreate = find_option("create",0,0)!=0;
  2400   2500     if( find_option("scgi", 0, 0)!=0 ) flags |= HTTP_SERVER_SCGI;
  2401   2501     if( zAltBase ){
  2402   2502       set_base_url(zAltBase);
         2503  +  }
         2504  +  g.sslNotAvailable = find_option("nossl", 0, 0)!=0;
         2505  +  if( find_option("https",0,0)!=0 ){
         2506  +    cgi_replace_parameter("HTTPS","on");
         2507  +  }else{
         2508  +    /* without --https, defaults to not available. */
         2509  +    g.sslNotAvailable = 1;
  2403   2510     }
  2404   2511     if( find_option("localhost", 0, 0)!=0 ){
  2405   2512       flags |= HTTP_SERVER_LOCALHOST;
  2406   2513     }
  2407   2514   
  2408   2515     /* We should be done with options.. */
  2409   2516     verify_all_options();
  2410   2517   
  2411   2518     if( g.argc!=2 && g.argc!=3 ) usage("?REPOSITORY?");
  2412         -  isUiCmd = g.argv[1][0]=='u';
  2413   2519     if( isUiCmd ){
  2414   2520       flags |= HTTP_SERVER_LOCALHOST|HTTP_SERVER_REPOLIST;
  2415   2521       g.useLocalauth = 1;
  2416   2522       allowRepoList = 1;
  2417   2523     }
  2418   2524     find_server_repository(2, fCreate);
  2419   2525     if( zPort ){
................................................................................
  2445   2551           }
  2446   2552         }
  2447   2553       }
  2448   2554   #else
  2449   2555       zBrowser = db_get("web-browser", "open");
  2450   2556   #endif
  2451   2557       if( zIpAddr ){
  2452         -      zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr);
         2558  +      zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
         2559  +                            zBrowser, zIpAddr, zInitPage);
  2453   2560       }else{
  2454         -      zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser);
         2561  +      zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
         2562  +                            zBrowser, zInitPage);
  2455   2563       }
  2456         -    if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
  2457         -    if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  2458   2564     }
         2565  +  if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
         2566  +  if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  2459   2567     db_close(1);
  2460   2568     if( cgi_http_server(iPort, mxPort, zBrowserCmd, zIpAddr, flags) ){
  2461   2569       fossil_fatal("unable to listen on TCP socket %d", iPort);
  2462   2570     }
  2463         -  g.sslNotAvailable = 1;
  2464   2571     g.httpIn = stdin;
  2465   2572     g.httpOut = stdout;
  2466   2573     if( g.fHttpTrace || g.fSqlTrace ){
  2467   2574       fprintf(stderr, "====== SERVER pid %d =======\n", getpid());
  2468   2575     }
  2469   2576     g.cgiOutput = 1;
  2470   2577     find_server_repository(2, 0);
................................................................................
  2476   2583     }
  2477   2584     process_one_web_page(zNotFound, glob_create(zFileGlob), allowRepoList);
  2478   2585   #else
  2479   2586     /* Win32 implementation */
  2480   2587     if( isUiCmd ){
  2481   2588       zBrowser = db_get("web-browser", "start");
  2482   2589       if( zIpAddr ){
  2483         -      zBrowserCmd = mprintf("%s http://%s:%%d/ &", zBrowser, zIpAddr);
         2590  +      zBrowserCmd = mprintf("%s http://%s:%%d/%s &",
         2591  +                            zBrowser, zIpAddr, zInitPage);
  2484   2592       }else{
  2485         -      zBrowserCmd = mprintf("%s http://localhost:%%d/ &", zBrowser);
         2593  +      zBrowserCmd = mprintf("%s http://localhost:%%d/%s &",
         2594  +                            zBrowser, zInitPage);
  2486   2595       }
  2487         -    if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
  2488         -    if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  2489   2596     }
         2597  +  if( g.repositoryOpen ) flags |= HTTP_SERVER_HAD_REPOSITORY;
         2598  +  if( g.localOpen ) flags |= HTTP_SERVER_HAD_CHECKOUT;
  2490   2599     db_close(1);
  2491   2600     if( allowRepoList ){
  2492   2601       flags |= HTTP_SERVER_REPOLIST;
  2493   2602     }
  2494   2603     if( win32_http_service(iPort, zNotFound, zFileGlob, flags) ){
  2495   2604       win32_http_server(iPort, mxPort, zBrowserCmd,
  2496   2605                         zStopperFile, zNotFound, zFileGlob, zIpAddr, flags);
................................................................................
  2503   2612   **
  2504   2613   ** Usage:  %fossil test-echo [--hex] ARGS...
  2505   2614   **
  2506   2615   ** Echo all command-line arguments (enclosed in [...]) to the screen so that
  2507   2616   ** wildcard expansion behavior of the host shell can be investigated.
  2508   2617   **
  2509   2618   ** With the --hex option, show the output as hexadecimal.  This can be used
  2510         -** to verify the fossil_filename_to_utf8() routine on Windows and Mac.
         2619  +** to verify the fossil_path_to_utf8() routine on Windows and Mac.
  2511   2620   */
  2512   2621   void test_echo_cmd(void){
  2513   2622     int i, j;
  2514   2623     if( find_option("hex",0,0)==0 ){
  2515   2624       fossil_print("g.nameOfExe = [%s]\n", g.nameOfExe);
  2516   2625       for(i=0; i<g.argc; i++){
  2517   2626         fossil_print("argv[%d] = [%s]\n", i, g.argv[i]);

Changes to src/main.mk.

   476    476                    -DSQLITE_ENABLE_LOCKING_STYLE=0 \
   477    477                    -DSQLITE_THREADSAFE=0 \
   478    478                    -DSQLITE_DEFAULT_FILE_FORMAT=4 \
   479    479                    -DSQLITE_OMIT_DEPRECATED \
   480    480                    -DSQLITE_ENABLE_EXPLAIN_COMMENTS \
   481    481                    -DSQLITE_ENABLE_FTS4 \
   482    482                    -DSQLITE_ENABLE_FTS3_PARENTHESIS \
   483         -                 -DSQLITE_ENABLE_DBSTAT_VTAB
          483  +                 -DSQLITE_ENABLE_DBSTAT_VTAB \
          484  +                 -DSQLITE_ENABLE_JSON1 \
          485  +                 -DSQLITE_ENABLE_FTS5
   484    486   
   485    487   # Setup the options used to compile the included SQLite shell.
   486    488   SHELL_OPTIONS = -Dmain=sqlite3_shell \
   487    489                   -DSQLITE_OMIT_LOAD_EXTENSION=1 \
   488    490                   -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) \
   489    491                   -DSQLITE_SHELL_DBNAME_PROC=fossil_open
   490    492   
................................................................................
   504    506   # The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or
   505    507   # set to 1.  If it is set to 1, the miniz library included in the
   506    508   # source tree should be used; otherwise, it should not.
   507    509   MINIZ_OBJ.0 =
   508    510   MINIZ_OBJ.1 = $(OBJDIR)/miniz.o
   509    511   MINIZ_OBJ.  = $(MINIZ_OBJ.0)
   510    512   
          513  +# The USE_LINENOISE variable may be undefined, set to 0, or set
          514  +# to 1. If it is set to 0, then there is no need to build or link
          515  +# the linenoise.o object.
          516  +LINENOISE_DEF.0 =
          517  +LINENOISE_DEF.1 = -DHAVE_LINENOISE
          518  +LINENOISE_DEF.  = $(LINENOISE_DEF.0)
          519  +LINENOISE_OBJ.0 =
          520  +LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
          521  +LINENOISE_OBJ.  = $(LINENOISE_OBJ.0)
          522  +
   511    523   
   512    524   EXTRAOBJ = \
   513    525    $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \
   514    526    $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) \
   515         - $(OBJDIR)/linenoise.o \
          527  + $(LINENOISE_OBJ.$(USE_LINENOISE)) \
   516    528    $(OBJDIR)/shell.o \
   517    529    $(OBJDIR)/th.o \
   518    530    $(OBJDIR)/th_lang.o \
   519    531    $(OBJDIR)/th_tcl.o \
   520    532    $(OBJDIR)/cson_amalgamation.o
   521    533   
   522    534   
................................................................................
  1619   1631   
  1620   1632   $(OBJDIR)/zip.h:	$(OBJDIR)/headers
  1621   1633   
  1622   1634   $(OBJDIR)/sqlite3.o:	$(SRCDIR)/sqlite3.c
  1623   1635   	$(XTCC) $(SQLITE_OPTIONS) $(SQLITE_CFLAGS) -c $(SRCDIR)/sqlite3.c -o $@
  1624   1636   
  1625   1637   $(OBJDIR)/shell.o:	$(SRCDIR)/shell.c $(SRCDIR)/sqlite3.h
  1626         -	$(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) -DHAVE_LINENOISE -c $(SRCDIR)/shell.c -o $@
         1638  +	$(XTCC) $(SHELL_OPTIONS) $(SHELL_CFLAGS) $(LINENOISE_DEF.$(USE_LINENOISE)) -c $(SRCDIR)/shell.c -o $@
  1627   1639   
  1628   1640   $(OBJDIR)/linenoise.o:	$(SRCDIR)/linenoise.c $(SRCDIR)/linenoise.h
  1629   1641   	$(XTCC) -c $(SRCDIR)/linenoise.c -o $@
  1630   1642   
  1631   1643   $(OBJDIR)/th.o:	$(SRCDIR)/th.c
  1632   1644   	$(XTCC) -c $(SRCDIR)/th.c -o $@
  1633   1645   

Changes to src/makemake.tcl.

   159    159     -DSQLITE_THREADSAFE=0
   160    160     -DSQLITE_DEFAULT_FILE_FORMAT=4
   161    161     -DSQLITE_OMIT_DEPRECATED
   162    162     -DSQLITE_ENABLE_EXPLAIN_COMMENTS
   163    163     -DSQLITE_ENABLE_FTS4
   164    164     -DSQLITE_ENABLE_FTS3_PARENTHESIS
   165    165     -DSQLITE_ENABLE_DBSTAT_VTAB
          166  +  -DSQLITE_ENABLE_JSON1
          167  +  -DSQLITE_ENABLE_FTS5
   166    168   }
   167    169   #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_FTS3=1
   168    170   #lappend SQLITE_OPTIONS -DSQLITE_ENABLE_STAT4
   169    171   #lappend SQLITE_OPTIONS -DSQLITE_WIN32_NO_ANSI
   170    172   #lappend SQLITE_OPTIONS -DSQLITE_WINNT_MAX_PATH_CHARS=4096
   171    173   
   172    174   # Options used to compile the included SQLite shell.
................................................................................
   333    335   
   334    336   # The FOSSIL_ENABLE_MINIZ variable may be undefined, set to 0, or
   335    337   # set to 1.  If it is set to 1, the miniz library included in the
   336    338   # source tree should be used; otherwise, it should not.
   337    339   MINIZ_OBJ.0 =
   338    340   MINIZ_OBJ.1 = $(OBJDIR)/miniz.o
   339    341   MINIZ_OBJ.  = $(MINIZ_OBJ.0)
          342  +
          343  +# The USE_LINENOISE variable may be undefined, set to 0, or set
          344  +# to 1. If it is set to 0, then there is no need to build or link
          345  +# the linenoise.o object.
          346  +LINENOISE_DEF.0 =
          347  +LINENOISE_DEF.1 = -DHAVE_LINENOISE
          348  +LINENOISE_DEF.  = $(LINENOISE_DEF.0)
          349  +LINENOISE_OBJ.0 =
          350  +LINENOISE_OBJ.1 = $(OBJDIR)/linenoise.o
          351  +LINENOISE_OBJ.  = $(LINENOISE_OBJ.0)
   340    352   }]
   341    353   
   342    354   writeln [string map [list <<<NEXT_LINE>>> \\] {
   343    355   EXTRAOBJ = <<<NEXT_LINE>>>
   344    356    $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) <<<NEXT_LINE>>>
   345    357    $(MINIZ_OBJ.$(FOSSIL_ENABLE_MINIZ)) <<<NEXT_LINE>>>
   346         - $(OBJDIR)/linenoise.o <<<NEXT_LINE>>>
          358  + $(LINENOISE_OBJ.$(USE_LINENOISE)) <<<NEXT_LINE>>>
   347    359    $(OBJDIR)/shell.o <<<NEXT_LINE>>>
   348    360    $(OBJDIR)/th.o <<<NEXT_LINE>>>
   349    361    $(OBJDIR)/th_lang.o <<<NEXT_LINE>>>
   350    362    $(OBJDIR)/th_tcl.o <<<NEXT_LINE>>>
   351    363    $(OBJDIR)/cson_amalgamation.o
   352    364   }]
   353    365   
................................................................................
   400    412     writeln "\$(OBJDIR)/$s.h:\t\$(OBJDIR)/headers\n"
   401    413   }
   402    414   
   403    415   writeln "\$(OBJDIR)/sqlite3.o:\t\$(SRCDIR)/sqlite3.c"
   404    416   writeln "\t\$(XTCC) \$(SQLITE_OPTIONS) \$(SQLITE_CFLAGS) -c \$(SRCDIR)/sqlite3.c -o \$@\n"
   405    417   
   406    418   writeln "\$(OBJDIR)/shell.o:\t\$(SRCDIR)/shell.c \$(SRCDIR)/sqlite3.h"
   407         -writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) -DHAVE_LINENOISE -c \$(SRCDIR)/shell.c -o \$@\n"
          419  +writeln "\t\$(XTCC) \$(SHELL_OPTIONS) \$(SHELL_CFLAGS) \$(LINENOISE_DEF.\$(USE_LINENOISE)) -c \$(SRCDIR)/shell.c -o \$@\n"
   408    420   
   409    421   writeln "\$(OBJDIR)/linenoise.o:\t\$(SRCDIR)/linenoise.c \$(SRCDIR)/linenoise.h"
   410    422   writeln "\t\$(XTCC) -c \$(SRCDIR)/linenoise.c -o \$@\n"
   411    423   
   412    424   writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
   413    425   writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$@\n"
   414    426   
................................................................................
   454    466   # This file is automatically generated.  Instead of editing this
   455    467   # file, edit "makemake.tcl" then run "tclsh makemake.tcl"
   456    468   # to regenerate this file.
   457    469   #
   458    470   # This is a makefile for use on Cygwin/Darwin/FreeBSD/Linux/Windows using
   459    471   # MinGW or MinGW-w64.
   460    472   #
          473  +# Some of the special options which can be passed to make
          474  +#   USE_WINDOWS=1    if building under a windows command prompt
          475  +#   X64=1            if using an unprefixed 64-bit mingw compiler
          476  +#
   461    477   
   462    478   #### Select one of MinGW, MinGW-w64 (32-bit) or MinGW-w64 (64-bit) compilers.
   463    479   #    By default, this is an empty string (i.e. use the native compiler).
   464    480   #
   465    481   PREFIX =
   466    482   # PREFIX = mingw32-
   467    483   # PREFIX = i686-pc-mingw32-
................................................................................
   497    513   #
   498    514   # FOSSIL_ENABLE_SSL = 1
   499    515   
   500    516   #### Automatically build OpenSSL when building Fossil (causes rebuild
   501    517   #    issues when building incrementally).
   502    518   #
   503    519   # FOSSIL_BUILD_SSL = 1
          520  +
          521  +#### Enable relative paths in external diff/gdiff
          522  +#
          523  +# FOSSIL_ENABLE_EXEC_REL_PATHS = 1
   504    524   
   505    525   #### Enable legacy treatment of mv/rm (skip checkout files)
   506    526   #
   507    527   # FOSSIL_ENABLE_LEGACY_MV_RM = 1
   508    528   
   509    529   #### Enable TH1 scripts in embedded documentation files
   510    530   #
................................................................................
   595    615   endif
   596    616   
   597    617   #### The directories where the OpenSSL include and library files are located.
   598    618   #    The recommended usage here is to use the Sysinternals junction tool
   599    619   #    to create a hard link between an "openssl-1.x" sub-directory of the
   600    620   #    Fossil source code directory and the target OpenSSL source directory.
   601    621   #
   602         -OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2d
          622  +OPENSSLDIR = $(SRCDIR)/../compat/openssl-1.0.2g
   603    623   OPENSSLINCDIR = $(OPENSSLDIR)/include
   604    624   OPENSSLLIBDIR = $(OPENSSLDIR)
   605    625   
   606    626   #### Either the directory where the Tcl library is installed or the Tcl
   607    627   #    source code directory resides (depending on the value of the macro
   608    628   #    FOSSIL_TCL_SOURCE).  If this points to the Tcl install directory,
   609    629   #    this directory must have "include" and "lib" sub-directories.  If
................................................................................
   642    662   
   643    663   #### C Compile and options for use in building executables that
   644    664   #    will run on the target platform.  This is usually the same
   645    665   #    as BCC, unless you are cross-compiling.  This C compiler builds
   646    666   #    the finished binary for fossil.  The BCC compiler above is used
   647    667   #    for building intermediate code-generator tools.
   648    668   #
   649         -TCC = $(PREFIX)gcc -Os -Wall
   650         -
   651         -#### When not using the miniz compression library, zlib is required.
   652         -#
   653         -ifndef FOSSIL_ENABLE_MINIZ
   654         -TCC += -L$(ZLIBDIR) -I$(ZINCDIR)
   655         -endif
          669  +TCC = $(PREFIX)gcc -Wall
   656    670   
   657    671   #### Add the necessary command line options to build with debugging
   658    672   #    symbols, if enabled.
   659    673   #
   660    674   ifdef FOSSIL_ENABLE_SYMBOLS
   661    675   TCC += -g
          676  +else
          677  +TCC += -Os
          678  +endif
          679  +
          680  +#### When not using the miniz compression library, zlib is required.
          681  +#
          682  +ifndef FOSSIL_ENABLE_MINIZ
          683  +TCC += -L$(ZLIBDIR) -I$(ZINCDIR)
   662    684   endif
   663    685   
   664    686   #### Compile resources for use in building executables that will run
   665    687   #    on the target platform.
   666    688   #
   667    689   RCC = $(PREFIX)windres -I$(SRCDIR)
   668    690   
................................................................................
   700    722   endif
   701    723   
   702    724   # With HTTPS support
   703    725   ifdef FOSSIL_ENABLE_SSL
   704    726   TCC += -DFOSSIL_ENABLE_SSL=1
   705    727   RCC += -DFOSSIL_ENABLE_SSL=1
   706    728   endif
          729  +
          730  +# With relative paths in external diff/gdiff
          731  +ifdef FOSSIL_ENABLE_EXEC_REL_PATHS
          732  +TCC += -DFOSSIL_ENABLE_EXEC_REL_PATHS=1
          733  +RCC += -DFOSSIL_ENABLE_EXEC_REL_PATHS=1
          734  +endif
   707    735   
   708    736   # With legacy treatment of mv/rm
   709    737   ifdef FOSSIL_ENABLE_LEGACY_MV_RM
   710    738   TCC += -DFOSSIL_ENABLE_LEGACY_MV_RM=1
   711    739   RCC += -DFOSSIL_ENABLE_LEGACY_MV_RM=1
   712    740   endif
   713    741   
................................................................................
  1010   1038   
  1011   1039   ifdef FOSSIL_BUILD_SSL
  1012   1040   APPTARGETS += openssl
  1013   1041   endif
  1014   1042   
  1015   1043   $(APPNAME):	$(APPTARGETS) $(OBJDIR)/headers $(CODECHECK1) $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o
  1016   1044   	$(CODECHECK1) $(TRANS_SRC)
  1017         -	$(TCC) -o $@ $(OBJ) $(EXTRAOBJ) $(LIB) $(OBJDIR)/fossil.o
         1045  +	$(TCC) -o $@ $(OBJ) $(EXTRAOBJ) $(OBJDIR)/fossil.o $(LIB)
  1018   1046   
  1019   1047   # This rule prevents make from using its default rules to try build
  1020   1048   # an executable named "manifest" out of the file named "manifest.c"
  1021   1049   #
  1022   1050   $(SRCDIR)/../manifest:
  1023   1051   	# noop
  1024   1052   
................................................................................
  1329   1357   FOSSIL_BUILD_ZLIB = 1
  1330   1358   !endif
  1331   1359   
  1332   1360   # Link everything except SQLite dynamically?
  1333   1361   !ifndef FOSSIL_DYNAMIC_BUILD
  1334   1362   FOSSIL_DYNAMIC_BUILD = 0
  1335   1363   !endif
         1364  +
         1365  +# Enable relative paths in external diff/gdiff?
         1366  +!ifndef FOSSIL_ENABLE_EXEC_REL_PATHS
         1367  +FOSSIL_ENABLE_EXEC_REL_PATHS = 0
         1368  +!endif
  1336   1369   
  1337   1370   # Enable the JSON API?
  1338   1371   !ifndef FOSSIL_ENABLE_JSON
  1339   1372   FOSSIL_ENABLE_JSON = 0
  1340   1373   !endif
  1341   1374   
  1342   1375   # Enable legacy treatment of the mv/rm commands?
................................................................................
  1371   1404   
  1372   1405   # Enable support for Windows XP with Visual Studio 201x?
  1373   1406   !ifndef FOSSIL_ENABLE_WINXP
  1374   1407   FOSSIL_ENABLE_WINXP = 0
  1375   1408   !endif
  1376   1409   
  1377   1410   !if $(FOSSIL_ENABLE_SSL)!=0
  1378         -SSLDIR    = $(B)\compat\openssl-1.0.2d
         1411  +SSLDIR    = $(B)\compat\openssl-1.0.2g
  1379   1412   SSLINCDIR = $(SSLDIR)\inc32
  1380   1413   !if $(FOSSIL_DYNAMIC_BUILD)!=0
  1381   1414   SSLLIBDIR = $(SSLDIR)\out32dll
  1382   1415   !else
  1383   1416   SSLLIBDIR = $(SSLDIR)\out32
  1384   1417   !endif
  1385   1418   SSLLFLAGS = /nologo /opt:ref /debug
................................................................................
  1547   1580   
  1548   1581   !if $(FOSSIL_ENABLE_SSL)!=0
  1549   1582   TCC       = $(TCC) /DFOSSIL_ENABLE_SSL=1
  1550   1583   RCC       = $(RCC) /DFOSSIL_ENABLE_SSL=1
  1551   1584   LIBS      = $(LIBS) $(SSLLIB)
  1552   1585   LIBDIR    = $(LIBDIR) /LIBPATH:$(SSLLIBDIR)
  1553   1586   !endif
         1587  +
         1588  +!if $(FOSSIL_ENABLE_EXEC_REL_PATHS)!=0
         1589  +TCC       = $(TCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1
         1590  +RCC       = $(RCC) /DFOSSIL_ENABLE_EXEC_REL_PATHS=1
         1591  +!endif
  1554   1592   
  1555   1593   !if $(FOSSIL_ENABLE_LEGACY_MV_RM)!=0
  1556   1594   TCC       = $(TCC) /DFOSSIL_ENABLE_LEGACY_MV_RM=1
  1557   1595   RCC       = $(RCC) /DFOSSIL_ENABLE_LEGACY_MV_RM=1
  1558   1596   !endif
  1559   1597   
  1560   1598   !if $(FOSSIL_ENABLE_TH1_DOCS)!=0

Changes to src/manifest.c.

   378    378   
   379    379     /* Every control artifact ends with a '\n' character.  Exit early
   380    380     ** if that is not the case for this artifact.
   381    381     */
   382    382     if( !isRepeat ) g.parseCnt[0]++;
   383    383     z = blob_materialize(pContent);
   384    384     n = blob_size(pContent);
   385         -  if( pErr && (n<=0 || z[n-1]!='\n') ){
          385  +  if( n<=0 || z[n-1]!='\n' ){
   386    386       blob_reset(pContent);
   387         -    blob_append(pErr, n ? "not terminated with \\n" : "zero-length", -1);
          387  +    blob_appendf(pErr, "%s", n ? "not terminated with \\n" : "zero-length");
   388    388       return 0;
   389    389     }
   390    390   
   391    391     /* Strip off the PGP signature if there is one.
   392    392     */
   393    393     remove_pgp_signature(&z, &n);
   394    394   
................................................................................
  1183   1183     }
  1184   1184     return mperm;
  1185   1185   }
  1186   1186   
  1187   1187   /*
  1188   1188   ** Add a single entry to the mlink table.  Also add the filename to
  1189   1189   ** the filename table if it is not there already.
         1190  +**
         1191  +** An mlink entry is always created if isPrimary is true.  But if
         1192  +** isPrimary is false (meaning that pmid is a merge parent of mid)
         1193  +** then the mlink entry is only created if there is already an mlink
         1194  +** from primary parent for the same file.
  1190   1195   */
  1191   1196   static void add_one_mlink(
  1192   1197     int pmid,                 /* The parent manifest */
  1193   1198     const char *zFromUuid,    /* UUID for content in parent */
  1194   1199     int mid,                  /* The record ID of the manifest */
  1195   1200     const char *zToUuid,      /* UUID for content in child */
  1196   1201     const char *zFilename,    /* Filename */
  1197   1202     const char *zPrior,       /* Previous filename. NULL if unchanged */
  1198   1203     int isPublic,             /* True if mid is not a private manifest */
  1199   1204     int isPrimary,            /* pmid is the primary parent of mid */
  1200   1205     int mperm                 /* 1: exec, 2: symlink */
  1201   1206   ){
  1202   1207     int fnid, pfnid, pid, fid;
  1203         -  static Stmt s1;
         1208  +  int doInsert;
         1209  +  static Stmt s1, s2;
  1204   1210   
  1205   1211     fnid = filename_to_fnid(zFilename);
  1206   1212     if( zPrior==0 ){
  1207   1213       pfnid = 0;
  1208   1214     }else{
  1209   1215       pfnid = filename_to_fnid(zPrior);
  1210   1216     }
................................................................................
  1215   1221     }
  1216   1222     if( zToUuid==0 || zToUuid[0]==0 ){
  1217   1223       fid = 0;
  1218   1224     }else{
  1219   1225       fid = uuid_to_rid(zToUuid, 1);
  1220   1226       if( isPublic ) content_make_public(fid);
  1221   1227     }
  1222         -  db_static_prepare(&s1,
  1223         -    "INSERT INTO mlink(mid,fid,pmid,pid,fnid,pfnid,mperm,isaux)"
  1224         -    "VALUES(:m,:f,:pm,:p,:n,:pfn,:mp,:isaux)"
  1225         -  );
  1226         -  db_bind_int(&s1, ":m", mid);
  1227         -  db_bind_int(&s1, ":f", fid);
  1228         -  db_bind_int(&s1, ":pm", pmid);
  1229         -  db_bind_int(&s1, ":p", pid);
  1230         -  db_bind_int(&s1, ":n", fnid);
  1231         -  db_bind_int(&s1, ":pfn", pfnid);
  1232         -  db_bind_int(&s1, ":mp", mperm);
  1233         -  db_bind_int(&s1, ":isaux", isPrimary==0);
  1234         -  db_exec(&s1);
         1228  +  if( isPrimary ){
         1229  +    doInsert = 1;
         1230  +  }else{
         1231  +    db_static_prepare(&s2,
         1232  +      "SELECT 1 FROM mlink WHERE mid=:m AND fnid=:n AND NOT isaux"
         1233  +    );
         1234  +    db_bind_int(&s2, ":m", mid);
         1235  +    db_bind_int(&s2, ":n", fnid);
         1236  +    doInsert = db_step(&s2)==SQLITE_ROW;
         1237  +    db_reset(&s2);
         1238  +  }
         1239  +  if( doInsert ){
         1240  +    db_static_prepare(&s1,
         1241  +      "INSERT INTO mlink(mid,fid,pmid,pid,fnid,pfnid,mperm,isaux)"
         1242  +      "VALUES(:m,:f,:pm,:p,:n,:pfn,:mp,:isaux)"
         1243  +    );
         1244  +    db_bind_int(&s1, ":m", mid);
         1245  +    db_bind_int(&s1, ":f", fid);
         1246  +    db_bind_int(&s1, ":pm", pmid);
         1247  +    db_bind_int(&s1, ":p", pid);
         1248  +    db_bind_int(&s1, ":n", fnid);
         1249  +    db_bind_int(&s1, ":pfn", pfnid);
         1250  +    db_bind_int(&s1, ":mp", mperm);
         1251  +    db_bind_int(&s1, ":isaux", isPrimary==0);
         1252  +    db_exec(&s1);
         1253  +  }
  1235   1254     if( pid && fid ){
  1236   1255       content_deltify(pid, fid, 0);
  1237   1256     }
  1238   1257   }
  1239   1258   
  1240   1259   /*
  1241   1260   ** Do a binary search to find a file in the p->aFile[] array.
................................................................................
  1344   1363   ** A single mlink entry is added for every file that changed content,
  1345   1364   ** name, and/or permissions going from pid to cid.
  1346   1365   **
  1347   1366   ** Deleted files have mlink.fid=0.
  1348   1367   ** Added files have mlink.pid=0.
  1349   1368   ** File added by merge have mlink.pid=-1
  1350   1369   ** Edited files have both mlink.pid!=0 and mlink.fid!=0
         1370  +**
         1371  +** Many mlink entries for merge parents will only be added if another mlink
         1372  +** entry already exists for the same file from the primary parent.  Therefore,
         1373  +** to ensure that all merge-parent mlink entries are properly created:
         1374  +**
         1375  +**    (1) Make this routine a no-op if pParent is a merge parent and the
         1376  +**        primary parent is a phantom.
         1377  +**    (2) Invoke this routine recursively for merge-parents if pParent is the
         1378  +**        primary parent.
  1351   1379   */
  1352   1380   static void add_mlink(
  1353         -  int pmid, Manifest *pParent,    /* Parent check-in */
  1354         -  int mid,  Manifest *pChild,     /* The child check-in */
  1355         -  int isPrim                      /* TRUE if pmid is the primary parent of mid */
         1381  +  int pmid, Manifest *pParent,  /* Parent check-in */
         1382  +  int mid,  Manifest *pChild,   /* The child check-in */
         1383  +  int isPrim                    /* TRUE if pmid is the primary parent of mid */
  1356   1384   ){
  1357   1385     Blob otherContent;
  1358   1386     int otherRid;
  1359   1387     int i, rc;
  1360   1388     ManifestFile *pChildFile, *pParentFile;
  1361   1389     Manifest **ppOther;
  1362   1390     static Stmt eq;
................................................................................
  1390   1418       if( *ppOther==0 ) return;
  1391   1419     }
  1392   1420     if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){
  1393   1421       manifest_destroy(*ppOther);
  1394   1422       return;
  1395   1423     }
  1396   1424     isPublic = !content_is_private(mid);
         1425  +  
         1426  +  /* If pParent is not the primary parent of pChild, and the primary
         1427  +  ** parent of pChild is a phantom, then abort this routine without
         1428  +  ** doing any work.  The mlink entries will be computed when the
         1429  +  ** primary parent dephantomizes.
         1430  +  */
         1431  +  if( !isPrim && otherRid==mid
         1432  +   && !db_exists("SELECT 1 FROM blob WHERE uuid=%Q AND size>0",
         1433  +                 pChild->azParent[0])
         1434  +  ){
         1435  +    manifest_cache_insert(*ppOther);
         1436  +    return;
         1437  +  }
  1397   1438   
  1398   1439     /* Try to make the parent manifest a delta from the child, if that
  1399   1440     ** is an appropriate thing to do.  For a new baseline, make the
  1400   1441     ** previous baseline a delta from the current baseline.
  1401   1442     */
  1402   1443     if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){
  1403   1444       content_deltify(pmid, mid, 0);
................................................................................
  1487   1528         if( pChildFile==0 && pParentFile->zUuid!=0 ){
  1488   1529           add_one_mlink(pmid, pParentFile->zUuid, mid, 0, pParentFile->zName, 0,
  1489   1530                         isPublic, isPrim, 0);
  1490   1531         }
  1491   1532       }
  1492   1533     }
  1493   1534     manifest_cache_insert(*ppOther);
         1535  +  
         1536  +  /* If pParent is the primary parent of pChild, also run this analysis
         1537  +  ** for all merge parents of pChild
         1538  +  */
         1539  +  if( isPrim ){
         1540  +    for(i=1; i<pChild->nParent; i++){
         1541  +      pmid = uuid_to_rid(pChild->azParent[i], 0);
         1542  +      if( pmid<=0 ) continue;
         1543  +      add_mlink(pmid, 0, mid, pChild, 0);
         1544  +    }
         1545  +  }
  1494   1546   }
  1495   1547   
  1496   1548   /*
  1497   1549   ** Setup to do multiple manifest_crosslink() calls.
  1498   1550   ** This is only required if processing ticket changes.
  1499   1551   */
  1500   1552   void manifest_crosslink_begin(void){
................................................................................
  1796   1848         }
  1797   1849         for(i=0; i<p->nParent; i++){
  1798   1850           int pid = uuid_to_rid(p->azParent[i], 1);
  1799   1851           db_multi_exec(
  1800   1852              "INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime, baseid)"
  1801   1853              "VALUES(%d, %d, %d, %.17g, %s)",
  1802   1854              pid, rid, i==0, p->rDate, zBaseId/*safe-for-%s*/);
  1803         -        add_mlink(pid, 0, rid, p, i==0);
  1804   1855           if( i==0 ) parentid = pid;
  1805   1856         }
         1857  +      add_mlink(parentid, 0, rid, p, 1);
  1806   1858         if( p->nParent>1 ){
  1807   1859           /* Change MLINK.PID from 0 to -1 for files that are added by merge. */
  1808   1860           db_multi_exec(
  1809   1861              "UPDATE mlink SET pid=-1"
  1810   1862              " WHERE mid=%d"
  1811   1863              "   AND pid=0"
  1812   1864              "   AND fnid IN "
................................................................................
  1954   2006     }
  1955   2007     if( p->type==CFTYPE_EVENT ){
  1956   2008       char *zTag = mprintf("event-%s", p->zEventId);
  1957   2009       int tagid = tag_findid(zTag, 1);
  1958   2010       int prior, subsequent;
  1959   2011       int nWiki;
  1960   2012       char zLength[40];
         2013  +    Stmt qatt;
  1961   2014       while( fossil_isspace(p->zWiki[0]) ) p->zWiki++;
  1962   2015       nWiki = strlen(p->zWiki);
  1963   2016       sqlite3_snprintf(sizeof(zLength), zLength, "%d", nWiki);
  1964   2017       tag_insert(zTag, 1, zLength, rid, p->rDate, rid);
  1965   2018       fossil_free(zTag);
  1966   2019       prior = db_int(0,
  1967   2020         "SELECT rid FROM tagxref"
................................................................................
  1995   2048           "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
  1996   2049           "VALUES('e',%.17g,%d,%d,%Q,%Q,"
  1997   2050           "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
  1998   2051           p->rEventDate, rid, tagid, p->zUser, p->zComment,
  1999   2052           TAG_BGCOLOR, rid
  2000   2053         );
  2001   2054       }
         2055  +    /* Locate and update comment for any attachments */
         2056  +    db_prepare(&qatt,
         2057  +       "SELECT attachid, src, target, filename FROM attachment"
         2058  +       " WHERE target=%Q",
         2059  +       p->zEventId
         2060  +    );
         2061  +    while( db_step(&qatt)==SQLITE_ROW ){
         2062  +      const char *zAttachId = db_column_text(&qatt, 0);
         2063  +      const char *zSrc = db_column_text(&qatt, 1);
         2064  +      const char *zTarget = db_column_text(&qatt, 2);
         2065  +      const char *zName = db_column_text(&qatt, 3);
         2066  +      const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
         2067  +      char *zComment;
         2068  +      if( isAdd ){
         2069  +        zComment = mprintf(
         2070  +             "Add attachment [/artifact/%!S|%h] to"
         2071  +             " tech note [/technote/%h|%.10h]",
         2072  +             zSrc, zName, zTarget, zTarget); 
         2073  +      }else{
         2074  +        zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
         2075  +             zName, zTarget);
         2076  +      }
         2077  +      db_multi_exec("UPDATE event SET comment=%Q, type='e'"
         2078  +                       " WHERE objid=%Q",
         2079  +                    zComment, zAttachId);
         2080  +      fossil_free(zComment);      
         2081  +    }
         2082  +    db_finalize(&qatt);
  2002   2083     }
  2003   2084     if( p->type==CFTYPE_TICKET ){
  2004   2085       char *zTag;
         2086  +    Stmt qatt;
  2005   2087       assert( manifest_crosslink_busy==1 );
  2006   2088       zTag = mprintf("tkt-%s", p->zTicketUuid);
  2007   2089       tag_insert(zTag, 1, 0, rid, p->rDate, rid);
  2008   2090       fossil_free(zTag);
  2009   2091       db_multi_exec("INSERT OR IGNORE INTO pending_tkt VALUES(%Q)",
  2010   2092                     p->zTicketUuid);
         2093  +    /* Locate and update comment for any attachments */
         2094  +    db_prepare(&qatt,
         2095  +       "SELECT attachid, src, target, filename FROM attachment"
         2096  +       " WHERE target=%Q",
         2097  +       p->zTicketUuid
         2098  +    );
         2099  +    while( db_step(&qatt)==SQLITE_ROW ){
         2100  +      const char *zAttachId = db_column_text(&qatt, 0);
         2101  +      const char *zSrc = db_column_text(&qatt, 1);
         2102  +      const char *zTarget = db_column_text(&qatt, 2);
         2103  +      const char *zName = db_column_text(&qatt, 3);
         2104  +      const char isAdd = (zSrc && zSrc[0]) ? 1 : 0;
         2105  +      char *zComment;
         2106  +      if( isAdd ){
         2107  +        zComment = mprintf(
         2108  +             "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
         2109  +             zSrc, zName, zTarget, zTarget);
         2110  +      }else{
         2111  +        zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
         2112  +             zName, zTarget, zTarget);
         2113  +      }
         2114  +      db_multi_exec("UPDATE event SET comment=%Q, type='t'"
         2115  +                       " WHERE objid=%Q",
         2116  +                    zComment, zAttachId);
         2117  +      fossil_free(zComment);      
         2118  +    }
         2119  +    db_finalize(&qatt);
  2011   2120     }
  2012   2121     if( p->type==CFTYPE_ATTACHMENT ){
  2013   2122       char *zComment = 0;
  2014   2123       const char isAdd = (p->zAttachSrc && p->zAttachSrc[0]) ? 1 : 0;
  2015         -    const char attachToType = fossil_is_uuid(p->zAttachTarget)
  2016         -      ? 't' /* attach to ticket */
  2017         -      : 'w' /* attach to wiki page */;
         2124  +    /* We assume that we're attaching to a wiki page until we
         2125  +    ** prove otherwise (which could on a later artifact if we
         2126  +    ** process the attachment artifact before the artifact to
         2127  +    ** which it is attached!) */
         2128  +    char attachToType = 'w';       
         2129  +    if( fossil_is_uuid(p->zAttachTarget) ){
         2130  +      if( db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'",
         2131  +            p->zAttachTarget)
         2132  +        ){
         2133  +        attachToType = 't';          /* Attaching to known ticket */
         2134  +      }else if( db_exists("SELECT 1 FROM tag WHERE tagname='event-%q'",
         2135  +                  p->zAttachTarget) 
         2136  +            ){
         2137  +        attachToType = 'e';          /* Attaching to known tech note */
         2138  +      }
         2139  +    }
  2018   2140       db_multi_exec(
  2019   2141          "INSERT INTO attachment(attachid, mtime, src, target,"
  2020   2142                                 "filename, comment, user)"
  2021   2143          "VALUES(%d,%.17g,%Q,%Q,%Q,%Q,%Q);",
  2022   2144          rid, p->rDate, p->zAttachSrc, p->zAttachTarget, p->zAttachName,
  2023   2145          (p->zComment ? p->zComment : ""), p->zUser
  2024   2146       );
................................................................................
  2035   2157           zComment = mprintf(
  2036   2158                "Add attachment [/artifact/%!S|%h] to wiki page [%h]",
  2037   2159                p->zAttachSrc, p->zAttachName, p->zAttachTarget);
  2038   2160         }else{
  2039   2161           zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
  2040   2162                p->zAttachName, p->zAttachTarget);
  2041   2163         }
         2164  +    }else if( 'e' == attachToType ){
         2165  +      if( isAdd ){
         2166  +        zComment = mprintf(
         2167  +          "Add attachment [/artifact/%!S|%h] to tech note [/technote/%h|%.10h]",
         2168  +          p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget); 
         2169  +      }else{
         2170  +        zComment = mprintf("Delete attachment \"%h\" from tech note [%.10h]",
         2171  +             p->zAttachName, p->zAttachTarget);
         2172  +      }      
  2042   2173       }else{
  2043   2174         if( isAdd ){
  2044   2175           zComment = mprintf(
  2045   2176                "Add attachment [/artifact/%!S|%h] to ticket [%!S|%S]",
  2046   2177                p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
  2047   2178         }else{
  2048   2179           zComment = mprintf("Delete attachment \"%h\" from ticket [%!S|%S]",
................................................................................
  2108   2239         }else if( strcmp(zName, "+user")==0 ){
  2109   2240           blob_appendf(&comment, " Change user to \"%h\".", zValue);
  2110   2241           continue;
  2111   2242         }else if( strcmp(zName, "+date")==0 ){
  2112   2243           blob_appendf(&comment, " Timestamp %h.", zValue);
  2113   2244           continue;
  2114   2245         }else if( memcmp(zName, "-sym-",5)==0 ){
  2115         -        if( !branchMove ) blob_appendf(&comment, " Cancel tag \"%h\"", &zName[5]);
         2246  +        if( !branchMove ){
         2247  +          blob_appendf(&comment, " Cancel tag \"%h\"", &zName[5]);
         2248  +        }
  2116   2249         }else if( memcmp(zName, "*sym-",5)==0 ){
  2117   2250           if( !branchMove ){
  2118   2251             blob_appendf(&comment, " Add propagating tag \"%h\"", &zName[5]);
  2119   2252           }
  2120   2253         }else if( memcmp(zName, "+sym-",5)==0 ){
  2121   2254           blob_appendf(&comment, " Add tag \"%h\"", &zName[5]);
  2122   2255         }else if( strcmp(zName, "+closed")==0 ){

Changes to src/markdown.md.

     1      1   # Markdown formatting rules
     2      2   
     3         -In addition to its native Wiki formatting syntax, Fossil supports Markdown syntax as specified by 
     4         -[John Gruber's original Markdown implementation](http://daringfireball.net/projects/markdown/). 
     5         -For lots of examples - not repeated here - please refer to its 
            3  +In addition to its native Wiki formatting syntax, Fossil supports Markdown syntax as specified by
            4  +[John Gruber's original Markdown implementation](http://daringfireball.net/projects/markdown/).
            5  +For lots of examples - not repeated here - please refer to its
     6      6   [syntax description](http://daringfireball.net/projects/markdown/syntax), of which the page you
     7      7   are reading is an extract.
     8      8   
     9      9   This page itself uses Markdown formatting.
    10     10   
    11     11   ## Summary
    12     12   
    13     13     - Block elements
    14     14   
    15     15         * A **paragraph** is a group of consecutive lines. Paragraphs are separated by blank lines.
    16     16   
    17         -      * A **Header** is a line of text underlined with equal signs or hyphens, or prefixed by a 
           17  +      * A **Header** is a line of text underlined with equal signs or hyphens, or prefixed by a
    18     18           number of hash marks.
    19     19   
    20     20         * **Block quotes** are blocks of text prefixed by '>'.
    21     21   
    22     22         * **Ordered list** items are prefixed by a number and a period. **Unordered list** items
    23     23           are prefixed by a hyphen, asterisk or plus sign. Prefix and item text are separated by
    24         -        whitespace. 
           24  +        whitespace.
    25     25   
    26     26         * **Code blocks** are formed by lines of text (possibly including empty lines) prefixed by
    27     27           at least 4 spaces or a tab.
    28     28   
    29     29         * A **horizontal rule** is a line consisting of 3 or more asterisks, hyphens or underscores,
    30     30           with optional whitespace between them.
    31     31   
................................................................................
    32     32     - Span elements
    33     33   
    34     34         * 3 types of **links** exist:
    35     35   
    36     36           - **automatic links** are URLs or email addresses enclosed in angle brackets
    37     37             ('<' and '>'), and are displayed as such.
    38     38   
    39         -        - **inline links** consist of the displayed link text in square brackets ('[' and ']'), 
    40         -          followed by the link target in parentheses. 
           39  +        - **inline links** consist of the displayed link text in square brackets ('[' and ']'),
           40  +          followed by the link target in parentheses.
    41     41   
    42         -        - **reference links** separate _link instance_ from _link definition_. A link instance 
    43         -          consists of the displayed link text in square brackets, followed by a link definition name 
    44         -          in square brackets. 
           42  +        - **reference links** separate _link instance_ from _link definition_. A link instance
           43  +          consists of the displayed link text in square brackets, followed by a link definition name
           44  +          in square brackets.
    45     45             The corresponding link definition can occur anywhere on the page, and consists
    46         -          of the link definition name in square brackets followed by a colon, whitespace and the 
           46  +          of the link definition name in square brackets followed by a colon, whitespace and the
    47     47             link target.
    48     48   
    49     49         * **Emphasis** can be given by wrapping text in one or two asterisks or underscores - use
    50     50           one for HTML `<em>`, and two for `<strong>` emphasis.
    51     51   
    52     52         * A **code span** is text wrapped in backticks ('`').
    53     53   
    54         -      * **Images** use a syntax much like inline or reference links, but with alt attribute text 
           54  +      * **Images** use a syntax much like inline or reference links, but with alt attribute text
    55     55           ('img alt=...') instead of link text, and the first pair of square
    56     56           brackets in an image instance prefixed by an exclamation mark.
    57     57   
    58     58     - **Inline HTML** is mostly interpreted automatically.
    59     59   
    60     60     - **Escaping** Markdown punctuation characters is done by prefixing them by a backslash ('\\').
    61     61   
................................................................................
    86     86   level.
    87     87   
    88     88   ### Block quotes
    89     89   
    90     90   Not every line in a paragraph needs to be prefixed by '>' in order to make it a block quote,
    91     91   only the first line.
    92     92   
    93         -Block quoted paragraphs can be nested by using multiple '>' characters as prefix. 
           93  +Block quoted paragraphs can be nested by using multiple '>' characters as prefix.
    94     94   
    95     95   Within a block quote, Markdown formatting (e.g. lists, emphasis) still works as normal.
    96     96   
    97     97   ### Lists
    98     98   
    99     99   A list item prefix need not occur first on its line; up to 3 leading spaces are allowed
   100    100   (4 spaces would make a code block out of the following text).
   101    101   
   102    102   For unordered lists, asterisks, hyphens and plus signs can be used interchangeably.
   103    103   
   104         -For ordered lists, arbitrary numbers can be used as part of an item prefix; the items will be 
   105         -renumbered during rendering. However, future implementations may demand that the number used 
          104  +For ordered lists, arbitrary numbers can be used as part of an item prefix; the items will be
          105  +renumbered during rendering. However, future implementations may demand that the number used
   106    106   for the first item in a list indicates an offset to be used for subsequent items.
   107    107   
   108    108   For list items spanning multiple lines, subsequent lines can be indented using an arbitrary amount
   109    109   of whitespace.
   110    110   
   111    111   List items will be wrapped in HTML `<p>` tags if they are separated by blank lines.
   112    112   
   113         -A list item may span multiple paragraphs. At least the first line of each such paragraph must 
          113  +A list item may span multiple paragraphs. At least the first line of each such paragraph must
   114    114   be indented using at least 4 spaces or a tab character.
   115    115   
   116    116   Block quotes within list items must have their '>' delimiters indented using 4 up to 7 spaces.
   117    117   
   118    118   Code blocks within list items need to be indented _twice_, that is, using 8 spaces or 2 tab
   119    119   characters.
   120    120   
................................................................................
   129    129   
   130    130   Regular Markdown syntax is not processed within code blocks.
   131    131   
   132    132   ### Links
   133    133   
   134    134   #### Automatic links
   135    135   
   136         -When rendering automatic links to email addresses, HTML encoding obfuscation is used to 
          136  +When rendering automatic links to email addresses, HTML encoding obfuscation is used to
   137    137   prevent some spambots from harvesting.
   138    138   
   139    139   #### Inline links
   140    140   
   141    141   Links to resources on the same server can use relative paths (i.e. can start with a '/').
   142    142   
   143         -An optional title for the link (e.g. to have mouseover text in the browser) may be given behind 
   144         -the link target but within the parentheses, in single and double quotes, and separated from the 
   145         -link target by whitespace. 
          143  +An optional title for the link (e.g. to have mouseover text in the browser) may be given behind
          144  +the link target but within the parentheses, in single and double quotes, and separated from the
          145  +link target by whitespace.
   146    146   
   147    147   #### Reference links
   148    148   
   149         -> Each reference link consists of 
          149  +> Each reference link consists of
   150    150   >
   151    151   >   - one or more _link instances_ at appropriate locations in the page text
   152    152   >   - a single _link definition_ at an arbitrary location on the page
   153         -> 
          153  +>
   154    154   > During rendering, each link instance is resolved, and the corresponding definition is
   155    155   > filled in. No separate link definition clauses occur in the rendered output.
   156         -> 
          156  +>
   157    157   > There are 3 fields involved in link instances and definitions:
   158    158   >
   159    159   >   - link text (i.e. the text that is displayed at the resulting link)
   160    160   >   - link definition name (i.e. an unique ID binding link instances to link definition)
   161    161   >   - link target (a target URL for the link)
   162    162   
   163    163   Multiple link instances may reference the same link definition using its link definition
................................................................................
   195    195   side of emphasis start or end punctuation characters.
   196    196   
   197    197   ### Code spans
   198    198   
   199    199   To include a literal backtick character in a code span, use multiple backticks as opening and
   200    200   closing delimiters.
   201    201   
   202         -Whitespace may exist immediately after the opening delimiter and before the closing delimiter 
          202  +Whitespace may exist immediately after the opening delimiter and before the closing delimiter
   203    203   of a code span, to allow for code fragments starting or ending with a backtick.
   204    204   
   205    205   Within a code span - like within a code block - angle brackets and ampersands are automatically encoded to make including
   206    206   HTML fragments easier.
   207    207   
   208    208   ### Images
   209    209   
   210    210   If necessary, HTML must be used to specify image dimensions. Markdown has no provision for this.
   211    211   
   212    212   ### Inline HTML
   213    213   
   214         -Start and end tags of 
          214  +Start and end tags of
   215    215   a HTML block level construct (`<div>`, `<table>` etc) must be separated from surrounding
   216    216   context using blank lines, and must both occur at the start of a line.
   217    217   
   218    218   No extra unwanted `<p>` HTML tags are added around HTML block level tags.
   219    219   
   220         -Markdown formatting within HTML block level tags is not processed; however, formatting within 
          220  +Markdown formatting within HTML block level tags is not processed; however, formatting within
   221    221   span level tags (e.g. `<mark>`) is processed normally.
   222    222   
   223    223   ### Escaping Markdown punctuation
   224    224   
   225    225   The following punctuation characters can be escaped using backslash:
   226    226   
   227    227     - \\   backslash

Changes to src/markdown_html.c.

    97     97   static void html_epilog(struct Blob *ob, void *opaque){
    98     98     INTER_BLOCK(ob);
    99     99     BLOB_APPEND_LITERAL(ob, "</div>\n");
   100    100   }
   101    101   
   102    102   static void html_raw_block(struct Blob *ob, struct Blob *text, void *opaque){
   103    103     char *data = blob_buffer(text);
   104         -  size_t first = 0, size = blob_size(text);
          104  +  size_t size = blob_size(text);
          105  +  Blob *title = (Blob*)opaque;
          106  +  while( size>0 && fossil_isspace(data[0]) ){ data++; size--; }
          107  +  while( size>0 && fossil_isspace(data[size-1]) ){ size--; }
          108  +  /* If the first raw block is an <h1> element, then use it as the title. */
          109  +  if( blob_size(ob)<=PROLOG_SIZE
          110  +   && size>9
          111  +   && title!=0
          112  +   && sqlite3_strnicmp("<h1",data,3)==0
          113  +   && sqlite3_strnicmp("</h1>", &data[size-5],5)==0
          114  +  ){
          115  +    int nTag = htmlTagLength(data);
          116  +    blob_append(title, data+nTag, size - nTag - 5);
          117  +    return;
          118  +  }
   105    119     INTER_BLOCK(ob);
   106         -  while( first<size && data[first]=='\n' ) first++;
   107         -  while( size>first && data[size-1]=='\n' ) size--;
   108         -  blob_append(ob, data+first, size-first);
          120  +  blob_append(ob, data, size);
   109    121     BLOB_APPEND_LITERAL(ob, "\n");
   110    122   }
   111    123   
   112    124   static void html_blockcode(struct Blob *ob, struct Blob *text, void *opaque){
   113    125     INTER_BLOCK(ob);
   114    126     BLOB_APPEND_LITERAL(ob, "<pre><code>");
   115    127     html_escape(ob, blob_buffer(text), blob_size(text));
................................................................................
   128    140     struct Blob *text,
   129    141     int level,
   130    142     void *opaque
   131    143   ){
   132    144     struct Blob *title = opaque;
   133    145     /* The first header at the beginning of a text is considered as
   134    146      * a title and not output. */
   135         -  if( blob_size(ob)<=PROLOG_SIZE && blob_size(title)==0 ){
          147  +  if( blob_size(ob)<=PROLOG_SIZE && title!=0 && blob_size(title)==0 ){
   136    148       BLOB_APPEND_BLOB(title, text);
          149  +    return;
   137    150     }
   138    151     INTER_BLOCK(ob);
   139    152     blob_appendf(ob, "<h%d>", level);
   140    153     BLOB_APPEND_BLOB(ob, text);
   141    154     blob_appendf(ob, "</h%d>", level);
   142    155   }
   143    156   
................................................................................
   252    265   }
   253    266   
   254    267   
   255    268   
   256    269   /* HTML span tags */
   257    270   
   258    271   static int html_raw_span(struct Blob *ob, struct Blob *text, void *opaque){
          272  +  /* If the document begins with a <h1> markup, take that as the header. */
   259    273     BLOB_APPEND_BLOB(ob, text);
   260    274     return 1;
   261    275   }
   262    276   
   263    277   static int html_autolink(
   264    278     struct Blob *ob,
   265    279     struct Blob *link,
................................................................................
   368    382   }
   369    383   
   370    384   
   371    385   static void html_normal_text(struct Blob *ob, struct Blob *text, void *opaque){
   372    386     html_escape(ob, blob_buffer(text), blob_size(text));
   373    387   }
   374    388   
   375         -
          389  +/*
          390  +** Convert markdown into HTML.
          391  +**
          392  +** The document title is placed in output_title if not NULL.  Or if
          393  +** output_title is NULL, the document title appears in the body.
          394  +*/
   376    395   void markdown_to_html(
   377         -  struct Blob *input_markdown,
   378         -  struct Blob *output_title,
   379         -  struct Blob *output_body
          396  +  struct Blob *input_markdown,   /* Markdown content to be rendered */
          397  +  struct Blob *output_title,     /* Put title here.  May be NULL */
          398  +  struct Blob *output_body       /* Put document body here. */
   380    399   ){
   381    400     struct mkd_renderer html_renderer = {
   382    401       /* prolog and epilog */
   383    402       html_prolog,
   384    403       html_epilog,
   385    404   
   386    405       /* block level elements */
................................................................................
   413    432   
   414    433       /* misc. parameters */
   415    434       64, /* maximum stack */
   416    435       "*_", /* emphasis characters */
   417    436       0 /* opaque data */
   418    437     };
   419    438     html_renderer.opaque = output_title;
   420         -  blob_reset(output_title);
          439  +  if( output_title ) blob_reset(output_title);
   421    440     blob_reset(output_body);
   422    441     markdown(output_body, input_markdown, &html_renderer);
   423    442   }

Changes to src/merge.c.

    24     24   
    25     25   /*
    26     26   ** Print information about a particular check-in.
    27     27   */
    28     28   void print_checkin_description(int rid, int indent, const char *zLabel){
    29     29     Stmt q;
    30     30     db_prepare(&q,
    31         -     "SELECT datetime(mtime%s),"
           31  +     "SELECT datetime(mtime,toLocal()),"
    32     32        "       coalesce(euser,user), coalesce(ecomment,comment),"
    33     33        "       (SELECT uuid FROM blob WHERE rid=%d),"
    34     34        "       (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
    35     35        "         WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
    36     36        "           AND tagxref.rid=%d AND tagxref.tagtype>0)"
    37         -     "  FROM event WHERE objid=%d", timeline_utc(), rid, rid, rid);
           37  +     "  FROM event WHERE objid=%d", rid, rid, rid);
    38     38     if( db_step(&q)==SQLITE_ROW ){
    39     39       const char *zTagList = db_column_text(&q, 4);
    40     40       char *zCom;
    41     41       if( zTagList && zTagList[0] ){
    42     42         zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList);
    43     43       }else{
    44     44         zCom = mprintf("%s", db_column_text(&q,2));
................................................................................
   225    225     verify_all_options();
   226    226     db_must_be_within_tree();
   227    227     if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
   228    228     vid = db_lget_int("checkout", 0);
   229    229     if( vid==0 ){
   230    230       fossil_fatal("nothing is checked out");
   231    231     }
          232  +  if( !dryRunFlag ){
          233  +    if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag,
          234  +                      db_get_int("autosync-tries", 1)) ){
          235  +      fossil_fatal("Cannot proceed with merge");
          236  +    }
          237  +  }
   232    238   
   233    239     /* Find mid, the artifactID of the version to be merged into the current
   234    240     ** check-out */
   235    241     if( g.argc==3 ){
   236    242       /* Mid is specified as an argument on the command-line */
   237    243       mid = name_to_typed_rid(g.argv[2], "ci");
   238    244       if( mid==0 || !is_a_version(mid) ){
................................................................................
   255    261           db_text(0, "SELECT value FROM tagxref"
   256    262                      " WHERE tagid=%d AND rid=%d AND tagtype>0",
   257    263                      TAG_BRANCH, vid)
   258    264         );
   259    265       }
   260    266       db_prepare(&q,
   261    267         "SELECT blob.uuid,"
   262         -          "   datetime(event.mtime%s),"
          268  +          "   datetime(event.mtime,toLocal()),"
   263    269             "   coalesce(ecomment, comment),"
   264    270             "   coalesce(euser, user)"
   265    271         "  FROM event, blob"
   266    272         " WHERE event.objid=%d AND blob.rid=%d",
   267         -      timeline_utc(), mid, mid
          273  +      mid, mid
   268    274       );
   269    275       if( db_step(&q)==SQLITE_ROW ){
   270    276         char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"",
   271    277               db_column_text(&q, 0), db_column_text(&q, 1),
   272    278               db_column_text(&q, 3), db_column_text(&q, 2));
   273    279         comment_print(zCom, db_column_text(&q,2), 0, -1, g.comFmtFlags);
   274    280         fossil_free(zCom);

Changes to src/name.c.

   152    152       if( rid ) return rid;
   153    153     }
   154    154   
   155    155     /* Date and times */
   156    156     if( memcmp(zTag, "date:", 5)==0 ){
   157    157       rid = db_int(0,
   158    158         "SELECT objid FROM event"
   159         -      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
          159  +      " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
   160    160         " ORDER BY mtime DESC LIMIT 1",
   161    161         &zTag[5], zType);
   162    162       return rid;
   163    163     }
   164    164     if( fossil_isdate(zTag) ){
   165    165       rid = db_int(0,
   166    166         "SELECT objid FROM event"
   167         -      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
          167  +      " WHERE mtime<=julianday(%Q,fromLocal()) AND type GLOB '%q'"
   168    168         " ORDER BY mtime DESC LIMIT 1",
   169    169         zTag, zType);
   170    170       if( rid) return rid;
   171    171     }
   172    172   
   173    173     /* Deprecated date & time formats:   "local:" + date-time and
   174    174     ** "utc:" + date-time */
................................................................................
   528    528   */
   529    529   void whatis_rid(int rid, int verboseFlag){
   530    530     Stmt q;
   531    531     int cnt;
   532    532   
   533    533     /* Basic information about the object. */
   534    534     db_prepare(&q,
   535         -     "SELECT uuid, size, datetime(mtime%s), ipaddr"
          535  +     "SELECT uuid, size, datetime(mtime,toLocal()), ipaddr"
   536    536        "  FROM blob, rcvfrom"
   537    537        " WHERE rid=%d"
   538    538        "   AND rcvfrom.rcvid=blob.rcvid",
   539         -     timeline_utc(), rid);
          539  +     rid);
   540    540     if( db_step(&q)==SQLITE_ROW ){
   541    541       if( verboseFlag ){
   542    542         fossil_print("artifact:   %s (%d)\n", db_column_text(&q,0), rid);
   543    543         fossil_print("size:       %d bytes\n", db_column_int(&q,1));
   544    544         fossil_print("received:   %s from %s\n",
   545    545            db_column_text(&q, 2),
   546    546            db_column_text(&q, 3));
................................................................................
   583    583       fossil_print("%s%s", zPrefix, db_column_text(&q,0));
   584    584     }
   585    585     if( cnt ) fossil_print("\n");
   586    586     db_finalize(&q);
   587    587   
   588    588     /* Check for entries on the timeline that reference this object */
   589    589     db_prepare(&q,
   590         -     "SELECT type, datetime(mtime%s),"
          590  +     "SELECT type, datetime(mtime,toLocal()),"
   591    591        "       coalesce(euser,user), coalesce(ecomment,comment)"
   592         -     "  FROM event WHERE objid=%d", timeline_utc(), rid);
          592  +     "  FROM event WHERE objid=%d", rid);
   593    593     if( db_step(&q)==SQLITE_ROW ){
   594    594       const char *zType;
   595    595       switch( db_column_text(&q,0)[0] ){
   596    596         case 'c':  zType = "Check-in";       break;
   597    597         case 'w':  zType = "Wiki-edit";      break;
   598    598         case 'e':  zType = "Event";          break;
   599    599         case 't':  zType = "Ticket-change";  break;
................................................................................
   605    605       fossil_print("comment:    ");
   606    606       comment_print(db_column_text(&q,3), 0, 12, -1, g.comFmtFlags);
   607    607     }
   608    608     db_finalize(&q);
   609    609   
   610    610     /* Check to see if this object is used as a file in a check-in */
   611    611     db_prepare(&q,
   612         -    "SELECT filename.name, blob.uuid, datetime(event.mtime%s),"
          612  +    "SELECT filename.name, blob.uuid, datetime(event.mtime,toLocal()),"
   613    613       "       coalesce(euser,user), coalesce(ecomment,comment)"
   614    614       "  FROM mlink, filename, blob, event"
   615    615       " WHERE mlink.fid=%d"
   616    616       "   AND filename.fnid=mlink.fnid"
   617    617       "   AND event.objid=mlink.mid"
   618    618       "   AND blob.rid=mlink.mid"
   619    619       " ORDER BY event.mtime DESC /*sort*/",
   620         -    timeline_utc(), rid);
          620  +    rid);
   621    621     while( db_step(&q)==SQLITE_ROW ){
   622    622       fossil_print("file:       %s\n", db_column_text(&q,0));
   623    623       fossil_print("            part of [%S] by %s on %s\n",
   624    624         db_column_text(&q, 1),
   625    625         db_column_text(&q, 3),
   626    626         db_column_text(&q, 2));
   627    627       fossil_print("            ");
................................................................................
   630    630     db_finalize(&q);
   631    631   
   632    632     /* Check to see if this object is used as an attachment */
   633    633     db_prepare(&q,
   634    634       "SELECT attachment.filename,"
   635    635       "       attachment.comment,"
   636    636       "       attachment.user,"
   637         -    "       datetime(attachment.mtime%s),"
          637  +    "       datetime(attachment.mtime,toLocal()),"
   638    638       "       attachment.target,"
   639    639       "       CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))"
   640    640       "            THEN 'ticket'"
   641    641       "       WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))"
   642    642       "            THEN 'wiki' END,"
   643    643       "       attachment.attachid,"
   644    644       "       (SELECT uuid FROM blob WHERE rid=attachid)"
   645    645       "  FROM attachment JOIN blob ON attachment.src=blob.uuid"
   646    646       " WHERE blob.rid=%d",
   647         -    timeline_utc(), rid
          647  +    rid
   648    648     );
   649    649     while( db_step(&q)==SQLITE_ROW ){
   650    650       fossil_print("attachment: %s\n", db_column_text(&q,0));
   651    651       fossil_print("            attached to %s %s\n",
   652    652                    db_column_text(&q,5), db_column_text(&q,4));
   653    653       if( verboseFlag ){
   654    654         fossil_print("            via %s (%d)\n",
................................................................................
   668    668   /*
   669    669   ** COMMAND: whatis*
   670    670   ** Usage: %fossil whatis NAME
   671    671   **
   672    672   ** Resolve the symbol NAME into its canonical 40-character SHA1-hash
   673    673   ** artifact name and provide a description of what role that artifact
   674    674   ** plays.
          675  +**
          676  +** Options:
          677  +**
          678  +**    --type TYPE          Only find artifacts of TYPE (one of: 'ci', 't',
          679  +**                         'w', 'g', or 'e').
          680  +**    -v|--verbose         Provide extra information (such as the RID)
   675    681   */
   676    682   void whatis_cmd(void){
   677    683     int rid;
   678    684     const char *zName;
   679    685     int verboseFlag;
   680    686     int i;
   681    687     const char *zType = 0;
................................................................................
   837    843       "   AND tagxref.srcid=blob.rid;",
   838    844       zWhere /*safe-for-%s*/
   839    845     );
   840    846   
   841    847     /* Cluster artifacts */
   842    848     db_multi_exec(
   843    849       "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n"
   844         -    "SELECT blob.rid, blob.uuid, tagxref.mtime, 'cluster', 'cluster'\n"
   845         -    "  FROM tagxref, blob\n"
          850  +    "SELECT blob.rid, blob.uuid, rcvfrom.mtime, 'cluster', 'cluster'\n"
          851  +    "  FROM tagxref, blob, rcvfrom\n"
   846    852       " WHERE (tagxref.rid %s)\n"
   847    853       "   AND tagxref.tagid=(SELECT tagid FROM tag WHERE tagname='cluster')\n"
   848         -    "   AND blob.rid=tagxref.rid;",
          854  +    "   AND blob.rid=tagxref.rid"
          855  +    "   AND rcvfrom.rcvid=blob.rcvid;",
   849    856       zWhere /*safe-for-%s*/
   850    857     );
   851    858   
   852    859     /* Ticket change artifacts */
   853    860     db_multi_exec(
   854    861       "INSERT OR IGNORE INTO description(rid,uuid,ctime,type,summary)\n"
   855    862       "SELECT blob.rid, blob.uuid, tagxref.mtime, 'ticket',\n"
................................................................................
   976    983   /*
   977    984   ** WEBPAGE: bloblist
   978    985   **
   979    986   ** Return a page showing all artifacts in the repository.  Query parameters:
   980    987   **
   981    988   **   n=N         Show N artifacts
   982    989   **   s=S         Start with artifact number S
          990  +**   unpub       Show only unpublished artifacts
   983    991   */
   984    992   void bloblist_page(void){
   985    993     Stmt q;
   986    994     int s = atoi(PD("s","0"));
   987    995     int n = atoi(PD("n","5000"));
   988    996     int mx = db_int(0, "SELECT max(rid) FROM blob");
          997  +  int unpubOnly = PB("unpub");
   989    998     char *zRange;
   990    999   
   991   1000     login_check_credentials();
   992   1001     if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
   993   1002     style_header("List Of Artifacts");
   994         -  if( mx>n && P("s")==0 ){
         1003  +  style_submenu_element("250 Largest", 0, "bigbloblist");
         1004  +  if( !unpubOnly && mx>n && P("s")==0 ){
   995   1005       int i;
   996   1006       @ <p>Select a range of artifacts to view:</p>
   997   1007       @ <ul>
   998   1008       for(i=1; i<=mx; i+=n){
   999   1009         @ <li> %z(href("%R/bloblist?s=%d&n=%d",i,n))
  1000   1010         @ %d(i)..%d(i+n-1<mx?i+n-1:mx)</a>
  1001   1011       }
  1002   1012       @ </ul>
  1003   1013       style_footer();
  1004   1014       return;
  1005   1015     }
  1006         -  if( mx>n ){
         1016  +  if( !unpubOnly && mx>n ){
  1007   1017       style_submenu_element("Index", "Index", "bloblist");
  1008   1018     }
  1009         -  zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
         1019  +  if( unpubOnly ){
         1020  +    zRange = mprintf("IN private");
         1021  +  }else{
         1022  +    zRange = mprintf("BETWEEN %d AND %d", s, s+n-1);
         1023  +  }
  1010   1024     describe_artifacts(zRange);
         1025  +  fossil_free(zRange);
  1011   1026     db_prepare(&q,
  1012   1027       "SELECT rid, uuid, summary, isPrivate FROM description ORDER BY rid"
  1013   1028     );
  1014   1029     @ <table cellpadding="0" cellspacing="0">
  1015   1030     while( db_step(&q)==SQLITE_ROW ){
  1016   1031       int rid = db_column_int(&q,0);
  1017   1032       const char *zUuid = db_column_text(&q, 1);
  1018   1033       const char *zDesc = db_column_text(&q, 2);
  1019         -    int isPriv = db_column_int(&q,2);
         1034  +    int isPriv = db_column_int(&q,3);
  1020   1035       @ <tr><td align="right">%d(rid)</td>
  1021         -    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%s(zUuid)</a>&nbsp;</td>
         1036  +    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
  1022   1037       @ <td align="left">%h(zDesc)</td>
  1023   1038       if( isPriv ){
  1024   1039         @ <td>(unpublished)</td>
  1025   1040       }
  1026   1041       @ </tr>
  1027   1042     }
  1028   1043     @ </table>
  1029   1044     db_finalize(&q);
  1030   1045     style_footer();
  1031   1046   }
         1047  +
         1048  +/*
         1049  +** WEBPAGE: bigbloblist
         1050  +**
         1051  +** Return a page showing the largest artifacts in the repository in order
         1052  +** of decreasing size.
         1053  +**
         1054  +**   n=N         Show the top N artifacts
         1055  +*/
         1056  +void bigbloblist_page(void){
         1057  +  Stmt q;
         1058  +  int n = atoi(PD("n","250"));
         1059  +
         1060  +  login_check_credentials();
         1061  +  if( !g.perm.Read ){ login_needed(g.anon.Read); return; }
         1062  +  style_header("%d Largest Artifacts", n);
         1063  +  db_multi_exec(
         1064  +    "CREATE TEMP TABLE toshow(rid INTEGER PRIMARY KEY);"
         1065  +    "INSERT INTO toshow(rid)"
         1066  +    "  SELECT rid FROM blob"
         1067  +    "   ORDER BY length(content) DESC"
         1068  +    "   LIMIT %d;", n
         1069  +  );
         1070  +  describe_artifacts("IN toshow");
         1071  +  db_prepare(&q,
         1072  +    "SELECT description.rid, description.uuid, description.summary,"
         1073  +    "       length(blob.content), coalesce(delta.srcid,''),"
         1074  +    "       datetime(description.ctime)"
         1075  +    "  FROM description, blob LEFT JOIN delta ON delta.rid=blob.rid"
         1076  +    " WHERE description.rid=blob.rid"
         1077  +    " ORDER BY length(content) DESC"
         1078  +  );
         1079  +  @ <table cellpadding="2" cellspacing="0" border="1" id="bigblobtab">
         1080  +  @ <thead><tr><th align="right">Size<th align="right">RID
         1081  +  @ <th align="right">Delta From<th>SHA1<th>Description<th>Date</tr></thead>
         1082  +  @ <tbody>
         1083  +  while( db_step(&q)==SQLITE_ROW ){
         1084  +    int rid = db_column_int(&q,0);
         1085  +    const char *zUuid = db_column_text(&q, 1);
         1086  +    const char *zDesc = db_column_text(&q, 2);
         1087  +    int sz = db_column_int(&q,3);
         1088  +    const char *zSrcId = db_column_text(&q,4);
         1089  +    const char *zDate = db_column_text(&q,5);
         1090  +    @ <tr><td align="right">%d(sz)</td>
         1091  +    @ <td align="right">%d(rid)</td>
         1092  +    @ <td align="right">%s(zSrcId)</td>
         1093  +    @ <td>&nbsp;%z(href("%R/info/%!S",zUuid))%S(zUuid)</a>&nbsp;</td>
         1094  +    @ <td align="left">%h(zDesc)</td>
         1095  +    @ <td align="left">%z(href("%R/timeline?c=%T",zDate))%s(zDate)</a></td>
         1096  +    @ </tr>
         1097  +  }
         1098  +  @ </tbody></table>
         1099  +  db_finalize(&q);
         1100  +  output_table_sorting_javascript("bigblobtab", "NnnttT", -1);
         1101  +  style_footer();
         1102  +}
  1032   1103   
  1033   1104   /*
  1034   1105   ** COMMAND: test-unsent
  1035   1106   **
  1036   1107   ** Usage: %fossil test-unsent
  1037   1108   **
  1038   1109   ** Show all artifacts in the unsent table

Changes to src/printf.c.

   230    230   
   231    231   
   232    232   
   233    233   /*
   234    234   ** The root program.  All variations call this core.
   235    235   **
   236    236   ** INPUTS:
   237         -**   func   This is a pointer to a function taking three arguments
   238         -**            1. A pointer to anything.  Same as the "arg" parameter.
   239         -**            2. A pointer to the list of characters to be output
   240         -**               (Note, this list is NOT null terminated.)
   241         -**            3. An integer number of characters to be output.
   242         -**               (Note: This number might be zero.)
   243         -**
   244         -**   arg    This is the pointer to anything which will be passed as the
   245         -**          first argument to "func".  Use it for whatever you like.
          237  +**   pBlob  This is the blob where the output will be built.
   246    238   **
   247    239   **   fmt    This is the format string, as in the usual print.
   248    240   **
   249    241   **   ap     This is a pointer to a list of arguments.  Same as in
   250    242   **          vfprint.
   251    243   **
   252    244   ** OUTPUTS:
................................................................................
   980    972     vfprintf(out, zFormat, ap);
   981    973     fprintf(out, "\n");
   982    974     va_end(ap);
   983    975     for(i=0; i<sizeof(azEnv)/sizeof(azEnv[0]); i++){
   984    976       char *p;
   985    977       if( (p = fossil_getenv(azEnv[i]))!=0 ){
   986    978         fprintf(out, "%s=%s\n", azEnv[i], p);
   987         -      fossil_filename_free(p);
          979  +      fossil_path_free(p);
   988    980       }else if( (z = P(azEnv[i]))!=0 ){
   989    981         fprintf(out, "%s=%s\n", azEnv[i], z);
   990    982       }
   991    983     }
   992    984     fclose(out);
   993    985   }
   994    986   

Changes to src/purge.c.

   490    490         blob_reset(&content);
   491    491       }
   492    492     /* The "checkins" subcommand goes here in alphabetical order, but it must
   493    493     ** be moved to the end since it is the default case */
   494    494     }else if( strncmp(zSubcmd, "list", n)==0 || strcmp(zSubcmd,"ls")==0 ){
   495    495       int showDetail = find_option("l","l",0)!=0;
   496    496       if( !db_table_exists("repository","purgeevent") ) return;
   497         -    db_prepare(&q, "SELECT peid, datetime(ctime,'unixepoch','localtime')"
          497  +    db_prepare(&q, "SELECT peid, datetime(ctime,'unixepoch',toLocal())"
   498    498                      " FROM purgeevent");
   499    499       while( db_step(&q)==SQLITE_ROW ){
   500    500         fossil_print("%4d on %s\n", db_column_int(&q,0), db_column_text(&q,1));
   501    501         if( showDetail ){
   502    502           purge_list_event_content(db_column_int(&q,0));
   503    503         }
   504    504       }

Changes to src/rebuild.c.

   531    531   **   --deanalyze       Remove ANALYZE tables from the database
   532    532   **   --force           Force the rebuild to complete even if errors are seen
   533    533   **   --ifneeded        Only do the rebuild if it would change the schema version
   534    534   **   --index           Always add in the full-text search index
   535    535   **   --noverify        Skip the verification of changes to the BLOB table
   536    536   **   --noindex         Always omit the full-text search index
   537    537   **   --pagesize N      Set the database pagesize to N. (512..65536 and power of 2)
          538  +**   --quiet           Only show output if there are errors
   538    539   **   --randomize       Scan artifacts in a random order
   539    540   **   --stats           Show artifact statistics after rebuilding
   540    541   **   --vacuum          Run VACUUM on the database after rebuilding
   541    542   **   --wal             Set Write-Ahead-Log journalling mode on the database
   542    543   **
   543    544   ** See also: deconstruct, reconstruct
   544    545   */
................................................................................
   884    885     DIR *d;
   885    886     struct dirent *pEntry;
   886    887     Blob aContent; /* content of the just read artifact */
   887    888     static int nFileRead = 0;
   888    889     void *zUnicodePath;
   889    890     char *zUtf8Name;
   890    891   
   891         -  zUnicodePath = fossil_utf8_to_filename(zPath);
          892  +  zUnicodePath = fossil_utf8_to_path(zPath, 1);
   892    893     d = opendir(zUnicodePath);
   893    894     if( d ){
   894    895       while( (pEntry=readdir(d))!=0 ){
   895    896         Blob path;
   896    897         char *zSubpath;
   897    898   
   898    899         if( pEntry->d_name[0]=='.' ){
   899    900           continue;
   900    901         }
   901         -      zUtf8Name = fossil_filename_to_utf8(pEntry->d_name);
          902  +      zUtf8Name = fossil_path_to_utf8(pEntry->d_name);
   902    903         zSubpath = mprintf("%s/%s", zPath, zUtf8Name);
   903         -      fossil_filename_free(zUtf8Name);
          904  +      fossil_path_free(zUtf8Name);
   904    905   #ifdef _DIRENT_HAVE_D_TYPE
   905    906         if( (pEntry->d_type==DT_UNKNOWN || pEntry->d_type==DT_LNK)
   906    907             ? (file_isdir(zSubpath)==1) : (pEntry->d_type==DT_DIR) )
   907    908   #else
   908    909         if( file_isdir(zSubpath)==1 )
   909    910   #endif
   910    911         {
................................................................................
   925    926         free(zSubpath);
   926    927       }
   927    928       closedir(d);
   928    929     }else {
   929    930       fossil_fatal("encountered error %d while trying to open \"%s\".",
   930    931                     errno, g.argv[3]);
   931    932     }
   932         -  fossil_filename_free(zUnicodePath);
          933  +  fossil_path_free(zUnicodePath);
   933    934   }
   934    935   
   935    936   /*
   936    937   ** COMMAND: reconstruct*
   937    938   **
   938    939   ** Usage: %fossil reconstruct FILENAME DIRECTORY
   939    940   **
................................................................................
   955    956     }
   956    957     db_create_repository(g.argv[2]);
   957    958     db_open_repository(g.argv[2]);
   958    959   
   959    960     /* We should be done with options.. */
   960    961     verify_all_options();
   961    962   
   962         -  db_open_config(0);
          963  +  db_open_config(0, 0);
   963    964     db_begin_transaction();
   964    965     db_initial_setup(0, 0, 0);
   965    966   
   966    967     fossil_print("Reading files from directory \"%s\"...\n", g.argv[3]);
   967    968     recon_read_dir(g.argv[3]);
   968    969     fossil_print("\nBuilding the Fossil repository...\n");
   969    970   

Changes to src/report.c.

   953    953   **       t      Sort by text
   954    954   **       n      Sort numerically
   955    955   **       k      Sort by the data-sortkey property
   956    956   **       x      This column is not sortable
   957    957   **
   958    958   ** Capital letters mean sort in reverse order.
   959    959   ** If there are fewer characters in zColumnTypes[] than their are columns,
   960         -** the all extra columns assume type "t" (text).
          960  +** then all extra columns assume type "t" (text).
   961    961   **
   962    962   ** The third parameter is the column that was initially sorted (using 1-based
   963    963   ** column numbers, like SQL).  Make this value 0 if none of the columns are
   964    964   ** initially sorted.  Make the value negative if the column is initially sorted
   965    965   ** in reverse order.
   966    966   **
   967    967   ** Clicking on the same column header twice in a row inverts the sort.
................................................................................
   971    971     const char *zColumnTypes,  /* String for column types */
   972    972     int iInitSort              /* Initially sorted column. Leftmost is 1. 0 for NONE */
   973    973   ){
   974    974     @ <script>
   975    975     @ function SortableTable(tableEl,columnTypes,initSort){
   976    976     @   this.tbody = tableEl.getElementsByTagName('tbody');
   977    977     @   this.columnTypes = columnTypes;
          978  +  @   var ncols = tableEl.rows[0].cells.length;
          979  +  @   for(var i = columnTypes.length; i<=ncols; i++){this.columnTypes += 't';}
   978    980     @   this.sort = function (cell) {
   979    981     @     var column = cell.cellIndex;
   980    982     @     var sortFn;
   981    983     @     switch( cell.sortType ){
   982         -  @       case "N": case "n":  sortFn = this.sortNumeric;  break;
   983         -  @       case "T": case "t":  sortFn = this.sortText;     break;
   984         -  @       case "K": case "k":  sortFn = this.sortKey;      break;
          984  +  if( strchr(zColumnTypes,'n') ){
          985  +    @       case "n": sortFn = this.sortNumeric;  break;
          986  +  }
          987  +  if( strchr(zColumnTypes,'N') ){
          988  +    @       case "N": sortFn = this.sortReverseNumeric;  break;
          989  +  }
          990  +  @       case "t": sortFn = this.sortText;  break;
          991  +  if( strchr(zColumnTypes,'T') ){
          992  +    @       case "T": sortFn = this.sortReverseText;  break;
          993  +  }
          994  +  if( strchr(zColumnTypes,'k') ){
          995  +    @       case "k": sortFn = this.sortKey;  break;
          996  +  }
          997  +  if( strchr(zColumnTypes,'K') ){
          998  +    @       case "K": sortFn = this.sortReverseKey;  break;
          999  +  }
   985   1000     @       default:  return;
   986   1001     @     }
   987   1002     @     this.sortIndex = column;
   988   1003     @     var newRows = new Array();
   989   1004     @     for (j = 0; j < this.tbody[0].rows.length; j++) {
   990   1005     @        newRows[j] = this.tbody[0].rows[j];
   991   1006     @     }
   992   1007     @     if( this.sortIndex==Math.abs(this.prevColumn)-1 ){
   993   1008     @       newRows.reverse();
   994   1009     @       this.prevColumn = -this.prevColumn;
   995   1010     @     }else{
   996   1011     @       newRows.sort(sortFn);
   997   1012     @       this.prevColumn = this.sortIndex+1;
   998         -  @       if( cell.sortType>="A" && cell.sortType<="Z" ){
   999         -  @         newRows.reverse();
  1000         -  @       }
  1001   1013     @     }
  1002   1014     @     for (i=0;i<newRows.length;i++) {
  1003   1015     @       this.tbody[0].appendChild(newRows[i]);
  1004   1016     @     }
  1005   1017     @     this.setHdrIcons();
  1006   1018     @   }
  1007   1019     @   this.setHdrIcons = function() {
................................................................................
  1021   1033     @       hdrCell.className = clsName;
  1022   1034     @     }
  1023   1035     @   }
  1024   1036     @   this.sortText = function(a,b) {
  1025   1037     @     var i = thisObject.sortIndex;
  1026   1038     @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  1027   1039     @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
  1028         -  @     if(aa==bb) return a.rowIndex-b.rowIndex;
  1029   1040     @     if(aa<bb) return -1;
         1041  +  @     if(aa==bb) return a.rowIndex-b.rowIndex;
  1030   1042     @     return 1;
  1031   1043     @   }
  1032         -  @   this.sortNumeric = function(a,b) {
  1033         -  @     var i = thisObject.sortIndex;
  1034         -  @     aa = parseFloat(a.cells[i].textContent);
  1035         -  @     if (isNaN(aa)) aa = 0;
  1036         -  @     bb = parseFloat(b.cells[i].textContent);
  1037         -  @     if (isNaN(bb)) bb = 0;
  1038         -  @     if(aa==bb) return a.rowIndex-b.rowIndex;
  1039         -  @     return aa-bb;
  1040         -  @   }
  1041         -  @   this.sortKey = function(a,b) {
  1042         -  @     var i = thisObject.sortIndex;
  1043         -  @     aa = a.cells[i].getAttribute("data-sortkey");
  1044         -  @     bb = b.cells[i].getAttribute("data-sortkey");
  1045         -  @     if(aa==bb) return a.rowIndex-b.rowIndex;
  1046         -  @     if(aa<bb) return -1;
  1047         -  @     return 1;
  1048         -  @   }
         1044  +  if( strchr(zColumnTypes,'T') ){
         1045  +    @   this.sortReverseText = function(a,b) {
         1046  +    @     var i = thisObject.sortIndex;
         1047  +    @     aa = a.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
         1048  +    @     bb = b.cells[i].textContent.replace(/^\W+/,'').toLowerCase();
         1049  +    @     if(aa<bb) return +1;
         1050  +    @     if(aa==bb) return a.rowIndex-b.rowIndex;
         1051  +    @     return -1;
         1052  +    @   }
         1053  +  }
         1054  +  if( strchr(zColumnTypes,'n') ){
         1055  +    @   this.sortNumeric = function(a,b) {
         1056  +    @     var i = thisObject.sortIndex;
         1057  +    @     aa = parseFloat(a.cells[i].textContent);
         1058  +    @     if (isNaN(aa)) aa = 0;
         1059  +    @     bb = parseFloat(b.cells[i].textContent);
         1060  +    @     if (isNaN(bb)) bb = 0;
         1061  +    @     if(aa==bb) return a.rowIndex-b.rowIndex;
         1062  +    @     return aa-bb;
         1063  +    @   }
         1064  +  }
         1065  +  if( strchr(zColumnTypes,'N') ){
         1066  +    @   this.sortReverseNumeric = function(a,b) {
         1067  +    @     var i = thisObject.sortIndex;
         1068  +    @     aa = parseFloat(a.cells[i].textContent);
         1069  +    @     if (isNaN(aa)) aa = 0;
         1070  +    @     bb = parseFloat(b.cells[i].textContent);
         1071  +    @     if (isNaN(bb)) bb = 0;
         1072  +    @     if(aa==bb) return a.rowIndex-b.rowIndex;
         1073  +    @     return bb-aa;
         1074  +    @   }
         1075  +  }
         1076  +  if( strchr(zColumnTypes,'k') ){
         1077  +    @   this.sortKey = function(a,b) {
         1078  +    @     var i = thisObject.sortIndex;
         1079  +    @     aa = a.cells[i].getAttribute("data-sortkey");
         1080  +    @     bb = b.cells[i].getAttribute("data-sortkey");
         1081  +    @     if(aa<bb) return -1;
         1082  +    @     if(aa==bb) return a.rowIndex-b.rowIndex;
         1083  +    @     return 1;
         1084  +    @   }
         1085  +  }
         1086  +  if( strchr(zColumnTypes,'K') ){
         1087  +    @   this.sortReverseKey = function(a,b) {
         1088  +    @     var i = thisObject.sortIndex;
         1089  +    @     aa = a.cells[i].getAttribute("data-sortkey");
         1090  +    @     bb = b.cells[i].getAttribute("data-sortkey");
         1091  +    @     if(aa<bb) return +1;
         1092  +    @     if(aa==bb) return a.rowIndex-b.rowIndex;
         1093  +    @     return -1;
         1094  +    @   }
         1095  +  }
  1049   1096     @   var x = tableEl.getElementsByTagName('thead');
  1050   1097     @   if(!(this.tbody && this.tbody[0].rows && this.tbody[0].rows.length>0)){
  1051   1098     @     return;
  1052   1099     @   }
  1053   1100     @   if(x && x[0].rows && x[0].rows.length > 0) {
  1054   1101     @     this.hdrRow = x[0].rows[0];
  1055   1102     @   } else {

Changes to src/search.c.

   565    565     blob_reset(&pattern);
   566    566     search_sql_setup(g.db);
   567    567   
   568    568     db_multi_exec(
   569    569        "CREATE TEMP TABLE srch(rid,uuid,date,comment,x);"
   570    570        "CREATE INDEX srch_idx1 ON srch(x);"
   571    571        "INSERT INTO srch(rid,uuid,date,comment,x)"
   572         -     "   SELECT blob.rid, uuid, datetime(event.mtime%s),"
          572  +     "   SELECT blob.rid, uuid, datetime(event.mtime,toLocal()),"
   573    573        "          coalesce(ecomment,comment),"
   574    574        "          search_score()"
   575    575        "     FROM event, blob"
   576    576        "    WHERE blob.rid=event.objid"
   577         -     "      AND search_match(coalesce(ecomment,comment));",
   578         -     timeline_utc()
          577  +     "      AND search_match(coalesce(ecomment,comment));"
   579    578     );
   580    579     iBest = db_int(0, "SELECT max(x) FROM srch");
   581    580     blob_append(&sql,
   582    581                 "SELECT rid, uuid, date, comment, 0, 0 FROM srch "
   583    582                 "WHERE 1 ", -1);
   584    583     if(!fAll){
   585    584       blob_append_sql(&sql,"AND x>%d ", iBest/3);

Changes to src/setup.c.

   123    123       "Show artifacts that are shunned by this repository");
   124    124     setup_menu_entry("Artifact Receipts Log", "rcvfromlist",
   125    125       "A record of received artifacts and their sources");
   126    126     setup_menu_entry("User Log", "access_log",
   127    127       "A record of login attempts");
   128    128     setup_menu_entry("Administrative Log", "admin_log",
   129    129       "View the admin_log entries");
          130  +  setup_menu_entry("Stats", "stat",
          131  +    "Repository Status Reports");
   130    132     setup_menu_entry("Sitemap", "sitemap",
   131    133       "Links to miscellaneous pages");
   132    134     setup_menu_entry("SQL", "admin_sql",
   133    135       "Enter raw SQL commands");
   134    136     setup_menu_entry("TH1", "admin_th1",
   135    137       "Enter raw TH1 commands");
   136    138     @ </table>
................................................................................
   142    144   ** WEBPAGE: setup_ulist
   143    145   **
   144    146   ** Show a list of users.  Clicking on any user jumps to the edit
   145    147   ** screen for that user.  Requires Admin privileges.
   146    148   */
   147    149   void setup_ulist(void){
   148    150     Stmt s;
   149         -  int prevLevel = 0;
   150    151   
   151    152     login_check_credentials();
   152    153     if( !g.perm.Admin ){
   153    154       login_needed(0);
   154    155       return;
   155    156     }
   156    157   
   157    158     style_submenu_element("Add", "Add User", "setup_uedit");
          159  +  style_submenu_element("Log", "Access Log", "access_log");
          160  +  style_submenu_element("Help", "Help", "setup_ulist_notes");
   158    161     style_header("User List");
   159         -  @ <table class="usetupLayoutTable">
   160         -  @ <tr><td class="usetupColumnLayout">
   161         -  @ <span class="note">Users:</span>
   162         -  @ <table class="usetupUserList">
   163         -  prevLevel = 0;
          162  +  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable'>
          163  +  @ <thead><tr><th>UID <th>Category <th>Capabilities <th>Info <th>Last Change</tr></thead>
          164  +  @ <tbody>
          165  +  db_prepare(&s,
          166  +     "SELECT uid, login, cap, date(mtime,'unixepoch')"
          167  +     "  FROM user"
          168  +     " WHERE login IN ('anonymous','nobody','developer','reader')"
          169  +     " ORDER BY login"
          170  +  );
          171  +  while( db_step(&s)==SQLITE_ROW ){
          172  +    int uid = db_column_int(&s, 0);
          173  +    const char *zLogin = db_column_text(&s, 1);
          174  +    const char *zCap = db_column_text(&s, 2);
          175  +    const char *zDate = db_column_text(&s, 4);
          176  +    @ <tr>
          177  +    @ <td><a href='setup_uedit?id=%d(uid)'>%d(uid)</a>
          178  +    @ <td><a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
          179  +    @ <td>%h(zCap)
          180  +    
          181  +    if( fossil_strcmp(zLogin,"anonymous")==0 ){
          182  +      @ <td>All logged-in users
          183  +    }else if( fossil_strcmp(zLogin,"developer")==0 ){
          184  +      @ <td>Users with '<b>v</b>' capability
          185  +    }else if( fossil_strcmp(zLogin,"nobody")==0 ){
          186  +      @ <td>All users without login
          187  +    }else if( fossil_strcmp(zLogin,"reader")==0 ){
          188  +      @ <td>Users with '<b>u</b>' capability
          189  +    }else{
          190  +      @ <td>
          191  +    }
          192  +    if( zDate && zDate[0] ){
          193  +      @ <td>%h(zDate)
          194  +    }else{
          195  +      @ <td>
          196  +    }
          197  +    @ </tr>
          198  +  }
          199  +  db_finalize(&s);
          200  +  @ </tbody></table>
          201  +  @ <div class='section'>Users</div>
          202  +  @ <table border=1 cellpadding=2 cellspacing=0 class='userTable' id='userlist'>
          203  +  @ <thead><tr>
          204  +  @ <th>ID<th>Login<th>Caps<th>Info<th>Date<th>Expire</tr></thead>
          205  +  @ <tbody>
   164    206     db_prepare(&s,
   165         -     "SELECT uid, login, cap, info, 1 FROM user"
   166         -     " WHERE login IN ('anonymous','nobody','developer','reader') "
   167         -     " UNION ALL "
   168         -     "SELECT uid, login, cap, info, 2 FROM user"
   169         -     " WHERE login NOT IN ('anonymous','nobody','developer','reader') "
   170         -     "ORDER BY 5, 2 COLLATE nocase"
          207  +     "SELECT uid, login, cap, info, date(mtime,'unixepoch'), lower(login) AS sortkey, "
          208  +     "       CASE WHEN info LIKE '%%expires 20%%'"
          209  +             "    THEN substr(info,instr(lower(info),'expires')+8,10)"
          210  +             "    END AS exp"
          211  +     "  FROM user"
          212  +     " WHERE login NOT IN ('anonymous','nobody','developer','reader')"
          213  +     " ORDER BY sortkey"
   171    214     );
   172    215     while( db_step(&s)==SQLITE_ROW ){
   173         -    int iLevel = db_column_int(&s, 4);
   174         -    const char *zCap = db_column_text(&s, 2);
          216  +    int uid = db_column_int(&s, 0);
   175    217       const char *zLogin = db_column_text(&s, 1);
   176         -    if( iLevel>prevLevel ){
   177         -      if( prevLevel>0 ){
   178         -        @ <tr><td colspan="3"><hr></td></tr>
   179         -      }
   180         -      if( iLevel==1 ){
   181         -        @ <tr>
   182         -        @   <th class="usetupListUser"
   183         -        @    style="text-align: right;padding-right: 20px;">Category</th>
   184         -        @   <th class="usetupListCap"
   185         -        @    style="text-align: center;padding-right: 15px;">Capabilities</th>
   186         -        @   <th class="usetupListCon"
   187         -        @    style="text-align: left;">Notes</th>
   188         -        @ </tr>
   189         -      }else{
   190         -        @ <tr>
   191         -        @   <th class="usetupListUser"
   192         -        @    style="text-align: right;padding-right: 20px;">User&nbsp;ID</th>
   193         -        @   <th class="usetupListCap"
   194         -        @    style="text-align: center;padding-right: 15px;">Capabilities</th>
   195         -        @   <th class="usetupListCon"
   196         -        @    style="text-align: left;">Contact&nbsp;Info</th>
   197         -        @ </tr>
   198         -      }
   199         -      prevLevel = iLevel;
   200         -    }
          218  +    const char *zCap = db_column_text(&s, 2);
          219  +    const char *zInfo = db_column_text(&s, 3);
          220  +    const char *zDate = db_column_text(&s, 4);
          221  +    const char *zSortKey = db_column_text(&s,5);
          222  +    const char *zExp = db_column_text(&s,6);
   201    223       @ <tr>
   202         -    @ <td class="usetupListUser"
   203         -    @     style="text-align: right;padding-right: 20px;white-space:nowrap;">
   204         -    if( g.perm.Admin && (zCap[0]!='s' || g.perm.Setup) ){
   205         -      @ <a href="setup_uedit?id=%d(db_column_int(&s,0))">
   206         -    }
   207         -    @ %h(zLogin)
   208         -    if( g.perm.Admin ){
   209         -      @ </a>
   210         -    }
   211         -    @ </td>
   212         -    @ <td class="usetupListCap" style="text-align: center;padding-right: 15px;">%s(zCap)</td>
   213         -    @ <td  class="usetupListCon"  style="text-align: left;">%h(db_column_text(&s,3))</td>
          224  +    @ <td><a href='setup_uedit?id=%d(uid)'>%d(uid)</a>
          225  +    @ <td data-sortkey='%h(zSortKey)'><a href='setup_uedit?id=%d(uid)'>%h(zLogin)</a>
          226  +    @ <td>%h(zCap)
          227  +    @ <td>%h(zInfo)
          228  +    @ <td>%h(zDate?zDate:"")
          229  +    @ <td>%h(zExp?zExp:"")
   214    230       @ </tr>
   215    231     }
   216         -  @ </table>
   217         -  @ </td><td class="usetupColumnLayout">
   218         -  @ <span class="note">Notes:</span>
          232  +  @ </tbody></table>
          233  +  db_finalize(&s);
          234  +  output_table_sorting_javascript("userlist","nktxTT",2);
          235  +  style_footer();
          236  +}
          237  +
          238  +/*
          239  +** WEBPAGE: setup_ulist_notes
          240  +**
          241  +** A documentation page showing notes about user configuration.  This information
          242  +** used to be a side-bar on the user list page, but has been factored out for
          243  +** improved presentation.
          244  +*/
          245  +void setup_ulist_notes(void){
          246  +  style_header("User Configuration Notes");
          247  +  @ <h1>User Configuration Notes:</h1>
   219    248     @ <ol>
   220    249     @ <li><p>The permission flags are as follows:</p>
   221    250     @ <table>
   222    251        @ <tr><th valign="top">a</th>
   223    252        @   <td><i>Admin:</i> Create and delete users</td></tr>
   224    253        @ <tr><th valign="top">b</th>
   225    254        @   <td><i>Attach:</i> Add attachments to wiki or tickets</td></tr>
................................................................................
   293    322     @ Users with privilege <span class="capability">v</span> inherit the combined
   294    323     @ privileges of <span class="usertype">developer</span>,
   295    324     @ <span class="usertype">anonymous</span>, and
   296    325     @ <span class="usertype">nobody</span>.
   297    326     @ </p></li>
   298    327     @
   299    328     @ </ol>
   300         -  @ </td></tr></table>
   301    329     style_footer();
   302         -  db_finalize(&s);
   303    330   }
          331  +
   304    332   
   305    333   /*
   306    334   ** Return true if zPw is a valid password string.  A valid
   307    335   ** password string is:
   308    336   **
   309    337   **  (1)  A zero-length string, or
   310    338   **  (2)  a string that contains a character other than '*'.
................................................................................
   326    354     const char *zId, *zLogin, *zInfo, *zCap, *zPw;
   327    355     const char *zGroup;
   328    356     const char *zOldLogin;
   329    357     int doWrite;
   330    358     int uid, i;
   331    359     int higherUser = 0;  /* True if user being edited is SETUP and the */
   332    360                          /* user doing the editing is ADMIN.  Disallow editing */
   333         -  char *inherit[128];
          361  +  const char *inherit[128];
   334    362     int a[128];
   335    363     const char *oa[128];
   336    364   
   337    365     /* Must have ADMIN privileges to access this page
   338    366     */
   339    367     login_check_credentials();
   340    368     if( !g.perm.Admin ){ login_needed(0); return; }
................................................................................
   904    932   ** Generate an entry box for an attribute.
   905    933   */
   906    934   void entry_attribute(
   907    935     const char *zLabel,   /* The text label on the entry box */
   908    936     int width,            /* Width of the entry box */
   909    937     const char *zVar,     /* The corresponding row in the VAR table */
   910    938     const char *zQParm,   /* The query parameter */
   911         -  char *zDflt,          /* Default value if VAR table entry does not exist */
          939  +  const char *zDflt,    /* Default value if VAR table entry does not exist */
   912    940     int disabled          /* 1 if disabled */
   913    941   ){
   914    942     const char *zVal = db_get(zVar, zDflt);
   915    943     const char *zQ = P(zQParm);
   916    944     if( zQ && fossil_strcmp(zQ,zVal)!=0 ){
   917    945       const int nZQ = (int)strlen(zQ);
   918    946       login_verify_csrf_secret();
................................................................................
   936    964     int rows,             /* Rows in the textarea */
   937    965     int cols,             /* Columns in the textarea */
   938    966     const char *zVar,     /* The corresponding row in the VAR table */
   939    967     const char *zQP,      /* The query parameter */
   940    968     const char *zDflt,    /* Default value if VAR table entry does not exist */
   941    969     int disabled          /* 1 if the textarea should  not be editable */
   942    970   ){
   943         -  const char *z = db_get(zVar, (char*)zDflt);
          971  +  const char *z = db_get(zVar, zDflt);
   944    972     const char *zQ = P(zQP);
   945    973     if( zQ && !disabled && fossil_strcmp(zQ,z)!=0){
   946    974       const int nZQ = (int)strlen(zQ);
   947    975       login_verify_csrf_secret();
   948    976       db_set(zVar, zQ, 0);
   949    977       admin_log("Set textarea_attribute %Q to: %.*s%s",
   950    978                 zVar, 20, zQ, (nZQ>20 ? "..." : ""));
................................................................................
   970    998     const char *zLabel,   /* The text label on the menu */
   971    999     const char *zVar,     /* The corresponding row in the VAR table */
   972   1000     const char *zQP,      /* The query parameter */
   973   1001     const char *zDflt,    /* Default value if VAR table entry does not exist */
   974   1002     int nChoice,          /* Number of choices */
   975   1003     const char *const *azChoice /* Choices. 2 per choice: (VAR value, Display) */
   976   1004   ){
   977         -  const char *z = db_get(zVar, (char*)zDflt);
         1005  +  const char *z = db_get(zVar, zDflt);
   978   1006     const char *zQ = P(zQP);
   979   1007     int i;
   980   1008     if( zQ && fossil_strcmp(zQ,z)!=0){
   981   1009       const int nZQ = (int)strlen(zQ);
   982   1010       login_verify_csrf_secret();
   983   1011       db_set(zVar, zQ, 0);
   984   1012       admin_log("Set multiple_choice_attribute %Q to: %.*s%s",
................................................................................
  1345   1373     @ <hr />
  1346   1374     onoff_attribute("Plaintext comments on timelines",
  1347   1375                     "timeline-plaintext", "tpt", 0, 0);
  1348   1376     @ <p>In timeline displays, check-in comments are displayed literally,
  1349   1377     @ without any wiki or HTML interpretation.  (Note: Use CSS to change
  1350   1378     @ display formatting features such as fonts and line-wrapping behavior.)</p>
  1351   1379   
         1380  +  @ <hr />
         1381  +  onoff_attribute("Truncate comment at first blank line",
         1382  +                  "timeline-truncate-at-blank", "ttb", 0, 0);
         1383  +  @ <p>In timeline displays, check-in comments are displayed only through
         1384  +  @ the first blank line.</p>
         1385  +
  1352   1386     @ <hr />
  1353   1387     onoff_attribute("Use Universal Coordinated Time (UTC)",
  1354   1388                     "timeline-utc", "utc", 1, 0);
  1355   1389     @ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
  1356   1390     @ Zulu) instead of in local time.  On this server, local time is currently
  1357   1391     tmDiff = db_double(0.0, "SELECT julianday('now')");
  1358   1392     tmDiff = db_double(0.0,
................................................................................
  1846   1880     const char *zQ = P("q");
  1847   1881     int go = P("go")!=0;
  1848   1882     login_check_credentials();
  1849   1883     if( !g.perm.Setup ){
  1850   1884       login_needed(0);
  1851   1885       return;
  1852   1886     }
         1887  +  add_content_sql_commands(g.db);
  1853   1888     db_begin_transaction();
  1854   1889     style_header("Raw SQL Commands");
  1855   1890     @ <p><b>Caution:</b> There are no restrictions on the SQL that can be
  1856   1891     @ run by this page.  You can do serious and irrepairable damage to the
  1857   1892     @ repository.  Proceed with extreme caution.</p>
  1858   1893     @
  1859         -  @ <p>Only a the first statement in the entry box will be run.
         1894  +  @ <p>Only the first statement in the entry box will be run.
  1860   1895     @ Any subsequent statements will be silently ignored.</p>
  1861   1896     @
  1862   1897     @ <p>Database names:<ul><li>repository &rarr; %s(db_name("repository"))
  1863   1898     if( g.zConfigDbName ){
  1864   1899       @ <li>config &rarr; %s(db_name("configdb"))
  1865   1900     }
  1866   1901     if( g.localOpen ){
................................................................................
  2033   2068       login_needed(0);
  2034   2069       return;
  2035   2070     }
  2036   2071     style_header("Admin Log");
  2037   2072     create_admin_log_table();
  2038   2073     limit = atoi(PD("n","20"));
  2039   2074     fLogEnabled = db_get_boolean("admin-log", 0);
  2040         -  @ <div>Admin logging is %s(fLogEnabled?"on":"off").</div>
         2075  +  @ <div>Admin logging is %s(fLogEnabled?"on":"off").
         2076  +  @ (Change this on the <a href="setup_settings">settings</a> page.)</div>
  2041   2077   
  2042   2078   
  2043   2079     @ <div>Limit results to: <span>
  2044   2080     admin_log_render_limits();
  2045   2081     @ </span></div>
  2046   2082   
  2047   2083     blob_append_sql(&qLog,

Changes to src/shell.c.

   161    161   static int enableTimer = 0;
   162    162   
   163    163   /* Return the current wall-clock time */
   164    164   static sqlite3_int64 timeOfDay(void){
   165    165     static sqlite3_vfs *clockVfs = 0;
   166    166     sqlite3_int64 t;
   167    167     if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0);
   168         -  if( clockVfs->iVersion>=1 && clockVfs->xCurrentTimeInt64!=0 ){
          168  +  if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){
   169    169       clockVfs->xCurrentTimeInt64(clockVfs, &t);
   170    170     }else{
   171    171       double r;
   172    172       clockVfs->xCurrentTime(clockVfs, &r);
   173    173       t = (sqlite3_int64)(r*86400000.0);
   174    174     }
   175    175     return t;
................................................................................
   325    325   
   326    326   /*
   327    327   ** Threat stdin as an interactive input if the following variable
   328    328   ** is true.  Otherwise, assume stdin is connected to a file or pipe.
   329    329   */
   330    330   static int stdin_is_interactive = 1;
   331    331   
          332  +/*
          333  +** On Windows systems we have to know if standard output is a console
          334  +** in order to translate UTF-8 into MBCS.  The following variable is
          335  +** true if translation is required.
          336  +*/
          337  +static int stdout_is_console = 1;
          338  +
   332    339   /*
   333    340   ** The following is the open SQLite database.  We make a pointer
   334    341   ** to this database a static variable so that it can be accessed
   335    342   ** by the SIGINT handler to interrupt database processing.
   336    343   */
   337    344   static sqlite3 *globalDb = 0;
   338    345   
................................................................................
   425    432     assert( 0==argc );
   426    433     assert( zShellStatic );
   427    434     UNUSED_PARAMETER(argc);
   428    435     UNUSED_PARAMETER(argv);
   429    436     sqlite3_result_text(context, zShellStatic, -1, SQLITE_STATIC);
   430    437   }
   431    438   
          439  +
          440  +/*
          441  +** Compute a string length that is limited to what can be stored in
          442  +** lower 30 bits of a 32-bit signed integer.
          443  +*/
          444  +static int strlen30(const char *z){
          445  +  const char *z2 = z;
          446  +  while( *z2 ){ z2++; }
          447  +  return 0x3fffffff & (int)(z2 - z);
          448  +}
   432    449   
   433    450   /*
   434    451   ** This routine reads a line of text from FILE in, stores
   435    452   ** the text in memory obtained from malloc() and returns a pointer
   436    453   ** to the text.  NULL is returned at end of file, or if malloc()
   437    454   ** fails.
   438    455   **
................................................................................
   461    478       if( n>0 && zLine[n-1]=='\n' ){
   462    479         n--;
   463    480         if( n>0 && zLine[n-1]=='\r' ) n--;
   464    481         zLine[n] = 0;
   465    482         break;
   466    483       }
   467    484     }
          485  +#if defined(_WIN32) || defined(WIN32)
          486  +  /* For interactive input on Windows systems, translate the 
          487  +  ** multi-byte characterset characters into UTF-8. */
          488  +  if( stdin_is_interactive ){
          489  +    extern char *sqlite3_win32_mbcs_to_utf8(const char*);
          490  +    char *zTrans = sqlite3_win32_mbcs_to_utf8(zLine);
          491  +    if( zTrans ){
          492  +      int nTrans = strlen30(zTrans)+1;
          493  +      if( nTrans>nLine ){
          494  +        zLine = realloc(zLine, nTrans);
          495  +        if( zLine==0 ){
          496  +          sqlite3_free(zTrans);
          497  +          return 0;
          498  +        }
          499  +      }
          500  +      memcpy(zLine, zTrans, nTrans);
          501  +      sqlite3_free(zTrans);
          502  +    }
          503  +  }
          504  +#endif /* defined(_WIN32) || defined(WIN32) */
   468    505     return zLine;
   469    506   }
   470    507   
   471    508   /*
   472    509   ** Retrieve a single line of input text.
   473    510   **
   474    511   ** If in==0 then read from standard input and prompt before each line.
................................................................................
   498    535       zResult = shell_readline(zPrompt);
   499    536       if( zResult && *zResult ) shell_add_history(zResult);
   500    537   #endif
   501    538     }
   502    539     return zResult;
   503    540   }
   504    541   
          542  +/*
          543  +** Render output like fprintf().  Except, if the output is going to the
          544  +** console and if this is running on a Windows machine, translate the
          545  +** output from UTF-8 into MBCS.
          546  +*/
          547  +#if defined(_WIN32) || defined(WIN32)
          548  +void utf8_printf(FILE *out, const char *zFormat, ...){
          549  +  va_list ap;
          550  +  va_start(ap, zFormat);
          551  +  if( stdout_is_console && (out==stdout || out==stderr) ){
          552  +    extern char *sqlite3_win32_utf8_to_mbcs(const char*);
          553  +    char *z1 = sqlite3_vmprintf(zFormat, ap);
          554  +    char *z2 = sqlite3_win32_utf8_to_mbcs(z1);
          555  +    sqlite3_free(z1);
          556  +    fputs(z2, out);
          557  +    sqlite3_free(z2);
          558  +  }else{
          559  +    vfprintf(out, zFormat, ap);
          560  +  }
          561  +  va_end(ap);
          562  +}
          563  +#elif !defined(utf8_printf)
          564  +# define utf8_printf fprintf
          565  +#endif
          566  +
          567  +/*
          568  +** Render output like fprintf().  This should not be used on anything that
          569  +** includes string formatting (e.g. "%s").
          570  +*/
          571  +#if !defined(raw_printf)
          572  +# define raw_printf fprintf
          573  +#endif
          574  +
   505    575   /*
   506    576   ** Shell output mode information from before ".explain on", 
   507    577   ** saved so that it can be restored by ".explain off"
   508    578   */
   509    579   typedef struct SavedModeInfo SavedModeInfo;
   510    580   struct SavedModeInfo {
   511    581     int valid;          /* Is there legit data in here? */
................................................................................
   518    588   ** State information about the database connection is contained in an
   519    589   ** instance of the following structure.
   520    590   */
   521    591   typedef struct ShellState ShellState;
   522    592   struct ShellState {
   523    593     sqlite3 *db;           /* The database */
   524    594     int echoOn;            /* True to echo input commands */
          595  +  int autoExplain;       /* Automatically turn on .explain mode */
   525    596     int autoEQP;           /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */
   526    597     int statsOn;           /* True to display memory stats before each finalize */
   527    598     int scanstatsOn;       /* True to display scan stats before each finalize */
          599  +  int countChanges;      /* True to display change counts */
   528    600     int backslashOn;       /* Resolve C-style \x escapes in SQL input text */
   529    601     int outCount;          /* Revert to stdout when reaching zero */
   530    602     int cnt;               /* Number of records displayed so far */
   531    603     FILE *out;             /* Write results here */
   532    604     FILE *traceOut;        /* Output for sqlite3_trace() */
   533    605     int nErr;              /* Number of errors seen */
   534    606     int mode;              /* An output mode setting */
          607  +  int cMode;             /* temporary output mode for the current query */
          608  +  int normalMode;        /* Output mode before ".explain on" */
   535    609     int writableSchema;    /* True if PRAGMA writable_schema=ON */
   536    610     int showHeader;        /* True to show column names in List or Column mode */
   537    611     unsigned shellFlgs;    /* Various flags */
   538    612     char *zDestTable;      /* Name of destination table when MODE_Insert */
   539    613     char colSeparator[20]; /* Column separator character for several modes */
   540    614     char rowSeparator[20]; /* Row separator character for MODE_Ascii */
   541    615     int colWidth[100];     /* Requested width of each column when in column mode*/
   542    616     int actualWidth[100];  /* Actual width of each column */
   543    617     char nullValue[20];    /* The text to print when a NULL comes back from
   544    618                            ** the database */
   545         -  SavedModeInfo normalMode;/* Holds the mode just before .explain ON */
   546    619     char outfile[FILENAME_MAX]; /* Filename for *out */
   547    620     const char *zDbFilename;    /* name of the database file */
   548    621     char *zFreeOnClose;         /* Filename to free when closing */
   549    622     const char *zVfs;           /* Name of VFS to use */
   550    623     sqlite3_stmt *pStmt;   /* Current statement if any. */
   551    624     FILE *pLog;            /* Write log output here */
   552    625     int *aiIndent;         /* Array of indents used in MODE_Explain */
................................................................................
   602    675   #define SEP_Record    "\x1E"
   603    676   
   604    677   /*
   605    678   ** Number of elements in an array
   606    679   */
   607    680   #define ArraySize(X)  (int)(sizeof(X)/sizeof(X[0]))
   608    681   
   609         -/*
   610         -** Compute a string length that is limited to what can be stored in
   611         -** lower 30 bits of a 32-bit signed integer.
   612         -*/
   613         -static int strlen30(const char *z){
   614         -  const char *z2 = z;
   615         -  while( *z2 ){ z2++; }
   616         -  return 0x3fffffff & (int)(z2 - z);
   617         -}
   618         -
   619    682   /*
   620    683   ** A callback for the sqlite3_log() interface.
   621    684   */
   622    685   static void shellLog(void *pArg, int iErrCode, const char *zMsg){
   623    686     ShellState *p = (ShellState*)pArg;
   624    687     if( p->pLog==0 ) return;
   625         -  fprintf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
          688  +  utf8_printf(p->pLog, "(%d) %s\n", iErrCode, zMsg);
   626    689     fflush(p->pLog);
   627    690   }
   628    691   
   629    692   /*
   630    693   ** Output the given string as a hex-encoded blob (eg. X'1234' )
   631    694   */
   632    695   static void output_hex_blob(FILE *out, const void *pBlob, int nBlob){
   633    696     int i;
   634    697     char *zBlob = (char *)pBlob;
   635         -  fprintf(out,"X'");
   636         -  for(i=0; i<nBlob; i++){ fprintf(out,"%02x",zBlob[i]&0xff); }
   637         -  fprintf(out,"'");
          698  +  raw_printf(out,"X'");
          699  +  for(i=0; i<nBlob; i++){ raw_printf(out,"%02x",zBlob[i]&0xff); }
          700  +  raw_printf(out,"'");
   638    701   }
   639    702   
   640    703   /*
   641    704   ** Output the given string as a quoted string using SQL quoting conventions.
   642    705   */
   643    706   static void output_quoted_string(FILE *out, const char *z){
   644    707     int i;
   645    708     int nSingle = 0;
   646    709     setBinaryMode(out);
   647    710     for(i=0; z[i]; i++){
   648    711       if( z[i]=='\'' ) nSingle++;
   649    712     }
   650    713     if( nSingle==0 ){
   651         -    fprintf(out,"'%s'",z);
          714  +    utf8_printf(out,"'%s'",z);
   652    715     }else{
   653         -    fprintf(out,"'");
          716  +    raw_printf(out,"'");
   654    717       while( *z ){
   655    718         for(i=0; z[i] && z[i]!='\''; i++){}
   656    719         if( i==0 ){
   657         -        fprintf(out,"''");
          720  +        raw_printf(out,"''");
   658    721           z++;
   659    722         }else if( z[i]=='\'' ){
   660         -        fprintf(out,"%.*s''",i,z);
          723  +        utf8_printf(out,"%.*s''",i,z);
   661    724           z += i+1;
   662    725         }else{
   663         -        fprintf(out,"%s",z);
          726  +        utf8_printf(out,"%s",z);
   664    727           break;
   665    728         }
   666    729       }
   667         -    fprintf(out,"'");
          730  +    raw_printf(out,"'");
   668    731     }
   669    732     setTextMode(out);
   670    733   }
   671    734   
   672    735   /*
   673    736   ** Output the given string as a quoted according to C or TCL quoting rules.
   674    737   */
................................................................................
   688    751       }else if( c=='\n' ){
   689    752         fputc('\\', out);
   690    753         fputc('n', out);
   691    754       }else if( c=='\r' ){
   692    755         fputc('\\', out);
   693    756         fputc('r', out);
   694    757       }else if( !isprint(c&0xff) ){
   695         -      fprintf(out, "\\%03o", c&0xff);
          758  +      raw_printf(out, "\\%03o", c&0xff);
   696    759       }else{
   697    760         fputc(c, out);
   698    761       }
   699    762     }
   700    763     fputc('"', out);
   701    764   }
   702    765   
................................................................................
   712    775               && z[i]!='<' 
   713    776               && z[i]!='&' 
   714    777               && z[i]!='>' 
   715    778               && z[i]!='\"' 
   716    779               && z[i]!='\'';
   717    780           i++){}
   718    781       if( i>0 ){
   719         -      fprintf(out,"%.*s",i,z);
          782  +      utf8_printf(out,"%.*s",i,z);
   720    783       }
   721    784       if( z[i]=='<' ){
   722         -      fprintf(out,"&lt;");
          785  +      raw_printf(out,"&lt;");
   723    786       }else if( z[i]=='&' ){
   724         -      fprintf(out,"&amp;");
          787  +      raw_printf(out,"&amp;");
   725    788       }else if( z[i]=='>' ){
   726         -      fprintf(out,"&gt;");
          789  +      raw_printf(out,"&gt;");
   727    790       }else if( z[i]=='\"' ){
   728         -      fprintf(out,"&quot;");
          791  +      raw_printf(out,"&quot;");
   729    792       }else if( z[i]=='\'' ){
   730         -      fprintf(out,"&#39;");
          793  +      raw_printf(out,"&#39;");
   731    794       }else{
   732    795         break;
   733    796       }
   734    797       z += i + 1;
   735    798     }
   736    799   }
   737    800   
................................................................................
   763    826   ** the separator, which may or may not be a comma.  p->nullValue is
   764    827   ** the null value.  Strings are quoted if necessary.  The separator
   765    828   ** is only issued if bSep is true.
   766    829   */
   767    830   static void output_csv(ShellState *p, const char *z, int bSep){
   768    831     FILE *out = p->out;
   769    832     if( z==0 ){
   770         -    fprintf(out,"%s",p->nullValue);
          833  +    utf8_printf(out,"%s",p->nullValue);
   771    834     }else{
   772    835       int i;
   773    836       int nSep = strlen30(p->colSeparator);
   774    837       for(i=0; z[i]; i++){
   775    838         if( needCsvQuote[((unsigned char*)z)[i]] 
   776    839            || (z[i]==p->colSeparator[0] && 
   777    840                (nSep==1 || memcmp(z, p->colSeparator, nSep)==0)) ){
................................................................................
   783    846         putc('"', out);
   784    847         for(i=0; z[i]; i++){
   785    848           if( z[i]=='"' ) putc('"', out);
   786    849           putc(z[i], out);
   787    850         }
   788    851         putc('"', out);
   789    852       }else{
   790         -      fprintf(out, "%s", z);
          853  +      utf8_printf(out, "%s", z);
   791    854       }
   792    855     }
   793    856     if( bSep ){
   794         -    fprintf(p->out, "%s", p->colSeparator);
          857  +    utf8_printf(p->out, "%s", p->colSeparator);
   795    858     }
   796    859   }
   797    860   
   798    861   #ifdef SIGINT
   799    862   /*
   800    863   ** This routine runs when the user presses Ctrl-C
   801    864   */
................................................................................
   817    880     char **azArg,    /* Text of each result column */
   818    881     char **azCol,    /* Column names */
   819    882     int *aiType      /* Column types */
   820    883   ){
   821    884     int i;
   822    885     ShellState *p = (ShellState*)pArg;
   823    886   
   824         -  switch( p->mode ){
          887  +  switch( p->cMode ){
   825    888       case MODE_Line: {
   826    889         int w = 5;
   827    890         if( azArg==0 ) break;
   828    891         for(i=0; i<nArg; i++){
   829    892           int len = strlen30(azCol[i] ? azCol[i] : "");
   830    893           if( len>w ) w = len;
   831    894         }
   832         -      if( p->cnt++>0 ) fprintf(p->out, "%s", p->rowSeparator);
          895  +      if( p->cnt++>0 ) utf8_printf(p->out, "%s", p->rowSeparator);
   833    896         for(i=0; i<nArg; i++){
   834         -        fprintf(p->out,"%*s = %s%s", w, azCol[i],
          897  +        utf8_printf(p->out,"%*s = %s%s", w, azCol[i],
   835    898                   azArg[i] ? azArg[i] : p->nullValue, p->rowSeparator);
   836    899         }
   837    900         break;
   838    901       }
   839    902       case MODE_Explain:
   840    903       case MODE_Column: {
          904  +      static const int aExplainWidths[] = {4, 13, 4, 4, 4, 13, 2, 13};
          905  +      const int *colWidth;
          906  +      int showHdr;
          907  +      char *rowSep;
          908  +      if( p->cMode==MODE_Column ){
          909  +        colWidth = p->colWidth;
          910  +        showHdr = p->showHeader;
          911  +        rowSep = p->rowSeparator;
          912  +      }else{
          913  +        colWidth = aExplainWidths;
          914  +        showHdr = 1;
          915  +        rowSep = SEP_Row;
          916  +      }
   841    917         if( p->cnt++==0 ){
   842    918           for(i=0; i<nArg; i++){
   843    919             int w, n;
   844    920             if( i<ArraySize(p->colWidth) ){
   845         -            w = p->colWidth[i];
          921  +            w = colWidth[i];
   846    922             }else{
   847    923               w = 0;
   848    924             }
   849    925             if( w==0 ){
   850    926               w = strlen30(azCol[i] ? azCol[i] : "");
   851    927               if( w<10 ) w = 10;
   852    928               n = strlen30(azArg && azArg[i] ? azArg[i] : p->nullValue);
   853    929               if( w<n ) w = n;
   854    930             }
   855    931             if( i<ArraySize(p->actualWidth) ){
   856    932               p->actualWidth[i] = w;
   857    933             }
   858         -          if( p->showHeader ){
          934  +          if( showHdr ){
   859    935               if( w<0 ){
   860         -              fprintf(p->out,"%*.*s%s",-w,-w,azCol[i],
   861         -                      i==nArg-1 ? p->rowSeparator : "  ");
          936  +              utf8_printf(p->out,"%*.*s%s",-w,-w,azCol[i],
          937  +                      i==nArg-1 ? rowSep : "  ");
   862    938               }else{
   863         -              fprintf(p->out,"%-*.*s%s",w,w,azCol[i],
   864         -                      i==nArg-1 ? p->rowSeparator : "  ");
          939  +              utf8_printf(p->out,"%-*.*s%s",w,w,azCol[i],
          940  +                      i==nArg-1 ? rowSep : "  ");
   865    941               }
   866    942             }
   867    943           }
   868         -        if( p->showHeader ){
          944  +        if( showHdr ){
   869    945             for(i=0; i<nArg; i++){
   870    946               int w;
   871    947               if( i<ArraySize(p->actualWidth) ){
   872    948                  w = p->actualWidth[i];
   873    949                  if( w<0 ) w = -w;
   874    950               }else{
   875    951                  w = 10;
   876    952               }
   877         -            fprintf(p->out,"%-*.*s%s",w,w,"-----------------------------------"
          953  +            utf8_printf(p->out,"%-*.*s%s",w,w,
          954  +                   "----------------------------------------------------------"
   878    955                      "----------------------------------------------------------",
   879         -                    i==nArg-1 ? p->rowSeparator : "  ");
          956  +                    i==nArg-1 ? rowSep : "  ");
   880    957             }
   881    958           }
   882    959         }
   883    960         if( azArg==0 ) break;
   884    961         for(i=0; i<nArg; i++){
   885    962           int w;
   886    963           if( i<ArraySize(p->actualWidth) ){
   887    964              w = p->actualWidth[i];
   888    965           }else{
   889    966              w = 10;
   890    967           }
   891         -        if( p->mode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){
          968  +        if( p->cMode==MODE_Explain && azArg[i] && strlen30(azArg[i])>w ){
   892    969             w = strlen30(azArg[i]);
   893    970           }
   894    971           if( i==1 && p->aiIndent && p->pStmt ){
   895    972             if( p->iIndent<p->nIndent ){
   896         -            fprintf(p->out, "%*.s", p->aiIndent[p->iIndent], "");
          973  +            utf8_printf(p->out, "%*.s", p->aiIndent[p->iIndent], "");
   897    974             }
   898    975             p->iIndent++;
   899    976           }
   900    977           if( w<0 ){
   901         -          fprintf(p->out,"%*.*s%s",-w,-w,
          978  +          utf8_printf(p->out,"%*.*s%s",-w,-w,
   902    979                 azArg[i] ? azArg[i] : p->nullValue,
   903         -              i==nArg-1 ? p->rowSeparator : "  ");
          980  +              i==nArg-1 ? rowSep : "  ");
   904    981           }else{
   905         -          fprintf(p->out,"%-*.*s%s",w,w,
          982  +          utf8_printf(p->out,"%-*.*s%s",w,w,
   906    983                 azArg[i] ? azArg[i] : p->nullValue,
   907         -              i==nArg-1 ? p->rowSeparator : "  ");
          984  +              i==nArg-1 ? rowSep : "  ");
   908    985           }
   909    986         }
   910    987         break;
   911    988       }
   912    989       case MODE_Semi:
   913    990       case MODE_List: {
   914    991         if( p->cnt++==0 && p->showHeader ){
   915    992           for(i=0; i<nArg; i++){
   916         -          fprintf(p->out,"%s%s",azCol[i],
          993  +          utf8_printf(p->out,"%s%s",azCol[i],
   917    994                     i==nArg-1 ? p->rowSeparator : p->colSeparator);
   918    995           }
   919    996         }
   920    997         if( azArg==0 ) break;
   921    998         for(i=0; i<nArg; i++){
   922    999           char *z = azArg[i];
   923   1000           if( z==0 ) z = p->nullValue;
   924         -        fprintf(p->out, "%s", z);
         1001  +        utf8_printf(p->out, "%s", z);
   925   1002           if( i<nArg-1 ){
   926         -          fprintf(p->out, "%s", p->colSeparator);
   927         -        }else if( p->mode==MODE_Semi ){
   928         -          fprintf(p->out, ";%s", p->rowSeparator);
         1003  +          utf8_printf(p->out, "%s", p->colSeparator);
         1004  +        }else if( p->cMode==MODE_Semi ){
         1005  +          utf8_printf(p->out, ";%s", p->rowSeparator);
   929   1006           }else{
   930         -          fprintf(p->out, "%s", p->rowSeparator);
         1007  +          utf8_printf(p->out, "%s", p->rowSeparator);
   931   1008           }
   932   1009         }
   933   1010         break;
   934   1011       }
   935   1012       case MODE_Html: {
   936   1013         if( p->cnt++==0 && p->showHeader ){
   937         -        fprintf(p->out,"<TR>");
         1014  +        raw_printf(p->out,"<TR>");
   938   1015           for(i=0; i<nArg; i++){
   939         -          fprintf(p->out,"<TH>");
         1016  +          raw_printf(p->out,"<TH>");
   940   1017             output_html_string(p->out, azCol[i]);
   941         -          fprintf(p->out,"</TH>\n");
         1018  +          raw_printf(p->out,"</TH>\n");
   942   1019           }
   943         -        fprintf(p->out,"</TR>\n");
         1020  +        raw_printf(p->out,"</TR>\n");
   944   1021         }
   945   1022         if( azArg==0 ) break;
   946         -      fprintf(p->out,"<TR>");
         1023  +      raw_printf(p->out,"<TR>");
   947   1024         for(i=0; i<nArg; i++){
   948         -        fprintf(p->out,"<TD>");
         1025  +        raw_printf(p->out,"<TD>");
   949   1026           output_html_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
   950         -        fprintf(p->out,"</TD>\n");
         1027  +        raw_printf(p->out,"</TD>\n");
   951   1028         }
   952         -      fprintf(p->out,"</TR>\n");
         1029  +      raw_printf(p->out,"</TR>\n");
   953   1030         break;
   954   1031       }
   955   1032       case MODE_Tcl: {
   956   1033         if( p->cnt++==0 && p->showHeader ){
   957   1034           for(i=0; i<nArg; i++){
   958   1035             output_c_string(p->out,azCol[i] ? azCol[i] : "");
   959         -          if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
         1036  +          if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
   960   1037           }
   961         -        fprintf(p->out, "%s", p->rowSeparator);
         1038  +        utf8_printf(p->out, "%s", p->rowSeparator);
   962   1039         }
   963   1040         if( azArg==0 ) break;
   964   1041         for(i=0; i<nArg; i++){
   965   1042           output_c_string(p->out, azArg[i] ? azArg[i] : p->nullValue);
   966         -        if(i<nArg-1) fprintf(p->out, "%s", p->colSeparator);
         1043  +        if(i<nArg-1) utf8_printf(p->out, "%s", p->colSeparator);
   967   1044         }
   968         -      fprintf(p->out, "%s", p->rowSeparator);
         1045  +      utf8_printf(p->out, "%s", p->rowSeparator);
   969   1046         break;
   970   1047       }
   971   1048       case MODE_Csv: {
   972   1049         setBinaryMode(p->out);
   973   1050         if( p->cnt++==0 && p->showHeader ){
   974   1051           for(i=0; i<nArg; i++){
   975   1052             output_csv(p, azCol[i] ? azCol[i] : "", i<nArg-1);
   976   1053           }
   977         -        fprintf(p->out, "%s", p->rowSeparator);
         1054  +        utf8_printf(p->out, "%s", p->rowSeparator);
   978   1055         }
   979   1056         if( nArg>0 ){
   980   1057           for(i=0; i<nArg; i++){
   981   1058             output_csv(p, azArg[i], i<nArg-1);
   982   1059           }
   983         -        fprintf(p->out, "%s", p->rowSeparator);
         1060  +        utf8_printf(p->out, "%s", p->rowSeparator);
   984   1061         }
   985   1062         setTextMode(p->out);
   986   1063         break;
   987   1064       }
   988   1065       case MODE_Insert: {
   989   1066         p->cnt++;
   990   1067         if( azArg==0 ) break;
   991         -      fprintf(p->out,"INSERT INTO %s",p->zDestTable);
         1068  +      utf8_printf(p->out,"INSERT INTO %s",p->zDestTable);
   992   1069         if( p->showHeader ){
   993         -        fprintf(p->out,"(");
         1070  +        raw_printf(p->out,"(");
   994   1071           for(i=0; i<nArg; i++){
   995   1072             char *zSep = i>0 ? ",": "";
   996         -          fprintf(p->out, "%s%s", zSep, azCol[i]);
         1073  +          utf8_printf(p->out, "%s%s", zSep, azCol[i]);
   997   1074           }
   998         -        fprintf(p->out,")");
         1075  +        raw_printf(p->out,")");
   999   1076         }
  1000         -      fprintf(p->out," VALUES(");
         1077  +      raw_printf(p->out," VALUES(");
  1001   1078         for(i=0; i<nArg; i++){
  1002   1079           char *zSep = i>0 ? ",": "";
  1003   1080           if( (azArg[i]==0) || (aiType && aiType[i]==SQLITE_NULL) ){
  1004         -          fprintf(p->out,"%sNULL",zSep);
         1081  +          utf8_printf(p->out,"%sNULL",zSep);
  1005   1082           }else if( aiType && aiType[i]==SQLITE_TEXT ){
  1006         -          if( zSep[0] ) fprintf(p->out,"%s",zSep);
         1083  +          if( zSep[0] ) utf8_printf(p->out,"%s",zSep);
  1007   1084             output_quoted_string(p->out, azArg[i]);
  1008   1085           }else if( aiType && (aiType[i]==SQLITE_INTEGER
  1009   1086                                || aiType[i]==SQLITE_FLOAT) ){
  1010         -          fprintf(p->out,"%s%s",zSep, azArg[i]);
         1087  +          utf8_printf(p->out,"%s%s",zSep, azArg[i]);
  1011   1088           }else if( aiType && aiType[i]==SQLITE_BLOB && p->pStmt ){
  1012   1089             const void *pBlob = sqlite3_column_blob(p->pStmt, i);
  1013   1090             int nBlob = sqlite3_column_bytes(p->pStmt, i);
  1014         -          if( zSep[0] ) fprintf(p->out,"%s",zSep);
         1091  +          if( zSep[0] ) utf8_printf(p->out,"%s",zSep);
  1015   1092             output_hex_blob(p->out, pBlob, nBlob);
  1016   1093           }else if( isNumber(azArg[i], 0) ){
  1017         -          fprintf(p->out,"%s%s",zSep, azArg[i]);
         1094  +          utf8_printf(p->out,"%s%s",zSep, azArg[i]);
  1018   1095           }else{
  1019         -          if( zSep[0] ) fprintf(p->out,"%s",zSep);
         1096  +          if( zSep[0] ) utf8_printf(p->out,"%s",zSep);
  1020   1097             output_quoted_string(p->out, azArg[i]);
  1021   1098           }
  1022   1099         }
  1023         -      fprintf(p->out,");\n");
         1100  +      raw_printf(p->out,");\n");
  1024   1101         break;
  1025   1102       }
  1026   1103       case MODE_Ascii: {
  1027   1104         if( p->cnt++==0 && p->showHeader ){
  1028   1105           for(i=0; i<nArg; i++){
  1029         -          if( i>0 ) fprintf(p->out, "%s", p->colSeparator);
  1030         -          fprintf(p->out,"%s",azCol[i] ? azCol[i] : "");
         1106  +          if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
         1107  +          utf8_printf(p->out,"%s",azCol[i] ? azCol[i] : "");
  1031   1108           }
  1032         -        fprintf(p->out, "%s", p->rowSeparator);
         1109  +        utf8_printf(p->out, "%s", p->rowSeparator);
  1033   1110         }
  1034   1111         if( azArg==0 ) break;
  1035   1112         for(i=0; i<nArg; i++){
  1036         -        if( i>0 ) fprintf(p->out, "%s", p->colSeparator);
  1037         -        fprintf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue);
         1113  +        if( i>0 ) utf8_printf(p->out, "%s", p->colSeparator);
         1114  +        utf8_printf(p->out,"%s",azArg[i] ? azArg[i] : p->nullValue);
  1038   1115         }
  1039         -      fprintf(p->out, "%s", p->rowSeparator);
         1116  +      utf8_printf(p->out, "%s", p->rowSeparator);
  1040   1117         break;
  1041   1118       }
  1042   1119     }
  1043   1120     return 0;
  1044   1121   }
  1045   1122   
  1046   1123   /*
................................................................................
  1073   1150         needQuote = 1;
  1074   1151         if( zName[i]=='\'' ) n++;
  1075   1152       }
  1076   1153     }
  1077   1154     if( needQuote ) n += 2;
  1078   1155     z = p->zDestTable = malloc( n+1 );
  1079   1156     if( z==0 ){
  1080         -    fprintf(stderr,"Error: out of memory\n");
         1157  +    raw_printf(stderr,"Error: out of memory\n");
  1081   1158       exit(1);
  1082   1159     }
  1083   1160     n = 0;
  1084   1161     if( needQuote ) z[n++] = '\'';
  1085   1162     for(i=0; zName[i]; i++){
  1086   1163       z[n++] = zName[i];
  1087   1164       if( zName[i]=='\'' ) z[n++] = '\'';
................................................................................
  1154   1231     sqlite3_stmt *pSelect;
  1155   1232     int rc;
  1156   1233     int nResult;
  1157   1234     int i;
  1158   1235     const char *z;
  1159   1236     rc = sqlite3_prepare_v2(p->db, zSelect, -1, &pSelect, 0);
  1160   1237     if( rc!=SQLITE_OK || !pSelect ){
  1161         -    fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db));
         1238  +    utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
         1239  +                sqlite3_errmsg(p->db));
  1162   1240       if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
  1163   1241       return rc;
  1164   1242     }
  1165   1243     rc = sqlite3_step(pSelect);
  1166   1244     nResult = sqlite3_column_count(pSelect);
  1167   1245     while( rc==SQLITE_ROW ){
  1168   1246       if( zFirstRow ){
  1169         -      fprintf(p->out, "%s", zFirstRow);
         1247  +      utf8_printf(p->out, "%s", zFirstRow);
  1170   1248         zFirstRow = 0;
  1171   1249       }
  1172   1250       z = (const char*)sqlite3_column_text(pSelect, 0);
  1173         -    fprintf(p->out, "%s", z);
         1251  +    utf8_printf(p->out, "%s", z);
  1174   1252       for(i=1; i<nResult; i++){ 
  1175         -      fprintf(p->out, ",%s", sqlite3_column_text(pSelect, i));
         1253  +      utf8_printf(p->out, ",%s", sqlite3_column_text(pSelect, i));
  1176   1254       }
  1177   1255       if( z==0 ) z = "";
  1178   1256       while( z[0] && (z[0]!='-' || z[1]!='-') ) z++;
  1179   1257       if( z[0] ){
  1180         -      fprintf(p->out, "\n;\n");
         1258  +      raw_printf(p->out, "\n;\n");
  1181   1259       }else{
  1182         -      fprintf(p->out, ";\n");
         1260  +      raw_printf(p->out, ";\n");
  1183   1261       }    
  1184   1262       rc = sqlite3_step(pSelect);
  1185   1263     }
  1186   1264     rc = sqlite3_finalize(pSelect);
  1187   1265     if( rc!=SQLITE_OK ){
  1188         -    fprintf(p->out, "/**** ERROR: (%d) %s *****/\n", rc, sqlite3_errmsg(p->db));
         1266  +    utf8_printf(p->out, "/**** ERROR: (%d) %s *****/\n", rc,
         1267  +                sqlite3_errmsg(p->db));
  1189   1268       if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++;
  1190   1269     }
  1191   1270     return rc;
  1192   1271   }
  1193   1272   
  1194   1273   /*
  1195   1274   ** Allocate space and save off current error string.
................................................................................
  1216   1295     int iCur;
  1217   1296     int iHiwtr;
  1218   1297   
  1219   1298     if( pArg && pArg->out ){
  1220   1299       
  1221   1300       iHiwtr = iCur = -1;
  1222   1301       sqlite3_status(SQLITE_STATUS_MEMORY_USED, &iCur, &iHiwtr, bReset);
  1223         -    fprintf(pArg->out,
         1302  +    raw_printf(pArg->out,
  1224   1303               "Memory Used:                         %d (max %d) bytes\n",
  1225   1304               iCur, iHiwtr);
  1226   1305       iHiwtr = iCur = -1;
  1227   1306       sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, &iCur, &iHiwtr, bReset);
  1228         -    fprintf(pArg->out, "Number of Outstanding Allocations:   %d (max %d)\n",
         1307  +    raw_printf(pArg->out, "Number of Outstanding Allocations:   %d (max %d)\n",
  1229   1308               iCur, iHiwtr);
  1230   1309       if( pArg->shellFlgs & SHFLG_Pagecache ){
  1231   1310         iHiwtr = iCur = -1;
  1232   1311         sqlite3_status(SQLITE_STATUS_PAGECACHE_USED, &iCur, &iHiwtr, bReset);
  1233         -      fprintf(pArg->out,
         1312  +      raw_printf(pArg->out,
  1234   1313                 "Number of Pcache Pages Used:         %d (max %d) pages\n",
  1235   1314                 iCur, iHiwtr);
  1236   1315       }
  1237   1316       iHiwtr = iCur = -1;
  1238   1317       sqlite3_status(SQLITE_STATUS_PAGECACHE_OVERFLOW, &iCur, &iHiwtr, bReset);
  1239         -    fprintf(pArg->out,
         1318  +    raw_printf(pArg->out,
  1240   1319               "Number of Pcache Overflow Bytes:     %d (max %d) bytes\n",
  1241   1320               iCur, iHiwtr);
  1242   1321       if( pArg->shellFlgs & SHFLG_Scratch ){
  1243   1322         iHiwtr = iCur = -1;
  1244   1323         sqlite3_status(SQLITE_STATUS_SCRATCH_USED, &iCur, &iHiwtr, bReset);
  1245         -      fprintf(pArg->out, "Number of Scratch Allocations Used:  %d (max %d)\n",
         1324  +      raw_printf(pArg->out,
         1325  +              "Number of Scratch Allocations Used:  %d (max %d)\n",
  1246   1326                 iCur, iHiwtr);
  1247   1327       }
  1248   1328       iHiwtr = iCur = -1;
  1249   1329       sqlite3_status(SQLITE_STATUS_SCRATCH_OVERFLOW, &iCur, &iHiwtr, bReset);
  1250         -    fprintf(pArg->out,
         1330  +    raw_printf(pArg->out,
  1251   1331               "Number of Scratch Overflow Bytes:    %d (max %d) bytes\n",
  1252   1332               iCur, iHiwtr);
  1253   1333       iHiwtr = iCur = -1;
  1254   1334       sqlite3_status(SQLITE_STATUS_MALLOC_SIZE, &iCur, &iHiwtr, bReset);
  1255         -    fprintf(pArg->out, "Largest Allocation:                  %d bytes\n",
         1335  +    raw_printf(pArg->out, "Largest Allocation:                  %d bytes\n",
  1256   1336               iHiwtr);
  1257   1337       iHiwtr = iCur = -1;
  1258   1338       sqlite3_status(SQLITE_STATUS_PAGECACHE_SIZE, &iCur, &iHiwtr, bReset);
  1259         -    fprintf(pArg->out, "Largest Pcache Allocation:           %d bytes\n",
         1339  +    raw_printf(pArg->out, "Largest Pcache Allocation:           %d bytes\n",
  1260   1340               iHiwtr);
  1261   1341       iHiwtr = iCur = -1;
  1262   1342       sqlite3_status(SQLITE_STATUS_SCRATCH_SIZE, &iCur, &iHiwtr, bReset);
  1263         -    fprintf(pArg->out, "Largest Scratch Allocation:          %d bytes\n",
         1343  +    raw_printf(pArg->out, "Largest Scratch Allocation:          %d bytes\n",
  1264   1344               iHiwtr);
  1265   1345   #ifdef YYTRACKMAXSTACKDEPTH
  1266   1346       iHiwtr = iCur = -1;
  1267   1347       sqlite3_status(SQLITE_STATUS_PARSER_STACK, &iCur, &iHiwtr, bReset);
  1268         -    fprintf(pArg->out, "Deepest Parser Stack:                %d (max %d)\n",
         1348  +    raw_printf(pArg->out, "Deepest Parser Stack:                %d (max %d)\n",
  1269   1349               iCur, iHiwtr);
  1270   1350   #endif
  1271   1351     }
  1272   1352   
  1273   1353     if( pArg && pArg->out && db ){
  1274   1354       if( pArg->shellFlgs & SHFLG_Lookaside ){
  1275   1355         iHiwtr = iCur = -1;
  1276   1356         sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_USED,
  1277   1357                           &iCur, &iHiwtr, bReset);
  1278         -      fprintf(pArg->out, "Lookaside Slots Used:                %d (max %d)\n",
         1358  +      raw_printf(pArg->out,
         1359  +              "Lookaside Slots Used:                %d (max %d)\n",
  1279   1360                 iCur, iHiwtr);
  1280   1361         sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_HIT,
  1281   1362                           &iCur, &iHiwtr, bReset);
  1282         -      fprintf(pArg->out, "Successful lookaside attempts:       %d\n", iHiwtr);
         1363  +      raw_printf(pArg->out, "Successful lookaside attempts:       %d\n",
         1364  +              iHiwtr);
  1283   1365         sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE,
  1284   1366                           &iCur, &iHiwtr, bReset);
  1285         -      fprintf(pArg->out, "Lookaside failures due to size:      %d\n", iHiwtr);
         1367  +      raw_printf(pArg->out, "Lookaside failures due to size:      %d\n",
         1368  +              iHiwtr);
  1286   1369         sqlite3_db_status(db, SQLITE_DBSTATUS_LOOKASIDE_MISS_FULL,
  1287   1370                           &iCur, &iHiwtr, bReset);
  1288         -      fprintf(pArg->out, "Lookaside failures due to OOM:       %d\n", iHiwtr);
         1371  +      raw_printf(pArg->out, "Lookaside failures due to OOM:       %d\n",
         1372  +              iHiwtr);
  1289   1373       }
  1290   1374       iHiwtr = iCur = -1;
  1291   1375       sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_USED, &iCur, &iHiwtr, bReset);
  1292         -    fprintf(pArg->out, "Pager Heap Usage:                    %d bytes\n",iCur);
         1376  +    raw_printf(pArg->out, "Pager Heap Usage:                    %d bytes\n",
         1377  +            iCur);
  1293   1378       iHiwtr = iCur = -1;
  1294   1379       sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_HIT, &iCur, &iHiwtr, 1);
  1295         -    fprintf(pArg->out, "Page cache hits:                     %d\n", iCur);
         1380  +    raw_printf(pArg->out, "Page cache hits:                     %d\n", iCur);
  1296   1381       iHiwtr = iCur = -1;
  1297   1382       sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_MISS, &iCur, &iHiwtr, 1);
  1298         -    fprintf(pArg->out, "Page cache misses:                   %d\n", iCur); 
         1383  +    raw_printf(pArg->out, "Page cache misses:                   %d\n", iCur); 
  1299   1384       iHiwtr = iCur = -1;
  1300   1385       sqlite3_db_status(db, SQLITE_DBSTATUS_CACHE_WRITE, &iCur, &iHiwtr, 1);
  1301         -    fprintf(pArg->out, "Page cache writes:                   %d\n", iCur); 
         1386  +    raw_printf(pArg->out, "Page cache writes:                   %d\n", iCur); 
  1302   1387       iHiwtr = iCur = -1;
  1303   1388       sqlite3_db_status(db, SQLITE_DBSTATUS_SCHEMA_USED, &iCur, &iHiwtr, bReset);
  1304         -    fprintf(pArg->out, "Schema Heap Usage:                   %d bytes\n",iCur); 
         1389  +    raw_printf(pArg->out, "Schema Heap Usage:                   %d bytes\n",
         1390  +            iCur); 
  1305   1391       iHiwtr = iCur = -1;
  1306   1392       sqlite3_db_status(db, SQLITE_DBSTATUS_STMT_USED, &iCur, &iHiwtr, bReset);
  1307         -    fprintf(pArg->out, "Statement Heap/Lookaside Usage:      %d bytes\n",iCur); 
         1393  +    raw_printf(pArg->out, "Statement Heap/Lookaside Usage:      %d bytes\n",
         1394  +            iCur); 
  1308   1395     }
  1309   1396   
  1310   1397     if( pArg && pArg->out && db && pArg->pStmt ){
  1311   1398       iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP,
  1312   1399                                  bReset);
  1313         -    fprintf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
         1400  +    raw_printf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
  1314   1401       iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
  1315         -    fprintf(pArg->out, "Sort Operations:                     %d\n", iCur);
         1402  +    raw_printf(pArg->out, "Sort Operations:                     %d\n", iCur);
  1316   1403       iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX,bReset);
  1317         -    fprintf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);
         1404  +    raw_printf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);
  1318   1405       iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
  1319         -    fprintf(pArg->out, "Virtual Machine Steps:               %d\n", iCur);
         1406  +    raw_printf(pArg->out, "Virtual Machine Steps:               %d\n", iCur);
  1320   1407     }
         1408  +
         1409  +  /* Do not remove this machine readable comment: extra-stats-output-here */
  1321   1410   
  1322   1411     return 0;
  1323   1412   }
  1324   1413   
  1325   1414   /*
  1326   1415   ** Display scan stats.
  1327   1416   */
................................................................................
  1330   1419     ShellState *pArg                /* Pointer to ShellState */
  1331   1420   ){
  1332   1421   #ifndef SQLITE_ENABLE_STMT_SCANSTATUS
  1333   1422     UNUSED_PARAMETER(db);
  1334   1423     UNUSED_PARAMETER(pArg);
  1335   1424   #else
  1336   1425     int i, k, n, mx;
  1337         -  fprintf(pArg->out, "-------- scanstats --------\n");
         1426  +  raw_printf(pArg->out, "-------- scanstats --------\n");
  1338   1427     mx = 0;
  1339   1428     for(k=0; k<=mx; k++){
  1340   1429       double rEstLoop = 1.0;
  1341   1430       for(i=n=0; 1; i++){
  1342   1431         sqlite3_stmt *p = pArg->pStmt;
  1343   1432         sqlite3_int64 nLoop, nVisit;
  1344   1433         double rEst;
................................................................................
  1348   1437           break;
  1349   1438         }
  1350   1439         sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_SELECTID, (void*)&iSid);
  1351   1440         if( iSid>mx ) mx = iSid;
  1352   1441         if( iSid!=k ) continue;
  1353   1442         if( n==0 ){
  1354   1443           rEstLoop = (double)nLoop;
  1355         -        if( k>0 ) fprintf(pArg->out, "-------- subquery %d -------\n", k);
         1444  +        if( k>0 ) raw_printf(pArg->out, "-------- subquery %d -------\n", k);
  1356   1445         }
  1357   1446         n++;
  1358   1447         sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_NVISIT, (void*)&nVisit);
  1359   1448         sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EST, (void*)&rEst);
  1360   1449         sqlite3_stmt_scanstatus(p, i, SQLITE_SCANSTAT_EXPLAIN, (void*)&zExplain);
  1361         -      fprintf(pArg->out, "Loop %2d: %s\n", n, zExplain);
         1450  +      utf8_printf(pArg->out, "Loop %2d: %s\n", n, zExplain);
  1362   1451         rEstLoop *= rEst;
  1363         -      fprintf(pArg->out, 
         1452  +      raw_printf(pArg->out, 
  1364   1453             "         nLoop=%-8lld nRow=%-8lld estRow=%-8lld estRow/Loop=%-8g\n",
  1365   1454             nLoop, nVisit, (sqlite3_int64)(rEstLoop+0.5), rEst
  1366   1455         );
  1367   1456       }
  1368   1457     }
  1369         -  fprintf(pArg->out, "---------------------------\n");
         1458  +  raw_printf(pArg->out, "---------------------------\n");
  1370   1459   #endif
  1371   1460   }
  1372   1461   
  1373   1462   /*
  1374   1463   ** Parameter azArray points to a zero-terminated array of strings. zStr
  1375   1464   ** points to a single nul-terminated string. Return non-zero if zStr
  1376   1465   ** is equal, according to strcmp(), to any of the strings in the array.
................................................................................
  1413   1502                              "NextIfOpen", "PrevIfOpen", 0 };
  1414   1503     const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead",
  1415   1504                               "Rewind", 0 };
  1416   1505     const char *azGoto[] = { "Goto", 0 };
  1417   1506   
  1418   1507     /* Try to figure out if this is really an EXPLAIN statement. If this
  1419   1508     ** cannot be verified, return early.  */
         1509  +  if( sqlite3_column_count(pSql)!=8 ){
         1510  +    p->cMode = p->mode;
         1511  +    return;
         1512  +  }
  1420   1513     zSql = sqlite3_sql(pSql);
  1421   1514     if( zSql==0 ) return;
  1422   1515     for(z=zSql; *z==' ' || *z=='\t' || *z=='\n' || *z=='\f' || *z=='\r'; z++);
  1423         -  if( sqlite3_strnicmp(z, "explain", 7) ) return;
         1516  +  if( sqlite3_strnicmp(z, "explain", 7) ){
         1517  +    p->cMode = p->mode;
         1518  +    return;
         1519  +  }
  1424   1520   
  1425   1521     for(iOp=0; SQLITE_ROW==sqlite3_step(pSql); iOp++){
  1426   1522       int i;
  1427   1523       int iAddr = sqlite3_column_int(pSql, 0);
  1428   1524       const char *zOp = (const char*)sqlite3_column_text(pSql, 1);
  1429   1525   
  1430   1526       /* Set p2 to the P2 field of the current opcode. Then, assuming that
................................................................................
  1433   1529       ** the current instruction is part of a sub-program generated by an
  1434   1530       ** SQL trigger or foreign key.  */
  1435   1531       int p2 = sqlite3_column_int(pSql, 3);
  1436   1532       int p2op = (p2 + (iOp-iAddr));
  1437   1533   
  1438   1534       /* Grow the p->aiIndent array as required */
  1439   1535       if( iOp>=nAlloc ){
         1536  +      if( iOp==0 ){
         1537  +        /* Do further verfication that this is explain output.  Abort if
         1538  +        ** it is not */
         1539  +        static const char *explainCols[] = {
         1540  +           "addr", "opcode", "p1", "p2", "p3", "p4", "p5", "comment" };
         1541  +        int jj;
         1542  +        for(jj=0; jj<ArraySize(explainCols); jj++){
         1543  +          if( strcmp(sqlite3_column_name(pSql,jj),explainCols[jj])!=0 ){
         1544  +            p->cMode = p->mode;
         1545  +            sqlite3_reset(pSql);
         1546  +            return;
         1547  +          }
         1548  +        }
         1549  +      }
  1440   1550         nAlloc += 100;
  1441   1551         p->aiIndent = (int*)sqlite3_realloc64(p->aiIndent, nAlloc*sizeof(int));
  1442   1552         abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int));
  1443   1553       }
  1444   1554       abYield[iOp] = str_in_array(zOp, azYield);
  1445   1555       p->aiIndent[iOp] = 0;
  1446   1556       p->nIndent = iOp+1;
................................................................................
  1515   1625           pArg->pStmt = pStmt;
  1516   1626           pArg->cnt = 0;
  1517   1627         }
  1518   1628   
  1519   1629         /* echo the sql statement if echo on */
  1520   1630         if( pArg && pArg->echoOn ){
  1521   1631           const char *zStmtSql = sqlite3_sql(pStmt);
  1522         -        fprintf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql);
         1632  +        utf8_printf(pArg->out, "%s\n", zStmtSql ? zStmtSql : zSql);
  1523   1633         }
  1524   1634   
  1525   1635         /* Show the EXPLAIN QUERY PLAN if .eqp is on */
  1526   1636         if( pArg && pArg->autoEQP ){
  1527   1637           sqlite3_stmt *pExplain;
  1528   1638           char *zEQP = sqlite3_mprintf("EXPLAIN QUERY PLAN %s",
  1529   1639                                        sqlite3_sql(pStmt));
  1530   1640           rc = sqlite3_prepare_v2(db, zEQP, -1, &pExplain, 0);
  1531   1641           if( rc==SQLITE_OK ){
  1532   1642             while( sqlite3_step(pExplain)==SQLITE_ROW ){
  1533         -            fprintf(pArg->out,"--EQP-- %d,", sqlite3_column_int(pExplain, 0));
  1534         -            fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 1));
  1535         -            fprintf(pArg->out,"%d,", sqlite3_column_int(pExplain, 2));
  1536         -            fprintf(pArg->out,"%s\n", sqlite3_column_text(pExplain, 3));
         1643  +            raw_printf(pArg->out,"--EQP-- %d,",sqlite3_column_int(pExplain, 0));
         1644  +            raw_printf(pArg->out,"%d,", sqlite3_column_int(pExplain, 1));
         1645  +            raw_printf(pArg->out,"%d,", sqlite3_column_int(pExplain, 2));
         1646  +            utf8_printf(pArg->out,"%s\n", sqlite3_column_text(pExplain, 3));
  1537   1647             }
  1538   1648           }
  1539   1649           sqlite3_finalize(pExplain);
  1540   1650           sqlite3_free(zEQP);
  1541   1651         }
  1542   1652   
  1543         -      /* If the shell is currently in ".explain" mode, gather the extra
  1544         -      ** data required to add indents to the output.*/
  1545         -      if( pArg && pArg->mode==MODE_Explain ){
  1546         -        explain_data_prepare(pArg, pStmt);
         1653  +      if( pArg ){
         1654  +        pArg->cMode = pArg->mode;
         1655  +        if( pArg->autoExplain
         1656  +         && sqlite3_column_count(pStmt)==8
         1657  +         && sqlite3_strlike("%EXPLAIN%", sqlite3_sql(pStmt),0)==0
         1658  +        ){
         1659  +          pArg->cMode = MODE_Explain;
         1660  +        }
         1661  +      
         1662  +        /* If the shell is currently in ".explain" mode, gather the extra
         1663  +        ** data required to add indents to the output.*/
         1664  +        if( pArg->cMode==MODE_Explain ){
         1665  +          explain_data_prepare(pArg, pStmt);
         1666  +        }
  1547   1667         }
  1548   1668   
  1549   1669         /* perform the first step.  this will tell us if we
  1550   1670         ** have a result set or not and how wide it is.
  1551   1671         */
  1552   1672         rc = sqlite3_step(pStmt);
  1553   1673         /* if we have a result set... */
................................................................................
  1569   1689               for(i=0; i<nCol; i++){
  1570   1690                 azCols[i] = (char *)sqlite3_column_name(pStmt, i);
  1571   1691               }
  1572   1692               do{
  1573   1693                 /* extract the data and data types */
  1574   1694                 for(i=0; i<nCol; i++){
  1575   1695                   aiTypes[i] = x = sqlite3_column_type(pStmt, i);
  1576         -                if( x==SQLITE_BLOB && pArg && pArg->mode==MODE_Insert ){
         1696  +                if( x==SQLITE_BLOB && pArg && pArg->cMode==MODE_Insert ){
  1577   1697                     azVals[i] = "";
  1578   1698                   }else{
  1579   1699                     azVals[i] = (char*)sqlite3_column_text(pStmt, i);
  1580   1700                   }
  1581   1701                   if( !azVals[i] && (aiTypes[i]!=SQLITE_NULL) ){
  1582   1702                     rc = SQLITE_NOMEM;
  1583   1703                     break; /* from for */
................................................................................
  1657   1777     zTable = azArg[0];
  1658   1778     zType = azArg[1];
  1659   1779     zSql = azArg[2];
  1660   1780     
  1661   1781     if( strcmp(zTable, "sqlite_sequence")==0 ){
  1662   1782       zPrepStmt = "DELETE FROM sqlite_sequence;\n";
  1663   1783     }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){
  1664         -    fprintf(p->out, "ANALYZE sqlite_master;\n");
         1784  +    raw_printf(p->out, "ANALYZE sqlite_master;\n");
  1665   1785     }else if( strncmp(zTable, "sqlite_", 7)==0 ){
  1666   1786       return 0;
  1667   1787     }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){
  1668   1788       char *zIns;
  1669   1789       if( !p->writableSchema ){
  1670         -      fprintf(p->out, "PRAGMA writable_schema=ON;\n");
         1790  +      raw_printf(p->out, "PRAGMA writable_schema=ON;\n");
  1671   1791         p->writableSchema = 1;
  1672   1792       }
  1673   1793       zIns = sqlite3_mprintf(
  1674   1794          "INSERT INTO sqlite_master(type,name,tbl_name,rootpage,sql)"
  1675   1795          "VALUES('table','%q','%q',0,'%q');",
  1676   1796          zTable, zTable, zSql);
  1677         -    fprintf(p->out, "%s\n", zIns);
         1797  +    utf8_printf(p->out, "%s\n", zIns);
  1678   1798       sqlite3_free(zIns);
  1679   1799       return 0;
  1680   1800     }else{
  1681         -    fprintf(p->out, "%s;\n", zSql);
         1801  +    utf8_printf(p->out, "%s;\n", zSql);
  1682   1802     }
  1683   1803   
  1684   1804     if( strcmp(zType, "table")==0 ){
  1685   1805       sqlite3_stmt *pTableInfo = 0;
  1686   1806       char *zSelect = 0;
  1687   1807       char *zTableInfo = 0;
  1688   1808       char *zTmp = 0;
................................................................................
  1751   1871   ){
  1752   1872     int rc;
  1753   1873     char *zErr = 0;
  1754   1874     rc = sqlite3_exec(p->db, zQuery, dump_callback, p, &zErr);
  1755   1875     if( rc==SQLITE_CORRUPT ){
  1756   1876       char *zQ2;
  1757   1877       int len = strlen30(zQuery);
  1758         -    fprintf(p->out, "/****** CORRUPTION ERROR *******/\n");
         1878  +    raw_printf(p->out, "/****** CORRUPTION ERROR *******/\n");
  1759   1879       if( zErr ){
  1760         -      fprintf(p->out, "/****** %s ******/\n", zErr);
         1880  +      utf8_printf(p->out, "/****** %s ******/\n", zErr);
  1761   1881         sqlite3_free(zErr);
  1762   1882         zErr = 0;
  1763   1883       }
  1764   1884       zQ2 = malloc( len+100 );
  1765   1885       if( zQ2==0 ) return rc;
  1766   1886       sqlite3_snprintf(len+100, zQ2, "%s ORDER BY rowid DESC", zQuery);
  1767   1887       rc = sqlite3_exec(p->db, zQ2, dump_callback, p, &zErr);
  1768   1888       if( rc ){
  1769         -      fprintf(p->out, "/****** ERROR: %s ******/\n", zErr);
         1889  +      utf8_printf(p->out, "/****** ERROR: %s ******/\n", zErr);
  1770   1890       }else{
  1771   1891         rc = SQLITE_CORRUPT;
  1772   1892       }
  1773   1893       sqlite3_free(zErr);
  1774   1894       free(zQ2);
  1775   1895     }
  1776   1896     return rc;
................................................................................
  1779   1899   /*
  1780   1900   ** Text of a help message
  1781   1901   */
  1782   1902   static char zHelp[] =
  1783   1903     ".backup ?DB? FILE      Backup DB (default \"main\") to FILE\n"
  1784   1904     ".bail on|off           Stop after hitting an error.  Default OFF\n"
  1785   1905     ".binary on|off         Turn binary output on or off.  Default OFF\n"
         1906  +  ".changes on|off        Show number of rows changed by SQL\n"
  1786   1907     ".clone NEWDB           Clone data into NEWDB from the existing database\n"
  1787   1908     ".databases             List names and files of attached databases\n"
  1788   1909     ".dbinfo ?DB?           Show status information about the database\n"
  1789   1910     ".dump ?TABLE? ...      Dump the database in an SQL text format\n"
  1790   1911     "                         If TABLE specified, only dump tables matching\n"
  1791   1912     "                         LIKE pattern TABLE.\n"
  1792   1913     ".echo on|off           Turn command echo on or off\n"
  1793   1914     ".eqp on|off            Enable or disable automatic EXPLAIN QUERY PLAN\n"
  1794   1915     ".exit                  Exit this program\n"
  1795         -  ".explain ?on|off?      Turn output mode suitable for EXPLAIN on or off.\n"
  1796         -  "                         With no args, it turns EXPLAIN on.\n"
         1916  +  ".explain ?on|off|auto? Turn EXPLAIN output mode on or off or to automatic\n"
  1797   1917     ".fullschema            Show schema and the content of sqlite_stat tables\n"
  1798   1918     ".headers on|off        Turn display of headers on or off\n"
  1799   1919     ".help                  Show this message\n"
  1800   1920     ".import FILE TABLE     Import data from FILE into TABLE\n"
  1801   1921     ".indexes ?TABLE?       Show names of all indexes\n"
  1802   1922     "                         If TABLE specified, only show indexes for tables\n"
  1803   1923     "                         matching LIKE pattern TABLE.\n"
................................................................................
  1841   1961     ".system CMD ARGS...    Run CMD ARGS... in a system shell\n"
  1842   1962     ".tables ?TABLE?        List names of tables\n"
  1843   1963     "                         If TABLE specified, only list tables matching\n"
  1844   1964     "                         LIKE pattern TABLE.\n"
  1845   1965     ".timeout MS            Try opening locked tables for MS milliseconds\n"
  1846   1966     ".timer on|off          Turn SQL timer on or off\n"
  1847   1967     ".trace FILE|off        Output each SQL statement as it is run\n"
         1968  +  ".vfsinfo ?AUX?         Information about the top-level VFS\n"
         1969  +  ".vfslist               List all available VFSes\n"
  1848   1970     ".vfsname ?AUX?         Print the name of the VFS stack\n"
  1849   1971     ".width NUM1 NUM2 ...   Set column widths for \"column\" mode\n"
  1850   1972     "                         Negative values right-justify\n"
  1851   1973   ;
  1852   1974   
  1853   1975   /* Forward reference */
  1854   1976   static int process_input(ShellState *p, FILE *in);
................................................................................
  1925   2047       sqlite3_open(p->zDbFilename, &p->db);
  1926   2048       globalDb = p->db;
  1927   2049       if( p->db && sqlite3_errcode(p->db)==SQLITE_OK ){
  1928   2050         sqlite3_create_function(p->db, "shellstatic", 0, SQLITE_UTF8, 0,
  1929   2051             shellstaticFunc, 0, 0);
  1930   2052       }
  1931   2053       if( p->db==0 || SQLITE_OK!=sqlite3_errcode(p->db) ){
  1932         -      fprintf(stderr,"Error: unable to open database \"%s\": %s\n", 
         2054  +      utf8_printf(stderr,"Error: unable to open database \"%s\": %s\n", 
  1933   2055             p->zDbFilename, sqlite3_errmsg(p->db));
  1934   2056         if( keepAlive ) return;
  1935   2057         exit(1);