Changes On Branch branch-1.28
Not logged in

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

Changes In Branch branch-1.28 Excluding Merge-Ins

This is equivalent to a diff from 565ba734d2 to 4778b1d07a

2014-04-22
10:01
Make sure SQLITE_TESTCTRL_BYTEORDER is defined. Closed-Leaf check-in: 4778b1d07a user: jan.nijtmans tags: branch-1.28
09:43
[a138dc97fc]: Fix a potential segfault when the SSH_CONNECTION environment variable is defined. 9d2ae6342c: In the command-line shell, run set writable_schema before running the ".clone" command. check-in: d7889a2e83 user: jan.nijtmans tags: branch-1.28
2014-04-10
08:36
Add test-cases for function file_relative_name(), three of them failing without [565ba734d2] check-in: 1762a72f0e user: jan.nijtmans tags: trunk
2014-04-09
21:55
Cherry-pick [c5b86115de]: Update version of OpenSSL that is referred to in the makefiles. Cherry-pick [565ba734d2]: Fix "fossil extras" when a "extra" entry matches partly with current directory name (reported by j. van den hoff). check-in: c779b68904 user: jan.nijtmans tags: branch-1.28
20:35
Fix "fossil extras" when a "extra" entry matches partly with current directory name (reported by j. van den hoff) check-in: 565ba734d2 user: jan.nijtmans tags: trunk
2014-04-08
14:10
Fix handling of directory link in /dir page, which was broken by [b34fda9692] check-in: 4cb4fd1a8d user: jan.nijtmans tags: trunk

Changes to BUILD.txt.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

If you have VC++ installed on your system, then consider:

   cd win; nmake /f Makefile.msc

If you have trouble, or you want to do something fancy, just look at
Makefile.classic. There are 6 configuration options that are all well
commented. Instead of editing the Makefile.classic, consider copying
Makefile.classic to an alternative name such as "GNUMakefile",
"BSDMakefile", or "makefile" and editing the copy.


BUILDING OUTSIDE THE SOURCE TREE

An out of source build is pretty easy:







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

If you have VC++ installed on your system, then consider:

   cd win; nmake /f Makefile.msc

If you have trouble, or you want to do something fancy, just look at
Makefile.classic. There are 6 configuration options that are all well
commented. Instead of editing the Makefile.classic, consider copying 
Makefile.classic to an alternative name such as "GNUMakefile",
"BSDMakefile", or "makefile" and editing the copy.


BUILDING OUTSIDE THE SOURCE TREE

An out of source build is pretty easy:
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  real makefile in src/main.mk.  The src/main.mk makefile is
  automatically generated by a TCL script found at src/makemake.tcl.
  Do not edit src/main.mk directly.  Update src/makemake.tcl and
  then rerun it.

* The *.h header files are automatically generated using a program
  called "makeheaders".  Source code to the makeheaders program is
  found in src/makeheaders.c.  Documentation is found in
  src/makeheaders.html.

* Most *.c source files are preprocessed using a program called
  "translate".  The sources to translate are found in src/translate.c.
  A header comment in src/translate.c explains in detail what it does.

* The src/mkindex.c program generates some C code that implements
  static lookup tables.  See the header comment in the source code
  for details on what it does.

Additional information on the build process is available from
http://www.fossil-scm.org/fossil/doc/trunk/www/makefile.wiki







|












59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  real makefile in src/main.mk.  The src/main.mk makefile is
  automatically generated by a TCL script found at src/makemake.tcl.
  Do not edit src/main.mk directly.  Update src/makemake.tcl and
  then rerun it.

* The *.h header files are automatically generated using a program
  called "makeheaders".  Source code to the makeheaders program is
  found in src/makeheaders.c.  Documentation is found in 
  src/makeheaders.html.

* Most *.c source files are preprocessed using a program called
  "translate".  The sources to translate are found in src/translate.c.
  A header comment in src/translate.c explains in detail what it does.

* The src/mkindex.c program generates some C code that implements
  static lookup tables.  See the header comment in the source code
  for details on what it does.

Additional information on the build process is available from
http://www.fossil-scm.org/fossil/doc/trunk/www/makefile.wiki

Changes to COPYRIGHT-BSD2.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Copyright 2007 D. Richard Hipp. All rights reserved.

Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the
following conditions are met:

   1. Redistributions of source code must retain the above
      copyright notice, this list of conditions and the
      following disclaimer.

   2. Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the
      following disclaimer in the documentation and/or other
      materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation
are those of the authors and contributors and should not be interpreted
as representing official policies, either expressed or implied, of anybody
else.


|
|



|







|
|

|

|
|

|
|


|
|


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Copyright 2007 D. Richard Hipp. All rights reserved.

Redistribution and use in source and binary forms, with or 
without modification, are permitted provided that the 
following conditions are met:

   1. Redistributions of source code must retain the above
      copyright notice, this list of conditions and the 
      following disclaimer.

   2. Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the
      following disclaimer in the documentation and/or other
      materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS 
OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE 
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation 
are those of the authors and contributors and should not be interpreted 
as representing official policies, either expressed or implied, of anybody
else.

Changes to Makefile.Cygwin.in.

34
35
36
37
38
39
40
41
42
43



44
45
46
47
48
49
50
51

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = tclsh

LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H
INSTALLDIR =$(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@



SQLITE_CFLAGS += -DSQLITE_WIN32_NO_ANSI
SQLITE_CFLAGS += -DSQLITE_WIN32_MAX_PATH_BYTES=4096
SQLITE_CFLAGS += -DSQLITE_MAX_MMAP_SIZE=0x7fff0000

include $(SRCDIR)/main.mk

distclean: clean
	rm -f autoconfig.h config.log Makefile







|


>
>
>








34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

#### Tcl shell for use in running the fossil testsuite.  If you do not
#    care about testing the end result, this can be blank.
#
TCLSH = tclsh

LIB = @LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
TCC += @EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H -DFOSSIL_OMIT_LOAD_AVERAGE
INSTALLDIR =$(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@
FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@
FOSSIL_ENABLE_TCL_PRIVATE_STUBS = @FOSSIL_ENABLE_TCL_PRIVATE_STUBS@
SQLITE_CFLAGS += -DSQLITE_WIN32_NO_ANSI
SQLITE_CFLAGS += -DSQLITE_WIN32_MAX_PATH_BYTES=4096
SQLITE_CFLAGS += -DSQLITE_MAX_MMAP_SIZE=0x7fff0000

include $(SRCDIR)/main.mk

distclean: clean
	rm -f autoconfig.h config.log Makefile

Changes to Makefile.in.

38
39
40
41
42
43
44



45
46
47
48
49
#
TCLSH = tclsh

LIB =	@LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
TCC +=	@EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H
INSTALLDIR = $(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@




include $(SRCDIR)/main.mk

distclean: clean
	rm -f autoconfig.h config.log Makefile







>
>
>





38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#
TCLSH = tclsh

LIB =	@LDFLAGS@ @EXTRA_LDFLAGS@ @LIBS@
TCC +=	@EXTRA_CFLAGS@ @CPPFLAGS@ @CFLAGS@ -DHAVE_AUTOCONFIG_H
INSTALLDIR = $(DESTDIR)@prefix@/bin
USE_SYSTEM_SQLITE = @USE_SYSTEM_SQLITE@
FOSSIL_ENABLE_TCL = @FOSSIL_ENABLE_TCL@
FOSSIL_ENABLE_TCL_STUBS = @FOSSIL_ENABLE_TCL_STUBS@
FOSSIL_ENABLE_TCL_PRIVATE_STUBS = @FOSSIL_ENABLE_TCL_PRIVATE_STUBS@

include $(SRCDIR)/main.mk

distclean: clean
	rm -f autoconfig.h config.log Makefile

Changes to auto.def.

86
87
88
89
90
91
92











93
94
95
96
97
98
99
#    # no-op.  Markdown is now enabled by default.
#}

if {[opt-bool static]} {
    # XXX: This will not work on all systems.
    define-append EXTRA_LDFLAGS -static
}












set tclpath [opt-val with-tcl]
if {$tclpath ne ""} {
    set tclprivatestubs [opt-bool with-tcl-private-stubs]
    # Note parse-tclconfig-sh is in autosetup/local.tcl
    if {$tclpath eq "1"} {
        if {$tclprivatestubs} {







>
>
>
>
>
>
>
>
>
>
>







86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#    # no-op.  Markdown is now enabled by default.
#}

if {[opt-bool static]} {
    # XXX: This will not work on all systems.
    define-append EXTRA_LDFLAGS -static
}

# Check for zlib, using the given location if specified
set zlibpath [opt-val with-zlib]
if {$zlibpath ne ""} {
    cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
    define-append EXTRA_CFLAGS -I$zlibpath
    define-append EXTRA_LDFLAGS -L$zlibpath
}
if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
    user-error "zlib not found please install it or specify the location with --with-zlib"
}

set tclpath [opt-val with-tcl]
if {$tclpath ne ""} {
    set tclprivatestubs [opt-bool with-tcl-private-stubs]
    # Note parse-tclconfig-sh is in autosetup/local.tcl
    if {$tclpath eq "1"} {
        if {$tclprivatestubs} {
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
            }
        }
    } else {
        user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
    }
}

# Check for zlib, using the given location if specified
set zlibpath [opt-val with-zlib]
if {$zlibpath ne ""} {
    cc-with [list -cflags "-I$zlibpath -L$zlibpath"]
    define-append EXTRA_CFLAGS -I$zlibpath
    define-append EXTRA_LDFLAGS -L$zlibpath
}
if {![cc-check-includes zlib.h] || ![cc-check-function-in-lib inflateEnd z]} {
    user-error "zlib not found please install it or specify the location with --with-zlib"
}

if {[opt-bool lineedit]} {
    # Need readline-compatible line editing
    cc-with {-includes stdio.h} {
        if {[cc-check-includes readline/readline.h] && [cc-check-function-in-lib readline readline]} {
            msg-result "Using readline for line editing"
        } elseif {[cc-check-includes editline/readline.h] && [cc-check-function-in-lib readline edit]} {
            define-feature editline







<
<
<
<
<
<
<
<
<
<
<







225
226
227
228
229
230
231











232
233
234
235
236
237
238
            }
        }
    } else {
        user-error "OpenSSL not found. Consider --with-openssl=none to disable HTTPS support"
    }
}












if {[opt-bool lineedit]} {
    # Need readline-compatible line editing
    cc-with {-includes stdio.h} {
        if {[cc-check-includes readline/readline.h] && [cc-check-function-in-lib readline readline]} {
            msg-result "Using readline for line editing"
        } elseif {[cc-check-includes editline/readline.h] && [cc-check-function-in-lib readline edit]} {
            define-feature editline
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
    # Last resort, may be Windows
    if {[string match *mingw* [get-define host]]} {
        define-append LIBS -lwsock32
    }
}
cc-check-function-in-lib iconv iconv

# Check for getloadavg(), and if it doesn't exist, define FOSSIL_OMIT_LOAD_AVERAGE
if {![cc-check-functions getloadavg]} {
  define FOSSIL_OMIT_LOAD_AVERAGE 1
}

# Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars
if {![cc-check-functions getpassphrase]} {
    # Haiku needs this
    cc-check-function-in-lib getpass bsd
}
cc-check-function-in-lib dlopen dl

make-template Makefile.in
make-template Makefile.Cygwin.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}







<
<
<
<
<










247
248
249
250
251
252
253





254
255
256
257
258
259
260
261
262
263
    # Last resort, may be Windows
    if {[string match *mingw* [get-define host]]} {
        define-append LIBS -lwsock32
    }
}
cc-check-function-in-lib iconv iconv






# Check for getpassphrase() for Solaris 10 where getpass() truncates to 10 chars
if {![cc-check-functions getpassphrase]} {
    # Haiku needs this
    cc-check-function-in-lib getpass bsd
}
cc-check-function-in-lib dlopen dl

make-template Makefile.in
make-template Makefile.Cygwin.in
make-config-header autoconfig.h -auto {USE_* FOSSIL_*}

Changes to src/add.c.

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <assert.h>
#include <dirent.h>
#include "cygsup.h"

/*
** This routine returns the names of files in a working checkout that
** are created by Fossil itself, and hence should not be added, deleted,
** or merge, and should be omitted from "clean" and "extras" lists.
**
** Return the N-th name.  The first name has N==0.  When all names have
** been used, return 0.
*/
const char *fossil_reserved_name(int N, int omitRepo){
  /* Possible names of the local per-checkout database file and
  ** its associated journals







|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <assert.h>
#include <dirent.h>
#include "cygsup.h"

/*
** This routine returns the names of files in a working checkout that
** are created by Fossil itself, and hence should not be added, deleted,
** or merge, and should be omitted from "clean" and "extra" lists.
**
** Return the N-th name.  The first name has N==0.  When all names have
** been used, return 0.
*/
const char *fossil_reserved_name(int N, int omitRepo){
  /* Possible names of the local per-checkout database file and
  ** its associated journals
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
**
** The --ignore and --clean options are comma-separate lists of glob patterns
** for files to be excluded.  Example:  '*.o,*.obj,*.exe'  If the --ignore
** option does not appear on the command line then the "ignore-glob" setting
** is used.  If the --clean option does not appear on the command line then
** the "clean-glob" setting is used.
**
** If files are attempted to be added explicitly on the command line which
** match "ignore-glob", a confirmation is asked first. This can be prevented
** using the -f|--force option.
**
** The --case-sensitive option determines whether or not filenames should
** be treated case sensitive or not. If the option is not given, the default
** depends on the global setting, or the operating system default, if not set.
**
** Options:
**
**    --case-sensitive <BOOL> override case-sensitive setting
**    --dotfiles              include files beginning with a dot (".")   
**    -f|--force              Add files without prompting
**    --ignore <CSG>          ignore files matching patterns from the 
**                            comma separated list of glob patterns.
**    --clean <CSG>           also ignore files matching patterns from
**                            the comma separated list of glob patterns.
** 
** See also: addremove, rm
*/
void add_cmd(void){
  int i;                     /* Loop counter */
  int vid;                   /* Currently checked out version */
  int nRoot;                 /* Full path characters in g.zLocalRoot */
  const char *zCleanFlag;    /* The --clean option or clean-glob setting */
  const char *zIgnoreFlag;   /* The --ignore option or ignore-glob setting */
  Glob *pIgnore, *pClean;    /* Ignore everything matching the glob patterns */
  unsigned scanFlags = 0;    /* Flags passed to vfile_scan() */
  int forceFlag;

  zCleanFlag = find_option("clean",0,1);
  zIgnoreFlag = find_option("ignore",0,1);
  forceFlag = find_option("force","f",0)!=0;
  if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
  capture_case_sensitive_option();
  db_must_be_within_tree();
  if( zCleanFlag==0 ){
    zCleanFlag = db_get("clean-glob", 0);
  }
  if( zIgnoreFlag==0 ){







<
<
<
<








<















<



<







220
221
222
223
224
225
226




227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
242
243
244
245
246
247
248
249

250
251
252

253
254
255
256
257
258
259
**
** The --ignore and --clean options are comma-separate lists of glob patterns
** for files to be excluded.  Example:  '*.o,*.obj,*.exe'  If the --ignore
** option does not appear on the command line then the "ignore-glob" setting
** is used.  If the --clean option does not appear on the command line then
** the "clean-glob" setting is used.
**




** The --case-sensitive option determines whether or not filenames should
** be treated case sensitive or not. If the option is not given, the default
** depends on the global setting, or the operating system default, if not set.
**
** Options:
**
**    --case-sensitive <BOOL> override case-sensitive setting
**    --dotfiles              include files beginning with a dot (".")   

**    --ignore <CSG>          ignore files matching patterns from the 
**                            comma separated list of glob patterns.
**    --clean <CSG>           also ignore files matching patterns from
**                            the comma separated list of glob patterns.
** 
** See also: addremove, rm
*/
void add_cmd(void){
  int i;                     /* Loop counter */
  int vid;                   /* Currently checked out version */
  int nRoot;                 /* Full path characters in g.zLocalRoot */
  const char *zCleanFlag;    /* The --clean option or clean-glob setting */
  const char *zIgnoreFlag;   /* The --ignore option or ignore-glob setting */
  Glob *pIgnore, *pClean;    /* Ignore everything matching the glob patterns */
  unsigned scanFlags = 0;    /* Flags passed to vfile_scan() */


  zCleanFlag = find_option("clean",0,1);
  zIgnoreFlag = find_option("ignore",0,1);

  if( find_option("dotfiles",0,0)!=0 ) scanFlags |= SCAN_ALL;
  capture_case_sensitive_option();
  db_must_be_within_tree();
  if( zCleanFlag==0 ){
    zCleanFlag = db_get("clean-glob", 0);
  }
  if( zIgnoreFlag==0 ){
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
      vfile_scan(&fullName, nRoot-1, scanFlags, pClean, pIgnore);
    }else if( isDir==0 ){
      fossil_warning("not found: %s", zName);
    }else if( file_access(zName, R_OK) ){
      fossil_fatal("cannot open %s", zName);
    }else{
      char *zTreeName = &zName[nRoot];
      if( !forceFlag && glob_match(pIgnore, zTreeName) ){
        Blob ans;
        char cReply;
        char *prompt = mprintf("file \"%s\" matches \"ignore-glob\".  "
                               "Add it (a=all/y/N)? ", zTreeName);
        prompt_user(prompt, &ans);
        cReply = blob_str(&ans)[0];
        blob_reset(&ans);
        if( cReply=='a' || cReply=='A' ){
          forceFlag = 1;
        }else if( cReply!='y' && cReply!='Y' ){
          blob_reset(&fullName);
          continue;
        }
      }
      db_multi_exec(
         "INSERT OR IGNORE INTO sfile(x) VALUES(%Q)",
         zTreeName
      );
    }
    blob_reset(&fullName);
  }







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







285
286
287
288
289
290
291















292
293
294
295
296
297
298
      vfile_scan(&fullName, nRoot-1, scanFlags, pClean, pIgnore);
    }else if( isDir==0 ){
      fossil_warning("not found: %s", zName);
    }else if( file_access(zName, R_OK) ){
      fossil_fatal("cannot open %s", zName);
    }else{
      char *zTreeName = &zName[nRoot];















      db_multi_exec(
         "INSERT OR IGNORE INTO sfile(x) VALUES(%Q)",
         zTreeName
      );
    }
    blob_reset(&fullName);
  }
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
**
** Usage: %fossil addremove ?OPTIONS?
**
** Do all necessary "add" and "rm" commands to synchronize the repository
** with the content of the working checkout:
**
**  *  All files in the checkout but not in the repository (that is,
**     all files displayed using the "extras" command) are added as
**     if by the "add" command.
**
**  *  All files in the repository but missing from the checkout (that is,
**     all files that show as MISSING with the "status" command) are
**     removed as if by the "rm" command.
**
** The command does not "commit".  You must run the "commit" separately







|







437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
**
** Usage: %fossil addremove ?OPTIONS?
**
** Do all necessary "add" and "rm" commands to synchronize the repository
** with the content of the working checkout:
**
**  *  All files in the checkout but not in the repository (that is,
**     all files displayed using the "extra" command) are added as
**     if by the "add" command.
**
**  *  All files in the repository but missing from the checkout (that is,
**     all files that show as MISSING with the "status" command) are
**     removed as if by the "rm" command.
**
** The command does not "commit".  You must run the "commit" separately
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
    const char *zFrom = db_column_text(&q, 0);
    const char *zTo = db_column_text(&q, 1);
    mv_one_file(vid, zFrom, zTo);
  }
  db_finalize(&q);
  db_end_transaction(0);
}

/*
** Function for stash_apply to be able to restore a file and indicate
** newly ADDED state.
*/
int stash_add_files_in_sfile(int vid){
  return add_files_in_sfile(vid);
}







<
<
<
<
<
<
<
<
668
669
670
671
672
673
674








    const char *zFrom = db_column_text(&q, 0);
    const char *zTo = db_column_text(&q, 1);
    mv_one_file(vid, zFrom, zTo);
  }
  db_finalize(&q);
  db_end_transaction(0);
}








Changes to src/allrepo.c.

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    if( zValue[0] ){
      blob_appendf(pExtra, " --%s %s", zArg, zValue);
    }else{
      blob_appendf(pExtra, " --%s \"\"", zArg);
    }
  }
}
static void collect_argv(Blob *pExtra, int iStart){
  int i;
  for(i=iStart; i<g.argc; i++){
    blob_appendf(pExtra, " %s", g.argv[i]);
  }
}


/*
** COMMAND: all
**
** Usage: %fossil all (changes|clean|extras|ignore|list|ls|pull|push|rebuild|sync)
**
** The ~/.fossil file records the location of all repositories for a
** user.  This command performs certain operations on all repositories
** that can be useful before or after a period of disconnected operation.
**
** On Win32 systems, the file is named "_fossil" and is located in
** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%.







<
<
<
<
<
<





|







63
64
65
66
67
68
69






70
71
72
73
74
75
76
77
78
79
80
81
82
    if( zValue[0] ){
      blob_appendf(pExtra, " --%s %s", zArg, zValue);
    }else{
      blob_appendf(pExtra, " --%s \"\"", zArg);
    }
  }
}








/*
** COMMAND: all
**
** Usage: %fossil all (changes|clean|extra|ignore|list|ls|pull|push|rebuild|sync)
**
** The ~/.fossil file records the location of all repositories for a
** user.  This command performs certain operations on all repositories
** that can be useful before or after a period of disconnected operation.
**
** On Win32 systems, the file is named "_fossil" and is located in
** %LOCALAPPDATA%, %APPDATA% or %HOMEPATH%.
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
**               effects cannot be undone.  Use of the --dry-run option to
**               carefully review the local checkouts to be operated upon
**               and the --whatif option to carefully review the files to
**               be deleted beforehand is highly recommended.  The command
**               line options supported by the clean command itself, if any
**               are present, are passed along verbatim.
**
**    extras     Shows "extra" files from all local checkouts.  The command
**               line options supported by the extra command itself, if any
**               are present, are passed along verbatim.
**
**    ignore     Arguments are repositories that should be ignored by
**               subsequent clean, extras, list, pull, push, rebuild, and
**               sync operations.  The -c|--ckout option causes the listed
**               local checkouts to be ignored instead.
**
**    list | ls  Display the location of all repositories.  The -c|--ckout
**               option causes all local checkouts to be listed instead.
**
**    pull       Run a "pull" operation on all repositories.  Only the
**               --verbose option is supported.
**
**    push       Run a "push" on all repositories.  Only the --verbose
**               option is supported.
**
**    rebuild    Rebuild on all repositories.  The command line options
**               supported by the rebuild command itself, if any are
**               present, are passed along verbatim.  The --force and
**               --randomize options are not supported.
**
**    sync       Run a "sync" on all repositories.  Only the --verbose
**               option is supported.
**
**    setting    Run the "setting", "set", or "unset" commands on all
**    set        repositories.  These command are particularly useful in
**    unset      conjunection with the "max-loadavg" setting which cannot
**               otherwise be set globally.
**
** Repositories are automatically added to the set of known repositories
** when one of the following commands are run against the repository:
** clone, info, pull, push, or sync.  Even previously ignored repositories
** are added back to the list of repositories by these commands.
**
** Options:
**   --showfile     Show the repository or checkout being operated upon.
**   --dontstop     Continue with other repositories even after an error.
**   --dry-run      If given, display instead of run actions.
*/
void all_cmd(void){
  int n;
  Stmt q;
  const char *zCmd;
  char *zSyscmd;
  char *zFossil;
  char *zQFilename;
  Blob extra;
  int useCheckouts = 0;
  int quiet = 0;
  int dryRunFlag = 0;
  int showFile = find_option("showfile",0,0)!=0;
  int stopOnError = find_option("dontstop",0,0)==0;
  int rc;
  int nToDel = 0;
  
  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }

  if( g.argc<3 ){
    usage("changes|clean|extras|ignore|list|ls|pull|push|rebuild|sync");
  }
  n = strlen(g.argv[2]);
  db_open_config(1);
  blob_zero(&extra);
  zCmd = g.argv[2];
  if( !login_is_nobody() ) blob_appendf(&extra, " -U %s", g.zLogin);
  if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
    zCmd = "list";
    useCheckouts = find_option("ckout","c",0)!=0;
  }else if( strncmp(zCmd, "clean", n)==0 ){
    zCmd = "clean --chdir";
    collect_argument(&extra, "allckouts",0);
    collect_argument_value(&extra, "case-sensitive");
    collect_argument_value(&extra, "clean");
    collect_argument(&extra, "dirsonly",0);
    collect_argument(&extra, "dotfiles",0);
    collect_argument(&extra, "emptydirs",0);
    collect_argument(&extra, "force","f");
    collect_argument_value(&extra, "ignore");
    collect_argument_value(&extra, "keep");
    collect_argument(&extra, "temp",0);
    collect_argument(&extra, "verbose","v");
    collect_argument(&extra, "whatif",0);
    useCheckouts = 1;
  }else if( strncmp(zCmd, "extras", n)==0 ){
    if( showFile ){
      zCmd = "extras --chdir";
    }else{
      zCmd = "extras --header --chdir";
    }
    collect_argument(&extra, "abs-paths",0);
    collect_argument_value(&extra, "case-sensitive");
    collect_argument(&extra, "dotfiles",0);
    collect_argument_value(&extra, "ignore");
    collect_argument(&extra, "rel-paths",0);
    useCheckouts = 1;
    stopOnError = 0;







|




|




















<
<
<
<
<






<














<










|





|


















|
<
|
<
<
<







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123





124
125
126
127
128
129

130
131
132
133
134
135
136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179

180



181
182
183
184
185
186
187
**               effects cannot be undone.  Use of the --dry-run option to
**               carefully review the local checkouts to be operated upon
**               and the --whatif option to carefully review the files to
**               be deleted beforehand is highly recommended.  The command
**               line options supported by the clean command itself, if any
**               are present, are passed along verbatim.
**
**    extra      Shows extra files from all local checkouts.  The command
**               line options supported by the extra command itself, if any
**               are present, are passed along verbatim.
**
**    ignore     Arguments are repositories that should be ignored by
**               subsequent clean, extra, list, pull, push, rebuild, and
**               sync operations.  The -c|--ckout option causes the listed
**               local checkouts to be ignored instead.
**
**    list | ls  Display the location of all repositories.  The -c|--ckout
**               option causes all local checkouts to be listed instead.
**
**    pull       Run a "pull" operation on all repositories.  Only the
**               --verbose option is supported.
**
**    push       Run a "push" on all repositories.  Only the --verbose
**               option is supported.
**
**    rebuild    Rebuild on all repositories.  The command line options
**               supported by the rebuild command itself, if any are
**               present, are passed along verbatim.  The --force and
**               --randomize options are not supported.
**
**    sync       Run a "sync" on all repositories.  Only the --verbose
**               option is supported.
**





** Repositories are automatically added to the set of known repositories
** when one of the following commands are run against the repository:
** clone, info, pull, push, or sync.  Even previously ignored repositories
** are added back to the list of repositories by these commands.
**
** Options:

**   --dontstop     Continue with other repositories even after an error.
**   --dry-run      If given, display instead of run actions.
*/
void all_cmd(void){
  int n;
  Stmt q;
  const char *zCmd;
  char *zSyscmd;
  char *zFossil;
  char *zQFilename;
  Blob extra;
  int useCheckouts = 0;
  int quiet = 0;
  int dryRunFlag = 0;

  int stopOnError = find_option("dontstop",0,0)==0;
  int rc;
  int nToDel = 0;
  
  dryRunFlag = find_option("dry-run","n",0)!=0;
  if( !dryRunFlag ){
    dryRunFlag = find_option("test",0,0)!=0; /* deprecated */
  }

  if( g.argc<3 ){
    usage("changes|clean|extra|ignore|list|ls|pull|push|rebuild|sync");
  }
  n = strlen(g.argv[2]);
  db_open_config(1);
  blob_zero(&extra);
  zCmd = g.argv[2];
  if( g.zLogin ) blob_appendf(&extra, " -U %s", g.zLogin);
  if( strncmp(zCmd, "list", n)==0 || strncmp(zCmd,"ls",n)==0 ){
    zCmd = "list";
    useCheckouts = find_option("ckout","c",0)!=0;
  }else if( strncmp(zCmd, "clean", n)==0 ){
    zCmd = "clean --chdir";
    collect_argument(&extra, "allckouts",0);
    collect_argument_value(&extra, "case-sensitive");
    collect_argument_value(&extra, "clean");
    collect_argument(&extra, "dirsonly",0);
    collect_argument(&extra, "dotfiles",0);
    collect_argument(&extra, "emptydirs",0);
    collect_argument(&extra, "force","f");
    collect_argument_value(&extra, "ignore");
    collect_argument_value(&extra, "keep");
    collect_argument(&extra, "temp",0);
    collect_argument(&extra, "verbose","v");
    collect_argument(&extra, "whatif",0);
    useCheckouts = 1;
  }else if( strncmp(zCmd, "extra", n)==0 ){

    zCmd = "extra --chdir";



    collect_argument(&extra, "abs-paths",0);
    collect_argument_value(&extra, "case-sensitive");
    collect_argument(&extra, "dotfiles",0);
    collect_argument_value(&extra, "ignore");
    collect_argument(&extra, "rel-paths",0);
    useCheckouts = 1;
    stopOnError = 0;
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
    collect_argument(&extra, "noverify",0);
    collect_argument_value(&extra, "pagesize");
    collect_argument(&extra, "vacuum",0);
    collect_argument(&extra, "deanalyze",0);
    collect_argument(&extra, "analyze",0);
    collect_argument(&extra, "wal",0);
    collect_argument(&extra, "stats",0);
  }else if( strncmp(zCmd, "setting", n)==0 ){
    zCmd = "setting -R";
    collect_argv(&extra, 3);
  }else if( strncmp(zCmd, "unset", n)==0 ){
    zCmd = "unset -R";
    collect_argv(&extra, 3);
  }else if( strncmp(zCmd, "sync", n)==0 ){
    zCmd = "sync -autourl -R";
    collect_argument(&extra, "verbose","v");
  }else if( strncmp(zCmd, "test-integrity", n)==0 ){
    collect_argument(&extra, "parse", 0);
    zCmd = "test-integrity";
  }else if( strncmp(zCmd, "test-orphans", n)==0 ){







<
<
<
<
<
<







199
200
201
202
203
204
205






206
207
208
209
210
211
212
    collect_argument(&extra, "noverify",0);
    collect_argument_value(&extra, "pagesize");
    collect_argument(&extra, "vacuum",0);
    collect_argument(&extra, "deanalyze",0);
    collect_argument(&extra, "analyze",0);
    collect_argument(&extra, "wal",0);
    collect_argument(&extra, "stats",0);






  }else if( strncmp(zCmd, "sync", n)==0 ){
    zCmd = "sync -autourl -R";
    collect_argument(&extra, "verbose","v");
  }else if( strncmp(zCmd, "test-integrity", n)==0 ){
    collect_argument(&extra, "parse", 0);
    zCmd = "test-integrity";
  }else if( strncmp(zCmd, "test-orphans", n)==0 ){
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
      }
      fossil_free(zSql);
    }
    db_end_transaction(0);
    return;
  }else{
    fossil_fatal("\"all\" subcommand should be one of: "
                 "changes clean extras ignore list ls push pull rebuild sync");
  }
  verify_all_options();
  zFossil = quoteFilename(g.nameOfExe);
  if( useCheckouts ){
    db_prepare(&q,
       "SELECT DISTINCT substr(name, 7), name COLLATE nocase"
       "  FROM global_config"







|







235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
      }
      fossil_free(zSql);
    }
    db_end_transaction(0);
    return;
  }else{
    fossil_fatal("\"all\" subcommand should be one of: "
                 "changes clean extra ignore list ls push pull rebuild sync");
  }
  verify_all_options();
  zFossil = quoteFilename(g.nameOfExe);
  if( useCheckouts ){
    db_prepare(&q,
       "SELECT DISTINCT substr(name, 7), name COLLATE nocase"
       "  FROM global_config"
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
      db_multi_exec("INSERT INTO todel VALUES(%Q)", db_column_text(&q, 1));
      nToDel++;
      continue;
    }
    if( zCmd[0]=='l' ){
      fossil_print("%s\n", zFilename);
      continue;
    }else if( showFile ){
      fossil_print("%s: %s\n", useCheckouts ? "checkout" : "repository",
                   zFilename);
    }
    zQFilename = quoteFilename(zFilename);
    zSyscmd = mprintf("%s %s %s%s",
                      zFossil, zCmd, zQFilename, blob_str(&extra));
    if( !quiet || dryRunFlag ){
      fossil_print("%s\n", zSyscmd);
      fflush(stdout);







<
<
<







268
269
270
271
272
273
274



275
276
277
278
279
280
281
      db_multi_exec("INSERT INTO todel VALUES(%Q)", db_column_text(&q, 1));
      nToDel++;
      continue;
    }
    if( zCmd[0]=='l' ){
      fossil_print("%s\n", zFilename);
      continue;



    }
    zQFilename = quoteFilename(zFilename);
    zSyscmd = mprintf("%s %s %s%s",
                      zFossil, zCmd, zQFilename, blob_str(&extra));
    if( !quiet || dryRunFlag ){
      fossil_print("%s\n", zSyscmd);
      fflush(stdout);

Changes to src/attach.c.

256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
      zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" 
                        " WHERE tagname GLOB 'tkt-%q*'", zTkt);
      if( zTkt==0 ) fossil_redirect_home();
    }
    zTarget = zTkt;
    zTargetType = mprintf("Ticket <a href=\"%s/tktview/%s\">%S</a>",
                          g.zTop, zTkt, zTkt);
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){







|







256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
    if( g.perm.ApndTkt==0 || g.perm.Attach==0 ) login_needed();
    if( !db_exists("SELECT 1 FROM tag WHERE tagname='tkt-%q'", zTkt) ){
      zTkt = db_text(0, "SELECT substr(tagname,5) FROM tag" 
                        " WHERE tagname GLOB 'tkt-%q*'", zTkt);
      if( zTkt==0 ) fossil_redirect_home();
    }
    zTarget = zTkt;
    zTargetType = mprintf("Ticket <a href=\"%s/tktview/%S\">%S</a>",
                          g.zTop, zTkt, zTkt);
  }
  if( zFrom==0 ) zFrom = mprintf("%s/home", g.zTop);
  if( P("cancel") ){
    cgi_redirect(zFrom);
  }
  if( P("ok") && szContent>0 && (goodCaptcha = captcha_is_correct()) ){
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    n = strlen(zComment);
    while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
    if( n>0 ){
      blob_appendf(&manifest, "C %#F\n", n, zComment);
    }
    zDate = date_in_standard_format("now");
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", login_name());
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    attach_put(&manifest, rid, needModerator);
    assert( blob_is_reset(&manifest) );
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }







|







307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
    n = strlen(zComment);
    while( n>0 && fossil_isspace(zComment[n-1]) ){ n--; }
    if( n>0 ){
      blob_appendf(&manifest, "C %#F\n", n, zComment);
    }
    zDate = date_in_standard_format("now");
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    attach_put(&manifest, rid, needModerator);
    assert( blob_is_reset(&manifest) );
    db_end_transaction(0);
    cgi_redirect(zFrom);
  }
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
      if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
    }
    zFile += n;
    if( zFile[0]==0 ) zFile = "unknown";
    blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
    zDate = date_in_standard_format("now");
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", login_name());
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest);
    manifest_crosslink(rid, &manifest, MC_NONE);
    db_end_transaction(0);
    @ <p>The attachment below has been deleted.</p>
  }







|







425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
      if( zFile[i]=='/' || zFile[i]=='\\' ) n = i;
    }
    zFile += n;
    if( zFile[0]==0 ) zFile = "unknown";
    blob_appendf(&manifest, "A %F %F\n", zFile, zTarget);
    zDate = date_in_standard_format("now");
    blob_appendf(&manifest, "D %s\n", zDate);
    blob_appendf(&manifest, "U %F\n", g.zLogin ? g.zLogin : "nobody");
    md5sum_blob(&manifest, &cksum);
    blob_appendf(&manifest, "Z %b\n", &cksum);
    rid = content_put(&manifest);
    manifest_crosslink(rid, &manifest, MC_NONE);
    db_end_transaction(0);
    @ <p>The attachment below has been deleted.</p>
  }
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
      return;
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
  style_header("Attachment Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%s", zUuid);

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))







|







461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
      return;
    }
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
  style_header("Attachment Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
    @ (%d(rid))
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
      output_text_with_line_numbers(z, zLn);
    }else{
      @ <pre>
      @ %h(z)
      @ </pre>
    }
  }else if( strncmp(zMime, "image/", 6)==0 ){
    @ <img src="%R/raw/%s(zSrc)?m=%s(zMime)"></img>
    style_submenu_element("Image", "Image", "%R/raw/%s?m=%s", zSrc, zMime);
  }else{
    int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
    @ <i>(file is %d(sz) bytes of binary data)</i>
  }
  @ </blockquote>
  manifest_destroy(pAttach);
  blob_reset(&attach);







|
|







529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
      output_text_with_line_numbers(z, zLn);
    }else{
      @ <pre>
      @ %h(z)
      @ </pre>
    }
  }else if( strncmp(zMime, "image/", 6)==0 ){
    @ <img src="%R/raw/%S(zSrc)?m=%s(zMime)"></img>
    style_submenu_element("Image", "Image", "%R/raw/%S?m=%s", zSrc, zMime);
  }else{
    int sz = db_int(0, "SELECT size FROM blob WHERE rid=%d", ridSrc);
    @ <i>(file is %d(sz) bytes of binary data)</i>
  }
  @ </blockquote>
  manifest_destroy(pAttach);
  blob_reset(&attach);

Changes to src/bag.c.

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#define bag_hash(i)  (i*101)

/*
** Change the size of the hash table on a bag so that
** it contains N slots
**
** Completely reconstruct the hash table from scratch.  Deleted
** entries (indicated by a -1) are removed.  When finished, it
** should be the case that p->cnt==p->used.
*/
static void bag_resize(Bag *p, int newSize){
  int i;
  Bag old;
  int nDel = 0;   /* Number of deleted entries */
  int nLive = 0;  /* Number of live entries */







|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#define bag_hash(i)  (i*101)

/*
** Change the size of the hash table on a bag so that
** it contains N slots
**
** Completely reconstruct the hash table from scratch.  Deleted
** entries (indicated by a -1) are removed.  When finished, it 
** should be the case that p->cnt==p->used.
*/
static void bag_resize(Bag *p, int newSize){
  int i;
  Bag old;
  int nDel = 0;   /* Number of deleted entries */
  int nLive = 0;  /* Number of live entries */

Changes to src/bisect.c.

368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    }else{
      g.argv[1] = "update";
      g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid);
      g.argc = 3;
      g.fNoSync = 1;
      update_cmd();
    }

    if( strncmp(zDisplay,"chart",m)==0 ){
      bisect_chart(1);
    }else if( strncmp(zDisplay, "log", m)==0 ){
      bisect_chart(0);
    }else if( strncmp(zDisplay, "status", m)==0 ){
      bisect_list(1);
    }







|







368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    }else{
      g.argv[1] = "update";
      g.argv[2] = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pMid->rid);
      g.argc = 3;
      g.fNoSync = 1;
      update_cmd();
    }
   
    if( strncmp(zDisplay,"chart",m)==0 ){
      bisect_chart(1);
    }else if( strncmp(zDisplay, "log", m)==0 ){
      bisect_chart(0);
    }else if( strncmp(zDisplay, "status", m)==0 ){
      bisect_list(1);
    }

Changes to src/blob.c.

222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
** Initialize a blob to a nul-terminated string.
** Any prior data in the blob is discarded.
*/
void blob_set(Blob *pBlob, const char *zStr){
  blob_init(pBlob, zStr, -1);
}

/*
** Initialize a blob to a nul-terminated string obtained from fossil_malloc().
** The blob will take responsibility for freeing the string.
*/
void blob_set_dynamic(Blob *pBlob, char *zStr){
  blob_init(pBlob, zStr, -1);
  pBlob->xRealloc = blobReallocMalloc;
}

/*
** Initialize a blob to an empty string.
*/
void blob_zero(Blob *pBlob){
  static const char zEmpty[] = "";
  assert_blob_is_reset(pBlob);
  pBlob->nUsed = 0;







<
<
<
<
<
<
<
<
<







222
223
224
225
226
227
228









229
230
231
232
233
234
235
** Initialize a blob to a nul-terminated string.
** Any prior data in the blob is discarded.
*/
void blob_set(Blob *pBlob, const char *zStr){
  blob_init(pBlob, zStr, -1);
}










/*
** Initialize a blob to an empty string.
*/
void blob_zero(Blob *pBlob){
  static const char zEmpty[] = "";
  assert_blob_is_reset(pBlob);
  pBlob->nUsed = 0;
784
785
786
787
788
789
790



791




























792
793
794
795
796
797

798
799
800
801
802
803
804
#if defined(_WIN32)
    if( fossil_utf8_to_console(blob_buffer(pBlob), nWrote, 0) >= 0 ){
      return nWrote;
    }
#endif
    fwrite(blob_buffer(pBlob), 1, nWrote, stdout);
  }else{



    file_mkfolder(zFilename, 1);




























    out = fossil_fopen(zFilename, "wb");
    if( out==0 ){
      fossil_fatal_recursive("unable to open file \"%s\" for writing",
                             zFilename);
      return 0;
    }

    blob_is_init(pBlob);
    nWrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out);
    fclose(out);
    if( nWrote!=blob_size(pBlob) ){
      fossil_fatal_recursive("short write: %d of %d bytes to %s", nWrote,
         blob_size(pBlob), zFilename);
    }







>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|

|
<


>







775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816

817
818
819
820
821
822
823
824
825
826
#if defined(_WIN32)
    if( fossil_utf8_to_console(blob_buffer(pBlob), nWrote, 0) >= 0 ){
      return nWrote;
    }
#endif
    fwrite(blob_buffer(pBlob), 1, nWrote, stdout);
  }else{
    int i, nName;
    char *zName, zBuf[1000];

    nName = strlen(zFilename);
    if( nName>=sizeof(zBuf) ){
      zName = mprintf("%s", zFilename);
    }else{
      zName = zBuf;
      memcpy(zName, zFilename, nName+1);
    }
    nName = file_simplify_name(zName, nName, 0);
    for(i=1; i<nName; i++){
      if( zName[i]=='/' ){
        zName[i] = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
        /*
        ** On Windows, local path looks like: C:/develop/project/file.txt
        ** The if stops us from trying to create a directory of a drive letter
        ** C: in this example.
        */
        if( !(i==2 && zName[1]==':') ){
#endif
          if( file_mkdir(zName, 1) && file_isdir(zName)!=1 ){
            fossil_fatal_recursive("unable to create directory %s", zName);
            return 0;
          }
#if defined(_WIN32) || defined(__CYGWIN__)
        }
#endif
        zName[i] = '/';
      }
    }
    out = fossil_fopen(zName, "wb");
    if( out==0 ){
      fossil_fatal_recursive("unable to open file \"%s\" for writing", zName);

      return 0;
    }
    if( zName!=zBuf ) free(zName);
    blob_is_init(pBlob);
    nWrote = fwrite(blob_buffer(pBlob), 1, blob_size(pBlob), out);
    fclose(out);
    if( nWrote!=blob_size(pBlob) ){
      fossil_fatal_recursive("short write: %d of %d bytes to %s", nWrote,
         blob_size(pBlob), zFilename);
    }
1071
1072
1073
1074
1075
1076
1077

1078

1079
1080
1081
1082
1083
1084
1085

1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101

1102


1103
1104
1105
1106
1107
1108
1109
1110
1111
** is either no BOM at all or an (le/be) UTF-16 BOM, a conversion to UTF-8 is
** done.  If useMbcs is false and there is no BOM, the input string is assumed
** to be UTF-8 already, so no conversion is done.
*/
void blob_to_utf8_no_bom(Blob *pBlob, int useMbcs){
  char *zUtf8;
  int bomSize = 0;

  int bomReverse = 0;

  if( starts_with_utf8_bom(pBlob, &bomSize) ){
    struct Blob temp;
    zUtf8 = blob_str(pBlob) + bomSize;
    blob_zero(&temp);
    blob_append(&temp, zUtf8, -1);
    blob_swap(pBlob, &temp);
    blob_reset(&temp);

  }else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){
    zUtf8 = blob_buffer(pBlob);
    if( bomReverse ){
      /* Found BOM, but with reversed bytes */
      unsigned int i = blob_size(pBlob);
      while( i>0 ){
        /* swap bytes of unicode representation */
        char zTemp = zUtf8[--i];
        zUtf8[i] = zUtf8[i-1];
        zUtf8[--i] = zTemp;
      }
    }
    /* Make sure the blob contains two terminating 0-bytes */
    blob_append(pBlob, "", 1);
    zUtf8 = blob_str(pBlob) + bomSize;
    zUtf8 = fossil_unicode_to_utf8(zUtf8);

    blob_set_dynamic(pBlob, zUtf8);


#if defined(_WIN32)
  }else if( useMbcs ){
    zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
    blob_reset(pBlob);
    blob_append(pBlob, zUtf8, -1);
    fossil_mbcs_free(zUtf8);
#endif /* _WIN32 */
  }
}







>

>







>
















>
|
>
>









1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
** is either no BOM at all or an (le/be) UTF-16 BOM, a conversion to UTF-8 is
** done.  If useMbcs is false and there is no BOM, the input string is assumed
** to be UTF-8 already, so no conversion is done.
*/
void blob_to_utf8_no_bom(Blob *pBlob, int useMbcs){
  char *zUtf8;
  int bomSize = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
  int bomReverse = 0;
#endif
  if( starts_with_utf8_bom(pBlob, &bomSize) ){
    struct Blob temp;
    zUtf8 = blob_str(pBlob) + bomSize;
    blob_zero(&temp);
    blob_append(&temp, zUtf8, -1);
    blob_swap(pBlob, &temp);
    blob_reset(&temp);
#if defined(_WIN32) || defined(__CYGWIN__)
  }else if( starts_with_utf16_bom(pBlob, &bomSize, &bomReverse) ){
    zUtf8 = blob_buffer(pBlob);
    if( bomReverse ){
      /* Found BOM, but with reversed bytes */
      unsigned int i = blob_size(pBlob);
      while( i>0 ){
        /* swap bytes of unicode representation */
        char zTemp = zUtf8[--i];
        zUtf8[i] = zUtf8[i-1];
        zUtf8[--i] = zTemp;
      }
    }
    /* Make sure the blob contains two terminating 0-bytes */
    blob_append(pBlob, "", 1);
    zUtf8 = blob_str(pBlob) + bomSize;
    zUtf8 = fossil_unicode_to_utf8(zUtf8);
    blob_zero(pBlob);
    blob_append(pBlob, zUtf8, -1);
    fossil_unicode_free(zUtf8);
#endif /* _WIN32 ||  __CYGWIN__ */
#if defined(_WIN32)
  }else if( useMbcs ){
    zUtf8 = fossil_mbcs_to_utf8(blob_str(pBlob));
    blob_reset(pBlob);
    blob_append(pBlob, zUtf8, -1);
    fossil_mbcs_free(zUtf8);
#endif /* _WIN32 */
  }
}

Changes to src/branch.c.

132
133
134
135
136
137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
      rootid);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTag = db_column_text(&q, 0);
    blob_appendf(&branch, "T -%F *\n", zTag);
  }
  db_finalize(&q);

  blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
  md5sum_blob(&branch, &mcksum);
  blob_appendf(&branch, "Z %b\n", &mcksum);
  if( !noSign && clearsign(&branch, &branch) ){
    Blob ans;
    char cReply;

    prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y'){
      db_end_transaction(1);
      fossil_exit(1);
    }
  }







|





>







132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
      rootid);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTag = db_column_text(&q, 0);
    blob_appendf(&branch, "T -%F *\n", zTag);
  }
  db_finalize(&q);

  blob_appendf(&branch, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
  md5sum_blob(&branch, &mcksum);
  blob_appendf(&branch, "Z %b\n", &mcksum);
  if( !noSign && clearsign(&branch, &branch) ){
    Blob ans;
    char cReply;
    blob_zero(&ans);
    prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y'){
      db_end_transaction(1);
      fossil_exit(1);
    }
  }
365
366
367
368
369
370
371






372
373
374
375
376
377
378
      @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li>
    }
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);






  style_footer();
}

/*
** This routine is called while for each check-in that is rendered by
** the timeline of a "brlist" page.  Add some additional hyperlinks
** to the end of the line.







>
>
>
>
>
>







366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
      @ <li>%z(href("%R/timeline?r=%T",zBr))%h(zBr)</a></li>
    }
  }
  if( cnt ){
    @ </ul>
  }
  db_finalize(&q);
  @ <script  type="text/JavaScript">
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}

/*
** This routine is called while for each check-in that is rendered by
** the timeline of a "brlist" page.  Add some additional hyperlinks
** to the end of the line.
414
415
416
417
418
419
420






421
422
    "%s AND blob.rid IN (SELECT rid FROM tagxref"
    "                     WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www(), TAG_BRANCH
  );
  www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
  db_finalize(&q);






  style_footer();
}







>
>
>
>
>
>


421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
    "%s AND blob.rid IN (SELECT rid FROM tagxref"
    "                     WHERE tagtype>0 AND tagid=%d AND srcid!=0)"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www(), TAG_BRANCH
  );
  www_print_timeline(&q, 0, 0, 0, brtimeline_extra);
  db_finalize(&q);
  @ <script  type="text/JavaScript">
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}

Changes to src/browse.c.

83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  int i, j;
  char *zSep = "";

  for(i=0; zPath[i]; i=j){
    for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    if( zPath[j] && g.perm.Hyperlink ){
      if( zCI ){
        char *zLink = href("%R/%s?name=%#T%s&ci=%s", zURI, j, zPath, zREx, zCI);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }else{
        char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }







|







83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
  int i, j;
  char *zSep = "";

  for(i=0; zPath[i]; i=j){
    for(j=i; zPath[j] && zPath[j]!='/'; j++){}
    if( zPath[j] && g.perm.Hyperlink ){
      if( zCI ){
        char *zLink = href("%R/%s?ci=%S&name=%#T%s", zURI, zCI, j, zPath,zREx);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }else{
        char *zLink = href("%R/%s?name=%#T%s", zURI, j, zPath, zREx);
        blob_appendf(pOut, "%s%z%#h</a>",
                     zSep, zLink, j-i, &zPath[i]);
      }
177
178
179
180
181
182
183



184
185
186
187
188
189
190
191
192
193
194
195
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip ){
    style_submenu_element("Tip", "Tip", "%s",
                          url_render(&sURI, "ci", "tip", 0, 0));
  }
  if( zCI ){



    @ <h2>Files of check-in [%z(href("vinfo?name=%s",zUuid))%.10s(zUuid)</a>]
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?ci=%s&name=%T", zUuid, zPrefix);
    if( nD==0 ){
      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
                            zUuid);
    }
  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }







>
>
>
|

|

|







177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
  if( linkTip ){
    style_submenu_element("Tip", "Tip", "%s",
                          url_render(&sURI, "ci", "tip", 0, 0));
  }
  if( zCI ){
    char zShort[20];
    memcpy(zShort, zUuid, 10);
    zShort[10] = 0;
    @ <h2>Files of check-in [%z(href("vinfo?name=%T",zUuid))%s(zShort)</a>]
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?ci=%S&name=%T", zUuid, zPrefix);
    if( nD==0 ){
      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
                            zUuid);
    }
  }else{
    @ <h2>The union of all files from all check-ins
    @ %s(blob_str(&dirname))</h2>
    zSubdirLink = mprintf("%R/dir?name=%T", zPrefix);
  }
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
  char *zREx = "";         /* Extra parameters for path hyperlinks */
  ReCompiled *pRE = 0;     /* Compiled regular expression */
  FileTreeNode *p;         /* One line of the tree */
  FileTree sTree;          /* The complete tree of files */
  HQuery sURI;             /* Hyperlink */
  int startExpanded;       /* True to start out with the tree expanded */
  int showDirOnly;         /* Show directories only.  Omit files */
  int nDir = 0;            /* Number of directories. Used for ID attributes */
  char *zProjectName = db_get("project-name", 0);

  if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
  memset(&sTree, 0, sizeof(sTree));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }







<







416
417
418
419
420
421
422

423
424
425
426
427
428
429
  char *zREx = "";         /* Extra parameters for path hyperlinks */
  ReCompiled *pRE = 0;     /* Compiled regular expression */
  FileTreeNode *p;         /* One line of the tree */
  FileTree sTree;          /* The complete tree of files */
  HQuery sURI;             /* Hyperlink */
  int startExpanded;       /* True to start out with the tree expanded */
  int showDirOnly;         /* Show directories only.  Omit files */

  char *zProjectName = db_get("project-name", 0);

  if( strcmp(PD("type",""),"flat")==0 ){ page_dir(); return; }
  memset(&sTree, 0, sizeof(sTree));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  while( nD>1 && zD[nD-2]=='/' ){ zD[(--nD)-1] = 0; }
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
      blob_appendf(&dirname, "matching \"%s\"", zRE);
    }
  }
  if( zCI ){
    style_submenu_element("All", "All", "%s",
                          url_render(&sURI, "ci", 0, 0, 0));
    if( nD==0 && !showDirOnly ){
      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%s",
                            zUuid);
    }
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }







|







487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
      blob_appendf(&dirname, "matching \"%s\"", zRE);
    }
  }
  if( zCI ){
    style_submenu_element("All", "All", "%s",
                          url_render(&sURI, "ci", 0, 0, 0));
    if( nD==0 && !showDirOnly ){
      style_submenu_element("File Ages", "File Ages", "%R/fileage?name=%S",
                            zUuid);
    }
  }
  if( linkTrunk ){
    style_submenu_element("Trunk", "Trunk", "%s",
                          url_render(&sURI, "ci", "trunk", 0, 0));
  }
512
513
514
515
516
517
518


519
520
521
522
523
524
525
526
    Stmt ins, q;
    ManifestFile *pFile;

    db_multi_exec(
        "CREATE TEMP TABLE filelist("
        "   x TEXT PRIMARY KEY COLLATE nocase,"
        "   uuid TEXT"


        ") WITHOUT ROWID;"
    );
    db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
    manifest_file_rewind(pM);
    while( (pFile = manifest_file_next(pM,0))!=0 ){
      if( nD>0
       && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
           || pFile->zName[nD-1]!='/')







>
>
|







514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
    Stmt ins, q;
    ManifestFile *pFile;

    db_multi_exec(
        "CREATE TEMP TABLE filelist("
        "   x TEXT PRIMARY KEY COLLATE nocase,"
        "   uuid TEXT"
        ")%s;",
        /* Can be removed as soon as SQLite 3.8.2 is sufficiently wide-spread */
        sqlite3_libversion_number()>=3008002 ? " WITHOUT ROWID" : ""
    );
    db_prepare(&ins, "INSERT OR IGNORE INTO filelist VALUES(:f,:u)");
    manifest_file_rewind(pM);
    while( (pFile = manifest_file_next(pM,0))!=0 ){
      if( nD>0
       && (fossil_strncmp(pFile->zName, zD, nD-1)!=0
           || pFile->zName[nD-1]!='/')
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606


607

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
  }

  if( zCI ){
    @ <h2>%d(nFile) %s(zObjType) of check-in
    if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
      @ "%h(zCI)"
    }
    @ [%z(href("vinfo?name=%s",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))</h2>
  }else{
    int n = db_int(0, "SELECT count(*) FROM plink");
    @ <h2>%d(nFile) %s(zObjType) from all %d(n) check-ins
    @ %s(blob_str(&dirname))</h2>
  }


  /* Generate tree of lists.
  **
  ** Each file and directory is a list element: <li>.  Files have class=file
  ** and if the filename as the suffix "xyz" the file also has class=file-xyz.
  ** Directories have class=dir.  The directory specfied by the name= query
  ** parameter (or the top-level directory if there is no name= query parameter)
  ** adds class=subdir.
  **
  ** The <li> element for directories also contains a sublist <ul>
  ** for the contents of that directory.
  */
  @ <div class="filetree"><ul>
  if( nD ){
    @ <li class="dir last">
  }else{
    @ <li class="dir subdir last">
  }
  @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a>
  @ <ul>
  for(p=sTree.pFirst, nDir=0; p; p=p->pNext){
    const char *zLastClass = p->isLast ? " last" : "";
    if( p->isDir ){
      const char *zSubdirClass = p->nFullName==nD-1 ? " subdir" : "";


      @ <li class="dir%s(zSubdirClass)%s(zLastClass)">

      @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a>
      if( startExpanded || p->nFullName<=nD ){
        @ <ul id="dir%d(nDir)">
      }else{
        @ <ul id="dir%d(nDir)" class="collapsed">
      }
      nDir++;
    }else if( !showDirOnly ){
      const char *zFileClass = fileext_class(p->zName);
      char *zLink;
      if( zCI ){
        zLink = href("%R/artifact/%s",p->zUuid);
      }else{
        zLink = href("%R/finfo?name=%T",p->zFullName);
      }
      @ <li class="%z(zFileClass)%s(zLastClass)">%z(zLink)%h(p->zName)</a>
    }
    if( p->isLast ){
      int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0);
      while( nClose-- > 0 ){
        @ </ul>
      }
    }
  }
  @ </ul>
  @ </ul></div>
  @ <script>(function(){
  @ function isExpanded(ul){
  @   return ul.className=='';
  @ }
  @
  @ function toggleDir(ul, useInitValue){
  @   if( !useInitValue ){
  @     expandMap[ul.id] = !isExpanded(ul);
  @     history.replaceState(expandMap, '');
  @   }
  @   ul.className = expandMap[ul.id] ? '' : 'collapsed';
  @ }
  @
  @ function toggleAll(tree, useInitValue){
  @   var lists = tree.querySelectorAll('.subdir > ul > li ul');
  @   if( !useInitValue ){
  @     var expand = true;  /* Default action: make all sublists visible */
  @     for( var i=0; lists[i]; i++ ){
  @       if( isExpanded(lists[i]) ){
  @         expand = false; /* Any already visible - make them all hidden */
  @         break;
  @       }
  @     }
  @     expandMap = {'*': expand};
  @     history.replaceState(expandMap, '');
  @   }
  @   var className = expandMap['*'] ? '' : 'collapsed';
  @   for( var i=0; lists[i]; i++ ){
  @     lists[i].className = className;
  @   }
  @ }
  @
  @ function checkState(){
  @   expandMap = history.state || {};
  @   if( '*' in expandMap ) toggleAll(outer_ul, true);
  @   for( var id in expandMap ){
  @     if( id!=='*' ) toggleDir(gebi(id), true);
  @   }
  @ }
  @
  @ function belowSubdir(node){
  @   do{
  @     node = node.parentNode;
  @     if( node==subdir ) return true;
  @   } while( node && node!=outer_ul );
  @   return false;
  @ }
  @
  @ var history = window.history || {};
  @ if( !history.replaceState ) history.replaceState = function(){};
  @ var outer_ul = document.querySelector('.filetree > ul');
  @ var subdir = outer_ul.querySelector('.subdir');
  @ var expandMap = {};
  @ checkState();
  @ outer_ul.onclick = function(e){
  @   e = e || window.event;
  @   var a = e.target || e.srcElement;
  @   if( a.nodeName!='A' ) return true;
  @   if( a.parentNode==subdir ){
  @     toggleAll(outer_ul);
  @     return false;
  @   }
  @   if( !belowSubdir(a) ) return true;
  @   var ul = a.nextSibling;
  @   while( ul && ul.nodeName!='UL' ) ul = ul.nextSibling;
  @   if( !ul ) return true; /* This is a file link, not a directory */
  @   toggleDir(ul);
  @   return false;
  @ }
  @ }())</script>
  style_footer();

  /* We could free memory used by sTree here if we needed to.  But
  ** the process is about to exit, so doing so would not really accomplish







|




















|

|



|
<

|
>
>
|
>


|

|

<

<


|



|











|
|


<
<
<
<
<
<
<
<
|

<
|
|
|
|
<
|

<
<
<
<
<
<

<
<
<
<
<
|
|


|
<
<
<
<
<
<
<
<
<
<


<
<
|
<
|





|



|







573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607

608
609
610
611
612
613
614
615
616
617
618
619

620

621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642








643
644

645
646
647
648

649
650






651





652
653
654
655
656










657
658


659

660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
  }

  if( zCI ){
    @ <h2>%d(nFile) %s(zObjType) of check-in
    if( sqlite3_strnicmp(zCI, zUuid, (int)strlen(zCI))!=0 ){
      @ "%h(zCI)"
    }
    @ [%z(href("vinfo?name=%T",zUuid))%S(zUuid)</a>] %s(blob_str(&dirname))</h2>
  }else{
    int n = db_int(0, "SELECT count(*) FROM plink");
    @ <h2>%d(nFile) %s(zObjType) from all %d(n) check-ins
    @ %s(blob_str(&dirname))</h2>
  }


  /* Generate tree of lists.
  **
  ** Each file and directory is a list element: <li>.  Files have class=file
  ** and if the filename as the suffix "xyz" the file also has class=file-xyz.
  ** Directories have class=dir.  The directory specfied by the name= query
  ** parameter (or the top-level directory if there is no name= query parameter)
  ** adds class=subdir.
  **
  ** The <li> element for directories also contains a sublist <ul>
  ** for the contents of that directory.
  */
  @ <div class="filetree"><ul>
  if( nD ){
    @ <li class="dir">
  }else{
    @ <li class="dir subdir">
  }
  @ %z(href("%s",url_render(&sURI,"name",0,0,0)))%h(zProjectName)</a>
  @ <ul>
  for(p=sTree.pFirst; p; p=p->pNext){

    if( p->isDir ){
      if( p->nFullName==nD-1 ){
        @ <li class="dir subdir">
      }else{
        @ <li class="dir">
      }
      @ %z(href("%s",url_render(&sURI,"name",p->zFullName,0,0)))%h(p->zName)</a>
      if( startExpanded || p->nFullName<=nD ){
        @ <ul>
      }else{
        @ <ul style='display:none;'>
      }

    }else if( !showDirOnly ){

      char *zLink;
      if( zCI ){
        zLink = href("%R/artifact/%S",p->zUuid);
      }else{
        zLink = href("%R/finfo?name=%T",p->zFullName);
      }
      @ <li class="%z(fileext_class(p->zName))">%z(zLink)%h(p->zName)</a>
    }
    if( p->isLast ){
      int nClose = p->iLevel - (p->pNext ? p->pNext->iLevel : 0);
      while( nClose-- > 0 ){
        @ </ul>
      }
    }
  }
  @ </ul>
  @ </ul></div>
  @ <script>(function(){
  @ function style(elem, prop){
  @   return window.getComputedStyle(elem).getPropertyValue(prop);
  @ }
  @








  @ function toggleAll(tree){
  @   var lists = tree.querySelectorAll('.subdir > ul > li ul');

  @   var display = 'block';  /* Default action: make all sublists visible */
  @   for( var i=0; lists[i]; i++ ){
  @     if( style(lists[i], 'display')!='none'){
  @       display = 'none'; /* Any already visible - make them all hidden */

  @       break;
  @     }






  @   }





  @   for( var i=0; lists[i]; i++ ){
  @     lists[i].style.display = display;
  @   }
  @ }
  @ 










  @ var outer_ul = document.querySelector('.filetree > ul');
  @ var subdir = outer_ul.querySelector('.subdir');


  @ outer_ul.onclick = function( e ){

  @   var a = e.target;
  @   if( a.nodeName!='A' ) return true;
  @   if( a.parentNode==subdir ){
  @     toggleAll(outer_ul);
  @     return false;
  @   }
  @   if( !subdir.contains(a) ) return true;
  @   var ul = a.nextSibling;
  @   while( ul && ul.nodeName!='UL' ) ul = ul.nextSibling;
  @   if( !ul ) return true; /* This is a file link, not a directory */
  @   ul.style.display = style(ul, 'display')=='none' ? 'block' : 'none';
  @   return false;
  @ }
  @ }())</script>
  style_footer();

  /* We could free memory used by sTree here if we needed to.  But
  ** the process is about to exit, so doing so would not really accomplish
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
  return zClass;
}

/*
** Look at all file containing in the version "vid".  Construct a
** temporary table named "fileage" that contains the file-id for each
** files, the pathname, the check-in where the file was added, and the
** mtime on that checkin. If zGlob and *zGlob then only files matching
** the given glob are computed.
*/
int compute_fileage(int vid, char const * zGlob){
  Manifest *pManifest;
  ManifestFile *pFile;
  int nFile = 0;
  double vmtime;
  Stmt ins;
  Stmt q1, q2, q3;
  Stmt upd;
  if(zGlob && !*zGlob) zGlob = NULL;
  db_multi_exec(
    /*"DROP TABLE IF EXISTS temp.fileage;"*/
    "CREATE TEMP TABLE fileage("
    "  fid INTEGER,"
    "  mid INTEGER,"
    "  mtime DATETIME,"
    "  pathname TEXT"
    ");"
    "CREATE INDEX fileage_fid ON fileage(fid);"
  );
  pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0);
  if( pManifest==0 ) return 1;
  manifest_file_rewind(pManifest);
  db_prepare(&ins,
     "INSERT INTO temp.fileage(fid, pathname)"
     "  SELECT rid, :path FROM blob WHERE uuid=:uuid"
  );
  while( (pFile = manifest_file_next(pManifest, 0))!=0 ){
    if(zGlob && !strglob(zGlob, pFile->zName)) continue;
    db_bind_text(&ins, ":uuid", pFile->zUuid);
    db_bind_text(&ins, ":path", pFile->zName);
    db_step(&ins);
    db_reset(&ins);
    nFile++;
  }
  db_finalize(&ins);







|
<

|







<


















<







697
698
699
700
701
702
703
704

705
706
707
708
709
710
711
712
713

714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731

732
733
734
735
736
737
738
  return zClass;
}

/*
** Look at all file containing in the version "vid".  Construct a
** temporary table named "fileage" that contains the file-id for each
** files, the pathname, the check-in where the file was added, and the
** mtime on that checkin.

*/
int compute_fileage(int vid){
  Manifest *pManifest;
  ManifestFile *pFile;
  int nFile = 0;
  double vmtime;
  Stmt ins;
  Stmt q1, q2, q3;
  Stmt upd;

  db_multi_exec(
    /*"DROP TABLE IF EXISTS temp.fileage;"*/
    "CREATE TEMP TABLE fileage("
    "  fid INTEGER,"
    "  mid INTEGER,"
    "  mtime DATETIME,"
    "  pathname TEXT"
    ");"
    "CREATE INDEX fileage_fid ON fileage(fid);"
  );
  pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0);
  if( pManifest==0 ) return 1;
  manifest_file_rewind(pManifest);
  db_prepare(&ins,
     "INSERT INTO temp.fileage(fid, pathname)"
     "  SELECT rid, :path FROM blob WHERE uuid=:uuid"
  );
  while( (pFile = manifest_file_next(pManifest, 0))!=0 ){

    db_bind_text(&ins, ":uuid", pFile->zUuid);
    db_bind_text(&ins, ":path", pFile->zName);
    db_step(&ins);
    db_reset(&ins);
    nFile++;
  }
  db_finalize(&ins);
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823

824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
  return 0;
}

/*
** WEBPAGE:  fileage
**
** Parameters:
**   name=VERSION   Selects the checkin version (default=tip).
**   glob=STRING    Only shows files matching this glob pattern
**                  (e.g. *.c or *.txt).
*/
void fileage_page(void){
  int rid;
  const char *zName;
  char *zBaseTime;
  char const * zGlob;
  Stmt q;
  double baseTime;
  int lastMid = -1;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  style_submenu_element("Tree-View", "Tree-View", "%R/tree?ci=%T", zName);
  style_header("File Ages", zName);
  zGlob = P("glob");
  compute_fileage(rid,zGlob);
  baseTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
  zBaseTime = db_text("","SELECT datetime(%.20g%s)", baseTime, timeline_utc());
  @ <h2>File Ages For Check-in
  @ %z(href("%R/info?name=%T",zName))%h(zName)</a></h2>
  @
  @ <p>The times given are relative to
  @ %z(href("%R/timeline?c=%T",zBaseTime))%s(zBaseTime)</a>, which is the







|
<
<





<



>










<
|







772
773
774
775
776
777
778
779


780
781
782
783
784

785
786
787
788
789
790
791
792
793
794
795
796
797
798

799
800
801
802
803
804
805
806
  return 0;
}

/*
** WEBPAGE:  fileage
**
** Parameters:
**   name=VERSION


*/
void fileage_page(void){
  int rid;
  const char *zName;
  char *zBaseTime;

  Stmt q;
  double baseTime;
  int lastMid = -1;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  if( zName==0 ) zName = "tip";
  rid = symbolic_name_to_rid(zName, "ci");
  if( rid==0 ){
    fossil_fatal("not a valid check-in: %s", zName);
  }
  style_submenu_element("Tree-View", "Tree-View", "%R/tree?ci=%T", zName);
  style_header("File Ages", zName);

  compute_fileage(rid);
  baseTime = db_double(0.0, "SELECT mtime FROM event WHERE objid=%d", rid);
  zBaseTime = db_text("","SELECT datetime(%.20g%s)", baseTime, timeline_utc());
  @ <h2>File Ages For Check-in
  @ %z(href("%R/info?name=%T",zName))%h(zName)</a></h2>
  @
  @ <p>The times given are relative to
  @ %z(href("%R/timeline?c=%T",zBaseTime))%s(zBaseTime)</a>, which is the
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
      }
    }else{
      zAge[0] = 0;
    }
    @ <tr>
    @ <td>%s(zAge)
    @ <td width="25">
    @ <td>%z(href("%R/artifact/%s?ln", zFUuid))%h(db_column_text(&q, 3))</a>
    @ </tr>
    @
  }
  @ <tr><td colspan=3><hr></tr>
  @ </table>
  db_finalize(&q);
  style_footer();
}







|








834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
      }
    }else{
      zAge[0] = 0;
    }
    @ <tr>
    @ <td>%s(zAge)
    @ <td width="25">
    @ <td>%z(href("%R/artifact/%S?ln", zFUuid))%h(db_column_text(&q, 3))</a>
    @ </tr>
    @
  }
  @ <tr><td colspan=3><hr></tr>
  @ </table>
  db_finalize(&q);
  style_footer();
}

Changes to src/captcha.c.

90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
      }
      z[k++] = ' ';
      z[k++] = ' ';
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;
}
#endif /* CAPTCHA==1 */


#if CAPTCHA==2
static const char *const azFont2[] = {
 /* 0 */







|







90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
      }
      z[k++] = ' ';
      z[k++] = ' ';
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;     
}
#endif /* CAPTCHA==1 */


#if CAPTCHA==2
static const char *const azFont2[] = {
 /* 0 */
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
 /* 7 */
 " ____ ",
 "|__  |",
 "  / / ",
 " /_/  ",

 /* 8 */
 " ___ ",
 "( _ )",
 "/ _ \\",
 "\\___/",

 /* 9 */
 " ___ ",
 "/ _ \\",







|







146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
 /* 7 */
 " ____ ",
 "|__  |",
 "  / / ",
 " /_/  ",

 /* 8 */
 " ___ ", 
 "( _ )",
 "/ _ \\",
 "\\___/",

 /* 9 */
 " ___ ",
 "/ _ \\",
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
      for(m=0; zChar[m]; m++){
        z[k++] = zChar[m];
      }
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;
}
#endif /* CAPTCHA==2 */

#if CAPTCHA==3
static const char *const azFont3[] = {
  /* 0 */
  "  ___  ",
  " / _ \\ ",
  "| | | |",
  "| | | |",
  "| |_| |",
  " \\___/ ",

  /* 1 */
  " __ ",
  "/_ |",
  " | |",
  " | |",
  " | |",
  " |_|",

  /* 2 */
  " ___  ",
  "|__ \\ ",
  "   ) |",
  "  / / ",
  " / /_ ",
  "|____|",

  /* 3 */
  " ____  ",
  "|___ \\ ",
  "  __) |",
  " |__ < ",
  " ___) |",
  "|____/ ",

  /* 4 */
  " _  _   ",
  "| || |  ",
  "| || |_ ",
  "|__   _|",
  "   | |  ",
  "   |_|  ",

  /* 5 */
  " _____ ",
  "| ____|",
  "| |__  ",
  "|___ \\ ",
  " ___) |",
  "|____/ ",

  /* 6 */
  "   __  ",
  "  / /  ",
  " / /_  ",
  "| '_ \\ ",
  "| (_) |",
  " \\___/ ",

  /* 7 */
  " ______ ",
  "|____  |",
  "    / / ",
  "   / /  ",
  "  / /   ",
  " /_/    ",

  /* 8 */
  "  ___  ",
  " / _ \\ ",
  "| (_) |",
  " > _ < ",
  "| (_) |",
  " \\___/ ",

  /* 9 */
  "  ___  ",
  " / _ \\ ",
  "| (_) |",
  " \\__, |",
  "   / / ",
  "  /_/  ",

  /* A */
  "          ",
  "    /\\    ",
  "   /  \\   ",
  "  / /\\ \\  ",
  " / ____ \\ ",
  "/_/    \\_\\",

  /* B */
  " ____  ",
  "|  _ \\ ",
  "| |_) |",
  "|  _ < ",
  "| |_) |",
  "|____/ ",

  /* C */
  "  _____ ",
  " / ____|",
  "| |     ",
  "| |     ",
  "| |____ ",
  " \\_____|",

  /* D */
  " _____  ",
  "|  __ \\ ",
  "| |  | |",
  "| |  | |",
  "| |__| |",
  "|_____/ ",

  /* E */
  " ______ ",
  "|  ____|",
  "| |__   ",
  "|  __|  ",
  "| |____ ",
  "|______|",

  /* F */
  " ______ ",
  "|  ____|",
  "| |__   ",
  "|  __|  ",
  "| |     ",
  "|_|     ",







|












|







|







|







|







|







|







|







|







|







|







|







|







|







|







|







216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
      for(m=0; zChar[m]; m++){
        z[k++] = zChar[m];
      }
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;     
}
#endif /* CAPTCHA==2 */

#if CAPTCHA==3
static const char *const azFont3[] = {
  /* 0 */
  "  ___  ",
  " / _ \\ ",
  "| | | |",
  "| | | |",
  "| |_| |",
  " \\___/ ",
                                                                                                               
  /* 1 */
  " __ ",
  "/_ |",
  " | |",
  " | |",
  " | |",
  " |_|",
                                                                                                               
  /* 2 */
  " ___  ",
  "|__ \\ ",
  "   ) |",
  "  / / ",
  " / /_ ",
  "|____|",
                                                                                                               
  /* 3 */
  " ____  ",
  "|___ \\ ",
  "  __) |",
  " |__ < ",
  " ___) |",
  "|____/ ",
                                                                                                               
  /* 4 */
  " _  _   ",
  "| || |  ",
  "| || |_ ",
  "|__   _|",
  "   | |  ",
  "   |_|  ",
                                                                                                               
  /* 5 */
  " _____ ",
  "| ____|",
  "| |__  ",
  "|___ \\ ",
  " ___) |",
  "|____/ ",
                                                                                                               
  /* 6 */
  "   __  ",
  "  / /  ",
  " / /_  ",
  "| '_ \\ ",
  "| (_) |",
  " \\___/ ",
                                                                                                               
  /* 7 */
  " ______ ",
  "|____  |",
  "    / / ",
  "   / /  ",
  "  / /   ",
  " /_/    ",
                                                                                                               
  /* 8 */
  "  ___  ",
  " / _ \\ ",
  "| (_) |",
  " > _ < ",
  "| (_) |",
  " \\___/ ",
                                                                                                               
  /* 9 */
  "  ___  ",
  " / _ \\ ",
  "| (_) |",
  " \\__, |",
  "   / / ",
  "  /_/  ",
                                                                                                               
  /* A */
  "          ",
  "    /\\    ",
  "   /  \\   ",
  "  / /\\ \\  ",
  " / ____ \\ ",
  "/_/    \\_\\",
                                                                                                               
  /* B */
  " ____  ",
  "|  _ \\ ",
  "| |_) |",
  "|  _ < ",
  "| |_) |",
  "|____/ ",
                                                                                                               
  /* C */
  "  _____ ",
  " / ____|",
  "| |     ",
  "| |     ",
  "| |____ ",
  " \\_____|",
                                                                                                               
  /* D */
  " _____  ",
  "|  __ \\ ",
  "| |  | |",
  "| |  | |",
  "| |__| |",
  "|_____/ ",
                                                                                                               
  /* E */
  " ______ ",
  "|  ____|",
  "| |__   ",
  "|  __|  ",
  "| |____ ",
  "|______|",
                                                                                                               
  /* F */
  " ______ ",
  "|  ____|",
  "| |__   ",
  "|  __|  ",
  "| |     ",
  "|_|     ",
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
      for(m=0; zChar[m]; m++){
        z[k++] = zChar[m];
      }
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;
}
#endif /* CAPTCHA==3 */

/*
** COMMAND: test-captcha
*/
void test_captcha(void){







|







406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
      for(m=0; zChar[m]; m++){
        z[k++] = zChar[m];
      }
    }
    z[k++] = '\n';
  }
  z[k] = 0;
  return z;     
}
#endif /* CAPTCHA==3 */

/*
** COMMAND: test-captcha
*/
void test_captcha(void){
476
477
478
479
480
481
482
483
484
485
486

487
488
489
490
491
492
493
494

/*
** Return true if a CAPTCHA is required for editing wiki or tickets or for
** adding attachments.
**
** A CAPTCHA is required in those cases if the user is not logged in (if they
** are user "nobody") and if the "require-captcha" setting is true.  The
** "require-captcha" setting is controlled on the Admin/Access page.  It
** defaults to true.
*/
int captcha_needed(void){

  return login_is_nobody() && db_get_boolean("require-captcha", 1);
}

/*
** If a captcha is required but the correct captcha code is not supplied
** in the query parameters, then return false (0).
**
** If no captcha is required or if the correct captcha is supplied, return







|



>
|







476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495

/*
** Return true if a CAPTCHA is required for editing wiki or tickets or for
** adding attachments.
**
** A CAPTCHA is required in those cases if the user is not logged in (if they
** are user "nobody") and if the "require-captcha" setting is true.  The
** "require-captcha" setting is controlled on the Admin/Access page.  It 
** defaults to true.
*/
int captcha_needed(void){
  if( g.zLogin!=0 ) return 0;
  return db_get_boolean("require-captcha", 1);
}

/*
** If a captcha is required but the correct captcha code is not supplied
** in the query parameters, then return false (0).
**
** If no captcha is required or if the correct captcha is supplied, return

Changes to src/cgi.c.

264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
      for( zTok = strtok_r(zBuf, ",\"",&zPos);
           zTok && fossil_stricmp(zTok,zETag);
           zTok =  strtok_r(0, ",\"",&zPos)){}
      fossil_free(zBuf);
      if(zTok) return 1;
    }
  }

  return 0;
}
#endif

/*
** Return true if the response should be sent with Content-Encoding: gzip.
*/







|







264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
      for( zTok = strtok_r(zBuf, ",\"",&zPos);
           zTok && fossil_stricmp(zTok,zETag);
           zTok =  strtok_r(0, ",\"",&zPos)){}
      fossil_free(zBuf);
      if(zTok) return 1;
    }
  }
  
  return 0;
}
#endif

/*
** Return true if the response should be sent with Content-Encoding: gzip.
*/
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
/*
** Add a query parameter.  The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(zName, mprintf("%s",zValue), 0);
}


/*
** Add a list of query parameters or cookies to the parameter set.
**
** Each parameter is of the form NAME=VALUE.  Both the NAME and the
** VALUE may be url-encoded ("+" for space, "%HH" for other special
** characters).  But this routine assumes that NAME contains no







|







495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
/*
** Add a query parameter.  The zName portion is fixed but a copy
** must be made of zValue.
*/
void cgi_setenv(const char *zName, const char *zValue){
  cgi_set_parameter_nocopy(zName, mprintf("%s",zValue), 0);
}
 

/*
** Add a list of query parameters or cookies to the parameter set.
**
** Each parameter is of the form NAME=VALUE.  Both the NAME and the
** VALUE may be url-encoded ("+" for space, "%HH" for other special
** characters).  But this routine assumes that NAME contains no
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
      *pnContent = i;
      i += nBoundry;
      break;
    }
  }
  *pz = &z[i];
  get_line_from_string(pz, pLen);
  return z;
}

/*
** Tokenize a line of text into as many as nArg tokens.  Make
** azArg[] point to the start of each token.
**
** Tokens consist of space or semi-colon delimited words or







|







613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
      *pnContent = i;
      i += nBoundry;
      break;
    }
  }
  *pz = &z[i];
  get_line_from_string(pz, pLen);
  return z;      
}

/*
** Tokenize a line of text into as many as nArg tokens.  Make
** azArg[] point to the start of each token.
**
** Tokens consist of space or semi-colon delimited words or
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
          char *z = azArg[++i];
          if( zName && z && fossil_islower(zName[0]) ){
            cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1);
          }
        }
      }
    }
  }
}


#ifdef FOSSIL_ENABLE_JSON
/*
** Internal helper for cson_data_source_FILE_n().
*/







|







720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
          char *z = azArg[++i];
          if( zName && z && fossil_islower(zName[0]) ){
            cgi_set_parameter_nocopy(mprintf("%s:mimetype",zName), z, 1);
          }
        }
      }
    }
  }        
}


#ifdef FOSSIL_ENABLE_JSON
/*
** Internal helper for cson_data_source_FILE_n().
*/
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
  }

  z = (char*)P("HTTP_COOKIE");
  if( z ){
    z = mprintf("%s",z);
    add_param_list(z, ';');
  }

  z = (char*)P("QUERY_STRING");
  if( z ){
    z = mprintf("%s",z);
    add_param_list(z, '&');
  }

  z = (char*)P("REMOTE_ADDR");
  if( z ){
    g.zIpAddr = mprintf("%s", z);
  }

  len = atoi(PD("CONTENT_LENGTH", "0"));
  g.zContentType = zType = P("CONTENT_TYPE");
  blob_zero(&g.cgiIn);
  if( len>0 && zType ){
    if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0
         || strncmp(zType,"multipart/form-data",19)==0 ){
      z = fossil_malloc( len+1 );
      len = fread(z, 1, len, g.httpIn);
      z[len] = 0;
      cgi_trace(z);
      if( zType[0]=='a' ){
        add_param_list(z, '&');







|















|







901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
  }

  z = (char*)P("HTTP_COOKIE");
  if( z ){
    z = mprintf("%s",z);
    add_param_list(z, ';');
  }
  
  z = (char*)P("QUERY_STRING");
  if( z ){
    z = mprintf("%s",z);
    add_param_list(z, '&');
  }

  z = (char*)P("REMOTE_ADDR");
  if( z ){
    g.zIpAddr = mprintf("%s", z);
  }

  len = atoi(PD("CONTENT_LENGTH", "0"));
  g.zContentType = zType = P("CONTENT_TYPE");
  blob_zero(&g.cgiIn);
  if( len>0 && zType ){
    if( fossil_strcmp(zType,"application/x-www-form-urlencoded")==0 
         || strncmp(zType,"multipart/form-data",19)==0 ){
      z = fossil_malloc( len+1 );
      len = fread(z, 1, len, g.httpIn);
      z[len] = 0;
      cgi_trace(z);
      if( zType[0]=='a' ){
        add_param_list(z, '&');
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
      g.json.isJsonMode = 1;
      cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
      /* FIXMEs:

      - See if fossil really needs g.cgiIn to be set for this purpose
      (i don't think it does). If it does then fill g.cgiIn and
      refactor to parse the JSON from there.

      - After parsing POST JSON, copy the "first layer" of keys/values
      to cgi_setenv(), honoring the upper-case distinction used
      in add_param_list(). However...

      - If we do that then we might get a disconnect in precedence of
      GET/POST arguments. i prefer for GET entries to take precedence
      over like-named POST entries, but in order for that to happen we







|







947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
      g.json.isJsonMode = 1;
      cgi_parse_POST_JSON(g.httpIn, (unsigned int)len);
      /* FIXMEs:

      - See if fossil really needs g.cgiIn to be set for this purpose
      (i don't think it does). If it does then fill g.cgiIn and
      refactor to parse the JSON from there.
      
      - After parsing POST JSON, copy the "first layer" of keys/values
      to cgi_setenv(), honoring the upper-case distinction used
      in add_param_list(). However...

      - If we do that then we might get a disconnect in precedence of
      GET/POST arguments. i prefer for GET entries to take precedence
      over like-named POST entries, but in order for that to happen we
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
** Return a pointer to a string containing the real IP address, or a
** NULL pointer to stick with the IP address previously computed and
** loaded into g.zIpAddr.
*/
static const char *cgi_accept_forwarded_for(const char *z){
  int i;
  if( fossil_strcmp(g.zIpAddr, "127.0.0.1")!=0 ) return 0;

  i = strlen(z)-1;
  while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--;
  return &z[++i];
}

/*
** Remove the first space-delimited token from a string and return







|







1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
** Return a pointer to a string containing the real IP address, or a
** NULL pointer to stick with the IP address previously computed and
** loaded into g.zIpAddr.
*/
static const char *cgi_accept_forwarded_for(const char *z){
  int i;
  if( fossil_strcmp(g.zIpAddr, "127.0.0.1")!=0 ) return 0;
  
  i = strlen(z)-1;
  while( i>=0 && z[i]!=',' && !fossil_isspace(z[i]) ) i--;
  return &z[++i];
}

/*
** Remove the first space-delimited token from a string and return
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
  cgi_setenv("REQUEST_URI", zToken);
  cgi_setenv("SCRIPT_NAME", "");
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  cgi_setenv("PATH_INFO", zToken);
  cgi_setenv("QUERY_STRING", &zToken[i]);
  if( zIpAddr==0 &&
        getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName,
                                &size)>=0
  ){
    zIpAddr = inet_ntoa(remoteName.sin_addr);
  }
  if( zIpAddr ){
    cgi_setenv("REMOTE_ADDR", zIpAddr);
    g.zIpAddr = mprintf("%s", zIpAddr);
  }

  /* Get all the optional fields that follow the first line.
  */
  while( fgets(zLine,sizeof(zLine),g.httpIn) ){
    char *zFieldName;
    char *zVal;

    cgi_trace(zLine);







|




|



|







1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
  cgi_setenv("REQUEST_URI", zToken);
  cgi_setenv("SCRIPT_NAME", "");
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  cgi_setenv("PATH_INFO", zToken);
  cgi_setenv("QUERY_STRING", &zToken[i]);
  if( zIpAddr==0 &&
        getpeername(fileno(g.httpIn), (struct sockaddr*)&remoteName, 
                                &size)>=0
  ){
    zIpAddr = inet_ntoa(remoteName.sin_addr);
  }
  if( zIpAddr ){   
    cgi_setenv("REMOTE_ADDR", zIpAddr);
    g.zIpAddr = mprintf("%s", zIpAddr);
  }
 
  /* Get all the optional fields that follow the first line.
  */
  while( fgets(zLine,sizeof(zLine),g.httpIn) ){
    char *zFieldName;
    char *zVal;

    cgi_trace(zLine);
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
  static char *zCmd = 0;
  char *z, *zToken;
  const char *zType = 0;
  int i, content_length = 0;
  char zLine[2000];     /* A single line of input. */

  if( zIpAddr ){
    if( nCycles==0 ){
      cgi_setenv("REMOTE_ADDR", zIpAddr);
      g.zIpAddr = mprintf("%s", zIpAddr);
    }
  }else{
    fossil_panic("missing SSH IP address");
  }
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){







|







1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
  static char *zCmd = 0;
  char *z, *zToken;
  const char *zType = 0;
  int i, content_length = 0;
  char zLine[2000];     /* A single line of input. */

  if( zIpAddr ){
    if( nCycles==0 ){   
      cgi_setenv("REMOTE_ADDR", zIpAddr);
      g.zIpAddr = mprintf("%s", zIpAddr);
    }
  }else{
    fossil_panic("missing SSH IP address");
  }
  if( fgets(zLine, sizeof(zLine),g.httpIn)==0 ){
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  if( nCycles==0 ){
    cgi_setenv("PATH_INFO", zToken);
  }else{
    cgi_replace_parameter("PATH_INFO", mprintf("%s",zToken));
  }

  /* Get all the optional fields that follow the first line.
  */
  while( fgets(zLine,sizeof(zLine),g.httpIn) ){
    char *zFieldName;
    char *zVal;

    cgi_trace(zLine);







|







1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
  for(i=0; zToken[i] && zToken[i]!='?'; i++){}
  if( zToken[i] ) zToken[i++] = 0;
  if( nCycles==0 ){
    cgi_setenv("PATH_INFO", zToken);
  }else{
    cgi_replace_parameter("PATH_INFO", mprintf("%s",zToken));
  }
 
  /* Get all the optional fields that follow the first line.
  */
  while( fgets(zLine,sizeof(zLine),g.httpIn) ){
    char *zFieldName;
    char *zVal;

    cgi_trace(zLine);
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
  fossil_free(zToFree);
  fgetc(g.httpIn);  /* Read past the "," separating header from content */
  cgi_init();
}


#if INTERFACE
/*
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */

#endif /* INTERFACE */








|







1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
  fossil_free(zToFree);
  fgetc(g.httpIn);  /* Read past the "," separating header from content */
  cgi_init();
}


#if INTERFACE
/* 
** Bitmap values for the flags parameter to cgi_http_server().
*/
#define HTTP_SERVER_LOCALHOST      0x0001     /* Bind to 127.0.0.1 only */
#define HTTP_SERVER_SCGI           0x0002     /* SCGI instead of HTTP */

#endif /* INTERFACE */

1704
1705
1706
1707
1708
1709
1710

1711
1712
1713

1714
1715
1716
1717
1718
1719
1720
    }else{
      fossil_fatal("unable to open listening socket on any"
                   " port in the range %d..%d", mnPort, mxPort);
    }
  }
  if( iPort>mxPort ) return 1;
  listen(listener,10);

  fossil_print("Listening for %s requests on TCP port %d\n",
     (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP",  iPort);
  fflush(stdout);

  if( zBrowser ){
    zBrowser = mprintf(zBrowser, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( memcmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */







>
|
|
|
>







1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
    }else{
      fossil_fatal("unable to open listening socket on any"
                   " port in the range %d..%d", mnPort, mxPort);
    }
  }
  if( iPort>mxPort ) return 1;
  listen(listener,10);
  if( iPort>mnPort ){
    fossil_print("Listening for %s requests on TCP port %d\n",
       (flags & HTTP_SERVER_SCGI)!=0?"SCGI":"HTTP",  iPort);
    fflush(stdout);
  }
  if( zBrowser ){
    zBrowser = mprintf(zBrowser, iPort);
#if defined(__CYGWIN__)
    /* On Cygwin, we can do better than "echo" */
    if( memcmp(zBrowser, "echo ", 5)==0 ){
      wchar_t *wUrl = fossil_utf8_to_unicode(zBrowser+5);
      wUrl[wcslen(wUrl)-2] = 0; /* Strip terminating " &" */
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
      }
    }
    /* Bury dead children */
    while( waitpid(0, 0, WNOHANG)>0 ){
      nchildren--;
    }
  }
  /* NOT REACHED */
  fossil_exit(1);
#endif
  /* NOT REACHED */
  return 0;
}









|







1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
      }
    }
    /* Bury dead children */
    while( waitpid(0, 0, WNOHANG)>0 ){
      nchildren--;
    }
  }
  /* NOT REACHED */  
  fossil_exit(1);
#endif
  /* NOT REACHED */
  return 0;
}


1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
  }else if( p->tm_mon>11 ){
    p->tm_year += p->tm_mon/12;
    p->tm_mon %= 12;
  }
  isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0);
  p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1;
  if( isLeapYr && p->tm_mon>1 ) p->tm_yday++;
  nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 +
         (p->tm_year+300)/400 + p->tm_yday;
  t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec;
  return t;
}

/*
** Check the objectTime against the If-Modified-Since request header. If the







|







1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
  }else if( p->tm_mon>11 ){
    p->tm_year += p->tm_mon/12;
    p->tm_mon %= 12;
  }
  isLeapYr = p->tm_year%4==0 && (p->tm_year%100!=0 || (p->tm_year+300)%400==0);
  p->tm_yday = priorDays[p->tm_mon] + p->tm_mday - 1;
  if( isLeapYr && p->tm_mon>1 ) p->tm_yday++;
  nDay = (p->tm_year-70)*365 + (p->tm_year-69)/4 -p->tm_year/100 + 
         (p->tm_year+300)/400 + p->tm_yday;
  t = ((nDay*24 + p->tm_hour)*60 + p->tm_min)*60 + p->tm_sec;
  return t;
}

/*
** Check the objectTime against the If-Modified-Since request header. If the

Changes to src/checkin.c.

177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
**    --rel-paths       Display pathnames relative to the current working
**                      directory.
**    --sha1sum         Verify file status using SHA1 hashing rather
**                      than relying on file mtimes.
**    --header          Identify the repository if there are changes
**    -v|--verbose      Say "(none)" if there are no changes
**
** See also: extras, ls, status
*/
void changes_cmd(void){
  Blob report;
  int vid;
  int useSha1sum = find_option("sha1sum", 0, 0)!=0;
  int showHdr = find_option("header",0,0)!=0;
  int verboseFlag = find_option("verbose","v",0)!=0;







|







177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
**    --rel-paths       Display pathnames relative to the current working
**                      directory.
**    --sha1sum         Verify file status using SHA1 hashing rather
**                      than relying on file mtimes.
**    --header          Identify the repository if there are changes
**    -v|--verbose      Say "(none)" if there are no changes
**
** See also: extra, ls, status
*/
void changes_cmd(void){
  Blob report;
  int vid;
  int useSha1sum = find_option("sha1sum", 0, 0)!=0;
  int showHdr = find_option("header",0,0)!=0;
  int verboseFlag = find_option("verbose","v",0)!=0;
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
**
**    --abs-paths       Display absolute pathnames.
**    --rel-paths       Display pathnames relative to the current working
**                      directory.
**    --sha1sum         Verify file status using SHA1 hashing rather
**                      than relying on file mtimes.
**
** See also: changes, extras, ls
*/
void status_cmd(void){
  int vid;
  db_must_be_within_tree();
       /* 012345678901234 */
  fossil_print("repository:   %s\n", db_repository_filename());
  fossil_print("local-root:   %s\n", g.zLocalRoot);







|







221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
**
**    --abs-paths       Display absolute pathnames.
**    --rel-paths       Display pathnames relative to the current working
**                      directory.
**    --sha1sum         Verify file status using SHA1 hashing rather
**                      than relying on file mtimes.
**
** See also: changes, extra, ls
*/
void status_cmd(void){
  int vid;
  db_must_be_within_tree();
       /* 012345678901234 */
  fossil_print("repository:   %s\n", db_repository_filename());
  fossil_print("local-root:   %s\n", g.zLocalRoot);
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
** extra information about each file.  If FILENAMES are included, only
** the files listed (or their children if they are directories) are shown.
**
** Options:
**   --age           Show when each file was committed
**   -v|--verbose    Provide extra information about each file.
**
** See also: changes, extras, status
*/
void ls_cmd(void){
  int vid;
  Stmt q;
  int verboseFlag;
  int showAge;
  char *zOrderBy = "pathname";







|







253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
** extra information about each file.  If FILENAMES are included, only
** the files listed (or their children if they are directories) are shown.
**
** Options:
**   --age           Show when each file was committed
**   -v|--verbose    Provide extra information about each file.
**
** See also: changes, extra, status
*/
void ls_cmd(void){
  int vid;
  Stmt q;
  int verboseFlag;
  int showAge;
  char *zOrderBy = "pathname";
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
** Pathnames are displayed according to the "relative-paths" setting,
** unless overridden by the --abs-paths or --rel-paths options.
**
** Options:
**    --abs-paths      Display absolute pathnames.
**    --case-sensitive <BOOL> override case-sensitive setting
**    --dotfiles       include files beginning with a dot (".")
**    --header         Identify the repository if there are extras
**    --ignore <CSG>   ignore files matching patterns from the argument
**    --rel-paths      Display pathnames relative to the current working
**                     directory.
**
** See also: changes, clean, status
*/
void extras_cmd(void){
  Stmt q;
  const char *zIgnoreFlag = find_option("ignore",0,1);
  unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;
  int showHdr = find_option("header",0,0)!=0;
  int cwdRelative = 0;
  Glob *pIgnore;
  Blob rewrittenPathname;
  const char *zPathname, *zDisplayName;

  if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
  capture_case_sensitive_option();







<






|



<







431
432
433
434
435
436
437

438
439
440
441
442
443
444
445
446
447

448
449
450
451
452
453
454
** Pathnames are displayed according to the "relative-paths" setting,
** unless overridden by the --abs-paths or --rel-paths options.
**
** Options:
**    --abs-paths      Display absolute pathnames.
**    --case-sensitive <BOOL> override case-sensitive setting
**    --dotfiles       include files beginning with a dot (".")

**    --ignore <CSG>   ignore files matching patterns from the argument
**    --rel-paths      Display pathnames relative to the current working
**                     directory.
**
** See also: changes, clean, status
*/
void extra_cmd(void){
  Stmt q;
  const char *zIgnoreFlag = find_option("ignore",0,1);
  unsigned scanFlags = find_option("dotfiles",0,0)!=0 ? SCAN_ALL : 0;

  int cwdRelative = 0;
  Glob *pIgnore;
  Blob rewrittenPathname;
  const char *zPathname, *zDisplayName;

  if( find_option("temp",0,0)!=0 ) scanFlags |= SCAN_TEMP;
  capture_case_sensitive_option();
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
      file_relative_name(zFullName, &rewrittenPathname, 0);
      free(zFullName);
      zDisplayName = blob_str(&rewrittenPathname);
      if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){
        zDisplayName += 2;  /* no unnecessary ./ prefix */
      }
    }
    if( showHdr ){
      showHdr = 0;
      fossil_print("Extras for %s at %s:\n", db_get("project-name","???"),
                   g.zLocalRoot);
    }
    fossil_print("%s\n", zDisplayName);
  }
  blob_reset(&rewrittenPathname);
  db_finalize(&q);
}

/*







<
<
<
<
<







475
476
477
478
479
480
481





482
483
484
485
486
487
488
      file_relative_name(zFullName, &rewrittenPathname, 0);
      free(zFullName);
      zDisplayName = blob_str(&rewrittenPathname);
      if( zDisplayName[0]=='.' && zDisplayName[1]=='/' ){
        zDisplayName += 2;  /* no unnecessary ./ prefix */
      }
    }





    fossil_print("%s\n", zDisplayName);
  }
  blob_reset(&rewrittenPathname);
  db_finalize(&q);
}

/*
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
**                     comma separated list of glob patterns.
**    --keep <CSG>     Keep files matching this comma separated
**                     list of glob patterns.
**    -n|--dry-run     If given, display instead of run actions.
**    --temp           Remove only Fossil-generated temporary files.
**    -v|--verbose     Show all files as they are removed.
**
** See also: addremove, extras, status
*/
void clean_cmd(void){
  int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
  int emptyDirsFlag, dirsOnlyFlag;
  unsigned scanFlags = 0;
  const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
  Glob *pIgnore, *pKeep, *pClean;







|







534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
**                     comma separated list of glob patterns.
**    --keep <CSG>     Keep files matching this comma separated
**                     list of glob patterns.
**    -n|--dry-run     If given, display instead of run actions.
**    --temp           Remove only Fossil-generated temporary files.
**    -v|--verbose     Show all files as they are removed.
**
** See also: addremove, extra, status
*/
void clean_cmd(void){
  int allFileFlag, allDirFlag, dryRunFlag, verboseFlag;
  int emptyDirsFlag, dirsOnlyFlag;
  unsigned scanFlags = 0;
  const char *zIgnoreFlag, *zKeepFlag, *zCleanFlag;
  Glob *pIgnore, *pKeep, *pClean;
605
606
607
608
609
610
611

612
613
614
615
616
617
618
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
        Blob ans;
        char cReply;
        char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
                               zName+nRoot);

        prompt_user(prompt, &ans);
        cReply = blob_str(&ans)[0];
        if( cReply=='a' || cReply=='A' ){
          allFileFlag = 1;
        }else if( cReply!='y' && cReply!='Y' ){
          blob_reset(&ans);
          continue;







>







598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      if( !allFileFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
        Blob ans;
        char cReply;
        char *prompt = mprintf("Remove unmanaged file \"%s\" (a=all/y/N)? ",
                               zName+nRoot);
        blob_zero(&ans);
        prompt_user(prompt, &ans);
        cReply = blob_str(&ans)[0];
        if( cReply=='a' || cReply=='A' ){
          allFileFlag = 1;
        }else if( cReply!='y' && cReply!='Y' ){
          blob_reset(&ans);
          continue;
646
647
648
649
650
651
652

653
654
655
656
657
658
659
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
        Blob ans;
        char cReply;
        char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ",
                               zName+nRoot);

        prompt_user(prompt, &ans);
        cReply = blob_str(&ans)[0];
        if( cReply=='a' || cReply=='A' ){
          allDirFlag = 1;
        }else if( cReply!='y' && cReply!='Y' ){
          blob_reset(&ans);
          continue;







>







640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      if( !allDirFlag && !dryRunFlag && !glob_match(pClean, zName+nRoot) ){
        Blob ans;
        char cReply;
        char *prompt = mprintf("Remove empty directory \"%s\" (a=all/y/N)? ",
                               zName+nRoot);
        blob_zero(&ans);
        prompt_user(prompt, &ans);
        cReply = blob_str(&ans)[0];
        if( cReply=='a' || cReply=='A' ){
          allDirFlag = 1;
        }else if( cReply!='y' && cReply!='Y' ){
          blob_reset(&ans);
          continue;
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
  blob_init(&prompt, zInit, -1);
#endif
  blob_append(&prompt,
    "\n"
    "# Enter commit message for this check-in. Lines beginning with # are ignored.\n"
    "#\n", -1
  );
  blob_appendf(&prompt, "# user: %s\n", p->zUserOvrd ? p->zUserOvrd : login_name());
  if( p->zBranch && p->zBranch[0] ){
    blob_appendf(&prompt, "# tags: %s\n#\n", p->zBranch);
  }else{
    char *zTags = info_tags_of_checkin(parent_rid, 1);
    if( zTags )  blob_appendf(&prompt, "# tags: %z\n#\n", zTags);
  }
  status_report(&prompt, "# ", 1, 0);







|







801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
  blob_init(&prompt, zInit, -1);
#endif
  blob_append(&prompt,
    "\n"
    "# Enter commit message for this check-in. Lines beginning with # are ignored.\n"
    "#\n", -1
  );
  blob_appendf(&prompt, "# user: %s\n", p->zUserOvrd ? p->zUserOvrd : g.zLogin);
  if( p->zBranch && p->zBranch[0] ){
    blob_appendf(&prompt, "# tags: %s\n#\n", p->zBranch);
  }else{
    char *zTags = info_tags_of_checkin(parent_rid, 1);
    if( zTags )  blob_appendf(&prompt, "# tags: %z\n#\n", zTags);
  }
  status_report(&prompt, "# ", 1, 0);
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
  Blob *pComment;             /* Check-in comment text */
  const char *zMimetype;      /* Mimetype of check-in command.  May be NULL */
  int verifyDate;             /* Verify that child is younger */
  int closeFlag;              /* Close the branch being committed */
  int integrateFlag;          /* Close merged-in branches */
  Blob *pCksum;               /* Repository checksum.  May be 0 */
  const char *zDateOvrd;      /* Date override.  If 0 then use 'now' */
  const char *zUserOvrd;      /* User override.  If 0 then use login_name() */
  const char *zBranch;        /* Branch name.  May be 0 */
  const char *zColor;         /* One-time background color.  May be 0 */
  const char *zBrClr;         /* Persistent branch color.  May be 0 */
  const char **azTag;         /* Tags to apply to this check-in */
};
#endif /* INTERFACE */








|







963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
  Blob *pComment;             /* Check-in comment text */
  const char *zMimetype;      /* Mimetype of check-in command.  May be NULL */
  int verifyDate;             /* Verify that child is younger */
  int closeFlag;              /* Close the branch being committed */
  int integrateFlag;          /* Close merged-in branches */
  Blob *pCksum;               /* Repository checksum.  May be 0 */
  const char *zDateOvrd;      /* Date override.  If 0 then use 'now' */
  const char *zUserOvrd;      /* User override.  If 0 then use g.zLogin */
  const char *zBranch;        /* Branch name.  May be 0 */
  const char *zColor;         /* One-time background color.  May be 0 */
  const char *zBrClr;         /* Persistent branch color.  May be 0 */
  const char **azTag;         /* Tags to apply to this check-in */
};
#endif /* INTERFACE */

1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
        vid, p->zBranch);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zBrTag = db_column_text(&q, 0);
      blob_appendf(pOut, "T -%F *\n", zBrTag);
    }
    db_finalize(&q);
  }
  blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : login_name());
  md5sum_blob(pOut, &mcksum);
  blob_appendf(pOut, "Z %b\n", &mcksum);
  if( pnFBcard ) *pnFBcard = nFBcard;
}

/*
** Issue a warning and give the user an opportunity to abandon out







|







1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
        vid, p->zBranch);
    while( db_step(&q)==SQLITE_ROW ){
      const char *zBrTag = db_column_text(&q, 0);
      blob_appendf(pOut, "T -%F *\n", zBrTag);
    }
    db_finalize(&q);
  }
  blob_appendf(pOut, "U %F\n", p->zUserOvrd ? p->zUserOvrd : g.zLogin);
  md5sum_blob(pOut, &mcksum);
  blob_appendf(pOut, "Z %b\n", &mcksum);
  if( pnFBcard ) *pnFBcard = nFBcard;
}

/*
** Issue a warning and give the user an opportunity to abandon out
1288
1289
1290
1291
1292
1293
1294



1295
1296
1297

1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
      }
      zDisable = "\"crnl-glob\" setting";
    }else{
      if( encodingOk ){
        return 0; /* We don't want encoding warnings for this file. */
      }
      zWarning = "Unicode";



      zDisable = "\"encoding-glob\" setting";
    }
    file_relative_name(zFilename, &fname, 0);

    zMsg = mprintf(
         "%s contains %s. Use --no-warnings or the %s to disable this warning.\n"
         "Commit anyhow (a=all/%sy/N)? ",
         blob_str(&fname), zWarning, zDisable, zConvert);
    prompt_user(zMsg, &ans);
    fossil_free(zMsg);
    cReply = blob_str(&ans)[0];
    if( cReply=='a' || cReply=='A' ){
      allOk = 1;
    }else if( *zConvert && (cReply=='c' || cReply=='C') ){
      char *zOrig = file_newname(zFilename, "original", 1);
      FILE *f;
      blob_write_to_file(p, zOrig);
      fossil_free(zOrig);
      f = fossil_fopen(zFilename, "wb");
      if( f==0 ){
        fossil_warning("cannot open %s for writing", zFilename);
      }else{
        if( fUnicode ) {
          int bomSize;
          const unsigned char *bom = get_utf8_bom(&bomSize);
          fwrite(bom, 1, bomSize, f);
          blob_to_utf8_no_bom(p, 0);
        }
        if( fHasAnyCr ){
          blob_to_lf_only(p);
        }
        fwrite(blob_buffer(p), 1, blob_size(p), f);
        fclose(f);
      }
      return 1;
    }else if( cReply!='y' && cReply!='Y' ){
      fossil_fatal("Abandoning commit due to %s in %s",
                   zWarning, blob_str(&fname));
    }
    blob_reset(&ans);
    blob_reset(&fname);







>
>
>



>















<
<
<
|
|
|
|
|
|
|
|
|
|
|
<







1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311



1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322

1323
1324
1325
1326
1327
1328
1329
      }
      zDisable = "\"crnl-glob\" setting";
    }else{
      if( encodingOk ){
        return 0; /* We don't want encoding warnings for this file. */
      }
      zWarning = "Unicode";
#if !defined(_WIN32) && !defined(__CYGWIN__)
      zConvert = ""; /* On Unix, we cannot easily convert Unicode files. */
#endif
      zDisable = "\"encoding-glob\" setting";
    }
    file_relative_name(zFilename, &fname, 0);
    blob_zero(&ans);
    zMsg = mprintf(
         "%s contains %s. Use --no-warnings or the %s to disable this warning.\n"
         "Commit anyhow (a=all/%sy/N)? ",
         blob_str(&fname), zWarning, zDisable, zConvert);
    prompt_user(zMsg, &ans);
    fossil_free(zMsg);
    cReply = blob_str(&ans)[0];
    if( cReply=='a' || cReply=='A' ){
      allOk = 1;
    }else if( *zConvert && (cReply=='c' || cReply=='C') ){
      char *zOrig = file_newname(zFilename, "original", 1);
      FILE *f;
      blob_write_to_file(p, zOrig);
      fossil_free(zOrig);
      f = fossil_fopen(zFilename, "wb");



      if( fUnicode ) {
        int bomSize;
        const unsigned char *bom = get_utf8_bom(&bomSize);
        fwrite(bom, 1, bomSize, f);
        blob_to_utf8_no_bom(p, 0);
      }
      if( fHasAnyCr ){
        blob_to_lf_only(p);
      }
      fwrite(blob_buffer(p), 1, blob_size(p), f);
      fclose(f);

      return 1;
    }else if( cReply!='y' && cReply!='Y' ){
      fossil_fatal("Abandoning commit due to %s in %s",
                   zWarning, blob_str(&fname));
    }
    blob_reset(&ans);
    blob_reset(&fname);
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
**    --no-warnings              omit all warnings about file contents
**    --nosign                   do not attempt to sign this commit with gpg
**    --private                  do not sync changes and their descendants
**    --sha1sum                  verify file status using SHA1 hashing rather
**                               than relying on file mtimes
**    --tag TAG-NAME             assign given tag TAG-NAME to the checkin
**
** See also: branch, changes, checkout, extras, sync
*/
void commit_cmd(void){
  int hasChanges;        /* True if unsaved changes exist */
  int vid;               /* blob-id of parent version */
  int nrid;              /* blob-id of a modified file */
  int nvid;              /* Blob-id of the new check-in */
  Blob comment;          /* Check-in comment */







|







1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
**    --no-warnings              omit all warnings about file contents
**    --nosign                   do not attempt to sign this commit with gpg
**    --private                  do not sync changes and their descendants
**    --sha1sum                  verify file status using SHA1 hashing rather
**                               than relying on file mtimes
**    --tag TAG-NAME             assign given tag TAG-NAME to the checkin
**
** See also: branch, changes, checkout, extra, sync
*/
void commit_cmd(void){
  int hasChanges;        /* True if unsaved changes exist */
  int vid;               /* blob-id of parent version */
  int nrid;              /* blob-id of a modified file */
  int nvid;              /* Blob-id of the new check-in */
  Blob comment;          /* Check-in comment */
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543

1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555

1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573

1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
  */
  if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){
    forceBaseline = 1;
  }

  /* Get the ID of the parent manifest artifact */
  vid = db_lget_int("checkout", 0);
  if( vid==0 ){
    useCksum = 1;
  }else if( content_is_private(vid) ){
    g.markPrivate = 1;
  }

  /*
  ** Autosync if autosync is enabled and this is not a private check-in.
  */
  if( !g.markPrivate ){
    if( autosync(SYNC_PULL) ){

      prompt_user("continue in spite of sync failure (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ){
        fossil_exit(1);
      }
    }
  }

  /* Require confirmation to continue with the check-in if there is
  ** clock skew
  */
  if( g.clockSkewSeen ){

    prompt_user("continue in spite of time skew (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }
  }

  /* There are two ways this command may be executed. If there are
  ** no arguments following the word "commit", then all modified files
  ** in the checked out directory are committed. If one or more arguments
  ** follows "commit", then only those files are committed.
  **
  ** After the following function call has returned, the Global.aCommitFile[]
  ** array is allocated to contain the "id" field from the vfile table
  ** for each file to be committed. Or, if aCommitFile is NULL, all files
  ** should be committed.
  */
  if( select_commit_files() ){

    prompt_user("continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ) fossil_exit(1);
  }
  isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0 OR id<-2");
  if( g.aCommitFile && isAMerge ){
    fossil_fatal("cannot do a partial commit of a merge");
  }

  /* Doing "fossil mv fileA fileB; fossil add fileA; fossil commit fileA"







<
<
|








>












>


















>


|







1521
1522
1523
1524
1525
1526
1527


1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
  */
  if( !forceDelta && !db_get_boolean("seen-delta-manifest",0) ){
    forceBaseline = 1;
  }

  /* Get the ID of the parent manifest artifact */
  vid = db_lget_int("checkout", 0);


  if( content_is_private(vid) ){
    g.markPrivate = 1;
  }

  /*
  ** Autosync if autosync is enabled and this is not a private check-in.
  */
  if( !g.markPrivate ){
    if( autosync(SYNC_PULL) ){
      blob_zero(&ans);
      prompt_user("continue in spite of sync failure (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ){
        fossil_exit(1);
      }
    }
  }

  /* Require confirmation to continue with the check-in if there is
  ** clock skew
  */
  if( g.clockSkewSeen ){
    blob_zero(&ans);
    prompt_user("continue in spite of time skew (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }
  }

  /* There are two ways this command may be executed. If there are
  ** no arguments following the word "commit", then all modified files
  ** in the checked out directory are committed. If one or more arguments
  ** follows "commit", then only those files are committed.
  **
  ** After the following function call has returned, the Global.aCommitFile[]
  ** array is allocated to contain the "id" field from the vfile table
  ** for each file to be committed. Or, if aCommitFile is NULL, all files
  ** should be committed.
  */
  if( select_commit_files() ){
    blob_zero(&ans);
    prompt_user("continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ) fossil_exit(1);;
  }
  isAMerge = db_exists("SELECT 1 FROM vmerge WHERE id=0 OR id<-2");
  if( g.aCommitFile && isAMerge ){
    fossil_fatal("cannot do a partial commit of a merge");
  }

  /* Doing "fossil mv fileA fileB; fossil add fileA; fossil commit fileA"
1667
1668
1669
1670
1671
1672
1673

1674
1675
1676
1677
1678
1679
1680
1681

1682
1683
1684
1685
1686
1687
1688
    blob_to_utf8_no_bom(&comment, 1);
  }else if(dryRunFlag){
    blob_zero(&comment);
  }else{
    char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'");
    prepare_commit_comment(&comment, zInit, &sCiInfo, vid);
    if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){

      prompt_user("unchanged check-in comment.  continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ) fossil_exit(1);
    }
    free(zInit);
  }
  if( blob_size(&comment)==0 ){
    if( !dryRunFlag ){

      prompt_user("empty check-in comment.  continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ){
        fossil_exit(1);
      }
    }
  }else{







>


|





>







1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
    blob_to_utf8_no_bom(&comment, 1);
  }else if(dryRunFlag){
    blob_zero(&comment);
  }else{
    char *zInit = db_text(0, "SELECT value FROM vvar WHERE name='ci-comment'");
    prepare_commit_comment(&comment, zInit, &sCiInfo, vid);
    if( zInit && zInit[0] && fossil_strcmp(zInit, blob_str(&comment))==0 ){
      blob_zero(&ans);
      prompt_user("unchanged check-in comment.  continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ) fossil_exit(1);;
    }
    free(zInit);
  }
  if( blob_size(&comment)==0 ){
    if( !dryRunFlag ){
      blob_zero(&ans);
      prompt_user("empty check-in comment.  continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply!='y' && cReply!='Y' ){
        fossil_exit(1);
      }
    }
  }else{
1807
1808
1809
1810
1811
1812
1813

1814
1815
1816
1817
1818
1819
1820
        blob_reset(&delta);
      }
    }else if( forceDelta ){
      fossil_fatal("unable to find a baseline-manifest for the delta");
    }
  }
  if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){

    prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }
  }








>







1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
        blob_reset(&delta);
      }
    }else if( forceDelta ){
      fossil_fatal("unable to find a baseline-manifest for the delta");
    }
  }
  if( !noSign && !g.markPrivate && clearsign(&manifest, &manifest) ){
    blob_zero(&ans);
    prompt_user("unable to sign manifest.  continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }
  }

Changes to src/checkout.c.

155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    }
    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){
      zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
      file_delete(zManFile);
      free(zManFile);
    }
  }

}

/*
** COMMAND: checkout*
** COMMAND: co*
**
** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS?
**    or: %fossil co ?VERSION | --latest? ?OPTIONS?
**
** Check out a version specified on the command-line.  This command
** will abort if there are edited files in the current checkout unless
** the --force option appears on the command-line.  The --keep option
** leaves files on disk unchanged, except the manifest and manifest.uuid
** files.
**
** The --latest flag can be used in place of VERSION to checkout the
** latest version in the repository.
**
** Options:
**    --force   Ignore edited files in the current checkout
**    --keep    Only update the manifest and manifest.uuid files
**
** See also: update
*/
void checkout_cmd(void){
  int forceFlag;                 /* Force checkout even if edits exist */
  int keepFlag;                  /* Do not change any files on disk */
  int latestFlag;                /* Checkout the latest version */
  char *zVers;                   /* Version to checkout */
  int promptFlag;                /* True to prompt before overwriting */
  int vid, prior;
  Blob cksum1, cksum1b, cksum2;

  db_must_be_within_tree();
  db_begin_transaction();
  forceFlag = find_option("force","f",0)!=0;
  keepFlag = find_option("keep",0,0)!=0;
  latestFlag = find_option("latest",0,0)!=0;
  promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
  if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){







|

















|














|







155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    }
    if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){
      zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
      file_delete(zManFile);
      free(zManFile);
    }
  }
    
}

/*
** COMMAND: checkout*
** COMMAND: co*
**
** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS?
**    or: %fossil co ?VERSION | --latest? ?OPTIONS?
**
** Check out a version specified on the command-line.  This command
** will abort if there are edited files in the current checkout unless
** the --force option appears on the command-line.  The --keep option
** leaves files on disk unchanged, except the manifest and manifest.uuid
** files.
**
** The --latest flag can be used in place of VERSION to checkout the
** latest version in the repository.
** 
** Options:
**    --force   Ignore edited files in the current checkout
**    --keep    Only update the manifest and manifest.uuid files
**
** See also: update
*/
void checkout_cmd(void){
  int forceFlag;                 /* Force checkout even if edits exist */
  int keepFlag;                  /* Do not change any files on disk */
  int latestFlag;                /* Checkout the latest version */
  char *zVers;                   /* Version to checkout */
  int promptFlag;                /* True to prompt before overwriting */
  int vid, prior;
  Blob cksum1, cksum1b, cksum2;
  
  db_must_be_within_tree();
  db_begin_transaction();
  forceFlag = find_option("force","f",0)!=0;
  keepFlag = find_option("keep",0,0)!=0;
  latestFlag = find_option("latest",0,0)!=0;
  promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
  if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){

Changes to src/clone.c.

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
**
** Options:
**    --admin-user|-A USERNAME   Make USERNAME the administrator
**    --once                     Don't save url.
**    --private                  Also clone private branches 
**    --ssl-identity=filename    Use the SSL identity if requested by the server
**    --ssh-command|-c 'command' Use this SSH command
**    --httpauth|-B 'user:pass'  Add HTTP Basic Authorization to requests
**
** See also: init
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */
  const char *zHttpAuth;      /* HTTP Authorization user:pass information */
  int nErr = 0;
  int bPrivate = 0;           /* Also clone private branches */
  int urlFlags = URL_PROMPT_PW | URL_REMEMBER;

  if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
  if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER;
  zHttpAuth = find_option("httpauth","B",1);
  zDefaultUser = find_option("admin-user","A",1);
  clone_ssh_find_options();
  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_fatal("file already exists: %s", g.argv[3]);
  }

  url_parse(g.argv[2], urlFlags);
  if( zDefaultUser==0 && g.url.user!=0 ) zDefaultUser = g.url.user;
  if( g.url.isFile ){
    file_copy(g.url.name, g.argv[3]);
    db_close(1);
    db_open_repository(g.argv[3]);
    db_record_repository_filename(g.argv[3]);
    url_remember();
    if( !bPrivate ) delete_private_content();
    shun_artifacts();
    db_create_default_users(1, zDefaultUser);







<






<






<












|
|
|







107
108
109
110
111
112
113

114
115
116
117
118
119

120
121
122
123
124
125

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
**
** Options:
**    --admin-user|-A USERNAME   Make USERNAME the administrator
**    --once                     Don't save url.
**    --private                  Also clone private branches 
**    --ssl-identity=filename    Use the SSL identity if requested by the server
**    --ssh-command|-c 'command' Use this SSH command

**
** See also: init
*/
void clone_cmd(void){
  char *zPassword;
  const char *zDefaultUser;   /* Optional name of the default user */

  int nErr = 0;
  int bPrivate = 0;           /* Also clone private branches */
  int urlFlags = URL_PROMPT_PW | URL_REMEMBER;

  if( find_option("private",0,0)!=0 ) bPrivate = SYNC_PRIVATE;
  if( find_option("once",0,0)!=0) urlFlags &= ~URL_REMEMBER;

  zDefaultUser = find_option("admin-user","A",1);
  clone_ssh_find_options();
  url_proxy_options();
  if( g.argc < 4 ){
    usage("?OPTIONS? FILE-OR-URL NEW-REPOSITORY");
  }
  db_open_config(0);
  if( file_size(g.argv[3])>0 ){
    fossil_fatal("file already exists: %s", g.argv[3]);
  }

  url_parse(g.argv[2], urlFlags);
  if( zDefaultUser==0 && g.urlUser!=0 ) zDefaultUser = g.urlUser;
  if( g.urlIsFile ){
    file_copy(g.urlName, g.argv[3]);
    db_close(1);
    db_open_repository(g.argv[3]);
    db_record_repository_filename(g.argv[3]);
    url_remember();
    if( !bPrivate ) delete_private_content();
    shun_artifacts();
    db_create_default_users(1, zDefaultUser);
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    db_begin_transaction();
    db_record_repository_filename(g.argv[3]);
    db_initial_setup(0, 0, zDefaultUser, 0);
    user_select();
    db_set("content-schema", CONTENT_SCHEMA, 0);
    db_set("aux-schema", AUX_SCHEMA, 0);
    db_set("rebuilt", get_version(), 0);
    remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, g.argv[2]);
    url_remember();
    if( g.zSSLIdentity!=0 ){
      /* If the --ssl-identity option was specified, store it as a setting */
      Blob fn;
      blob_zero(&fn);
      file_canonical_name(g.zSSLIdentity, &fn, 0);
      db_set("ssl-identity", blob_str(&fn), 0);







<







157
158
159
160
161
162
163

164
165
166
167
168
169
170
    db_begin_transaction();
    db_record_repository_filename(g.argv[3]);
    db_initial_setup(0, 0, zDefaultUser, 0);
    user_select();
    db_set("content-schema", CONTENT_SCHEMA, 0);
    db_set("aux-schema", AUX_SCHEMA, 0);
    db_set("rebuilt", get_version(), 0);

    url_remember();
    if( g.zSSLIdentity!=0 ){
      /* If the --ssl-identity option was specified, store it as a setting */
      Blob fn;
      blob_zero(&fn);
      file_canonical_name(g.zSSLIdentity, &fn, 0);
      db_set("ssl-identity", blob_str(&fn), 0);
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
  rebuild_db(0, 1, 0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
  db_end_transaction(0);
}

/*
** If user chooses to use HTTP Authentication over unencrypted HTTP,
** remember decision.  Otherwise, if the URL is being changed and no 
** preference has been indicated, err on the safe side and revert the
** decision. Set the global preference if the URL is not being changed.
*/
void remember_or_get_http_auth(
  const char *zHttpAuth,  /* Credentials in the form "user:password" */
  int fRemember,          /* True to remember credentials for later reuse */
  const char *zUrl        /* URL for which these credentials apply */
){
  char *zKey = mprintf("http-auth:%s", g.url.canonical);
  if( zHttpAuth && zHttpAuth[0] ){
    g.zHttpAuth = mprintf("%s", zHttpAuth);
  }
  if( fRemember ){
    if( g.zHttpAuth && g.zHttpAuth[0] ){
      set_httpauth(g.zHttpAuth);
    }else if( zUrl && zUrl[0] ){
      db_unset(zKey, 0);
    }else{
      g.zHttpAuth = get_httpauth();
    }
  }else if( g.zHttpAuth==0 && zUrl==0 ){
    g.zHttpAuth = get_httpauth();
  }
  free(zKey);
}

/*
** Get the HTTP Authorization preference from db.
*/
char *get_httpauth(void){
  char *zKey = mprintf("http-auth:%s", g.url.canonical);
  return unobscure(db_get(zKey, 0));
  free(zKey);
}

/*
** Set the HTTP Authorization preference in db.
*/
void set_httpauth(const char *zHttpAuth){
  char *zKey = mprintf("http-auth:%s", g.url.canonical);
  db_set(zKey, obscure(zHttpAuth), 0);
  free(zKey);
}

/*
** Look for SSH clone command line options and setup in globals.
*/
void clone_ssh_find_options(void){
  const char *zSshCmd;        /* SSH command string */

  zSshCmd = find_option("ssh-command","c",1);







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







194
195
196
197
198
199
200















































201
202
203
204
205
206
207
  rebuild_db(0, 1, 0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (password is \"%s\")\n", g.zLogin, zPassword);
  db_end_transaction(0);
}
















































/*
** Look for SSH clone command line options and setup in globals.
*/
void clone_ssh_find_options(void){
  const char *zSshCmd;        /* SSH command string */

  zSshCmd = find_option("ssh-command","c",1);

Changes to src/comformat.c.

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
*/
int comment_print(const char *zText, int indent, int lineLength){
  int tlen = lineLength - indent;
  int si, sk, i, k;
  int doIndent = 0;
  char *zBuf;
  char zBuffer[400];
  int lineCnt = 0;

  if( zText==0 ) zText = "(NULL)";
  if( tlen<=0 ){
    tlen = strlen(zText);
  }
  if( tlen >= (sizeof(zBuffer)) ){
    zBuf = fossil_malloc(tlen+1);
  }else{
    zBuf = zBuffer;







|

<







32
33
34
35
36
37
38
39
40

41
42
43
44
45
46
47
*/
int comment_print(const char *zText, int indent, int lineLength){
  int tlen = lineLength - indent;
  int si, sk, i, k;
  int doIndent = 0;
  char *zBuf;
  char zBuffer[400];
  int lineCnt = 0; 


  if( tlen<=0 ){
    tlen = strlen(zText);
  }
  if( tlen >= (sizeof(zBuffer)) ){
    zBuf = fossil_malloc(tlen+1);
  }else{
    zBuf = zBuffer;

Changes to src/config.h.

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#  endif
#else
# include <sys/types.h>
# include <signal.h>
# include <pwd.h>
#endif

/*
** Utility macro to wrap an argument with double quotes.
*/
#if !defined(COMPILER_STRINGIFY)
#  define COMPILER_STRINGIFY(x)  COMPILER_STRINGIFY1(x)
#  define COMPILER_STRINGIFY1(x) #x
#endif

/*
** Define the compiler variant, used to compile the project
*/
#if !defined(COMPILER_NAME)
#  if defined(__DMC__)
#    if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#      define COMPILER_NAME "dmc-" COMPILER_VERSION
#    else
#      define COMPILER_NAME "dmc"
#    endif
#  elif defined(__POCC__)
#    if defined(_M_X64)
#      if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#        define COMPILER_NAME "pellesc64-" COMPILER_VERSION
#      else
#        define COMPILER_NAME "pellesc64"
#      endif
#    else
#      if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#        define COMPILER_NAME "pellesc32-" COMPILER_VERSION
#      else
#        define COMPILER_NAME "pellesc32"
#      endif
#    endif
#  elif defined(_MSC_VER)
#    if !defined(COMPILER_VERSION)
#      define COMPILER_VERSION COMPILER_STRINGIFY(_MSC_VER)
#    endif
#    if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#      define COMPILER_NAME "msc-" COMPILER_VERSION
#    else
#      define COMPILER_NAME "msc"
#    endif
#  elif defined(__MINGW32__)
#    if !defined(COMPILER_VERSION)
#      if defined(__MINGW_VERSION)
#        if defined(__GNUC__)
#          if defined(__VERSION__)
#            define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc-" __VERSION__
#          else
#            define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION) "-gcc"
#          endif
#        else
#          define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW_VERSION)
#        endif
#      elif defined(__MINGW32_VERSION)
#        if defined(__GNUC__)
#          if defined(__VERSION__)
#            define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc-" __VERSION__
#          else
#            define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION) "-gcc"
#          endif
#        else
#          define COMPILER_VERSION COMPILER_STRINGIFY(__MINGW32_VERSION)
#        endif
#      endif
#    endif
#    if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#      define COMPILER_NAME "mingw32-" COMPILER_VERSION
#    else
#      define COMPILER_NAME "mingw32"
#    endif
#  elif defined(_WIN32)
#    define COMPILER_NAME "win32"
#  elif defined(__GNUC__)
#    if !defined(COMPILER_VERSION)
#      if defined(__VERSION__)
#        define COMPILER_VERSION __VERSION__
#      endif
#    endif
#    if defined(COMPILER_VERSION) && !defined(NO_COMPILER_VERSION)
#      define COMPILER_NAME "gcc-" COMPILER_VERSION
#    else
#      define COMPILER_NAME "gcc"
#    endif
#  else
#    define COMPILER_NAME "unknown"
#  endif
#endif

#if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION)








<
<
<
<
<
<
<
<





<
<
<
|
<


<
<
<
|
<

<
<
<
|
<


<
<
<
<
<
<
|
<

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<



<
<
<
<
<
<
<
<
|
<







60
61
62
63
64
65
66








67
68
69
70
71



72

73
74



75

76



77

78
79






80

81


























82

83
84
85








86

87
88
89
90
91
92
93
#  endif
#else
# include <sys/types.h>
# include <signal.h>
# include <pwd.h>
#endif









/*
** Define the compiler variant, used to compile the project
*/
#if !defined(COMPILER_NAME)
#  if defined(__DMC__)



#    define COMPILER_NAME "dmc"

#  elif defined(__POCC__)
#    if defined(_M_X64)



#      define COMPILER_NAME "pellesc64"

#    else



#      define COMPILER_NAME "pellesc32"

#    endif
#  elif defined(_MSC_VER)






#    define COMPILER_NAME "msc"

#  elif defined(__MINGW32__)


























#    define COMPILER_NAME "mingw32"

#  elif defined(_WIN32)
#    define COMPILER_NAME "win32"
#  elif defined(__GNUC__)








#    define COMPILER_NAME "gcc-" __VERSION__

#  else
#    define COMPILER_NAME "unknown"
#  endif
#endif

#if !defined(_RC_COMPILE_) && !defined(SQLITE_AMALGAMATION)

184
185
186
187
188
189
190
191
192
193
194
195
196
197
198

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type.
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
*/
#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define FOSSIL_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define FOSSIL_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))







|







121
122
123
124
125
126
127
128
129
130
131
132
133
134
135

/*
** The following macros are used to cast pointers to integers and
** integers to pointers.  The way you do this varies from one compiler
** to the next, so we have developed the following set of #if statements
** to generate appropriate macros for a wide range of compilers.
**
** The correct "ANSI" way to do this is to use the intptr_t type. 
** Unfortunately, that typedef is not available on all compilers, or
** if it is available, it requires an #include of specific headers
** that vary from one machine to the next.
*/
#if defined(__PTRDIFF_TYPE__)  /* This case should work for GCC */
# define FOSSIL_INT_TO_PTR(X)  ((void*)(__PTRDIFF_TYPE__)(X))
# define FOSSIL_PTR_TO_INT(X)  ((int)(__PTRDIFF_TYPE__)(X))

Changes to src/configure.c.

922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
      usage(mprintf("%s AREA ?URL?", zMethod));
    }
    mask = configure_name_to_mask(g.argv[3], 1);
    if( g.argc==5 ){
      zServer = g.argv[4];
    }
    url_parse(zServer, URL_PROMPT_PW);
    if( g.url.protocol==0 ) fossil_fatal("no server URL specified");
    user_select();
    url_enable_proxy("via proxy: ");
    if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT;
    if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,(unsigned)mask);
    }else if( strncmp(zMethod, "pull", n)==0 ){







|







922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
      usage(mprintf("%s AREA ?URL?", zMethod));
    }
    mask = configure_name_to_mask(g.argv[3], 1);
    if( g.argc==5 ){
      zServer = g.argv[4];
    }
    url_parse(zServer, URL_PROMPT_PW);
    if( g.urlProtocol==0 ) fossil_fatal("no server URL specified");
    user_select();
    url_enable_proxy("via proxy: ");
    if( legacyFlag ) mask |= CONFIGSET_OLDFORMAT;
    if( overwriteFlag ) mask |= CONFIGSET_OVERWRITE;
    if( strncmp(zMethod, "push", n)==0 ){
      client_sync(0,0,(unsigned)mask);
    }else if( strncmp(zMethod, "pull", n)==0 ){

Changes to src/content.c.

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  bag_clear(&contentCache.available);
  bag_clear(&contentCache.inCache);
  contentCache.n = 0;
  contentCache.szTotal = 0;
}

/*
** Return the srcid associated with rid.  Or return 0 if rid is
** original content and not a delta.
*/
static int findSrcid(int rid){
  static Stmt q;
  int srcid;
  db_static_prepare(&q, "SELECT srcid FROM delta WHERE rid=:rid");
  db_bind_int(&q, ":rid", rid);







|







112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  bag_clear(&contentCache.available);
  bag_clear(&contentCache.inCache);
  contentCache.n = 0;
  contentCache.szTotal = 0;
}

/*
** Return the srcid associated with rid.  Or return 0 if rid is 
** original content and not a delta.
*/
static int findSrcid(int rid){
  static Stmt q;
  int srcid;
  db_static_prepare(&q, "SELECT srcid FROM delta WHERE rid=:rid");
  db_bind_int(&q, ":rid", rid);
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
** Check to see if content is available for artifact "rid".  Return
** true if it is.  Return false if rid is a phantom or depends on
** a phantom.
*/
int content_is_available(int rid){
  int srcid;
  int depth = 0;  /* Limit to recursion depth */
  while( depth++ < 10000000 ){
    if( bag_find(&contentCache.missing, rid) ){
      return 0;
    }
    if( bag_find(&contentCache.available, rid) ){
      return 1;
    }
    if( content_size(rid, -1)<0 ){







|







152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
** Check to see if content is available for artifact "rid".  Return
** true if it is.  Return false if rid is a phantom or depends on
** a phantom.
*/
int content_is_available(int rid){
  int srcid;
  int depth = 0;  /* Limit to recursion depth */
  while( depth++ < 10000000 ){  
    if( bag_find(&contentCache.missing, rid) ){
      return 0;
    }
    if( bag_find(&contentCache.available, rid) ){
      return 1;
    }
    if( content_size(rid, -1)<0 ){
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
      db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
    }

    /* Recursively dephantomize all artifacts that are derived by
    ** delta from artifact rid and which have not already been
    ** cross-linked.  */
    nChildUsed = 0;
    db_prepare(&q,
       "SELECT rid FROM delta"
       " WHERE srcid=%d"
       "   AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)",
       rid
    );
    while( db_step(&q)==SQLITE_ROW ){
      int child = db_column_int(&q, 0);







|







414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
      db_multi_exec("DELETE FROM orphan WHERE baseline=%d", rid);
    }

    /* Recursively dephantomize all artifacts that are derived by
    ** delta from artifact rid and which have not already been
    ** cross-linked.  */
    nChildUsed = 0;
    db_prepare(&q, 
       "SELECT rid FROM delta"
       " WHERE srcid=%d"
       "   AND NOT EXISTS(SELECT 1 FROM mlink WHERE mid=delta.rid)",
       rid
    );
    while( db_step(&q)==SQLITE_ROW ){
      int child = db_column_int(&q, 0);
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
}

/*
** Write content into the database.  Return the record ID.  If the
** content is already in the database, just return the record ID.
**
** If srcId is specified, then pBlob is delta content from
** the srcId record.  srcId might be a phantom.
**
** pBlob is normally uncompressed text.  But if nBlob>0 then the
** pBlob value has already been compressed and nBlob is its uncompressed
** size.  If nBlob>0 then zUuid must be valid.
**
** zUuid is the UUID of the artifact, if it is specified.  When srcId is
** specified then zUuid must always be specified.  If srcId is zero,







|







453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
}

/*
** Write content into the database.  Return the record ID.  If the
** content is already in the database, just return the record ID.
**
** If srcId is specified, then pBlob is delta content from
** the srcId record.  srcId might be a phantom.  
**
** pBlob is normally uncompressed text.  But if nBlob>0 then the
** pBlob value has already been compressed and nBlob is its uncompressed
** size.  If nBlob>0 then zUuid must be valid.
**
** zUuid is the UUID of the artifact, if it is specified.  When srcId is
** specified then zUuid must always be specified.  If srcId is zero,
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
  int size;
  int rid;
  Stmt s1;
  Blob cmpr;
  Blob hash;
  int markAsUnclustered = 0;
  int isDephantomize = 0;

  assert( g.repositoryOpen );
  assert( pBlob!=0 );
  assert( srcId==0 || zUuid!=0 );
  if( zUuid==0 ){
    assert( nBlob==0 );
    sha1sum_blob(pBlob, &hash);
  }else{







|







484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
  int size;
  int rid;
  Stmt s1;
  Blob cmpr;
  Blob hash;
  int markAsUnclustered = 0;
  int isDephantomize = 0;
  
  assert( g.repositoryOpen );
  assert( pBlob!=0 );
  assert( srcId==0 || zUuid!=0 );
  if( zUuid==0 ){
    assert( nBlob==0 );
    sha1sum_blob(pBlob, &hash);
  }else{
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599

  /* If the srcId is specified, then the data we just added is
  ** really a delta.  Record this fact in the delta table.
  */
  if( srcId ){
    db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId);
  }
  if( !isDephantomize && bag_find(&contentCache.missing, rid) &&
      (srcId==0 || content_is_available(srcId)) ){
    content_mark_available(rid);
  }
  if( isDephantomize ){
    after_dephantomize(rid, 0);
  }

  /* Add the element to the unclustered table if has never been
  ** previously seen.
  */
  if( markAsUnclustered ){
    db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d)", rid);
  }








|






|







578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599

  /* If the srcId is specified, then the data we just added is
  ** really a delta.  Record this fact in the delta table.
  */
  if( srcId ){
    db_multi_exec("REPLACE INTO delta(rid,srcid) VALUES(%d,%d)", rid, srcId);
  }
  if( !isDephantomize && bag_find(&contentCache.missing, rid) && 
      (srcId==0 || content_is_available(srcId)) ){
    content_mark_available(rid);
  }
  if( isDephantomize ){
    after_dephantomize(rid, 0);
  }
  
  /* Add the element to the unclustered table if has never been
  ** previously seen.
  */
  if( markAsUnclustered ){
    db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d)", rid);
  }

626
627
628
629
630
631
632
633
634
635
636
637
638
639
640

/*
** Create a new phantom with the given UUID and return its artifact ID.
*/
int content_new(const char *zUuid, int isPrivate){
  int rid;
  static Stmt s1, s2, s3;

  assert( g.repositoryOpen );
  db_begin_transaction();
  if( uuid_is_shunned(zUuid) ){
    db_end_transaction(0);
    return 0;
  }
  db_static_prepare(&s1,







|







626
627
628
629
630
631
632
633
634
635
636
637
638
639
640

/*
** Create a new phantom with the given UUID and return its artifact ID.
*/
int content_new(const char *zUuid, int isPrivate){
  int rid;
  static Stmt s1, s2, s3;
  
  assert( g.repositoryOpen );
  db_begin_transaction();
  if( uuid_is_shunned(zUuid) ){
    db_end_transaction(0);
    return 0;
  }
  db_static_prepare(&s1,
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
  int rc;
  db_static_prepare(&s1,
    "SELECT 1 FROM private WHERE rid=:rid"
  );
  db_bind_int(&s1, ":rid", rid);
  rc = db_step(&s1);
  db_reset(&s1);
  return rc==SQLITE_ROW;
}

/*
** Make sure an artifact is public.
*/
void content_make_public(int rid){
  static Stmt s1;
  db_static_prepare(&s1,
    "DELETE FROM private WHERE rid=:rid"
  );
  db_bind_int(&s1, ":rid", rid);







|



|







725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
  int rc;
  db_static_prepare(&s1,
    "SELECT 1 FROM private WHERE rid=:rid"
  );
  db_bind_int(&s1, ":rid", rid);
  rc = db_step(&s1);
  db_reset(&s1);
  return rc==SQLITE_ROW;  
}

/*
** Make sure an artifact is public.  
*/
void content_make_public(int rid){
  static Stmt s1;
  db_static_prepare(&s1,
    "DELETE FROM private WHERE rid=:rid"
  );
  db_bind_int(&s1, ":rid", rid);
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
** the source of the delta.  It is OK to delta private->private and
** public->private and public->public.  Just no private->public delta.
**
** If srcid is a delta that depends on rid, then srcid is
** converted to undeltaed text.
**
** If either rid or srcid contain less than 50 bytes, or if the
** resulting delta does not achieve a compression of at least 25%
** the rid is left untouched.
**
** Return 1 if a delta is made and 0 if no delta occurs.
*/
int content_deltify(int rid, int srcid, int force){
  int s;
  Blob data, src, delta;







|







756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
** the source of the delta.  It is OK to delta private->private and
** public->private and public->public.  Just no private->public delta.
**
** If srcid is a delta that depends on rid, then srcid is
** converted to undeltaed text.
**
** If either rid or srcid contain less than 50 bytes, or if the
** resulting delta does not achieve a compression of at least 25% 
** the rid is left untouched.
**
** Return 1 if a delta is made and 0 if no delta occurs.
*/
int content_deltify(int rid, int srcid, int force){
  int s;
  Blob data, src, delta;
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
    fossil_print(
      "public artifact %S (%d) is a delta from private artifact %S (%d)\n",
      zId, rid, zSrc, srcid
    );
    nErr++;
  }
  db_finalize(&q);

  db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid");
  total = db_int(0, "SELECT max(rid) FROM blob");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    int size = db_column_int(&q, 2);
    n1++;







|







881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
    fossil_print(
      "public artifact %S (%d) is a delta from private artifact %S (%d)\n",
      zId, rid, zSrc, srcid
    );
    nErr++;
  }
  db_finalize(&q);
    
  db_prepare(&q, "SELECT rid, uuid, size FROM blob ORDER BY rid");
  total = db_int(0, "SELECT max(rid) FROM blob");
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    const char *zUuid = db_column_text(&q, 1);
    int size = db_column_int(&q, 2);
    n1++;
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
    blob_reset(&cksum);
    n2++;
  }
  db_finalize(&q);
  fossil_print("%d non-phantom blobs (out of %d total) checked:  %d errors\n",
               n2, n1, nErr);
  if( bParse ){
    static const char *const azType[] = { 0, "manifest", "cluster",
        "control", "wiki", "ticket", "attachment", "event" };
    int i;
    fossil_print("%d total control artifacts\n", nCA);
    for(i=1; i<count(azType); i++){
      if( anCA[i] ) fossil_print("  %d %ss\n", anCA[i], azType[i]);
    }
  }
}







|
|







943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
    blob_reset(&cksum);
    n2++;
  }
  db_finalize(&q);
  fossil_print("%d non-phantom blobs (out of %d total) checked:  %d errors\n",
               n2, n1, nErr);
  if( bParse ){
    const char *azType[] = { 0, "manifest", "cluster", "control", "wiki",
                             "ticket", "attachment", "event" };
    int i;
    fossil_print("%d total control artifacts\n", nCA);
    for(i=1; i<count(azType); i++){
      if( anCA[i] ) fossil_print("  %d %ss\n", anCA[i], azType[i]);
    }
  }
}
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
    fossil_print("%s: %s\n         %s %s %S (%d) %s\n",
                  zErrType, zUuid, zRole, zCFType, zSrc, p->rid, zDate);
    if( zDetail && zDetail[0] ){
      fossil_print("         %s\n", zDetail);
    }
    fossil_free(zSrc);
    fossil_free(zDate);
    rc = 1;
  }
  return rc;
}

/*
** COMMAND: test-missing
**







|







1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
    fossil_print("%s: %s\n         %s %s %S (%d) %s\n",
                  zErrType, zUuid, zRole, zCFType, zSrc, p->rid, zDate);
    if( zDetail && zDetail[0] ){
      fossil_print("         %s\n", zDetail);
    }
    fossil_free(zSrc);
    fossil_free(zDate);
    rc = 1; 
  }
  return rc;
}

/*
** COMMAND: test-missing
**
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
    content_get(rid, &content);
    p = manifest_parse(&content, rid, 0);
    if( p ){
      nArtifact++;
      nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0);
      nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0);
      for(i=0; i<p->nFile; i++){
        nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of",
                             p->aFile[i].zName);
      }
      for(i=0; i<p->nParent; i++){
        nErr += check_exists(p->azParent[i], flags, p, "parent of", 0);
      }
      for(i=0; i<p->nCherrypick; i++){
        nErr +=  check_exists(p->aCherrypick[i].zCPTarget+1, flags, p,
                              "cherry-pick target of", 0);
        nErr +=  check_exists(p->aCherrypick[i].zCPBase, flags, p,
                              "cherry-pick baseline of", 0);
      }
      for(i=0; i<p->nCChild; i++){
        nErr += check_exists(p->azCChild[i], flags, p, "in", 0);
      }
      for(i=0; i<p->nTag; i++){
        nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0);
      }
      manifest_destroy(p);
    }
  }
  db_finalize(&q);
  if( nErr>0 || quietFlag==0 ){
    fossil_print("%d missing or shunned references in %d control artifacts\n",
                 nErr, nArtifact);
  }
}







|

















|








1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
    content_get(rid, &content);
    p = manifest_parse(&content, rid, 0);
    if( p ){
      nArtifact++;
      nErr += check_exists(p->zBaseline, flags, p, "baseline of", 0);
      nErr += check_exists(p->zAttachSrc, flags, p, "file of", 0);
      for(i=0; i<p->nFile; i++){
        nErr += check_exists(p->aFile[i].zUuid, flags, p, "file of", 
                             p->aFile[i].zName);
      }
      for(i=0; i<p->nParent; i++){
        nErr += check_exists(p->azParent[i], flags, p, "parent of", 0);
      }
      for(i=0; i<p->nCherrypick; i++){
        nErr +=  check_exists(p->aCherrypick[i].zCPTarget+1, flags, p,
                              "cherry-pick target of", 0);
        nErr +=  check_exists(p->aCherrypick[i].zCPBase, flags, p,
                              "cherry-pick baseline of", 0);
      }
      for(i=0; i<p->nCChild; i++){
        nErr += check_exists(p->azCChild[i], flags, p, "in", 0);
      }
      for(i=0; i<p->nTag; i++){
        nErr += check_exists(p->aTag[i].zUuid, flags, p, "target of", 0);
      }
      manifest_destroy(p);      
    }
  }
  db_finalize(&q);
  if( nErr>0 || quietFlag==0 ){
    fossil_print("%d missing or shunned references in %d control artifacts\n",
                 nErr, nArtifact);
  }
}

Changes to src/db.c.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#if INTERFACE
/*
** An single SQL statement is represented as an instance of the following
** structure.
*/
struct Stmt {
  Blob sql;               /* The SQL for this statement */
  sqlite3_stmt *pStmt;    /* The results of sqlite3_prepare_v2() */
  Stmt *pNext, *pPrev;    /* List of all unfinalized statements */
  int nStep;              /* Number of sqlite3_step() calls */
};

/*
** Copy this to initialize a Stmt object to a clean/empty state. This
** is useful to help avoid assertions when performing cleanup in some







|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#if INTERFACE
/*
** An single SQL statement is represented as an instance of the following
** structure.
*/
struct Stmt {
  Blob sql;               /* The SQL for this statement */
  sqlite3_stmt *pStmt;    /* The results of sqlite3_prepare() */
  Stmt *pNext, *pPrev;    /* List of all unfinalized statements */
  int nStep;              /* Number of sqlite3_step() calls */
};

/*
** Copy this to initialize a Stmt object to a clean/empty state. This
** is useful to help avoid assertions when performing cleanup in some
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
** the following structure.
*/
static struct DbLocalData {
  int nBegin;               /* Nesting depth of BEGIN */
  int doRollback;           /* True to force a rollback */
  int nCommitHook;          /* Number of commit hooks */
  Stmt *pAllStmt;           /* List of all unfinalized statements */
  int nPrepare;             /* Number of calls to sqlite3_prepare_v2() */
  int nDeleteOnFail;        /* Number of entries in azDeleteOnFail[] */
  struct sCommitHook {
    int (*xHook)(void);         /* Functions to call at db_end_transaction() */
    int sequence;               /* Call functions in sequence order */
  } aHook[5];
  char *azDeleteOnFail[3];  /* Files to delete on a failure */
  char *azBeforeCommit[5];  /* Commands to run prior to COMMIT */







|







106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
** the following structure.
*/
static struct DbLocalData {
  int nBegin;               /* Nesting depth of BEGIN */
  int doRollback;           /* True to force a rollback */
  int nCommitHook;          /* Number of commit hooks */
  Stmt *pAllStmt;           /* List of all unfinalized statements */
  int nPrepare;             /* Number of calls to sqlite3_prepare() */
  int nDeleteOnFail;        /* Number of entries in azDeleteOnFail[] */
  struct sCommitHook {
    int (*xHook)(void);         /* Functions to call at db_end_transaction() */
    int sequence;               /* Call functions in sequence order */
  } aHook[5];
  char *azDeleteOnFail[3];  /* Files to delete on a failure */
  char *azBeforeCommit[5];  /* Commands to run prior to COMMIT */
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
int db_bind_double(Stmt *pStmt, const char *zParamName, double rValue){
  return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue);
}
int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
  return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                           -1, SQLITE_STATIC);
}
int db_bind_text16(Stmt *pStmt, const char *zParamName, const char *zValue){
  return sqlite3_bind_text16(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                             -1, SQLITE_STATIC);
}
int db_bind_null(Stmt *pStmt, const char *zParamName){
  return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}
int db_bind_blob(Stmt *pStmt, const char *zParamName, Blob *pBlob){
  return sqlite3_bind_blob(pStmt->pStmt, paramIdx(pStmt, zParamName),
                          blob_buffer(pBlob), blob_size(pBlob), SQLITE_STATIC);
}







<
<
<
<







314
315
316
317
318
319
320




321
322
323
324
325
326
327
int db_bind_double(Stmt *pStmt, const char *zParamName, double rValue){
  return sqlite3_bind_double(pStmt->pStmt, paramIdx(pStmt, zParamName), rValue);
}
int db_bind_text(Stmt *pStmt, const char *zParamName, const char *zValue){
  return sqlite3_bind_text(pStmt->pStmt, paramIdx(pStmt, zParamName), zValue,
                           -1, SQLITE_STATIC);
}




int db_bind_null(Stmt *pStmt, const char *zParamName){
  return sqlite3_bind_null(pStmt->pStmt, paramIdx(pStmt, zParamName));
}
int db_bind_blob(Stmt *pStmt, const char *zParamName, Blob *pBlob){
  return sqlite3_bind_blob(pStmt->pStmt, paramIdx(pStmt, zParamName),
                          blob_buffer(pBlob), blob_size(pBlob), SQLITE_STATIC);
}
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
** Open a database file.  Return a pointer to the new database
** connection.  An error results in process abort.
*/
LOCAL sqlite3 *db_open(const char *zDbName){
  int rc;
  sqlite3 *db;

#if defined(__CYGWIN__) && USE_SYSTEM_SQLITE+0!=1
  zDbName = fossil_utf8_to_filename(zDbName);
#endif
  if( g.fSqlTrace ) fossil_trace("-- sqlite3_open: [%s]\n", zDbName);
  rc = sqlite3_open_v2(
       zDbName, &db,
       SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
       g.zVfsName







|







709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
** Open a database file.  Return a pointer to the new database
** connection.  An error results in process abort.
*/
LOCAL sqlite3 *db_open(const char *zDbName){
  int rc;
  sqlite3 *db;

#if defined(__CYGWIN__)
  zDbName = fossil_utf8_to_filename(zDbName);
#endif
  if( g.fSqlTrace ) fossil_trace("-- sqlite3_open: [%s]\n", zDbName);
  rc = sqlite3_open_v2(
       zDbName, &db,
       SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
       g.zVfsName
829
830
831
832
833
834
835



836
837
838
839
840
841
842
843

844
845
846

847
848
849
850
851
852
853
  if( file_isdir(zHome)!=1 ){
    fossil_fatal("invalid home directory: %s", zHome);
  }
#if defined(_WIN32) || defined(__CYGWIN__)
  /* . filenames give some window systems problems and many apps problems */
  zDbName = mprintf("%//_fossil", zHome);
#else



  zDbName = mprintf("%s/.fossil", zHome);
#endif
  if( file_size(zDbName)<1024*3 ){
    if( file_access(zHome, W_OK) ){
      fossil_fatal("home directory %s must be writeable", zHome);
    }
    db_init_database(zDbName, zConfigSchema, (char*)0);
  }

  if( file_access(zDbName, W_OK) ){
    fossil_fatal("configuration file %s must be writeable", zDbName);
  }

  if( useAttach ){
    db_open_or_attach(zDbName, "configdb", &g.useAttach);
    g.dbConfig = 0;
    g.zConfigDbType = 0;
  }else{
    g.useAttach = 0;
    g.dbConfig = db_open(zDbName);







>
>
>



<
<
<


>



>







825
826
827
828
829
830
831
832
833
834
835
836
837



838
839
840
841
842
843
844
845
846
847
848
849
850
851
  if( file_isdir(zHome)!=1 ){
    fossil_fatal("invalid home directory: %s", zHome);
  }
#if defined(_WIN32) || defined(__CYGWIN__)
  /* . filenames give some window systems problems and many apps problems */
  zDbName = mprintf("%//_fossil", zHome);
#else
  if( file_access(zHome, W_OK) ){
    fossil_fatal("home directory %s must be writeable", zHome);
  }
  zDbName = mprintf("%s/.fossil", zHome);
#endif
  if( file_size(zDbName)<1024*3 ){



    db_init_database(zDbName, zConfigSchema, (char*)0);
  }
#if defined(_WIN32) || defined(__CYGWIN__)
  if( file_access(zDbName, W_OK) ){
    fossil_fatal("configuration file %s must be writeable", zDbName);
  }
#endif
  if( useAttach ){
    db_open_or_attach(zDbName, "configdb", &g.useAttach);
    g.dbConfig = 0;
    g.zConfigDbType = 0;
  }else{
    g.useAttach = 0;
    g.dbConfig = db_open(zDbName);
1020
1021
1022
1023
1024
1025
1026



1027

1028
1029
1030
1031
1032
1033
1034
    }else{
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
#endif
      fossil_panic("not a valid repository: %s", zDbName);
    }
  }



  g.zRepositoryName = mprintf("%s", zDbName);

  db_open_or_attach(g.zRepositoryName, "repository", 0);
  g.repositoryOpen = 1;
  /* Cache "allow-symlinks" option, because we'll need it on every stat call */
  g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
}

/*







>
>
>

>







1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
    }else{
#ifdef FOSSIL_ENABLE_JSON
      g.json.resultCode = FSL_JSON_E_DB_NOT_VALID;
#endif
      fossil_panic("not a valid repository: %s", zDbName);
    }
  }
#if defined(__CYGWIN__)
  g.zRepositoryName = fossil_utf8_to_filename(zDbName);
#else
  g.zRepositoryName = mprintf("%s", zDbName);
#endif
  db_open_or_attach(g.zRepositoryName, "repository", 0);
  g.repositoryOpen = 1;
  /* Cache "allow-symlinks" option, because we'll need it on every stat call */
  g.allowSymlinks = db_get_boolean("allow-symlinks", 0);
}

/*
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
** Open a connection to the local repository in FILENAME.  A checkout
** for the repository is created with its root at the working directory.
** If VERSION is specified then that version is checked out.  Otherwise
** the latest version is checked out.  No files other than "manifest"
** and "manifest.uuid" are modified if the --keep option is present.
**
** Options:
**   --empty    Initialize checkout as being empty, but still connected
**              with the local repository. If you commit this checkout,
**              it will become a new "initial" commit in the repository.
**   --keep     Only modify the manifest and manifest.uuid files
**   --nested   Allow opening a repository inside an opened checkout
**
** See also: close
*/
void cmd_open(void){
  int emptyFlag;
  int keepFlag;
  int allowNested;
  char **oldArgv;
  int oldArgc;
  static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0 };

  url_proxy_options();
  emptyFlag = find_option("empty",0,0)!=0;
  keepFlag = find_option("keep",0,0)!=0;
  allowNested = find_option("nested",0,0)!=0;
  if( g.argc!=3 && g.argc!=4 ){
    usage("REPOSITORY-FILENAME ?VERSION?");
  }
  if( !allowNested && db_open_local(0) ){
    fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot);







<
<
<






<







<







1985
1986
1987
1988
1989
1990
1991



1992
1993
1994
1995
1996
1997

1998
1999
2000
2001
2002
2003
2004

2005
2006
2007
2008
2009
2010
2011
** Open a connection to the local repository in FILENAME.  A checkout
** for the repository is created with its root at the working directory.
** If VERSION is specified then that version is checked out.  Otherwise
** the latest version is checked out.  No files other than "manifest"
** and "manifest.uuid" are modified if the --keep option is present.
**
** Options:



**   --keep     Only modify the manifest and manifest.uuid files
**   --nested   Allow opening a repository inside an opened checkout
**
** See also: close
*/
void cmd_open(void){

  int keepFlag;
  int allowNested;
  char **oldArgv;
  int oldArgc;
  static char *azNewArgv[] = { 0, "checkout", "--prompt", 0, 0, 0 };

  url_proxy_options();

  keepFlag = find_option("keep",0,0)!=0;
  allowNested = find_option("nested",0,0)!=0;
  if( g.argc!=3 && g.argc!=4 ){
    usage("REPOSITORY-FILENAME ?VERSION?");
  }
  if( !allowNested && db_open_local(0) ){
    fossil_fatal("already within an open tree rooted at %s", g.zLocalRoot);
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
  db_lset("repository", g.argv[2]);
  db_record_repository_filename(g.argv[2]);
  db_lset_int("checkout", 0);
  oldArgv = g.argv;
  oldArgc = g.argc;
  azNewArgv[0] = g.argv[0];
  g.argv = azNewArgv;
  if( !emptyFlag){
    g.argc = 3;
    if( oldArgc==4 ){
      azNewArgv[g.argc-1] = oldArgv[3];
    }else if( !db_exists("SELECT 1 FROM event WHERE type='ci'") ){
      azNewArgv[g.argc-1] = "--latest";
    }else{
      azNewArgv[g.argc-1] = db_get("main-branch", "trunk");
    }
    if( keepFlag ){
      azNewArgv[g.argc++] = "--keep";
    }
    checkout_cmd();
  }
  g.argc = 2;
  info_cmd();
}

/*
** Print the value of a setting named zName
*/







<
|
|
|
|
|
|
|
|
|
|
|
|
<







2026
2027
2028
2029
2030
2031
2032

2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044

2045
2046
2047
2048
2049
2050
2051
  db_lset("repository", g.argv[2]);
  db_record_repository_filename(g.argv[2]);
  db_lset_int("checkout", 0);
  oldArgv = g.argv;
  oldArgc = g.argc;
  azNewArgv[0] = g.argv[0];
  g.argv = azNewArgv;

  g.argc = 3;
  if( oldArgc==4 ){
    azNewArgv[g.argc-1] = oldArgv[3];
  }else if( !db_exists("SELECT 1 FROM event WHERE type='ci'") ){
    azNewArgv[g.argc-1] = "--latest";
  }else{
    azNewArgv[g.argc-1] = db_get("main-branch", "trunk");
  }
  if( keepFlag ){
    azNewArgv[g.argc++] = "--keep";
  }
  checkout_cmd();

  g.argc = 2;
  info_cmd();
}

/*
** Print the value of a setting named zName
*/
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
  { "http-port",        0,             16, 0, 0, "8080"                },
  { "https-login",      0,              0, 0, 0, "off"                 },
  { "ignore-glob",      0,             40, 1, 0, ""                    },
  { "keep-glob",        0,             40, 1, 0, ""                    },
  { "localauth",        0,              0, 0, 0, "off"                 },
  { "main-branch",      0,             40, 0, 0, "trunk"               },
  { "manifest",         0,              0, 1, 0, "off"                 },
  { "max-loadavg",      0,             25, 0, 0, "0.0"                 },
  { "max-upload",       0,             25, 0, 0, "250000"              },
  { "mtime-changes",    0,              0, 0, 0, "on"                  },
  { "pgp-command",      0,             40, 0, 0, "gpg --clearsign -o " },
  { "proxy",            0,             32, 0, 0, "off"                 },
  { "relative-paths",   0,              0, 0, 0, "on"                  },
  { "repo-cksum",       0,              0, 0, 0, "on"                  },
  { "self-register",    0,              0, 0, 0, "off"                 },







<







2136
2137
2138
2139
2140
2141
2142

2143
2144
2145
2146
2147
2148
2149
  { "http-port",        0,             16, 0, 0, "8080"                },
  { "https-login",      0,              0, 0, 0, "off"                 },
  { "ignore-glob",      0,             40, 1, 0, ""                    },
  { "keep-glob",        0,             40, 1, 0, ""                    },
  { "localauth",        0,              0, 0, 0, "off"                 },
  { "main-branch",      0,             40, 0, 0, "trunk"               },
  { "manifest",         0,              0, 1, 0, "off"                 },

  { "max-upload",       0,             25, 0, 0, "250000"              },
  { "mtime-changes",    0,              0, 0, 0, "on"                  },
  { "pgp-command",      0,             40, 0, 0, "gpg --clearsign -o " },
  { "proxy",            0,             32, 0, 0, "off"                 },
  { "relative-paths",   0,              0, 0, 0, "on"                  },
  { "repo-cksum",       0,              0, 0, 0, "on"                  },
  { "self-register",    0,              0, 0, 0, "off"                 },
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
**
**    main-branch      The primary branch for the project.  Default: trunk
**
**    manifest         If enabled, automatically create files "manifest" and
**     (versionable)   "manifest.uuid" in every checkout.  The SQLite and
**                     Fossil repositories both require this.  Default: off.
**
**    max-loadavg      Some CPU-intensive web pages (ex: /zip, /tarball, /blame)
**                     are disallowed if the system load average goes above this
**                     value.  "0.0" means no limit.  This only works on unix.
**                     Only local settings of this value make a difference since
**                     when running as a web-server, Fossil does not open the
**                     global configuration database.
**
**    max-upload       A limit on the size of uplink HTTP requests.  The
**                     default is 250000 bytes.
**
**    mtime-changes    Use file modification times (mtimes) to detect when
**                     files have been modified.  (Default "on".)
**
**    pgp-command      Command used to clear-sign manifests at check-in.







<
<
<
<
<
<
<







2286
2287
2288
2289
2290
2291
2292







2293
2294
2295
2296
2297
2298
2299
**
**    main-branch      The primary branch for the project.  Default: trunk
**
**    manifest         If enabled, automatically create files "manifest" and
**     (versionable)   "manifest.uuid" in every checkout.  The SQLite and
**                     Fossil repositories both require this.  Default: off.
**







**    max-upload       A limit on the size of uplink HTTP requests.  The
**                     default is 250000 bytes.
**
**    mtime-changes    Use file modification times (mtimes) to detect when
**                     files have been modified.  (Default "on".)
**
**    pgp-command      Command used to clear-sign manifests at check-in.

Changes to src/delta.c.

465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
    *(zDelta++) = ':';
    memcpy(zDelta, &zOut[base], lenOut-base);
    zDelta += lenOut-base;
  }
  /* Output the final checksum record. */
  putInt(checksum(zOut, lenOut), &zDelta);
  *(zDelta++) = ';';
  fossil_free(collide);
  return zDelta - zOrigDelta; 
}

/*
** Return the size (in bytes) of the output from applying
** a delta. 
**







|







465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
    *(zDelta++) = ':';
    memcpy(zDelta, &zOut[base], lenOut-base);
    zDelta += lenOut-base;
  }
  /* Output the final checksum record. */
  putInt(checksum(zOut, lenOut), &zDelta);
  *(zDelta++) = ';';
  free(collide);
  return zDelta - zOrigDelta; 
}

/*
** Return the size (in bytes) of the output from applying
** a delta. 
**

Changes to src/descendants.c.

155
156
157
158
159
160
161




































162
163
164
165
166
167
168
}

/*
** Load the record ID rid and up to N-1 closest ancestors into
** the "ok" table.
*/
void compute_ancestors(int rid, int N, int directOnly){




































  db_multi_exec(
    "WITH RECURSIVE "
    "  ancestor(rid, mtime) AS ("
    "    SELECT %d, mtime FROM event WHERE objid=%d "
    "    UNION "
    "    SELECT plink.pid, event.mtime"
    "      FROM ancestor, plink, event"







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
}

/*
** Load the record ID rid and up to N-1 closest ancestors into
** the "ok" table.
*/
void compute_ancestors(int rid, int N, int directOnly){
#if USE_SYSTEM_SQLITE+0==1
  if( sqlite3_libversion_number()<3008003 ){
    Bag seen;
    PQueue queue;
    Stmt ins;
    Stmt q;
    bag_init(&seen);
    pqueuex_init(&queue);
    bag_insert(&seen, rid);
    pqueuex_insert(&queue, rid, 0.0, 0);
    db_prepare(&ins, "INSERT OR IGNORE INTO ok VALUES(:rid)");
    db_prepare(&q,
      "SELECT a.pid, b.mtime FROM plink a LEFT JOIN plink b ON b.cid=a.pid"
      " WHERE a.cid=:rid %s",
      directOnly ? " AND a.isprim" : ""
    );
    while( (N--)>0 && (rid = pqueuex_extract(&queue, 0))!=0 ){
      db_bind_int(&ins, ":rid", rid);
      db_step(&ins);
      db_reset(&ins);
      db_bind_int(&q, ":rid", rid);
      while( db_step(&q)==SQLITE_ROW ){
        int pid = db_column_int(&q, 0);
        double mtime = db_column_double(&q, 1);
        if( bag_insert(&seen, pid) ){
          pqueuex_insert(&queue, pid, -mtime, 0);
        }
      }
      db_reset(&q);
    }
    bag_clear(&seen);
    pqueuex_clear(&queue);
    db_finalize(&ins);
    db_finalize(&q);
  } else
#endif
  db_multi_exec(
    "WITH RECURSIVE "
    "  ancestor(rid, mtime) AS ("
    "    SELECT %d, mtime FROM event WHERE objid=%d "
    "    UNION "
    "    SELECT plink.pid, event.mtime"
    "      FROM ancestor, plink, event"

Changes to src/diff.c.

27
28
29
30
31
32
33
34
35
36
37

38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/*
** Flag parameters to the text_diff() routine used to control the formatting
** of the diff output.
*/
#define DIFF_CONTEXT_MASK ((u64)0x0000ffff) /* Lines of context. Default if 0 */
#define DIFF_WIDTH_MASK   ((u64)0x00ff0000) /* side-by-side column width */
#define DIFF_IGNORE_EOLWS ((u64)0x01000000) /* Ignore end-of-line whitespace */
#define DIFF_IGNORE_ALLWS ((u64)0x03000000) /* Ignore all whitespace */
#define DIFF_SIDEBYSIDE   ((u64)0x04000000) /* Generate a side-by-side diff */
#define DIFF_VERBOSE      ((u64)0x08000000) /* Missing shown as empty files */
#define DIFF_BRIEF        ((u64)0x10000000) /* Show filenames only */

#define DIFF_HTML         ((u64)0x20000000) /* Render for HTML */
#define DIFF_LINENO       ((u64)0x40000000) /* Show line numbers */

#define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
#define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */
#define DIFF_STRIP_EOLCR  (((u64)0x10)<<32) /* Strip trailing CR */

/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/
#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"

#define DIFF_CANNOT_COMPUTE_SYMLINK \
    "cannot compute difference between symlink and regular file\n"

#define DIFF_TOO_MANY_CHANGES \
    "more than 10,000 changes\n"

#define DIFF_WHITESPACE_ONLY \
    "whitespace changes only\n"

/*
** Maximum length of a line in a text file, in bytes.  (2**13 = 8192 bytes)
*/
#define LENGTH_MASK_SZ  13
#define LENGTH_MASK     ((1<<LENGTH_MASK_SZ)-1)

#endif /* INTERFACE */

/*
** Information about each line of a file being diffed.
**
** The lower LENGTH_MASK_SZ bits of the hash (DLine.h) are the length
** of the line.  If any line is longer than LENGTH_MASK characters,
** the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
  const char *z;        /* The text of the line */
  unsigned int h;       /* Hash of the line */
  unsigned short indent;  /* Indent of the line. Only !=0 with -w/-Z option */
  unsigned short n;     /* number of bytes */
  unsigned int iNext;   /* 1+(Index of next line with same the same hash) */

  /* an array of DLine elements serves two purposes.  The fields
  ** above are one per line of input text.  But each entry is also
  ** a bucket in a hash table, as follows: */
  unsigned int iHash;   /* 1+(first entry in the hash chain) */
};

/*
** Length of a dline
*/
#define LENGTH(X)   ((X)->n)

/*
** A context for running a raw diff.
**
** The aEdit[] array describes the raw diff.  Each triple of integers in
** aEdit[] means:
**







<
|
|
|
>
|
|
>




<














<
<
<



















<
<











|







27
28
29
30
31
32
33

34
35
36
37
38
39
40
41
42
43
44

45
46
47
48
49
50
51
52
53
54
55
56
57
58



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*
** Flag parameters to the text_diff() routine used to control the formatting
** of the diff output.
*/
#define DIFF_CONTEXT_MASK ((u64)0x0000ffff) /* Lines of context. Default if 0 */
#define DIFF_WIDTH_MASK   ((u64)0x00ff0000) /* side-by-side column width */
#define DIFF_IGNORE_EOLWS ((u64)0x01000000) /* Ignore end-of-line whitespace */

#define DIFF_SIDEBYSIDE   ((u64)0x02000000) /* Generate a side-by-side diff */
#define DIFF_VERBOSE      ((u64)0x04000000) /* Missing shown as empty files */
#define DIFF_BRIEF        ((u64)0x08000000) /* Show filenames only */
#define DIFF_INLINE       ((u64)0x00000000) /* Inline (not side-by-side) diff */
#define DIFF_HTML         ((u64)0x10000000) /* Render for HTML */
#define DIFF_LINENO       ((u64)0x20000000) /* Show line numbers */
#define DIFF_WS_WARNING   ((u64)0x40000000) /* Warn about whitespace */
#define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
#define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
#define DIFF_CONTEXT_EX   (((u64)0x04)<<32) /* Use context even if zero */
#define DIFF_NOTTOOBIG    (((u64)0x08)<<32) /* Only display if not too big */


/*
** These error messages are shared in multiple locations.  They are defined
** here for consistency.
*/
#define DIFF_CANNOT_COMPUTE_BINARY \
    "cannot compute difference between binary files\n"

#define DIFF_CANNOT_COMPUTE_SYMLINK \
    "cannot compute difference between symlink and regular file\n"

#define DIFF_TOO_MANY_CHANGES \
    "more than 10,000 changes\n"




/*
** Maximum length of a line in a text file, in bytes.  (2**13 = 8192 bytes)
*/
#define LENGTH_MASK_SZ  13
#define LENGTH_MASK     ((1<<LENGTH_MASK_SZ)-1)

#endif /* INTERFACE */

/*
** Information about each line of a file being diffed.
**
** The lower LENGTH_MASK_SZ bits of the hash (DLine.h) are the length
** of the line.  If any line is longer than LENGTH_MASK characters,
** the file is considered binary.
*/
typedef struct DLine DLine;
struct DLine {
  const char *z;        /* The text of the line */
  unsigned int h;       /* Hash of the line */


  unsigned int iNext;   /* 1+(Index of next line with same the same hash) */

  /* an array of DLine elements serves two purposes.  The fields
  ** above are one per line of input text.  But each entry is also
  ** a bucket in a hash table, as follows: */
  unsigned int iHash;   /* 1+(first entry in the hash chain) */
};

/*
** Length of a dline
*/
#define LENGTH(X)   ((X)->h & LENGTH_MASK)

/*
** A context for running a raw diff.
**
** The aEdit[] array describes the raw diff.  Each triple of integers in
** aEdit[] means:
**
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  int *aEdit;        /* Array of copy/delete/insert triples */
  int nEdit;         /* Number of integers (3x num of triples) in aEdit[] */
  int nEditAlloc;    /* Space allocated for aEdit[] */
  DLine *aFrom;      /* File on left side of the diff */
  int nFrom;         /* Number of lines in aFrom[] */
  DLine *aTo;        /* File on right side of the diff */
  int nTo;           /* Number of lines in aTo[] */
  int (*same_fn)(const DLine *, const DLine *); /* Function to be used for comparing */
};

/*
** Return an array of DLine objects containing a pointer to the
** start of each line and a hash of that line.  The lower
** bits of the hash store the length of each line.
**
** Trailing whitespace is removed from each line.  2010-08-20:  Not any
** more.  If trailing whitespace is ignored, the "patch" command gets
** confused by the diff output.  Ticket [a9f7b23c2e376af5b0e5b]
**
** Return 0 if the file is binary or contains a line that is
** too long.
**
** Profiling show that in most cases this routine consumes the bulk of
** the CPU time on a diff.
*/
static DLine *break_into_lines(const char *z, int n, int *pnLine, u64 diffFlags){
  int nLine, i, j, k, s, x;
  unsigned int h, h2;
  DLine *a;

  /* Count the number of lines.  Allocate space to hold
  ** the returned array.
  */
  for(i=j=0, nLine=1; i<n; i++, j++){







<

















|
|







106
107
108
109
110
111
112

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
  int *aEdit;        /* Array of copy/delete/insert triples */
  int nEdit;         /* Number of integers (3x num of triples) in aEdit[] */
  int nEditAlloc;    /* Space allocated for aEdit[] */
  DLine *aFrom;      /* File on left side of the diff */
  int nFrom;         /* Number of lines in aFrom[] */
  DLine *aTo;        /* File on right side of the diff */
  int nTo;           /* Number of lines in aTo[] */

};

/*
** Return an array of DLine objects containing a pointer to the
** start of each line and a hash of that line.  The lower
** bits of the hash store the length of each line.
**
** Trailing whitespace is removed from each line.  2010-08-20:  Not any
** more.  If trailing whitespace is ignored, the "patch" command gets
** confused by the diff output.  Ticket [a9f7b23c2e376af5b0e5b]
**
** Return 0 if the file is binary or contains a line that is
** too long.
**
** Profiling show that in most cases this routine consumes the bulk of
** the CPU time on a diff.
*/
static DLine *break_into_lines(const char *z, int n, int *pnLine, int ignoreWS){
  int nLine, i, j, k, x;
  unsigned int h, h2;
  DLine *a;

  /* Count the number of lines.  Allocate space to hold
  ** the returned array.
  */
  for(i=j=0, nLine=1; i<n; i++, j++){
162
163
164
165
166
167
168
169
170

171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
  if( n==0 ){
    *pnLine = 0;
    return a;
  }

  /* Fill in the array */
  for(i=0; i<nLine; i++){
    for(j=0; z[j] && z[j]!='\n'; j++){}
    a[i].z = z;

    k = j;
    if( diffFlags & DIFF_STRIP_EOLCR ){
      if( k>0 && z[k-1]=='\r' ){ k--; }
    }
    a[i].n = k;
    s = 0;
    if( diffFlags & DIFF_IGNORE_EOLWS ){
      while( k>0 && fossil_isspace(z[k-1]) ){ k--; }
    }
    if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
      int numws = 0;
      while( s<k && fossil_isspace(z[s]) ){ s++; }
      for(h=0, x=s; x<k; x++){
        if( fossil_isspace(z[x]) ){
          ++numws;
        }else{
          h = h ^ (h<<2) ^ z[x];
        }
      }
      k -= numws;
    }else{
      for(h=0, x=s; x<k; x++){
        h = h ^ (h<<2) ^ z[x];
      }
    }
    a[i].indent = s;
    a[i].h = h = (h<<LENGTH_MASK_SZ) | (k-s);
    h2 = h % nLine;
    a[i].iNext = a[h2].iHash;
    a[h2].iHash = i+1;
    z += j+1;
  }

  /* Return results */
  *pnLine = nLine;
  return a;
}

/*
** Return true if two DLine elements are identical.
*/
static int same_dline(const DLine *pA, const DLine *pB){
  return pA->h==pB->h && memcmp(pA->z,pB->z, pA->h&LENGTH_MASK)==0;
}

/*
** Return true if two DLine elements are identical, ignoring
** all whitespace. The indent field of pA/pB already points
** to the first non-space character in the string.
*/

static int same_dline_ignore_allws(const DLine *pA, const DLine *pB){
  int a = pA->indent, b = pB->indent;
  if( pA->h==pB->h ){
    while( a<pA->n && b<pB->n ){
      if( pA->z[a++] != pB->z[b++] ) return 0;
      while( a<pA->n && fossil_isspace(pA->z[a])) ++a;
      while( b<pB->n && fossil_isspace(pB->z[b])) ++b;
    }
    return pA->n-a == pB->n-b;
  }
  return 0;
}

/*
** Return true if the regular expression *pRe matches any of the
** N dlines
*/
static int re_dline_match(







<

>

<
<
<
<
<
<
|
<
<
<
<
|
<
<
<
|
|
<
<
<
<
<
<
<
<
|














|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







156
157
158
159
160
161
162

163
164
165






166




167



168
169








170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186



















187
188
189
190
191
192
193
  if( n==0 ){
    *pnLine = 0;
    return a;
  }

  /* Fill in the array */
  for(i=0; i<nLine; i++){

    a[i].z = z;
    for(j=0; z[j] && z[j]!='\n'; j++){}
    k = j;






    while( ignoreWS && k>0 && fossil_isspace(z[k-1]) ){ k--; }




    for(h=0, x=0; x<=k; x++){



      h = h ^ (h<<2) ^ z[x];
    }








    a[i].h = h = (h<<LENGTH_MASK_SZ) | k;;
    h2 = h % nLine;
    a[i].iNext = a[h2].iHash;
    a[h2].iHash = i+1;
    z += j+1;
  }

  /* Return results */
  *pnLine = nLine;
  return a;
}

/*
** Return true if two DLine elements are identical.
*/
static int same_dline(DLine *pA, DLine *pB){
  return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;



















}

/*
** Return true if the regular expression *pRe matches any of the
** N dlines
*/
static int re_dline_match(
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
    if( pRe && re_dline_match(pRe, pLine, 1)==0 ){
      cPrefix = ' ';
    }else if( cPrefix=='+' ){
      blob_append(pOut, "<span class=\"diffadd\">", -1);
    }else if( cPrefix=='-' ){
      blob_append(pOut, "<span class=\"diffrm\">", -1);
    }
    htmlize_to_blob(pOut, pLine->z, pLine->n);
    if( cPrefix!=' ' ){
      blob_append(pOut, "</span>", -1);
    }
  }else{
    blob_append(pOut, pLine->z, pLine->n);
  }
  blob_append(pOut, "\n", 1);
}

/*
** Add two line numbers to the beginning of an output line for a context
** diff.  One or the other of the two numbers might be zero, which means







|




|







219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
    if( pRe && re_dline_match(pRe, pLine, 1)==0 ){
      cPrefix = ' ';
    }else if( cPrefix=='+' ){
      blob_append(pOut, "<span class=\"diffadd\">", -1);
    }else if( cPrefix=='-' ){
      blob_append(pOut, "<span class=\"diffrm\">", -1);
    }
    htmlize_to_blob(pOut, pLine->z, (pLine->h & LENGTH_MASK));
    if( cPrefix!=' ' ){
      blob_append(pOut, "</span>", -1);
    }
  }else{
    blob_append(pOut, pLine->z, pLine->h & LENGTH_MASK);
  }
  blob_append(pOut, "\n", 1);
}

/*
** Add two line numbers to the beginning of an output line for a context
** diff.  One or the other of the two numbers might be zero, which means
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
        appendDiffLine(pOut, '+', &B[b+j], html, pRe);
      }
      b += m;
      if( i<nr-1 ){
        m = R[r+i*3+3];
        for(j=0; j<m; j++){
          if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
          appendDiffLine(pOut, ' ', &A[a+j], html, 0);
        }
        b += m;
        a += m;
      }
    }

    /* Show the final common area */
    assert( nr==i );
    m = R[r+nr*3];
    if( m>nContext ) m = nContext;
    for(j=0; j<m; j++){
      if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
      appendDiffLine(pOut, ' ', &A[a+j], html, 0);
    }
  }
}

/*
** Status of a single output line
*/







|












|







400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
        appendDiffLine(pOut, '+', &B[b+j], html, pRe);
      }
      b += m;
      if( i<nr-1 ){
        m = R[r+i*3+3];
        for(j=0; j<m; j++){
          if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
          appendDiffLine(pOut, ' ', &B[b+j], html, 0);
        }
        b += m;
        a += m;
      }
    }

    /* Show the final common area */
    assert( nr==i );
    m = R[r+nr*3];
    if( m>nContext ) m = nContext;
    for(j=0; j<m; j++){
      if( showLn ) appendDiffLineno(pOut, a+j+1, b+j+1, html);
      appendDiffLine(pOut, ' ', &B[b+j], html, 0);
    }
  }
}

/*
** Status of a single output line
*/
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
** rendering to width bytes if col is SBS_TXTA and escHtml is false.
**
** This comment contains multibyte unicode characters (ü, Æ, ð) in order
** to test the ability of the diff code to handle such characters.
*/
static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
  Blob *pCol = p->apCols[col];
  int n = pLine->n;
  int i;   /* Number of input characters consumed */
  int k;   /* Cursor position */
  int needEndSpan = 0;
  const char *zIn = pLine->z;
  int w = p->width;
  int colorize = p->escHtml;
  if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){







|







474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
** rendering to width bytes if col is SBS_TXTA and escHtml is false.
**
** This comment contains multibyte unicode characters (ü, Æ, ð) in order
** to test the ability of the diff code to handle such characters.
*/
static void sbsWriteText(SbsLine *p, DLine *pLine, int col){
  Blob *pCol = p->apCols[col];
  int n = pLine->h & LENGTH_MASK;
  int i;   /* Number of input characters consumed */
  int k;   /* Cursor position */
  int needEndSpan = 0;
  const char *zIn = pLine->z;
  int w = p->width;
  int colorize = p->escHtml;
  if( colorize && p->pRe && re_dline_match(p->pRe, pLine, 1)==0 ){
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
  int nLeftDiff;       /* nLeft - nPrefix - nSuffix */
  int nRightDiff;      /* nRight - nPrefix - nSuffix */
  int aLCS[4];         /* Bounds of common middle segment */
  static const char zClassRm[]   = "<span class=\"diffrm\">";
  static const char zClassAdd[]  = "<span class=\"diffadd\">";
  static const char zClassChng[] = "<span class=\"diffchng\">";

  nLeft = pLeft->n;
  zLeft = pLeft->z;
  nRight = pRight->n;
  zRight = pRight->z;
  nShort = nLeft<nRight ? nLeft : nRight;

  nPrefix = 0;
  while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){
    nPrefix++;
  }







|

|







718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
  int nLeftDiff;       /* nLeft - nPrefix - nSuffix */
  int nRightDiff;      /* nRight - nPrefix - nSuffix */
  int aLCS[4];         /* Bounds of common middle segment */
  static const char zClassRm[]   = "<span class=\"diffrm\">";
  static const char zClassAdd[]  = "<span class=\"diffadd\">";
  static const char zClassChng[] = "<span class=\"diffchng\">";

  nLeft = pLeft->h & LENGTH_MASK;
  zLeft = pLeft->z;
  nRight = pRight->h & LENGTH_MASK;
  zRight = pRight->z;
  nShort = nLeft<nRight ? nLeft : nRight;

  nPrefix = 0;
  while( nPrefix<nShort && zLeft[nPrefix]==zRight[nPrefix] ){
    nPrefix++;
  }
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
  int score;                 /* Final score.  0..100 */
  unsigned char c;           /* Character being examined */
  unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */
  unsigned char aNext[252];  /* aNext[i] = index in zB[] of next zB[i] char */

  zA = pA->z;
  zB = pB->z;
  nA = pA->n;
  nB = pB->n;
  while( nA>0 && fossil_isspace(zA[0]) ){ nA--; zA++; }
  while( nA>0 && fossil_isspace(zA[nA-1]) ){ nA--; }
  while( nB>0 && fossil_isspace(zB[0]) ){ nB--; zB++; }
  while( nB>0 && fossil_isspace(zB[nB-1]) ){ nB--; }
  if( nA>250 ) nA = 250;
  if( nB>250 ) nB = 250;
  avg = (nA+nB)/2;







|
|







866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
  int score;                 /* Final score.  0..100 */
  unsigned char c;           /* Character being examined */
  unsigned char aFirst[256]; /* aFirst[X] = index in zB[] of first char X */
  unsigned char aNext[252];  /* aNext[i] = index in zB[] of next zB[i] char */

  zA = pA->z;
  zB = pB->z;
  nA = pA->h & LENGTH_MASK;
  nB = pB->h & LENGTH_MASK;
  while( nA>0 && fossil_isspace(zA[0]) ){ nA--; zA++; }
  while( nA>0 && fossil_isspace(zA[nA-1]) ){ nA--; }
  while( nB>0 && fossil_isspace(zB[0]) ){ nB--; zB++; }
  while( nB>0 && fossil_isspace(zB[nB-1]) ){ nB--; }
  if( nA>250 ) nA = 250;
  if( nB>250 ) nB = 250;
  avg = (nA+nB)/2;
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
      s.iStart = s.iEnd = -1;
      sbsWriteText(&s, &A[a+j], SBS_TXTA);
      sbsWriteMarker(&s, "   ", "");
      sbsWriteLineno(&s, b+j, SBS_LNB);
      sbsWriteText(&s, &B[b+j], SBS_TXTB);
    }
  }

  if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
    blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
    for(i=SBS_LNA; i<=SBS_TXTB; i++){
      sbsWriteColumn(pOut, s.apCols[i], i);
      blob_reset(s.apCols[i]);
    }
    blob_append(pOut, "</tr></table>\n", -1);







|







1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
      s.iStart = s.iEnd = -1;
      sbsWriteText(&s, &A[a+j], SBS_TXTA);
      sbsWriteMarker(&s, "   ", "");
      sbsWriteLineno(&s, b+j, SBS_LNB);
      sbsWriteText(&s, &B[b+j], SBS_TXTB);
    }
  }
  
  if( s.escHtml && blob_size(s.apCols[SBS_LNA])>0 ){
    blob_append(pOut, "<table class=\"sbsdiffcols\"><tr>\n", -1);
    for(i=SBS_LNA; i<=SBS_TXTB; i++){
      sbsWriteColumn(pOut, s.apCols[i], i);
      blob_reset(s.apCols[i]);
    }
    blob_append(pOut, "</tr></table>\n", -1);
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
  int i, j;                  /* Loop counters */
  int k;                     /* Length of a candidate subsequence */
  int iSXb = iS1;            /* Best match so far */
  int iSYb = iS2;            /* Best match so far */

  for(i=iS1; i<iE1-mxLength; i++){
    for(j=iS2; j<iE2-mxLength; j++){
      if( !p->same_fn(&p->aFrom[i], &p->aTo[j]) ) continue;
      if( mxLength && !p->same_fn(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){
        continue;
      }
      k = 1;
      while( i+k<iE1 && j+k<iE2 && p->same_fn(&p->aFrom[i+k],&p->aTo[j+k]) ){
        k++;
      }
      if( k>mxLength ){
        iSXb = i;
        iSYb = j;
        mxLength = k;
      }







|
|



|







1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
  int i, j;                  /* Loop counters */
  int k;                     /* Length of a candidate subsequence */
  int iSXb = iS1;            /* Best match so far */
  int iSYb = iS2;            /* Best match so far */

  for(i=iS1; i<iE1-mxLength; i++){
    for(j=iS2; j<iE2-mxLength; j++){
      if( !same_dline(&p->aFrom[i], &p->aTo[j]) ) continue;
      if( mxLength && !same_dline(&p->aFrom[i+mxLength], &p->aTo[j+mxLength]) ){
        continue;
      }
      k = 1;
      while( i+k<iE1 && j+k<iE2 && same_dline(&p->aFrom[i+k],&p->aTo[j+k]) ){
        k++;
      }
      if( k>mxLength ){
        iSXb = i;
        iSYb = j;
        mxLength = k;
      }
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
  iSYb = iSYp = iS2;
  iEYb = iEYp = iS2;
  mid = (iE1 + iS1)/2;
  for(i=iS1; i<iE1; i++){
    int limit = 0;
    j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
    while( j>0
      && (j-1<iS2 || j>=iE2 || !p->same_fn(&p->aFrom[i], &p->aTo[j-1]))
    ){
      if( limit++ > 10 ){
        j = 0;
        break;
      }
      j = p->aTo[j-1].iNext;
    }
    if( j==0 ) continue;
    assert( i>=iSXb && i>=iSXp );
    if( i<iEXb && j>=iSYb && j<iEYb ) continue;
    if( i<iEXp && j>=iSYp && j<iEYp ) continue;
    iSX = i;
    iSY = j-1;
    pA = &p->aFrom[iSX-1];
    pB = &p->aTo[iSY-1];
    n = minInt(iSX-iS1, iSY-iS2);
    for(k=0; k<n && p->same_fn(pA,pB); k++, pA--, pB--){}
    iSX -= k;
    iSY -= k;
    iEX = i+1;
    iEY = j;
    pA = &p->aFrom[iEX];
    pB = &p->aTo[iEY];
    n = minInt(iE1-iEX, iE2-iEY);
    for(k=0; k<n && p->same_fn(pA,pB); k++, pA++, pB++){}
    iEX += k;
    iEY += k;
    skew = (iSX-iS1) - (iSY-iS2);
    if( skew<0 ) skew = -skew;
    dist = (iSX+iEX)/2 - mid;
    if( dist<0 ) dist = -dist;
    score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist);







|
















|







|







1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
  iSYb = iSYp = iS2;
  iEYb = iEYp = iS2;
  mid = (iE1 + iS1)/2;
  for(i=iS1; i<iE1; i++){
    int limit = 0;
    j = p->aTo[p->aFrom[i].h % p->nTo].iHash;
    while( j>0
      && (j-1<iS2 || j>=iE2 || !same_dline(&p->aFrom[i], &p->aTo[j-1]))
    ){
      if( limit++ > 10 ){
        j = 0;
        break;
      }
      j = p->aTo[j-1].iNext;
    }
    if( j==0 ) continue;
    assert( i>=iSXb && i>=iSXp );
    if( i<iEXb && j>=iSYb && j<iEYb ) continue;
    if( i<iEXp && j>=iSYp && j<iEYp ) continue;
    iSX = i;
    iSY = j-1;
    pA = &p->aFrom[iSX-1];
    pB = &p->aTo[iSY-1];
    n = minInt(iSX-iS1, iSY-iS2);
    for(k=0; k<n && same_dline(pA,pB); k++, pA--, pB--){}
    iSX -= k;
    iSY -= k;
    iEX = i+1;
    iEY = j;
    pA = &p->aFrom[iEX];
    pB = &p->aTo[iEY];
    n = minInt(iE1-iEX, iE2-iEY);
    for(k=0; k<n && same_dline(pA,pB); k++, pA++, pB++){}
    iEX += k;
    iEY += k;
    skew = (iSX-iS1) - (iSY-iS2);
    if( skew<0 ) skew = -skew;
    dist = (iSX+iEX)/2 - mid;
    if( dist<0 ) dist = -dist;
    score = (iEX - iSX)*(sqlite3_int64)span - (skew + dist);
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
*/
static void diff_all(DContext *p){
  int mnE, iS, iE1, iE2;

  /* Carve off the common header and footer */
  iE1 = p->nFrom;
  iE2 = p->nTo;
  while( iE1>0 && iE2>0 && p->same_fn(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){
    iE1--;
    iE2--;
  }
  mnE = iE1<iE2 ? iE1 : iE2;
  for(iS=0; iS<mnE && p->same_fn(&p->aFrom[iS],&p->aTo[iS]); iS++){}

  /* do the difference */
  if( iS>0 ){
    appendTriple(p, iS, 0, 0);
  }
  diff_step(p, iS, iE1, iS, iE2);
  if( iE1<p->nFrom ){







|




|







1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
*/
static void diff_all(DContext *p){
  int mnE, iS, iE1, iE2;

  /* Carve off the common header and footer */
  iE1 = p->nFrom;
  iE2 = p->nTo;
  while( iE1>0 && iE2>0 && same_dline(&p->aFrom[iE1-1], &p->aTo[iE2-1]) ){
    iE1--;
    iE2--;
  }
  mnE = iE1<iE2 ? iE1 : iE2;
  for(iS=0; iS<mnE && same_dline(&p->aFrom[iS],&p->aTo[iS]); iS++){}

  /* do the difference */
  if( iS>0 ){
    appendTriple(p, iS, 0, 0);
  }
  diff_step(p, iS, iE1, iS, iE2);
  if( iE1<p->nFrom ){
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
    lnFrom += cpy;
    lnTo += cpy;

    /* Shift insertions toward the beginning of the file */
    while( cpy>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];  /* Line before start of insert */
      DLine *pBtm = &p->aTo[lnTo+ins-1];  /* Last line inserted */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift insertions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aTo[lnTo];       /* First line inserted */
      DLine *pBtm = &p->aTo[lnTo+ins];   /* First line past end of insert */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }

    /* Shift deletions toward the beginning of the file */
    while( cpy>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];     /* Line before start of delete */
      DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift deletions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom];     /* First line deleted */
      DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
      if( p->same_fn(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }







|












|












|












|







1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
    lnFrom += cpy;
    lnTo += cpy;

    /* Shift insertions toward the beginning of the file */
    while( cpy>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];  /* Line before start of insert */
      DLine *pBtm = &p->aTo[lnTo+ins-1];  /* Last line inserted */
      if( same_dline(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift insertions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del==0 && ins>0 ){
      DLine *pTop = &p->aTo[lnTo];       /* First line inserted */
      DLine *pBtm = &p->aTo[lnTo+ins];   /* First line past end of insert */
      if( same_dline(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop+1)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }

    /* Shift deletions toward the beginning of the file */
    while( cpy>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom-1];     /* Line before start of delete */
      DLine *pBtm = &p->aFrom[lnFrom+del-1]; /* Last line deleted */
      if( same_dline(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop+1)+LENGTH(pBtm)<=LENGTH(pTop)+LENGTH(pBtm-1) ) break;
      lnFrom--;
      lnTo--;
      p->aEdit[r]--;
      p->aEdit[r+3]++;
      cpy--;
    }

    /* Shift deletions toward the end of the file */
    while( r+3<p->nEdit && p->aEdit[r+3]>0 && del>0 && ins==0 ){
      DLine *pTop = &p->aFrom[lnFrom];     /* First line deleted */
      DLine *pBtm = &p->aFrom[lnFrom+del]; /* First line past end of delete */
      if( same_dline(pTop, pBtm)==0 ) break;
      if( LENGTH(pTop)+LENGTH(pBtm-1)<=LENGTH(pTop)+LENGTH(pBtm) ) break;
      lnFrom++;
      lnTo++;
      p->aEdit[r]++;
      p->aEdit[r+3]--;
      cpy++;
    }
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
int *text_diff(
  Blob *pA_Blob,   /* FROM file */
  Blob *pB_Blob,   /* TO file */
  Blob *pOut,      /* Write diff here if not NULL */
  ReCompiled *pRe, /* Only output changes where this Regexp matches */
  u64 diffFlags    /* DIFF_* flags defined above */
){
  int ignoreWs; /* Ignore whitespace */
  DContext c;

  if( diffFlags & DIFF_INVERT ){
    Blob *pTemp = pA_Blob;
    pA_Blob = pB_Blob;
    pB_Blob = pTemp;
  }
  ignoreWs = (diffFlags & DIFF_IGNORE_ALLWS)!=0;
  blob_to_utf8_no_bom(pA_Blob, 0);
  blob_to_utf8_no_bom(pB_Blob, 0);

  /* Prepare the input files */
  memset(&c, 0, sizeof(c));
  if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
    c.same_fn = same_dline_ignore_allws;
  }else{
    c.same_fn = same_dline;
  }
  c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                             &c.nFrom, diffFlags);
  c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
                           &c.nTo, diffFlags);
  if( c.aFrom==0 || c.aTo==0 ){
    fossil_free(c.aFrom);
    fossil_free(c.aTo);
    if( pOut ){
      diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags);
    }
    return 0;
  }

  /* Compute the difference */
  diff_all(&c);
  if( ignoreWs && c.nEdit==6 && c.aEdit[1]==0 && c.aEdit[2]==0 ){
    fossil_free(c.aFrom);
    fossil_free(c.aTo);
    fossil_free(c.aEdit);
    if( pOut ) diff_errmsg(pOut, DIFF_WHITESPACE_ONLY, diffFlags);
    return 0;
  }
  if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){
    int i, m, n;
    int *a = c.aEdit;
    int mx = c.nEdit;
    for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
    if( n>10000 ){
      fossil_free(c.aFrom);
      fossil_free(c.aTo);
      fossil_free(c.aEdit);
      if( pOut ) diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags);
      return 0;
    }
  }
  if( (diffFlags & DIFF_NOOPT)==0 ){
    diff_optimize(&c);
  }








|







|
<
<



<
<
<
<
<

|

|











<
<
<
<
<
<
<









|







1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766


1767
1768
1769





1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784







1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
int *text_diff(
  Blob *pA_Blob,   /* FROM file */
  Blob *pB_Blob,   /* TO file */
  Blob *pOut,      /* Write diff here if not NULL */
  ReCompiled *pRe, /* Only output changes where this Regexp matches */
  u64 diffFlags    /* DIFF_* flags defined above */
){
  int ignoreEolWs; /* Ignore whitespace at the end of lines */
  DContext c;

  if( diffFlags & DIFF_INVERT ){
    Blob *pTemp = pA_Blob;
    pA_Blob = pB_Blob;
    pB_Blob = pTemp;
  }
  ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0;



  /* Prepare the input files */
  memset(&c, 0, sizeof(c));





  c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                             &c.nFrom, ignoreEolWs);
  c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
                           &c.nTo, ignoreEolWs);
  if( c.aFrom==0 || c.aTo==0 ){
    fossil_free(c.aFrom);
    fossil_free(c.aTo);
    if( pOut ){
      diff_errmsg(pOut, DIFF_CANNOT_COMPUTE_BINARY, diffFlags);
    }
    return 0;
  }

  /* Compute the difference */
  diff_all(&c);







  if( (diffFlags & DIFF_NOTTOOBIG)!=0 ){
    int i, m, n;
    int *a = c.aEdit;
    int mx = c.nEdit;
    for(i=m=n=0; i<mx; i+=3){ m += a[i]; n += a[i+1]+a[i+2]; }
    if( n>10000 ){
      fossil_free(c.aFrom);
      fossil_free(c.aTo);
      fossil_free(c.aEdit);
      diff_errmsg(pOut, DIFF_TOO_MANY_CHANGES, diffFlags);
      return 0;
    }
  }
  if( (diffFlags & DIFF_NOOPT)==0 ){
    diff_optimize(&c);
  }

1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
  }
}

/*
** Process diff-related command-line options and return an appropriate
** "diffFlags" integer.
**
**   --brief                    Show filenames only    DIFF_BRIEF
**   -c|--context N             N lines of context.    DIFF_CONTEXT_MASK
**   --html                     Format for HTML        DIFF_HTML
**   --invert                   Invert the diff        DIFF_INVERT
**   -n|--linenum               Show line numbers      DIFF_LINENO
**   --noopt                    Disable optimization   DIFF_NOOPT
**   --strip-trailing-cr        Strip trailing CR      DIFF_STRIP_EOLCR
**   --unified                  Unified diff.          ~DIFF_SIDEBYSIDE
**   -w|--ignore-all-space      Ignore all whitespaces DIFF_IGNORE_ALLWS
**   -W|--width N               N character lines.     DIFF_WIDTH_MASK
**   -y|--side-by-side          Side-by-side diff.     DIFF_SIDEBYSIDE
**   -Z|--ignore-trailing-space Ignore eol-whitespaces DIFF_IGNORE_EOLWS
*/
u64 diff_options(void){
  u64 diffFlags = 0;
  const char *z;
  int f;
  if( find_option("ignore-trailing-space","Z",0)!=0 ){
    diffFlags = DIFF_IGNORE_EOLWS;
  }
  if( find_option("ignore-all-space","w",0)!=0 ){
    diffFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
  }
  if( find_option("strip-trailing-cr",0,0)!=0 ){
    diffFlags |= DIFF_STRIP_EOLCR;
  }
  if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
  if( find_option("unified",0,0)!=0 ) diffFlags &= ~DIFF_SIDEBYSIDE;
  if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>=0 ){
    if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
    diffFlags |= f + DIFF_CONTEXT_EX;
  }
  if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){







|
|
|
|
|
|
|
|
<
|
<
<





<
<
<
<
<
<
<
<
<







1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834

1835


1836
1837
1838
1839
1840









1841
1842
1843
1844
1845
1846
1847
  }
}

/*
** Process diff-related command-line options and return an appropriate
** "diffFlags" integer.
**
**   --brief                Show filenames only    DIFF_BRIEF
**   --context|-c N         N lines of context.    DIFF_CONTEXT_MASK
**   --html                 Format for HTML        DIFF_HTML
**   --invert               Invert the diff        DIFF_INVERT
**   --linenum|-n           Show line numbers      DIFF_LINENO
**   --noopt                Disable optimization   DIFF_NOOPT
**   --side-by-side|-y      Side-by-side diff.     DIFF_SIDEBYSIDE
**   --unified              Unified diff.          ~DIFF_SIDEBYSIDE

**   --width|-W N           N character lines.     DIFF_WIDTH_MASK


*/
u64 diff_options(void){
  u64 diffFlags = 0;
  const char *z;
  int f;









  if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
  if( find_option("unified",0,0)!=0 ) diffFlags &= ~DIFF_SIDEBYSIDE;
  if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>=0 ){
    if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
    diffFlags |= f + DIFF_CONTEXT_EX;
  }
  if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
** of the following structure.
*/
typedef struct Annotator Annotator;
struct Annotator {
  DContext c;       /* The diff-engine context */
  struct AnnLine {  /* Lines of the original files... */
    const char *z;       /* The text of the line */
    short int n;         /* Number of bytes (omitting trailing \n) */
    short int iVers;     /* Level at which tag was set */
  } *aOrig;
  int nOrig;        /* Number of elements in aOrig[] */
  int nVers;        /* Number of versions analyzed */
  int bLimit;       /* True if the iLimit was reached */
  struct AnnVers {
    const char *zFUuid;   /* File being analyzed */







|







1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
** of the following structure.
*/
typedef struct Annotator Annotator;
struct Annotator {
  DContext c;       /* The diff-engine context */
  struct AnnLine {  /* Lines of the original files... */
    const char *z;       /* The text of the line */
    short int n;         /* Number of bytes (omitting trailing space and \n) */
    short int iVers;     /* Level at which tag was set */
  } *aOrig;
  int nOrig;        /* Number of elements in aOrig[] */
  int nVers;        /* Number of versions analyzed */
  int bLimit;       /* True if the iLimit was reached */
  struct AnnVers {
    const char *zFUuid;   /* File being analyzed */
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
};

/*
** Initialize the annotation process by specifying the file that is
** to be annotated.  The annotator takes control of the input Blob and
** will release it when it is finished with it.
*/
static int annotation_start(Annotator *p, Blob *pInput, u64 diffFlags){
  int i;

  memset(p, 0, sizeof(*p));
  if( (diffFlags & DIFF_IGNORE_ALLWS)==DIFF_IGNORE_ALLWS ){
    p->c.same_fn = same_dline_ignore_allws;
  }else{
    p->c.same_fn = same_dline;
  }
  p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,
                              diffFlags);
  if( p->c.aTo==0 ){
    return 1;
  }
  p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo );
  for(i=0; i<p->c.nTo; i++){
    p->aOrig[i].z = p->c.aTo[i].z;
    p->aOrig[i].n = p->c.aTo[i].n;
    p->aOrig[i].iVers = -1;
  }
  p->nOrig = p->c.nTo;
  return 0;
}

/*
** The input pParent is the next most recent ancestor of the file
** being annotated.  Do another step of the annotation.  Return true
** if additional annotation is required.  zPName is the tag to insert
** on each line of the file being annotated that was contributed by
** pParent.  Memory to hold zPName is leaked.
*/
static int annotation_step(Annotator *p, Blob *pParent, int iVers, u64 diffFlags){
  int i, j;
  int lnTo;

  /* Prepare the parent file to be diffed */
  p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent),
                                &p->c.nFrom, diffFlags);
  if( p->c.aFrom==0 ){
    return 1;
  }

  /* Compute the differences going from pParent to the file being
  ** annotated. */
  diff_all(&p->c);







|



<
<
<
<
<
|
<






|













|





|







1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958





1959

1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
};

/*
** Initialize the annotation process by specifying the file that is
** to be annotated.  The annotator takes control of the input Blob and
** will release it when it is finished with it.
*/
static int annotation_start(Annotator *p, Blob *pInput){
  int i;

  memset(p, 0, sizeof(*p));





  p->c.aTo = break_into_lines(blob_str(pInput), blob_size(pInput),&p->c.nTo,1);

  if( p->c.aTo==0 ){
    return 1;
  }
  p->aOrig = fossil_malloc( sizeof(p->aOrig[0])*p->c.nTo );
  for(i=0; i<p->c.nTo; i++){
    p->aOrig[i].z = p->c.aTo[i].z;
    p->aOrig[i].n = p->c.aTo[i].h & LENGTH_MASK;
    p->aOrig[i].iVers = -1;
  }
  p->nOrig = p->c.nTo;
  return 0;
}

/*
** The input pParent is the next most recent ancestor of the file
** being annotated.  Do another step of the annotation.  Return true
** if additional annotation is required.  zPName is the tag to insert
** on each line of the file being annotated that was contributed by
** pParent.  Memory to hold zPName is leaked.
*/
static int annotation_step(Annotator *p, Blob *pParent, int iVers){
  int i, j;
  int lnTo;

  /* Prepare the parent file to be diffed */
  p->c.aFrom = break_into_lines(blob_str(pParent), blob_size(pParent),
                                &p->c.nFrom, 1);
  if( p->c.aFrom==0 ){
    return 1;
  }

  /* Compute the differences going from pParent to the file being
  ** annotated. */
  diff_all(&p->c);
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
  free(p->c.aFrom);

  /* Return no errors */
  return 0;
}


/* Annotation flags (any DIFF flag can be used as Annotation flag as well) */
#define ANN_FILE_VERS   (((u64)0x20)<<32) /* Show file vers rather than commit vers */
#define ANN_FILE_ANCEST (((u64)0x40)<<32) /* Prefer check-ins in the ANCESTOR table */

/*
** Compute a complete annotation on a file.  The file is identified
** by its filename number (filename.fnid) and the baseline in which
** it was checked in (mlink.mid).
*/
static void annotate_file(
  Annotator *p,        /* The annotator */
  int fnid,            /* The name of the file to be annotated */
  int mid,             /* Use the version of the file in this check-in */
  int iLimit,          /* Limit the number of levels if greater than zero */
  u64 annFlags         /* Flags to alter the annotation */
){
  Blob toAnnotate;     /* Text of the final (mid) version of the file */
  Blob step;           /* Text of previous revision */
  int rid;             /* Artifact ID of the file being annotated */
  Stmt q;              /* Query returning all ancestor versions */
  Stmt ins;            /* Inserts into the temporary VSEEN table */
  int cnt = 0;         /* Number of versions examined */

  /* Initialize the annotation */
  rid = db_int(0, "SELECT fid FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid);
  if( rid==0 ){
    fossil_fatal("file #%d is unchanged in manifest #%d", fnid, mid);
  }
  if( !content_get(rid, &toAnnotate) ){
    fossil_fatal("unable to retrieve content of artifact #%d", rid);
  }
  if( iLimit<=0 ) iLimit = 1000000000;
  blob_to_utf8_no_bom(&toAnnotate, 0);
  annotation_start(p, &toAnnotate, annFlags);
  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE IF NOT EXISTS vseen(rid INTEGER PRIMARY KEY);"
     "DELETE FROM vseen;"
  );

  db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)");







|
|
|











|

















<
|







2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054

2055
2056
2057
2058
2059
2060
2061
2062
  free(p->c.aFrom);

  /* Return no errors */
  return 0;
}


/* Annotation flags */
#define ANN_FILE_VERS    0x01   /* Show file vers rather than commit vers */
#define ANN_FILE_ANCEST  0x02   /* Prefer check-ins in the ANCESTOR table */

/*
** Compute a complete annotation on a file.  The file is identified
** by its filename number (filename.fnid) and the baseline in which
** it was checked in (mlink.mid).
*/
static void annotate_file(
  Annotator *p,        /* The annotator */
  int fnid,            /* The name of the file to be annotated */
  int mid,             /* Use the version of the file in this check-in */
  int iLimit,          /* Limit the number of levels if greater than zero */
  int annFlags         /* Flags to alter the annotation */
){
  Blob toAnnotate;     /* Text of the final (mid) version of the file */
  Blob step;           /* Text of previous revision */
  int rid;             /* Artifact ID of the file being annotated */
  Stmt q;              /* Query returning all ancestor versions */
  Stmt ins;            /* Inserts into the temporary VSEEN table */
  int cnt = 0;         /* Number of versions examined */

  /* Initialize the annotation */
  rid = db_int(0, "SELECT fid FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid);
  if( rid==0 ){
    fossil_fatal("file #%d is unchanged in manifest #%d", fnid, mid);
  }
  if( !content_get(rid, &toAnnotate) ){
    fossil_fatal("unable to retrieve content of artifact #%d", rid);
  }
  if( iLimit<=0 ) iLimit = 1000000000;

  annotation_start(p, &toAnnotate);
  db_begin_transaction();
  db_multi_exec(
     "CREATE TEMP TABLE IF NOT EXISTS vseen(rid INTEGER PRIMARY KEY);"
     "DELETE FROM vseen;"
  );

  db_prepare(&ins, "INSERT OR IGNORE INTO vseen(rid) VALUES(:rid)");
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
    p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0]));
    p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0));
    p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1));
    p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2));
    p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3));
    if( p->nVers ){
      content_get(rid, &step);
      blob_to_utf8_no_bom(&step, 0);
      annotation_step(p, &step, p->nVers-1, annFlags);
      blob_reset(&step);
    }
    p->nVers++;
    db_bind_int(&ins, ":rid", rid);
    db_step(&ins);
    db_reset(&ins);
    db_reset(&q);







<
|







2082
2083
2084
2085
2086
2087
2088

2089
2090
2091
2092
2093
2094
2095
2096
    p->aVers = fossil_realloc(p->aVers, (p->nVers+1)*sizeof(p->aVers[0]));
    p->aVers[p->nVers].zFUuid = fossil_strdup(db_column_text(&q, 0));
    p->aVers[p->nVers].zMUuid = fossil_strdup(db_column_text(&q, 1));
    p->aVers[p->nVers].zDate = fossil_strdup(db_column_text(&q, 2));
    p->aVers[p->nVers].zUser = fossil_strdup(db_column_text(&q, 3));
    if( p->nVers ){
      content_get(rid, &step);

      annotation_step(p, &step, p->nVers-1);
      blob_reset(&step);
    }
    p->nVers++;
    db_bind_int(&ins, ":rid", rid);
    db_step(&ins);
    db_reset(&ins);
    db_reset(&q);
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
/*
** Return a color from a gradient.
*/
unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){
  unsigned c;   /* Result color */
  unsigned x1, x2;
  if( i==0 || n==0 ) return c1;
  else if(i>=n) return c2;
  x1 = (c1>>16)&0xff;
  x2 = (c2>>16)&0xff;
  c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000;
  x1 = (c1>>8)&0xff;
  x2 = (c2>>8)&0xff;
  c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00;
  x1 = c1&0xff;
  x2 = c2&0xff;
  c |= (x1*(n-i) + x2*i)/n & 0xff;
  return c;
}

/*
** WEBPAGE: annotate
** WEBPAGE: blame
** WEBPAGE: praise
**
** Query parameters:
**
**    checkin=ID          The manifest ID at which to start the annotation
**    filename=FILENAME   The filename.
**    filevers            Show file versions rather than check-in versions
**    log=BOOLEAN         Show a log of versions analyzed
**    limit=N             Limit the search depth to N ancestors
*/
void annotation_page(void){
  int mid;
  int fnid;
  int i;
  int iLimit;            /* Depth limit */
  u64 annFlags = (ANN_FILE_ANCEST|DIFF_STRIP_EOLCR);
  int showLog = 0;       /* True to display the log */
  int ignoreWs = 0;      /* Ignore whitespace */
  const char *zFilename; /* Name of file to annotate */
  const char *zCI;       /* The check-in containing zFilename */
  Annotator ann;
  HQuery url;
  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]!='a';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  showLog = atoi(PD("log","1"));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( exclude_spiders("annotate") ) return;
  load_control();
  mid = name_to_typed_rid(PD("checkin","0"),"ci");
  zFilename = P("filename");
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
  iLimit = atoi(PD("limit","20"));
  if( P("filevers") ) annFlags |= ANN_FILE_VERS;
  ignoreWs = P("w")!=0;
  if( ignoreWs ) annFlags |= DIFF_IGNORE_ALLWS;
  if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
    fossil_redirect_home();
  }

  /* compute the annotation */
  compute_direct_ancestors(mid, 10000000);
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  zCI = ann.aVers[0].zMUuid;

  /* generate the web page */
  style_header("Annotation For %h", zFilename);
  if( bBlame ){
    url_initialize(&url, "blame");
  }else{
    url_initialize(&url, "annotate");
  }
  url_add_parameter(&url, "checkin", P("checkin"));
  url_add_parameter(&url, "filename", zFilename);
  if( iLimit!=20 ){
    url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit));
  }
  url_add_parameter(&url, "log", showLog ? "1" : "0");
  if( ignoreWs ){
    url_add_parameter(&url, "w", "");
    style_submenu_element("Show Whitespace Changes", "Show Whitespace Changes",
        "%s", url_render(&url, "w", 0, 0, 0));
  }else{
    style_submenu_element("Ignore Whitespace", "Ignore Whitespace",
        "%s", url_render(&url, "w", "", 0, 0));
  }
  if( showLog ){
    style_submenu_element("Hide Log", "Hide Log",
       "%s", url_render(&url, "log", "0", 0, 0));
  }else{
    style_submenu_element("Show Log", "Show Log",
       "%s", url_render(&url, "log", "1", 0, 0));
  }







<















<














|

<






|






<






<
<











<
<
<
|
<






<
<
<
<
<
<
<
<







2107
2108
2109
2110
2111
2112
2113

2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128

2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144

2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157

2158
2159
2160
2161
2162
2163


2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174



2175

2176
2177
2178
2179
2180
2181








2182
2183
2184
2185
2186
2187
2188
/*
** Return a color from a gradient.
*/
unsigned gradient_color(unsigned c1, unsigned c2, int n, int i){
  unsigned c;   /* Result color */
  unsigned x1, x2;
  if( i==0 || n==0 ) return c1;

  x1 = (c1>>16)&0xff;
  x2 = (c2>>16)&0xff;
  c = (x1*(n-i) + x2*i)/n<<16 & 0xff0000;
  x1 = (c1>>8)&0xff;
  x2 = (c2>>8)&0xff;
  c |= (x1*(n-i) + x2*i)/n<<8 & 0xff00;
  x1 = c1&0xff;
  x2 = c2&0xff;
  c |= (x1*(n-i) + x2*i)/n & 0xff;
  return c;
}

/*
** WEBPAGE: annotate
** WEBPAGE: blame

**
** Query parameters:
**
**    checkin=ID          The manifest ID at which to start the annotation
**    filename=FILENAME   The filename.
**    filevers            Show file versions rather than check-in versions
**    log=BOOLEAN         Show a log of versions analyzed
**    limit=N             Limit the search depth to N ancestors
*/
void annotation_page(void){
  int mid;
  int fnid;
  int i;
  int iLimit;            /* Depth limit */
  int annFlags = ANN_FILE_ANCEST;  
  int showLog = 0;       /* True to display the log */

  const char *zFilename; /* Name of file to annotate */
  const char *zCI;       /* The check-in containing zFilename */
  Annotator ann;
  HQuery url;
  struct AnnVers *p;
  unsigned clr1, clr2, clr;
  int bBlame = g.zPath[0]=='b';/* True for BLAME output.  False for ANNOTATE. */

  /* Gather query parameters */
  showLog = atoi(PD("log","1"));
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( exclude_spiders("annotate") ) return;

  mid = name_to_typed_rid(PD("checkin","0"),"ci");
  zFilename = P("filename");
  fnid = db_int(0, "SELECT fnid FROM filename WHERE name=%Q", zFilename);
  if( mid==0 || fnid==0 ){ fossil_redirect_home(); }
  iLimit = atoi(PD("limit","20"));
  if( P("filevers") ) annFlags |= ANN_FILE_VERS;


  if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d AND fnid=%d",mid,fnid) ){
    fossil_redirect_home();
  }

  /* compute the annotation */
  compute_direct_ancestors(mid, 10000000);
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  zCI = ann.aVers[0].zMUuid;

  /* generate the web page */
  style_header("Annotation For %h", zFilename);



  url_initialize(&url, "annotate");

  url_add_parameter(&url, "checkin", P("checkin"));
  url_add_parameter(&url, "filename", zFilename);
  if( iLimit!=20 ){
    url_add_parameter(&url, "limit", sqlite3_mprintf("%d", iLimit));
  }
  url_add_parameter(&url, "log", showLog ? "1" : "0");








  if( showLog ){
    style_submenu_element("Hide Log", "Hide Log",
       "%s", url_render(&url, "log", "0", 0, 0));
  }else{
    style_submenu_element("Show Log", "Show Log",
       "%s", url_render(&url, "log", "1", 0, 0));
  }
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
  }else{
    clr1 = 0xffb5b5;  /* Recent changes: red (hot) */
    clr2 = 0xb5e0ff;  /* Older changes: blue (cold) */
  }
  for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
    clr = gradient_color(clr1, clr2, ann.nVers-1, i);
    ann.aVers[i].zBgColor = mprintf("#%06x", clr);
  }

  if( showLog ){
    char *zLink = href("%R/finfo?name=%t&ci=%s",zFilename,zCI);
    @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
    @ <ol>
    for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
      @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
      @ check-in %z(href("%R/info/%s",p->zMUuid))%.10s(p->zMUuid)</a>
      @ artifact %z(href("%R/artifact/%s",p->zFUuid))%.10s(p->zFUuid)</a>
      @ </span>
#if 0
      if( i>0 ){
        char *zLink = xhref("target='infowindow'",
                            "%R/fdiff?v1=%S&v2=%S&sbs=1",
                            p->zFUuid,ann.aVers[0].zFUuid);
        @ %z(zLink)[diff-to-top]</a>
        if( i>1 ){
           zLink = xhref("target='infowindow'",
                         "%R/fdiff?v1=%S&v2=%S&sbs=1",
                         p->zFUuid,p[-1].zFUuid);
           @ %z(zLink)[diff-to-previous]</a>
        }
      }
#endif
    }
    @ </ol>
    @ <hr>
  }
  if( !ann.bLimit ){
    @ <h2>Origin for each line in
    @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
    @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
    iLimit = ann.nVers+10;
  }else{
    @ <h2>Lines added by the %d(iLimit) most recent ancestors of
    @ %z(href("%R/finfo?name=%h&ci=%s", zFilename, zCI))%h(zFilename)</a>
    @ from check-in %z(href("%R/info/%s",zCI))%S(zCI)</a>:</h2>
  }
  @ <pre>
  for(i=0; i<ann.nOrig; i++){
    int iVers = ann.aOrig[i].iVers;
    char *z = (char*)ann.aOrig[i].z;
    int n = ann.aOrig[i].n;
    char zPrefix[300];
    z[n] = 0;
    if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;

    if( bBlame ){
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %13.13s:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
      }
    }else{
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        char *zLink = xhref("target='infowindow'", "%R/info/%s", p->zMUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %4d:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1);
      }
    }
    @ %s(zPrefix) %h(z)

  }
  @ </pre>
  style_footer();
}

/*
** COMMAND: annotate
** COMMAND: blame
** COMMAND: praise
**
** %fossil (annotate|blame|praise) ?OPTIONS? FILENAME
**
** Output the text of a file with markings to show when each line of
** the file was last modified.  The "annotate" command shows line numbers
** and omits the username.  The "blame" and "praise" commands show the user
** who made each checkin and omits the line number.
**
** Options:
**   --filevers                 Show file version numbers rather than check-in versions
**   -l|--log                   List all versions analyzed
**   -n|--limit N               Only look backwards in time by N versions
**   -w|--ignore-all-space      Ignore white space when comparing lines
**   -Z|--ignore-trailing-space Ignore whitespace at line end
**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
  int fnid;         /* Filename ID */
  int fid;          /* File instance ID */
  int mid;          /* Manifest where file was checked in */
  int cid;          /* Checkout ID */
  Blob treename;    /* FILENAME translated to canonical form */
  char *zFilename;  /* Canonical filename */
  Annotator ann;    /* The annotation of the file */
  int i;            /* Loop counter */
  const char *zLimit; /* The value to the -n|--limit option */
  int iLimit;       /* How far back in time to look */
  int showLog;      /* True to show the log */
  int fileVers;     /* Show file version instead of check-in versions */
  u64 annFlags = 0; /* Flags to control annotation properties */
  int bBlame = 0;   /* True for BLAME output.  False for ANNOTATE. */

  bBlame = g.argv[1][0]!='a';
  zLimit = find_option("limit","n",1);
  if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1";
  iLimit = atoi(zLimit);
  showLog = find_option("log","l",0)!=0;
  if( find_option("ignore-trailing-space","Z",0)!=0 ){
    annFlags = DIFF_IGNORE_EOLWS;
  }
  if( find_option("ignore-all-space","w",0)!=0 ){
    annFlags = DIFF_IGNORE_ALLWS; /* stronger than DIFF_IGNORE_EOLWS */
  }
  fileVers = find_option("filevers",0,0)!=0;
  db_must_be_within_tree();
  if( g.argc<3 ) {
    usage("FILENAME");
  }
  file_tree_name(g.argv[2], &treename, 1);
  zFilename = blob_str(&treename);







|


|




|
|




















|
|
|



|
|













|











|



















<

|



|
|


|
|
|
<
<
















|


|




<
<
<
<
<
<







2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293

2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305


2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329






2330
2331
2332
2333
2334
2335
2336
  }else{
    clr1 = 0xffb5b5;  /* Recent changes: red (hot) */
    clr2 = 0xb5e0ff;  /* Older changes: blue (cold) */
  }
  for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
    clr = gradient_color(clr1, clr2, ann.nVers-1, i);
    ann.aVers[i].zBgColor = mprintf("#%06x", clr);
  }  

  if( showLog ){
    char *zLink = href("%R/finfo?name=%t&ci=%S",zFilename,zCI);
    @ <h2>Ancestors of %z(zLink)%h(zFilename)</a> analyzed:</h2>
    @ <ol>
    for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
      @ <li><span style='background-color:%s(p->zBgColor);'>%s(p->zDate)
      @ check-in %z(href("%R/info/%S",p->zMUuid))%.10s(p->zMUuid)</a>
      @ artifact %z(href("%R/artifact/%S",p->zFUuid))%.10s(p->zFUuid)</a>
      @ </span>
#if 0
      if( i>0 ){
        char *zLink = xhref("target='infowindow'",
                            "%R/fdiff?v1=%S&v2=%S&sbs=1",
                            p->zFUuid,ann.aVers[0].zFUuid);
        @ %z(zLink)[diff-to-top]</a>
        if( i>1 ){
           zLink = xhref("target='infowindow'",
                         "%R/fdiff?v1=%S&v2=%S&sbs=1",
                         p->zFUuid,p[-1].zFUuid);
           @ %z(zLink)[diff-to-previous]</a>
        }
      }
#endif
    }
    @ </ol>
    @ <hr>
  }
  if( !ann.bLimit ){
    @ <h2>Origin for each line in 
    @ %z(href("%R/finfo?name=%h&ci=%S", zFilename, zCI))%h(zFilename)</a>
    @ from check-in %z(href("%R/info/%S",zCI))%S(zCI)</a>:</h2>
    iLimit = ann.nVers+10;
  }else{
    @ <h2>Lines added by the %d(iLimit) most recent ancestors of
    @ %z(href("%R/finfo?name=%h&ci=%S", zFilename, zCI))%h(zFilename)</a>
    @ from check-in %z(href("%R/info/%S",zCI))%S(zCI)</a>:</h2>
  }
  @ <pre>
  for(i=0; i<ann.nOrig; i++){
    int iVers = ann.aOrig[i].iVers;
    char *z = (char*)ann.aOrig[i].z;
    int n = ann.aOrig[i].n;
    char zPrefix[300];
    z[n] = 0;
    if( iLimit>ann.nVers && iVers<0 ) iVers = ann.nVers-1;

    if( bBlame ){
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        char *zLink = xhref("target='infowindow'", "%R/info/%S", p->zMUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %13.13s:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, p->zUser);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%36s", "");
      }
    }else{
      if( iVers>=0 ){
        struct AnnVers *p = ann.aVers+iVers;
        char *zLink = xhref("target='infowindow'", "%R/info/%S", p->zMUuid);
        sqlite3_snprintf(sizeof(zPrefix), zPrefix,
             "<span style='background-color:%s'>"
             "%s%.10s</a> %s</span> %4d:",
             p->zBgColor, zLink, p->zMUuid, p->zDate, i+1);
        fossil_free(zLink);
      }else{
        sqlite3_snprintf(sizeof(zPrefix), zPrefix, "%22s%4d:", "", i+1);
      }
    }
    @ %s(zPrefix) %h(z)

  }
  @ </pre>
  style_footer();
}

/*
** COMMAND: annotate
** COMMAND: blame

**
** %fossil (annotate|blame) ?OPTIONS? FILENAME
**
** Output the text of a file with markings to show when each line of
** the file was last modified.  The "annotate" command shows line numbers
** and omits the username.  The "blame" command shows the user who made each
** checkin and omits the line number.
**
** Options:
**   --filevers      Show file version numbers rather than check-in versions
**   -l|--log        List all versions analyzed
**   -n|--limit N    Only look backwards in time by N versions


**
** See also: info, finfo, timeline
*/
void annotate_cmd(void){
  int fnid;         /* Filename ID */
  int fid;          /* File instance ID */
  int mid;          /* Manifest where file was checked in */
  int cid;          /* Checkout ID */
  Blob treename;    /* FILENAME translated to canonical form */
  char *zFilename;  /* Canonical filename */
  Annotator ann;    /* The annotation of the file */
  int i;            /* Loop counter */
  const char *zLimit; /* The value to the -n|--limit option */
  int iLimit;       /* How far back in time to look */
  int showLog;      /* True to show the log */
  int fileVers;     /* Show file version instead of check-in versions */
  int annFlags = 0; /* Flags to control annotation properties */
  int bBlame = 0;   /* True for BLAME output.  False for ANNOTATE. */

  bBlame = g.argv[1][0]=='b';
  zLimit = find_option("limit","n",1);
  if( zLimit==0 || zLimit[0]==0 ) zLimit = "-1";
  iLimit = atoi(zLimit);
  showLog = find_option("log","l",0)!=0;






  fileVers = find_option("filevers",0,0)!=0;
  db_must_be_within_tree();
  if( g.argc<3 ) {
    usage("FILENAME");
  }
  file_tree_name(g.argv[2], &treename, 1);
  zFilename = blob_str(&treename);
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
  mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
          " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
          " ORDER BY ancestor.generation ASC LIMIT 1",
          fid, fnid);
  if( mid==0 ){
    fossil_fatal("unable to find manifest");
  }
  annFlags |= (ANN_FILE_ANCEST|DIFF_STRIP_EOLCR);
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  if( showLog ){
    struct AnnVers *p;
    for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
      fossil_print("version %3d: %s %.10s file %.10s\n",
                   i+1, p->zDate, p->zMUuid, p->zFUuid);
    }







|







2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
  mid = db_int(0, "SELECT mlink.mid FROM mlink, ancestor "
          " WHERE mlink.fid=%d AND mlink.fnid=%d AND mlink.mid=ancestor.rid"
          " ORDER BY ancestor.generation ASC LIMIT 1",
          fid, fnid);
  if( mid==0 ){
    fossil_fatal("unable to find manifest");
  }
  annFlags |= ANN_FILE_ANCEST;
  annotate_file(&ann, fnid, mid, iLimit, annFlags);
  if( showLog ){
    struct AnnVers *p;
    for(p=ann.aVers, i=0; i<ann.nVers; i++, p++){
      fossil_print("version %3d: %s %.10s file %.10s\n",
                   i+1, p->zDate, p->zMUuid, p->zFUuid);
    }

Changes to src/diffcmd.c.

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
void diff_print_filenames(const char *zLeft, const char *zRight, u64 diffFlags){
  char *z = 0;
  if( diffFlags & DIFF_BRIEF ){
    /* no-op */
  }else if( diffFlags & DIFF_SIDEBYSIDE ){
    int w = diff_width(diffFlags);
    int n1 = strlen(zLeft);
    int n2 = strlen(zRight);
    int x;
    if( n1==n2 && fossil_strcmp(zLeft,zRight)==0 ){
      if( n1>w*2 ) n1 = w*2;
      x = w*2+17 - (n1+2);
      z = mprintf("%.*c %.*s %.*c\n",
                 x/2, '=', n1, zLeft, (x+1)/2, '=');
    }else{
      if( w<20 ) w = 20;
      if( n1>w-10 ) n1 = w - 10;
      if( n2>w-10 ) n2 = w - 10;
      z = mprintf("%.*c %.*s %.*c versus %.*c %.*s %.*c\n",
                  (w-n1+10)/2, '=', n1, zLeft, (w-n1+1)/2, '=',
                  (w-n2)/2, '=', n2, zRight, (w-n2+1)/2, '=');
    }
  }else{
    z = mprintf("--- %s\n+++ %s\n", zLeft, zRight);
  }
  fossil_print("%s", z);
  fossil_free(z);
}








<

<
|
|
|
|
<
<
<
<
<
<
<
<







47
48
49
50
51
52
53

54

55
56
57
58








59
60
61
62
63
64
65
void diff_print_filenames(const char *zLeft, const char *zRight, u64 diffFlags){
  char *z = 0;
  if( diffFlags & DIFF_BRIEF ){
    /* no-op */
  }else if( diffFlags & DIFF_SIDEBYSIDE ){
    int w = diff_width(diffFlags);
    int n1 = strlen(zLeft);

    int x;

    if( n1>w*2 ) n1 = w*2;
    x = w*2+17 - (n1+2);
    z = mprintf("%.*c %.*s %.*c\n",
                x/2, '=', n1, zLeft, (x+1)/2, '=');








  }else{
    z = mprintf("--- %s\n+++ %s\n", zLeft, zRight);
  }
  fossil_print("%s", z);
  fossil_free(z);
}

606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696


697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
  }
  return db_get(zName, zDefault);
}

/* A Tcl/Tk script used to render diff output.
*/
static const char zDiffScript[] =
@ set prog {
@ package require Tk
@
@ array set CFG {
@   TITLE      {Fossil Diff}
@   LN_COL_BG  #dddddd
@   LN_COL_FG  #444444
@   TXT_COL_BG #ffffff
@   TXT_COL_FG #000000
@   MKR_COL_BG #444444
@   MKR_COL_FG #dddddd
@   CHNG_BG    #d0d0ff
@   ADD_BG     #c0ffc0
@   RM_BG      #ffc0c0
@   HR_FG      #888888
@   HR_PAD_TOP 4
@   HR_PAD_BTM 8
@   FN_BG      #444444
@   FN_FG      #ffffff
@   FN_PAD     5
@   ERR_FG     #ee0000
@   PADX       5
@   WIDTH      80
@   HEIGHT     45
@   LB_HEIGHT  25
@ }
@
@ if {![namespace exists ttk]} {
@   interp alias {} ::ttk::scrollbar {} ::scrollbar
@   interp alias {} ::ttk::menubutton {} ::menubutton
@ }
@
@ proc dehtml {x} {
@   set x [regsub -all {<[^>]*>} $x {}]
@   return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
@ }
@
@ proc cols {} {
@   return [list .lnA .txtA .mkr .lnB .txtB]
@ }
@
@ proc colType {c} {
@   regexp {[a-z]+} $c type
@   return $type
@ }
@
@ proc getLine {difftxt N iivar} {
@   upvar $iivar ii
@   if {$ii>=$N} {return -1}
@   set x [lindex $difftxt $ii]
@   incr ii
@   return $x
@ }
@
@ proc readDiffs {fossilcmd} {
@   global difftxt
@   if {![info exists difftxt]} {
@     set in [open $fossilcmd r]
@     fconfigure $in -encoding utf-8
@     set difftxt [split [read $in] \n]
@     close $in
@   }
@   set N [llength $difftxt]
@   set ii 0
@   set nDiffs 0
@   array set widths {txt 0 ln 0 mkr 0}
@   while {[set line [getLine $difftxt $N ii]] != -1} {
@     set fn2 {}
@     if {![regexp {^=+ (.*?) =+ versus =+ (.*?) =+$} $line all fn fn2]
@      && ![regexp {^=+ (.*?) =+$} $line all fn]
@     } {
@       continue
@     }
@     set errMsg ""
@     set line [getLine $difftxt $N ii]
@     if {[string compare -length 6 $line "<table"]
@      && ![regexp {<p[^>]*>(.+)} $line - errMsg]} {
@       continue
@     }
@     incr nDiffs
@     set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}]
@     .wfiles.lb insert end $fn
@
@     foreach c [cols] {


@       if {$nDiffs > 1} {
@         $c insert end \n -
@       }
@       if {[colType $c] eq "txt"} {
@         $c insert end $fn\n fn
@         if {$fn2!=""} {set fn $fn2}
@       } else {
@         $c insert end \n fn
@       }
@       $c insert end \n -
@
@       if {$errMsg ne ""} continue
@       while {[getLine $difftxt $N ii] ne "<pre>"} continue
@       set type [colType $c]
@       set str {}
@       while {[set line [getLine $difftxt $N ii]] ne "</pre>"} {
@         set len [string length [dehtml $line]]
@         if {$len > $widths($type)} {
@           set widths($type) $len
@         }
@         append str $line\n
@       }
@
@       set re {<span class="diff([a-z]+)">([^<]*)</span>}
@       # Use \r as separator since it can't appear in the diff output (it gets
@       # converted to a space).
@       set str [regsub -all $re $str "\r\\1\r\\2\r"]
@       foreach {pre class mid} [split $str \r] {
@         if {$class ne ""} {
@           $c insert end [dehtml $pre] - [dehtml $mid] [list $class -]
@         } else {
@           $c insert end [dehtml $pre] -
@         }
@       }
@     }
@
@     if {$errMsg ne ""} {
@       foreach c {.txtA .txtB} {$c insert end [string trim $errMsg] err}
@       foreach c [cols] {$c insert end \n -}
@     }
@   }
@
@   foreach c [cols] {
@     set type [colType $c]
@     if {$type ne "txt"} {
@       $c config -width $widths($type)
@     }
@     $c config -state disabled
@   }
@   if {$nDiffs <= [.wfiles.lb cget -height]} {
@     .wfiles.lb config -height $nDiffs
@     grid remove .wfiles.sb
@   }
@
@   return $nDiffs
@ }
@
@ proc viewDiff {idx} {
@   .txtA yview $idx
@   .txtA xview moveto 0
@ }
@
@ proc cycleDiffs {{reverse 0}} {
@   if {$reverse} {
@     set range [.txtA tag prevrange fn @0,0 1.0]
@     if {$range eq ""} {
@       viewDiff {fn.last -1c}
@     } else {
@       viewDiff [lindex $range 0]
@     }
@   } else {
@     set range [.txtA tag nextrange fn {@0,0 +1c} end]
@     if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
@       viewDiff fn.first
@     } else {
@       viewDiff [lindex $range 0]
@     }
@   }
@ }
@
@ proc xvis {col} {
@   set view [$col xview]
@   return [expr {[lindex $view 1]-[lindex $view 0]}]
@ }
@
@ proc scroll-x {args} {
@   set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
@   eval $c xview $args
@ }
@
@ interp alias {} scroll-y {} .txtA yview
@
@ proc noop {args} {}
@
@ proc enableSync {axis} {
@   update idletasks
@   interp alias {} sync-$axis {}
@   rename _sync-$axis sync-$axis
@ }
@
@ proc disableSync {axis} {
@   rename sync-$axis _sync-$axis
@   interp alias {} sync-$axis {} noop
@ }
@
@ proc sync-x {col first last} {
@   disableSync x
@   $col xview moveto [expr {$first*[xvis $col]/($last-$first)}]
@   foreach side {A B} {
@     set sb .sbx$side
@     set xview [.txt$side xview]
@     if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} {







<

|

















<





|




|




|



|




|
<
<
<
<
<
<
<
<

<
<
|
|
<
<
<
<
<


|
<
|
<
<


<
<
|
<





|

>
>





<




|
<
<


|






|












<
<
<
<
|
|
|











|


|




|

















|




|




|

|

|





|




|







596
597
598
599
600
601
602

603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621

622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646








647


648
649





650
651
652

653


654
655


656

657
658
659
660
661
662
663
664
665
666
667
668
669
670

671
672
673
674
675


676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697




698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
  }
  return db_get(zName, zDefault);
}

/* A Tcl/Tk script used to render diff output.
*/
static const char zDiffScript[] =

@ package require Tk
@ 
@ array set CFG {
@   TITLE      {Fossil Diff}
@   LN_COL_BG  #dddddd
@   LN_COL_FG  #444444
@   TXT_COL_BG #ffffff
@   TXT_COL_FG #000000
@   MKR_COL_BG #444444
@   MKR_COL_FG #dddddd
@   CHNG_BG    #d0d0ff
@   ADD_BG     #c0ffc0
@   RM_BG      #ffc0c0
@   HR_FG      #888888
@   HR_PAD_TOP 4
@   HR_PAD_BTM 8
@   FN_BG      #444444
@   FN_FG      #ffffff
@   FN_PAD     5

@   PADX       5
@   WIDTH      80
@   HEIGHT     45
@   LB_HEIGHT  25
@ }
@ 
@ if {![namespace exists ttk]} {
@   interp alias {} ::ttk::scrollbar {} ::scrollbar
@   interp alias {} ::ttk::menubutton {} ::menubutton
@ }
@ 
@ proc dehtml {x} {
@   set x [regsub -all {<[^>]*>} $x {}]
@   return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
@ }
@ 
@ proc cols {} {
@   return [list .lnA .txtA .mkr .lnB .txtB]
@ }
@ 
@ proc colType {c} {
@   regexp {[a-z]+} $c type
@   return $type
@ }
@ 








@ proc readDiffs {fossilcmd} {


@   set in [open $fossilcmd r]
@   fconfigure $in -encoding utf-8





@   set nDiffs 0
@   array set widths {txt 0 ln 0 mkr 0}
@   while {[gets $in line] != -1} {

@     if {![regexp {^=+\s+(.*?)\s+=+$} $line all fn]} {


@       continue
@     }


@     if {[string compare -length 6 [gets $in] "<table"]} {

@       continue
@     }
@     incr nDiffs
@     set idx [expr {$nDiffs > 1 ? [.txtA index end] : "1.0"}]
@     .wfiles.lb insert end $fn
@     
@     foreach c [cols] {
@       while {[gets $in] ne "<pre>"} continue
@       
@       if {$nDiffs > 1} {
@         $c insert end \n -
@       }
@       if {[colType $c] eq "txt"} {
@         $c insert end $fn\n fn

@       } else {
@         $c insert end \n fn
@       }
@       $c insert end \n -
@        


@       set type [colType $c]
@       set str {}
@       while {[set line [gets $in]] ne "</pre>"} {
@         set len [string length [dehtml $line]]
@         if {$len > $widths($type)} {
@           set widths($type) $len
@         }
@         append str $line\n
@       }
@       
@       set re {<span class="diff([a-z]+)">([^<]*)</span>}
@       # Use \r as separator since it can't appear in the diff output (it gets
@       # converted to a space).
@       set str [regsub -all $re $str "\r\\1\r\\2\r"]
@       foreach {pre class mid} [split $str \r] {
@         if {$class ne ""} {
@           $c insert end [dehtml $pre] - [dehtml $mid] [list $class -]
@         } else {
@           $c insert end [dehtml $pre] -
@         }
@       }
@     }




@   }
@   close $in
@   
@   foreach c [cols] {
@     set type [colType $c]
@     if {$type ne "txt"} {
@       $c config -width $widths($type)
@     }
@     $c config -state disabled
@   }
@   if {$nDiffs <= [.wfiles.lb cget -height]} {
@     .wfiles.lb config -height $nDiffs
@     grid remove .wfiles.sb
@   }
@   
@   return $nDiffs
@ }
@ 
@ proc viewDiff {idx} {
@   .txtA yview $idx
@   .txtA xview moveto 0
@ }
@ 
@ proc cycleDiffs {{reverse 0}} {
@   if {$reverse} {
@     set range [.txtA tag prevrange fn @0,0 1.0]
@     if {$range eq ""} {
@       viewDiff {fn.last -1c}
@     } else {
@       viewDiff [lindex $range 0]
@     }
@   } else {
@     set range [.txtA tag nextrange fn {@0,0 +1c} end]
@     if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
@       viewDiff fn.first
@     } else {
@       viewDiff [lindex $range 0]
@     }
@   }
@ }
@ 
@ proc xvis {col} {
@   set view [$col xview]
@   return [expr {[lindex $view 1]-[lindex $view 0]}]
@ }
@ 
@ proc scroll-x {args} {
@   set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
@   eval $c xview $args
@ }
@ 
@ interp alias {} scroll-y {} .txtA yview
@ 
@ proc noop {args} {}
@ 
@ proc enableSync {axis} {
@   update idletasks
@   interp alias {} sync-$axis {}
@   rename _sync-$axis sync-$axis
@ }
@ 
@ proc disableSync {axis} {
@   rename sync-$axis _sync-$axis
@   interp alias {} sync-$axis {} noop
@ }
@ 
@ proc sync-x {col first last} {
@   disableSync x
@   $col xview moveto [expr {$first*[xvis $col]/($last-$first)}]
@   foreach side {A B} {
@     set sb .sbx$side
@     set xview [.txt$side xview]
@     if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} {
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
@     grid .sby
@     .sby set $first $last
@   } else {
@     grid remove .sby
@   }
@   enableSync y
@ }
@
@ wm withdraw .
@ wm title . $CFG(TITLE)
@ wm iconname . $CFG(TITLE)
@ bind . <q> exit
@ bind . <Destroy> {after 0 exit}
@ bind . <Tab> {cycleDiffs; break}
@ bind . <<PrevWindow>> {cycleDiffs 1; break}
@ bind . <Return> {
@   event generate .bb.files <1>
@   event generate .bb.files <ButtonRelease-1>
@   break
@ }
@ foreach {key axis args} {
@   Up    y {scroll -5 units}
@   Down  y {scroll 5 units}
@   Left  x {scroll -5 units}
@   Right x {scroll 5 units}
@   Prior y {scroll -1 page}
@   Next  y {scroll 1 page}
@   Home  y {moveto 0}
@   End   y {moveto 1}
@ } {
@   bind . <$key> "scroll-$axis $args; break"
@   bind . <Shift-$key> continue
@ }
@
@ frame .bb
@ ::ttk::menubutton .bb.files -text "Files"
@ toplevel .wfiles
@ wm withdraw .wfiles
@ update idletasks
@ wm transient .wfiles .
@ wm overrideredirect .wfiles 1
@ listbox .wfiles.lb -width 0 -height $CFG(LB_HEIGHT) -activestyle none \
@   -yscroll {.wfiles.sb set}
@ ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview}
@ grid .wfiles.lb .wfiles.sb -sticky ns
@ bind .bb.files <1> {
@   set x [winfo rootx %W]
@   set y [expr {[winfo rooty %W]+[winfo height %W]}]
@   wm geometry .wfiles +$x+$y
@   wm deiconify .wfiles
@   focus .wfiles.lb
@ }
@ bind .wfiles <FocusOut> {wm withdraw .wfiles}







|








|
|















|
<
|









|







786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
@     grid .sby
@     .sby set $first $last
@   } else {
@     grid remove .sby
@   }
@   enableSync y
@ }
@ 
@ wm withdraw .
@ wm title . $CFG(TITLE)
@ wm iconname . $CFG(TITLE)
@ bind . <q> exit
@ bind . <Destroy> {after 0 exit}
@ bind . <Tab> {cycleDiffs; break}
@ bind . <<PrevWindow>> {cycleDiffs 1; break}
@ bind . <Return> {
@   event generate .files <1>
@   event generate .files <ButtonRelease-1>
@   break
@ }
@ foreach {key axis args} {
@   Up    y {scroll -5 units}
@   Down  y {scroll 5 units}
@   Left  x {scroll -5 units}
@   Right x {scroll 5 units}
@   Prior y {scroll -1 page}
@   Next  y {scroll 1 page}
@   Home  y {moveto 0}
@   End   y {moveto 1}
@ } {
@   bind . <$key> "scroll-$axis $args; break"
@   bind . <Shift-$key> continue
@ }
@ 

@ ::ttk::menubutton .files -text "Files"
@ toplevel .wfiles
@ wm withdraw .wfiles
@ update idletasks
@ wm transient .wfiles .
@ wm overrideredirect .wfiles 1
@ listbox .wfiles.lb -width 0 -height $CFG(LB_HEIGHT) -activestyle none \
@   -yscroll {.wfiles.sb set}
@ ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview}
@ grid .wfiles.lb .wfiles.sb -sticky ns
@ bind .files <1> {
@   set x [winfo rootx %W]
@   set y [expr {[winfo rooty %W]+[winfo height %W]}]
@   wm geometry .wfiles +$x+$y
@   wm deiconify .wfiles
@   focus .wfiles.lb
@ }
@ bind .wfiles <FocusOut> {wm withdraw .wfiles}
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
@     break
@   }
@ }
@ bind .wfiles.lb <Motion> {
@   %W selection clear 0 end
@   %W selection set @%x,%y
@ }
@
@ foreach {side syncCol} {A .txtB B .txtA} {
@   set ln .ln$side
@   text $ln
@   $ln tag config - -justify right
@
@   set txt .txt$side
@   text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
@     -xscroll "sync-x $syncCol"
@   catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
@   foreach tag {add rm chng} {
@     $txt tag config $tag -background $CFG([string toupper $tag]_BG)
@     $txt tag lower $tag
@   }
@   $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
@     -justify center
@   $txt tag config err -foreground $CFG(ERR_FG)
@ }
@ text .mkr
@
@ foreach c [cols] {
@   set keyPrefix [string toupper [colType $c]]_COL_
@   if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
@   $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
@     -padx $CFG(PADX) -yscroll sync-y
@   $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
@      -foreground $CFG(HR_FG)
@   $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
@   bindtags $c ". $c Text all"
@   bind $c <1> {focus %W}
@ }
@
@ ::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
@ ::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
@ ::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal
@ frame .spacer
@
@ if {[readDiffs $fossilcmd] == 0} {
@   tk_messageBox -type ok -title $CFG(TITLE) -message "No changes"
@   exit
@ }
@ update idletasks
@
@ proc saveDiff {} {
@   set fn [tk_getSaveFile]
@   set out [open $fn wb]
@   puts $out "#!/usr/bin/tclsh\n#\n# Run this script using 'tclsh' or 'wish'"
@   puts $out "# to see the graphical diff.\n#"
@   puts $out "set fossilcmd {}"
@   puts $out "set prog [list $::prog]"
@   puts $out "set difftxt \173"
@   foreach e $::difftxt {puts $out [list $e]}
@   puts $out "\175"
@   puts $out "eval \$prog"
@   close $out
@ }
@ proc invertDiff {} {
@   global CFG
@   array set x [grid info .txtA]
@   if {$x(-column)==1} {
@     grid config .lnB -column 0
@     grid config .txtB -column 1
@     .txtB tag config add -background $CFG(RM_BG)
@     grid config .lnA -column 3
@     grid config .txtA -column 4
@     .txtA tag config rm -background $CFG(ADD_BG)
@   } else {
@     grid config .lnA -column 0
@     grid config .txtA -column 1
@     .txtA tag config rm -background $CFG(RM_BG)
@     grid config .lnB -column 3
@     grid config .txtB -column 4
@     .txtB tag config add -background $CFG(ADD_BG)
@   }
@   .mkr config -state normal
@   set clt [.mkr search -all < 1.0 end]
@   set cgt [.mkr search -all > 1.0 end]
@   foreach c $clt {.mkr replace $c "$c +1 chars" >}
@   foreach c $cgt {.mkr replace $c "$c +1 chars" <}
@   .mkr config -state disabled
@ }
@ ::ttk::button .bb.quit -text {Quit} -command exit
@ ::ttk::button .bb.invert -text {Invert} -command invertDiff
@ ::ttk::button .bb.save -text {Save As...} -command saveDiff
@ pack .bb.quit .bb.invert -side left
@ if {$fossilcmd!=""} {pack .bb.save -side left}
@ pack .bb.files -side left
@ grid rowconfigure . 1 -weight 1
@ grid columnconfigure . 1 -weight 1
@ grid columnconfigure . 4 -weight 1
@ grid .bb -row 0 -columnspan 6
@ eval grid [cols] -row 1 -sticky nsew
@ grid .sby -row 1 -column 5 -sticky ns
@ grid .sbxA -row 2 -columnspan 2 -sticky ew
@ grid .spacer -row 2 -column 2
@ grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew
@
@ .spacer config -height [winfo height .sbxA]
@ wm deiconify .
@ }
@ eval $prog
;

/*
** Show diff output in a Tcl/Tk window, in response to the --tk option
** to the diff command.
**
** If fossil has direct access to a Tcl interpreter (either loaded
** dynamically through stubs or linked in statically), we can use it
** directly. Otherwise:
** (1) Write the Tcl/Tk script used for rendering into a temp file.
** (2) Invoke "tclsh" on the temp file using fossil_system().
** (3) Delete the temp file.
*/







|




|










<


|











|




|





|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<



|








<
<





|







846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868

869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894












































895
896
897
898
899
900
901
902
903
904
905
906


907
908
909
910
911
912
913
914
915
916
917
918
919
@     break
@   }
@ }
@ bind .wfiles.lb <Motion> {
@   %W selection clear 0 end
@   %W selection set @%x,%y
@ }
@ 
@ foreach {side syncCol} {A .txtB B .txtA} {
@   set ln .ln$side
@   text $ln
@   $ln tag config - -justify right
@   
@   set txt .txt$side
@   text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
@     -xscroll "sync-x $syncCol"
@   catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
@   foreach tag {add rm chng} {
@     $txt tag config $tag -background $CFG([string toupper $tag]_BG)
@     $txt tag lower $tag
@   }
@   $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
@     -justify center

@ }
@ text .mkr
@ 
@ foreach c [cols] {
@   set keyPrefix [string toupper [colType $c]]_COL_
@   if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
@   $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
@     -padx $CFG(PADX) -yscroll sync-y
@   $c tag config hr -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
@      -foreground $CFG(HR_FG)
@   $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
@   bindtags $c ". $c Text all"
@   bind $c <1> {focus %W}
@ }
@ 
@ ::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
@ ::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
@ ::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal
@ frame .spacer
@ 
@ if {[readDiffs $fossilcmd] == 0} {
@   tk_messageBox -type ok -title $CFG(TITLE) -message "No changes"
@   exit
@ }
@ update idletasks
@ 












































@ grid rowconfigure . 1 -weight 1
@ grid columnconfigure . 1 -weight 1
@ grid columnconfigure . 4 -weight 1
@ grid .files -row 0 -columnspan 6
@ eval grid [cols] -row 1 -sticky nsew
@ grid .sby -row 1 -column 5 -sticky ns
@ grid .sbxA -row 2 -columnspan 2 -sticky ew
@ grid .spacer -row 2 -column 2
@ grid .sbxB -row 2 -column 3 -columnspan 2 -sticky ew
@
@ .spacer config -height [winfo height .sbxA]
@ wm deiconify .


;

/*
** Show diff output in a Tcl/Tk window, in response to the --tk option
** to the diff command.
** 
** If fossil has direct access to a Tcl interpreter (either loaded
** dynamically through stubs or linked in statically), we can use it
** directly. Otherwise:
** (1) Write the Tcl/Tk script used for rendering into a temp file.
** (2) Invoke "tclsh" on the temp file using fossil_system().
** (3) Delete the temp file.
*/
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
      ** for testing and debugging. */
      if( strglob("*-script",z) && i<g.argc-1 ){
        i++;
        zTempFile = g.argv[i];
        continue;
      }
    }
    if( sqlite3_strglob("*}*",z) ){
      blob_appendf(&script, " {%/}", z);
    }else{
      int j;
      blob_append(&script, " ", 1);
      for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
    }
  }
  blob_appendf(&script, "}\n%s", zDiffScript);
  if( zTempFile ){
    blob_write_to_file(&script, zTempFile);
    fossil_print("To see diff, run: tclsh \"%s\"\n", zTempFile);
  }else{
#if defined(FOSSIL_ENABLE_TCL)







<
<
<
<
|
|
<







936
937
938
939
940
941
942




943
944

945
946
947
948
949
950
951
      ** for testing and debugging. */
      if( strglob("*-script",z) && i<g.argc-1 ){
        i++;
        zTempFile = g.argv[i];
        continue;
      }
    }




    blob_append(&script, " ", 1);
    shell_escape(&script, z);

  }
  blob_appendf(&script, "}\n%s", zDiffScript);
  if( zTempFile ){
    blob_write_to_file(&script, zTempFile);
    fossil_print("To see diff, run: tclsh \"%s\"\n", zTempFile);
  }else{
#if defined(FOSSIL_ENABLE_TCL)
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
**
** Show the difference between the current version of each of the FILEs
** specified (as they exist on disk) and that same file as it was checked
** out.  Or if the FILE arguments are omitted, show the unsaved changed
** currently in the working check-out.
**
** If the "--from VERSION" or "-r VERSION" option is used it specifies
** the source check-in for the diff operation.  If not specified, the
** source check-in is the base check-in for the current check-out.
**
** If the "--to VERSION" option appears, it specifies the check-in from
** which the second version of the file or files is taken.  If there is
** no "--to" option then the (possibly edited) files in the current check-out
** are used.
**







|







1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
**
** Show the difference between the current version of each of the FILEs
** specified (as they exist on disk) and that same file as it was checked
** out.  Or if the FILE arguments are omitted, show the unsaved changed
** currently in the working check-out.
**
** If the "--from VERSION" or "-r VERSION" option is used it specifies
** the source check-in for the diff operation.  If not specified, the 
** source check-in is the base check-in for the current check-out.
**
** If the "--to VERSION" option appears, it specifies the check-in from
** which the second version of the file or files is taken.  If there is
** no "--to" option then the (possibly edited) files in the current check-out
** are used.
**
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
** when using an external diff program.
**
** The "--binary" option causes files matching the glob PATTERN to be treated
** as binary when considering if they should be used with external diff program.
** This option overrides the "binary-glob" setting.
**
** Options:
**   --binary PATTERN           Treat files that match the glob PATTERN as binary
**   --branch BRANCH            Show diff of all changes on BRANCH
**   --brief                    Show filenames only
**   --context|-c N             Use N lines of context
**   --diff-binary BOOL         Include binary files when using external commands
**   --from|-r VERSION          select VERSION as source for the diff
**   --internal|-i              use internal diff logic
**   --side-by-side|-y          side-by-side diff
**   --strip-trailing-cr        Strip trailing CR
**   --tk                       Launch a Tcl/Tk GUI for display
**   --to VERSION               select VERSION as target for the diff
**   --unified                  unified diff
**   -v|--verbose               output complete text of added or deleted files
**   -w|--ignore-all-space      Ignore white space when comparing lines
**   -W|--width <num>           Width of lines in side-by-side diff
**   -Z|--ignore-trailing-space Ignore changes to end-of-line whitespace
*/
void diff_cmd(void){
  int isGDiff;               /* True for gdiff.  False for normal diff */
  int isInternDiff;          /* True for internal diff */
  int verboseFlag;           /* True if -v or --verbose flag is used */
  const char *zFrom;         /* Source version number */
  const char *zTo;           /* Target version number */







|
|
|
|
|
|
|
|
<
|
|
|
|
<
|
<







1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041

1042
1043
1044
1045

1046

1047
1048
1049
1050
1051
1052
1053
** when using an external diff program.
**
** The "--binary" option causes files matching the glob PATTERN to be treated
** as binary when considering if they should be used with external diff program.
** This option overrides the "binary-glob" setting.
**
** Options:
**   --binary PATTERN    Treat files that match the glob PATTERN as binary
**   --branch BRANCH     Show diff of all changes on BRANCH
**   --brief             Show filenames only
**   --context|-c N      Use N lines of context 
**   --diff-binary BOOL  Include binary files when using external commands
**   --from|-r VERSION   select VERSION as source for the diff
**   --internal|-i       use internal diff logic
**   --side-by-side|-y   side-by-side diff

**   --tk                Launch a Tcl/Tk GUI for display
**   --to VERSION        select VERSION as target for the diff
**   --unified           unified diff
**   -v|--verbose        output complete text of added or deleted files

**   -W|--width          Width of lines in side-by-side diff

*/
void diff_cmd(void){
  int isGDiff;               /* True for gdiff.  False for normal diff */
  int isInternDiff;          /* True for internal diff */
  int verboseFlag;           /* True if -v or --verbose flag is used */
  const char *zFrom;         /* Source version number */
  const char *zTo;           /* Target version number */
1163
1164
1165
1166
1167
1168
1169

1170
1171
1172
1173
1174
1175
1176
  zBranch = find_option("branch", 0, 1);
  diffFlags = diff_options();
  verboseFlag = find_option("verbose","v",0)!=0;
  if( !verboseFlag ){
    verboseFlag = find_option("new-file","N",0)!=0; /* deprecated */
  }
  if( verboseFlag ) diffFlags |= DIFF_VERBOSE;

  if( zBranch ){
    if( zTo || zFrom ){
      fossil_fatal("cannot use --from or --to with --branch");
    }
    zTo = zBranch;
    zFrom = mprintf("root:%s", zBranch);
  }







>







1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
  zBranch = find_option("branch", 0, 1);
  diffFlags = diff_options();
  verboseFlag = find_option("verbose","v",0)!=0;
  if( !verboseFlag ){
    verboseFlag = find_option("new-file","N",0)!=0; /* deprecated */
  }
  if( verboseFlag ) diffFlags |= DIFF_VERBOSE;

  if( zBranch ){
    if( zTo || zFrom ){
      fossil_fatal("cannot use --from or --to with --branch");
    }
    zTo = zBranch;
    zFrom = mprintf("root:%s", zBranch);
  }

Changes to src/doc.c.

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495

    /* Get the file content */
    if( content_get(rid, &filebody)==0 ){
      goto doc_not_found;
    }
    db_end_transaction(0);
  }
  blob_to_utf8_no_bom(&filebody, 0);

  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user 
  */
  zMime = P("mimetype");
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);







<







481
482
483
484
485
486
487

488
489
490
491
492
493
494

    /* Get the file content */
    if( content_get(rid, &filebody)==0 ){
      goto doc_not_found;
    }
    db_end_transaction(0);
  }


  /* The file is now contained in the filebody blob.  Deliver the
  ** file to the user 
  */
  zMime = P("mimetype");
  if( zMime==0 ){
    zMime = mimetype_from_name(zName);

Changes to src/event.c.

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
      @ <div>
    }
    blob_init(&comment, pEvent->zComment, -1);
    wiki_convert(&comment, 0, WIKI_INLINE);
    blob_reset(&comment);
    @ </div>
    @ </blockquote><hr />
  }

  wiki_convert(&tail, 0, 0);
  style_footer();
  manifest_destroy(pEvent);
}

/*







|







183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
      @ <div>
    }
    blob_init(&comment, pEvent->zComment, -1);
    wiki_convert(&comment, 0, WIKI_INLINE);
    blob_reset(&comment);
    @ </div>
    @ </blockquote><hr />
  }  

  wiki_convert(&tail, 0, 0);
  style_footer();
  manifest_destroy(pEvent);
}

/*
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    int nEventId = strlen(zEventId);
    if( nEventId!=40 || !validate16(zEventId, 40) ){
      fossil_redirect_home();
      return;
    }
  }
  zTag = mprintf("event-%s", zEventId);
  rid = db_int(0,
    "SELECT rid FROM tagxref"
    " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
    " ORDER BY mtime DESC", zTag
  );
  free(zTag);

  /* Need both check-in and wiki-write or wiki-create privileges in order







|







225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    int nEventId = strlen(zEventId);
    if( nEventId!=40 || !validate16(zEventId, 40) ){
      fossil_redirect_home();
      return;
    }
  }
  zTag = mprintf("event-%s", zEventId);
  rid = db_int(0, 
    "SELECT rid FROM tagxref"
    " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
    " ORDER BY mtime DESC", zTag
  );
  free(zTag);

  /* Need both check-in and wiki-write or wiki-create privileges in order
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
      Blob tags, one;
      int i, j;
      Stmt q;
      char *zBlob;

      /* Load the tags string into a blob */
      blob_zero(&tags);
      blob_append(&tags, zTags, -1);

      /* Collapse all sequences of whitespace and "," characters into
      ** a single space character */
      zBlob = blob_str(&tags);
      for(i=j=0; zBlob[i]; i++, j++){
        if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
          while( fossil_isspace(zBlob[i+1]) ){ i++; }







|







310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
      Blob tags, one;
      int i, j;
      Stmt q;
      char *zBlob;

      /* Load the tags string into a blob */
      blob_zero(&tags);
      blob_append(&tags, zTags, -1); 

      /* Collapse all sequences of whitespace and "," characters into
      ** a single space character */
      zBlob = blob_str(&tags);
      for(i=j=0; zBlob[i]; i++, j++){
        if( fossil_isspace(zBlob[i]) || zBlob[i]==',' ){
          while( fossil_isspace(zBlob[i+1]) ){ i++; }
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
      /* Extract the tags in sorted order and make an entry in the
      ** artifact for each. */
      db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
      while( db_step(&q)==SQLITE_ROW ){
        blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
      }
      db_finalize(&q);
    }
    if( !login_is_nobody() ){
      blob_appendf(&event, "U %F\n", login_name());
    }
    blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
    md5sum_blob(&event, &cksum);
    blob_appendf(&event, "Z %b\n", &cksum);
    blob_reset(&cksum);
    nrid = content_put(&event);
    db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);







|
|
|







339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
      /* Extract the tags in sorted order and make an entry in the
      ** artifact for each. */
      db_prepare(&q, "SELECT x FROM newtags ORDER BY x");
      while( db_step(&q)==SQLITE_ROW ){
        blob_appendf(&event, "T +sym-%F *\n", db_column_text(&q, 0));
      }
      db_finalize(&q);
    }        
    if( g.zLogin ){
      blob_appendf(&event, "U %F\n", g.zLogin);
    }
    blob_appendf(&event, "W %d\n%s\n", strlen(zBody), zBody);
    md5sum_blob(&event, &cksum);
    blob_appendf(&event, "Z %b\n", &cksum);
    blob_reset(&cksum);
    nrid = content_put(&event);
    db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid);
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
  @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
  @ <td valign="top">
  @   <input type="text" name="t" size="25" value="%h(zETime)" />
  @ </td></tr>

  @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
  @ <td valign="top">
  @ <textarea name="c" class="eventedit" cols="80"
  @  rows="3" wrap="virtual">%h(zComment)</textarea>
  @ </td></tr>

  @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
  @ <td valign="top">
  render_color_chooser(0, zClr, 0, "clr", "cclr");
  @ </td></tr>

  @ <tr><th align="right" valign="top">Tags:</th>
  @ <td valign="top">
  @   <input type="text" name="g" size="40" value="%h(zTags)" />
  @ </td></tr>

  @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
  @ <td valign="top">
  @ <textarea name="w" class="eventedit" cols="80"
  @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
  @ </td></tr>

  @ <tr><td colspan="2">
  @ <input type="submit" name="preview" value="Preview Your Changes" />
  @ <input type="submit" name="submit" value="Apply These Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  @ </td></tr></table>
  @ </div></form>
  style_footer();
}







|







|




|


|











409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
  @ <tr><th align="right" valign="top">Event&nbsp;Time (UTC):</th>
  @ <td valign="top">
  @   <input type="text" name="t" size="25" value="%h(zETime)" />
  @ </td></tr>

  @ <tr><th align="right" valign="top">Timeline&nbsp;Comment:</th>
  @ <td valign="top">
  @ <textarea name="c" class="eventedit" cols="80" 
  @  rows="3" wrap="virtual">%h(zComment)</textarea>
  @ </td></tr>

  @ <tr><th align="right" valign="top">Background&nbsp;Color:</th>
  @ <td valign="top">
  render_color_chooser(0, zClr, 0, "clr", "cclr");
  @ </td></tr>
  
  @ <tr><th align="right" valign="top">Tags:</th>
  @ <td valign="top">
  @   <input type="text" name="g" size="40" value="%h(zTags)" />
  @ </td></tr>
  
  @ <tr><th align="right" valign="top">Page&nbsp;Content:</th>
  @ <td valign="top">
  @ <textarea name="w" class="eventedit" cols="80" 
  @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
  @ </td></tr>

  @ <tr><td colspan="2">
  @ <input type="submit" name="preview" value="Preview Your Changes" />
  @ <input type="submit" name="submit" value="Apply These Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  @ </td></tr></table>
  @ </div></form>
  style_footer();
}

Changes to src/file.c.

391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
*/
void file_copy(const char *zFrom, const char *zTo){
  FILE *in, *out;
  int got;
  char zBuf[8192];
  in = fossil_fopen(zFrom, "rb");
  if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom);
  file_mkfolder(zTo, 0);
  out = fossil_fopen(zTo, "wb");
  if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo);
  while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
    fwrite(zBuf, 1, got, out);
  }
  fclose(in);
  fclose(out);
}

/*
** COMMAND: test-file-copy
**
** Usage: %fossil test-file-copy SOURCE DESTINATION
**
** Make a copy of the file at SOURCE into a new name DESTINATION.  Any
** directories in the path leading up to DESTINATION that do not already
** exist are created automatically.
*/
void test_file_copy(void){
  if( g.argc!=4 ){
    fossil_fatal("Usage: %s test-file-copy SOURCE DESTINATION", g.argv[0]);
  }
  file_copy(g.argv[2], g.argv[3]);
}

/*
** Set or clear the execute bit on a file.  Return true if a change
** occurred and false if this routine is a no-op.
*/
int file_wd_setexe(const char *zFilename, int onoff){
  int rc = 0;
#if !defined(_WIN32)







<









<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







391
392
393
394
395
396
397

398
399
400
401
402
403
404
405
406
















407
408
409
410
411
412
413
*/
void file_copy(const char *zFrom, const char *zTo){
  FILE *in, *out;
  int got;
  char zBuf[8192];
  in = fossil_fopen(zFrom, "rb");
  if( in==0 ) fossil_fatal("cannot open \"%s\" for reading", zFrom);

  out = fossil_fopen(zTo, "wb");
  if( out==0 ) fossil_fatal("cannot open \"%s\" for writing", zTo);
  while( (got=fread(zBuf, 1, sizeof(zBuf), in))>0 ){
    fwrite(zBuf, 1, got, out);
  }
  fclose(in);
  fclose(out);
}

















/*
** Set or clear the execute bit on a file.  Return true if a change
** occurred and false if this routine is a no-op.
*/
int file_wd_setexe(const char *zFilename, int onoff){
  int rc = 0;
#if !defined(_WIN32)
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
#endif
    fossil_filename_free(zMbcs);
    return rc;
  }
  return 0;
}

/*
** Create the tree of directories in which zFilename belongs, if that sequence
** of directories does not already exist.
*/
void file_mkfolder(const char *zFilename, int forceFlag){
  int i, nName;
  char *zName;

  nName = strlen(zFilename);
  zName = mprintf("%s", zFilename);
  nName = file_simplify_name(zName, nName, 0);
  for(i=1; i<nName; i++){
    if( zName[i]=='/' ){
      zName[i] = 0;
#if defined(_WIN32) || defined(__CYGWIN__)
      /*
      ** On Windows, local path looks like: C:/develop/project/file.txt
      ** The if stops us from trying to create a directory of a drive letter
      ** C: in this example.
      */
      if( !(i==2 && zName[1]==':') ){
#endif
        if( file_mkdir(zName, forceFlag) && file_isdir(zName)!=1 ){
          fossil_fatal_recursive("unable to create directory %s", zName);
          return;
        }
#if defined(_WIN32) || defined(__CYGWIN__)
      }
#endif
      zName[i] = '/';
    }
  }
  free(zName);
}

/*
** Removes the directory named in the argument, if it exists.  The directory
** must be empty and cannot be the current directory or the root directory.
**
** Returns zero upon success.
*/
int file_rmdir(const char *zName){







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







515
516
517
518
519
520
521



































522
523
524
525
526
527
528
#endif
    fossil_filename_free(zMbcs);
    return rc;
  }
  return 0;
}




































/*
** Removes the directory named in the argument, if it exists.  The directory
** must be empty and cannot be the current directory or the root directory.
**
** Returns zero upon success.
*/
int file_rmdir(const char *zName){
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
  *pJ = i-1;
  return 1;
}

/*
** Simplify a filename by
**
**  * Remove extended path prefix on windows and cygwin
**  * Convert all \ into / on windows and cygwin
**  * removing any trailing and duplicate /
**  * removing /./
**  * removing /A/../
**
** Changes are made in-place.  Return the new name length.
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
*/
int file_simplify_name(char *z, int n, int slash){
  int i = 1, j;
  if( n<0 ) n = strlen(z);

  /* On windows and cygwin convert all \ characters to /
   * and remove extended path prefix if present */
#if defined(_WIN32) || defined(__CYGWIN__)
  for(j=0; j<n; j++){
    if( z[j]=='\\' ) z[j] = '/';
  }
  if( n>3 && !memcmp(z, "//?/", 4) ){
    if( fossil_strnicmp(z+4,"UNC", 3) ){
      i += 4;
      z[0] = z[4];
    }else{
      i += 6;
      z[0] = '/';
    }
  }
#endif

  /* Removing trailing "/" characters */
  if( !slash ){
    while( n>1 && z[n-1]=='/' ){ n--; }
  }

  /* Remove duplicate '/' characters.  Except, two // at the beginning
  ** of a pathname is allowed since this is important on windows. */
  for(j=1; i<n; i++){
    z[j++] = z[i];
    while( z[i]=='/' && i<n-1 && z[i+1]=='/' ) i++;
  }
  n = j;

  /* Skip over zero or more initial "./" sequences */
  for(i=0; i<n-1 && z[i]=='.' && z[i+1]=='/'; i+=2){}







<










|


|
<

|
|
<
<
<
<
<
<
<
<
<










|







642
643
644
645
646
647
648

649
650
651
652
653
654
655
656
657
658
659
660
661
662

663
664
665









666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
  *pJ = i-1;
  return 1;
}

/*
** Simplify a filename by
**

**  * Convert all \ into / on windows and cygwin
**  * removing any trailing and duplicate /
**  * removing /./
**  * removing /A/../
**
** Changes are made in-place.  Return the new name length.
** If the slash parameter is non-zero, the trailing slash, if any,
** is retained.
*/
int file_simplify_name(char *z, int n, int slash){
  int i, j;
  if( n<0 ) n = strlen(z);

  /* On windows and cygwin convert all \ characters to / */

#if defined(_WIN32) || defined(__CYGWIN__)
  for(i=0; i<n; i++){
    if( z[i]=='\\' ) z[i] = '/';









  }
#endif

  /* Removing trailing "/" characters */
  if( !slash ){
    while( n>1 && z[n-1]=='/' ){ n--; }
  }

  /* Remove duplicate '/' characters.  Except, two // at the beginning
  ** of a pathname is allowed since this is important on windows. */
  for(i=j=1; i<n; i++){
    z[j++] = z[i];
    while( z[i]=='/' && i<n-1 && z[i+1]=='/' ) i++;
  }
  n = j;

  /* Skip over zero or more initial "./" sequences */
  for(i=0; i<n-1 && z[i]=='.' && z[i+1]=='/'; i+=2){}
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
  char *zLocalRoot;
  Blob full;
  int nFull;
  char *zFull;
  int (*xCmp)(const char*,const char*,int);

  blob_zero(pOut);
  if( !g.localOpen ){
    blob_appendf(pOut, "%s", zOrigName);
    return 1;
  }
  file_canonical_name(g.zLocalRoot, &localRoot, 1);
  nLocalRoot = blob_size(&localRoot);
  zLocalRoot = blob_buffer(&localRoot);
  assert( nLocalRoot>0 && zLocalRoot[nLocalRoot-1]=='/' );
  file_canonical_name(zOrigName, &full, 0);
  nFull = blob_size(&full);
  zFull = blob_buffer(&full);
  if( filenames_are_case_sensitive() ){
    xCmp = fossil_strncmp;
  }else{
    xCmp = fossil_strnicmp;
  }

  /* Special case.  zOrigName refers to g.zLocalRoot directory. */
  if( (nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0)
      || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){
    blob_append(pOut, ".", 1);
    blob_reset(&localRoot);
    blob_reset(&full);
    return 1;
  }








<
<
|
<














|







981
982
983
984
985
986
987


988

989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
  char *zLocalRoot;
  Blob full;
  int nFull;
  char *zFull;
  int (*xCmp)(const char*,const char*,int);

  blob_zero(pOut);


  db_must_be_within_tree();

  file_canonical_name(g.zLocalRoot, &localRoot, 1);
  nLocalRoot = blob_size(&localRoot);
  zLocalRoot = blob_buffer(&localRoot);
  assert( nLocalRoot>0 && zLocalRoot[nLocalRoot-1]=='/' );
  file_canonical_name(zOrigName, &full, 0);
  nFull = blob_size(&full);
  zFull = blob_buffer(&full);
  if( filenames_are_case_sensitive() ){
    xCmp = fossil_strncmp;
  }else{
    xCmp = fossil_strnicmp;
  }

  /* Special case.  zOrigName refers to g.zLocalRoot directory. */
  if( (nFull==nLocalRoot-1 && xCmp(zLocalRoot, zFull, nFull)==0) 
      || (nFull==1 && zFull[0]=='/' && nLocalRoot==1 && zLocalRoot[0]=='/') ){
    blob_append(pOut, ".", 1);
    blob_reset(&localRoot);
    blob_reset(&full);
    return 1;
  }

1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
** Options:
**   --case-sensitive B   Enable or disable case-sensitive filenames.  B is
**                        a boolean: "yes", "no", "true", "false", etc.
*/
void cmd_test_tree_name(void){
  int i;
  Blob x;
  db_find_and_open_repository(0,0);
  blob_zero(&x);
  capture_case_sensitive_option();
  for(i=2; i<g.argc; i++){
    if( file_tree_name(g.argv[i], &x, 1) ){
      fossil_print("%s\n", blob_buffer(&x));
      blob_reset(&x);
    }







<







1030
1031
1032
1033
1034
1035
1036

1037
1038
1039
1040
1041
1042
1043
** Options:
**   --case-sensitive B   Enable or disable case-sensitive filenames.  B is
**                        a boolean: "yes", "no", "true", "false", etc.
*/
void cmd_test_tree_name(void){
  int i;
  Blob x;

  blob_zero(&x);
  capture_case_sensitive_option();
  for(i=2; i<g.argc; i++){
    if( file_tree_name(g.argv[i], &x, 1) ){
      fossil_print("%s\n", blob_buffer(&x));
      blob_reset(&x);
    }

Changes to src/finfo.c.

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    " mlink.pfnid",                                  /* Previous filename */
    timeline_utc(), TAG_BRANCH
  );
  if( firstChngOnly ){
#if 0
    blob_appendf(&sql, ", min(event.mtime)");
#else
    blob_appendf(&sql,
        ", min(CASE (SELECT value FROM tagxref"
                    " WHERE tagtype>0 AND tagid=%d"
                    "   AND tagxref.rid=mlink.mid)"
             " WHEN 'trunk' THEN event.mtime-10000 ELSE event.mtime END)",
    TAG_BRANCH);
#endif
  }







|







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    " mlink.pfnid",                                  /* Previous filename */
    timeline_utc(), TAG_BRANCH
  );
  if( firstChngOnly ){
#if 0
    blob_appendf(&sql, ", min(event.mtime)");
#else
    blob_appendf(&sql, 
        ", min(CASE (SELECT value FROM tagxref"
                    " WHERE tagtype>0 AND tagid=%d"
                    "   AND tagxref.rid=mlink.mid)"
             " WHEN 'trunk' THEN event.mtime-10000 ELSE event.mtime END)",
    TAG_BRANCH);
#endif
  }
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
  if( P("showsql")!=0 ){
    @ <p>SQL: %h(blob_str(&sql))</p>
  }
  blob_reset(&sql);
  blob_zero(&title);
  if( baseCheckin ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
    char *zLink = href("%R/info/%s", zUuid);
    blob_appendf(&title, "Ancestors of file ");
    hyperlinked_path(zFilename, &title, zUuid, "tree", "");
    blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
    fossil_free(zUuid);
  }else{
    blob_appendf(&title, "History of files named ");
    hyperlinked_path(zFilename, &title, 0, "tree", "");







|







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
  if( P("showsql")!=0 ){
    @ <p>SQL: %h(blob_str(&sql))</p>
  }
  blob_reset(&sql);
  blob_zero(&title);
  if( baseCheckin ){
    char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", baseCheckin);
    char *zLink = href("%R/info/%S", zUuid);
    blob_appendf(&title, "Ancestors of file ");
    hyperlinked_path(zFilename, &title, zUuid, "tree", "");
    blob_appendf(&title, " from check-in %z%.10s</a>", zLink, zUuid);
    fossil_free(zUuid);
  }else{
    blob_appendf(&title, "History of files named ");
    hyperlinked_path(zFilename, &title, 0, "tree", "");
402
403
404
405
406
407
408


409
410
411
412
413
414
415
    const char *zCkin = db_column_text(&q,7);
    const char *zBgClr = db_column_text(&q, 8);
    const char *zBr = db_column_text(&q, 9);
    int fmid = db_column_int(&q, 10);
    int pfnid = db_column_int(&q, 11);
    int gidx;
    char zTime[10];


    if( zBr==0 ) zBr = "trunk";
    if( uBg ){
      zBgClr = hash_color(zUser);
    }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
      zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
    }
    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr,







>
>







402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
    const char *zCkin = db_column_text(&q,7);
    const char *zBgClr = db_column_text(&q, 8);
    const char *zBr = db_column_text(&q, 9);
    int fmid = db_column_int(&q, 10);
    int pfnid = db_column_int(&q, 11);
    int gidx;
    char zTime[10];
    char zShort[20];
    char zShortCkin[20];
    if( zBr==0 ) zBr = "trunk";
    if( uBg ){
      zBgClr = hash_color(zUser);
    }else if( brBg || zBgClr==0 || zBgClr[0]==0 ){
      zBgClr = strcmp(zBr,"trunk")==0 ? "" : hash_color(zBr);
    }
    gidx = graph_add_row(pGraph, frid, fpid>0 ? 1 : 0, &fpid, zBr, zBgClr,
426
427
428
429
430
431
432


433
434
435
436
437
438
439
    @ %z(href("%R/timeline?c=%t",zDate))%s(zTime)</a></td>
    @ <td class="timelineGraph"><div id="m%d(gidx)"></div></td>
    if( zBgClr && zBgClr[0] ){
      @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
    }else{
      @ <td class="timelineTableCell">
    }


    if( zUuid ){
      if( fpid==0 ){
        @ <b>Added</b>
      }else if( pfnid ){
        char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
                                  pfnid);
        @ <b>Renamed</b> from







>
>







428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    @ %z(href("%R/timeline?c=%t",zDate))%s(zTime)</a></td>
    @ <td class="timelineGraph"><div id="m%d(gidx)"></div></td>
    if( zBgClr && zBgClr[0] ){
      @ <td class="timelineTableCell" style="background-color: %h(zBgClr);">
    }else{
      @ <td class="timelineTableCell">
    }
    sqlite3_snprintf(sizeof(zShort), zShort, "%.10s", zUuid);
    sqlite3_snprintf(sizeof(zShortCkin), zShortCkin, "%.10s", zCkin);
    if( zUuid ){
      if( fpid==0 ){
        @ <b>Added</b>
      }else if( pfnid ){
        char *zPrevName = db_text(0, "SELECT name FROM filename WHERE fnid=%d",
                                  pfnid);
        @ <b>Renamed</b> from
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
        @ <b>Renamed</b> to
        @ %z(href("%R/finfo?name=%t",zNewName))%h(zNewName)</a> by check-in
        fossil_free(zNewName);
      }else{
        @ <b>Deleted</b> by check-in
      }
    }
    hyperlink_to_uuid(zCkin);
    @ %w(zCom) (user:
    hyperlink_to_user(zUser, zDate, "");
    @ branch: %h(zBr))
    if( g.perm.Hyperlink && zUuid ){
      const char *z = zFilename;
      @ %z(href("%R/annotate?filename=%h&checkin=%s",z,zCkin))
      @ [annotate]</a>
      @ %z(href("%R/blame?filename=%h&checkin=%s",z,zCkin))
      @ [blame]</a>
      @ %z(href("%R/timeline?n=200&uf=%s",zUuid))[checkins&nbsp;using]</a>
      if( fpid ){
        @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zPUuid,zUuid))[diff]</a>
      }
    }
    if( fDebug & FINFO_DEBUG_MLINK ){
      int srcid = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", frid);
      int sz = db_int(0, "SELECT length(content) FROM blob WHERE rid=%d", frid);
      @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) sz=%d(sz)
      if( srcid ){







|





|

|

|

|







456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
        @ <b>Renamed</b> to
        @ %z(href("%R/finfo?name=%t",zNewName))%h(zNewName)</a> by check-in
        fossil_free(zNewName);
      }else{
        @ <b>Deleted</b> by check-in
      }
    }
    hyperlink_to_uuid(zShortCkin);
    @ %w(zCom) (user:
    hyperlink_to_user(zUser, zDate, "");
    @ branch: %h(zBr))
    if( g.perm.Hyperlink && zUuid ){
      const char *z = zFilename;
      @ %z(href("%R/annotate?checkin=%S&filename=%h",zCkin,z))
      @ [annotate]</a>
      @ %z(href("%R/blame?checkin=%S&filename=%h",zCkin,z))
      @ [blame]</a>
      @ %z(href("%R/timeline?n=200&uf=%S",zUuid))[checkins&nbsp;using]</a>
      if( fpid ){
        @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zPUuid,zUuid))[diff]</a>
      }
    }
    if( fDebug & FINFO_DEBUG_MLINK ){
      int srcid = db_int(0, "SELECT srcid FROM delta WHERE rid=%d", frid);
      int sz = db_int(0, "SELECT length(content) FROM blob WHERE rid=%d", frid);
      @ <br>fid=%d(frid) pid=%d(fpid) mid=%d(fmid) sz=%d(sz)
      if( srcid ){

Changes to src/glob.c.

152
153
154
155
156
157
158










159


















































160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
**
**     [...]      Matches one character from the enclosed list of
**                characters.
**
**     [^...]     Matches one character not in the enclosed list.
*/
int strglob(const char *zGlob, const char *z){










  return sqlite3_strglob(zGlob, z)==0;


















































}

/*
** Return true (non-zero) if zString matches any of the patterns in
** the Glob.  The value returned is actually a 1-based index of the pattern
** that matched.  Return 0 if none of the patterns match zString.
**
** A NULL glob matches nothing.
*/
int glob_match(Glob *pGlob, const char *zString){
  int i;
  if( pGlob==0 ) return 0;
  for(i=0; i<pGlob->nPattern; i++){
    if( sqlite3_strglob(pGlob->azPattern[i], zString)==0 ) return i+1;
  }
  return 0;
}

/*
** Free all memory associated with the given Glob object
*/







>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>













|







152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
**
**     [...]      Matches one character from the enclosed list of
**                characters.
**
**     [^...]     Matches one character not in the enclosed list.
*/
int strglob(const char *zGlob, const char *z){
  int c, c2;
  int invert;
  int seen;

  while( (c = (*(zGlob++)))!=0 ){
    if( c=='*' ){
      while( (c=(*(zGlob++))) == '*' || c=='?' ){
        if( c=='?' && (*(z++))==0 ) return 0;
      }
      if( c==0 ){
        return 1;
      }else if( c=='[' ){
        while( *z && strglob(zGlob-1,z)==0 ){
          z++;
        }
        return (*z)!=0;
      }
      while( (c2 = (*(z++)))!=0 ){
        while( c2!=c ){
          c2 = *(z++);
          if( c2==0 ) return 0;
        }
        if( strglob(zGlob,z) ) return 1;
      }
      return 0;
    }else if( c=='?' ){
      if( (*(z++))==0 ) return 0;
    }else if( c=='[' ){
      int prior_c = 0;
      seen = 0;
      invert = 0;
      c = *(z++);
      if( c==0 ) return 0;
      c2 = *(zGlob++);
      if( c2=='^' ){
        invert = 1;
        c2 = *(zGlob++);
      }
      if( c2==']' ){
        if( c==']' ) seen = 1;
        c2 = *(zGlob++);
      }
      while( c2 && c2!=']' ){
        if( c2=='-' && zGlob[0]!=']' && zGlob[0]!=0 && prior_c>0 ){
          c2 = *(zGlob++);
          if( c>=prior_c && c<=c2 ) seen = 1;
          prior_c = 0;
        }else{
          if( c==c2 ){
            seen = 1;
          }
          prior_c = c2;
        }
        c2 = *(zGlob++);
      }
      if( c2==0 || (seen ^ invert)==0 ) return 0;
    }else{
      if( c!=(*(z++)) ) return 0;
    }
  }
  return *z==0;
}

/*
** Return true (non-zero) if zString matches any of the patterns in
** the Glob.  The value returned is actually a 1-based index of the pattern
** that matched.  Return 0 if none of the patterns match zString.
**
** A NULL glob matches nothing.
*/
int glob_match(Glob *pGlob, const char *zString){
  int i;
  if( pGlob==0 ) return 0;
  for(i=0; i<pGlob->nPattern; i++){
    if( strglob(pGlob->azPattern[i], zString) ) return i+1;
  }
  return 0;
}

/*
** Free all memory associated with the given Glob object
*/

Changes to src/gzip.c.

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
** Add nIn bytes of content from pIn to the gzip file.
*/
#define GZIP_BUFSZ 100000
void gzip_step(const char *pIn, int nIn){
  char *zOutBuf;
  int nOut;

  nOut = nIn + nIn/10 + 100;
  if( nOut<100000 ) nOut = 100000;
  zOutBuf = fossil_malloc(nOut);
  gzip.stream.avail_in = nIn;
  gzip.stream.next_in = (unsigned char*)pIn;
  gzip.stream.avail_out = nOut;
  gzip.stream.next_out = (unsigned char*)zOutBuf;







|







71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
** Add nIn bytes of content from pIn to the gzip file.
*/
#define GZIP_BUFSZ 100000
void gzip_step(const char *pIn, int nIn){
  char *zOutBuf;
  int nOut;
  
  nOut = nIn + nIn/10 + 100;
  if( nOut<100000 ) nOut = 100000;
  zOutBuf = fossil_malloc(nOut);
  gzip.stream.avail_in = nIn;
  gzip.stream.next_in = (unsigned char*)pIn;
  gzip.stream.avail_out = nOut;
  gzip.stream.next_out = (unsigned char*)zOutBuf;

Changes to src/http.c.

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81






82
83
84
85
86
87
88
**
** This file contains code that implements the client-side HTTP protocol
*/
#include "config.h"
#include "http.h"
#include <assert.h>

#ifdef _WIN32
#include <io.h>
#ifndef isatty
#define isatty(d) _isatty(d)
#endif
#ifndef fileno
#define fileno(s) _fileno(s)
#endif
#endif

/* Maximum number of HTTP Authorization attempts */
#define MAX_HTTP_AUTH 2

/* Keep track of HTTP Basic Authorization failures */
static int fSeenHttpAuth = 0;

/*
** Construct the "login" card with the client credentials.
**
**       login LOGIN NONCE SIGNATURE
**
** The LOGIN is the user id of the client.  NONCE is the sha1 checksum
** of all payload that follows the login card.  SIGNATURE is the sha1
** checksum of the nonce followed by the user password.
**
** Write the constructed login card into pLogin.  pLogin is initialized
** by this routine.
*/
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
  Blob nonce;          /* The nonce */
  const char *zLogin;  /* The user login name */
  const char *zPw;     /* The user password */
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */

  blob_zero(pLogin);
  if( g.url.user==0 || fossil_strcmp(g.url.user, "anonymous")==0 ){
     return;  /* If no login card for users "nobody" and "anonymous" */
  }
  if( g.url.isSsh ){
     return;  /* If no login card for SSH: */
  }
  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);
  zLogin = g.url.user;
  if( g.url.passwd ){
    zPw = g.url.passwd;
  }else if( g.cgiOutput ){
    /* Password failure while doing a sync from the web interface */
    cgi_printf("*** incorrect or missing password for user %h\n", zLogin);
    zPw = 0;
  }else{
    /* Password failure while doing a sync from the command-line interface */
    url_prompt_for_password();
    zPw = g.url.passwd;
  }







  /* The login card wants the SHA1 hash of the password, so convert the
  ** password to its SHA1 hash it it isn't already a SHA1 hash.
  */
  /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */
  if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0);








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<




















|


|






|
|
|







|

>
>
>
>
>
>







17
18
19
20
21
22
23
















24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
**
** This file contains code that implements the client-side HTTP protocol
*/
#include "config.h"
#include "http.h"
#include <assert.h>

















/*
** Construct the "login" card with the client credentials.
**
**       login LOGIN NONCE SIGNATURE
**
** The LOGIN is the user id of the client.  NONCE is the sha1 checksum
** of all payload that follows the login card.  SIGNATURE is the sha1
** checksum of the nonce followed by the user password.
**
** Write the constructed login card into pLogin.  pLogin is initialized
** by this routine.
*/
static void http_build_login_card(Blob *pPayload, Blob *pLogin){
  Blob nonce;          /* The nonce */
  const char *zLogin;  /* The user login name */
  const char *zPw;     /* The user password */
  Blob pw;             /* The nonce with user password appended */
  Blob sig;            /* The signature field */

  blob_zero(pLogin);
  if( g.urlUser==0 || fossil_strcmp(g.urlUser, "anonymous")==0 ){
     return;  /* If no login card for users "nobody" and "anonymous" */
  }
  if( g.urlIsSsh ){
     return;  /* If no login card for SSH: */
  }
  blob_zero(&nonce);
  blob_zero(&pw);
  sha1sum_blob(pPayload, &nonce);
  blob_copy(&pw, &nonce);
  zLogin = g.urlUser;
  if( g.urlPasswd ){
    zPw = g.urlPasswd;
  }else if( g.cgiOutput ){
    /* Password failure while doing a sync from the web interface */
    cgi_printf("*** incorrect or missing password for user %h\n", zLogin);
    zPw = 0;
  }else{
    /* Password failure while doing a sync from the command-line interface */
    url_prompt_for_password();
    zPw = g.urlPasswd;
  }

  /* If the first character of the password is "#", then that character is
  ** not really part of the password - it is an indicator that we should
  ** use Basic Authentication.  So skip that character.
  */
  if( zPw && zPw[0]=='#' ) zPw++;

  /* The login card wants the SHA1 hash of the password, so convert the
  ** password to its SHA1 hash it it isn't already a SHA1 hash.
  */
  /* fossil_print("\nzPw=[%s]\n", zPw); // TESTING ONLY */
  if( zPw && zPw[0] ) zPw = sha1_shared_secret(zPw, zLogin, 0);

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
** the complete payload (including the login card) already compressed.
*/
static void http_build_header(Blob *pPayload, Blob *pHdr){
  int i;
  const char *zSep;

  blob_zero(pHdr);
  i = strlen(g.url.path);
  if( i>0 && g.url.path[i-1]=='/' ){
    zSep = "";
  }else{
    zSep = "/";
  }
  blob_appendf(pHdr, "POST %s%sxfer/xfer HTTP/1.0\r\n", g.url.path, zSep);
  if( g.url.proxyAuth ){
    blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.url.proxyAuth);
  }
  if( g.zHttpAuth && g.zHttpAuth[0] ){
    const char *zCredentials = g.zHttpAuth;
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);

  }
  blob_appendf(pHdr, "Host: %s\r\n", g.url.hostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.url.isSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));
}

/*
** Use Fossil credentials for HTTP Basic Authorization prompt
*/
static int use_fossil_creds_for_httpauth_prompt(void){
  Blob x;
  char c;
  prompt_user("Use Fossil username and password (y/N)? ", &x);
  c = blob_str(&x)[0];
  blob_reset(&x);
  return ( c=='y' || c=='Y' );
}

/*
** Prompt to save HTTP Basic Authorization information
*/
static int save_httpauth_prompt(void){
  Blob x;
  char c;
  if( (g.url.flags & URL_REMEMBER)==0 ) return 0;
  prompt_user("Remember Basic Authorization credentials (Y/n)? ", &x);
  c = blob_str(&x)[0];
  blob_reset(&x);
  return ( c!='n' && c!='N' );
}

/*
** Get the HTTP Basic Authorization credentials from the user 
** when 401 is received.
*/
char *prompt_for_httpauth_creds(void){
  Blob x;
  char *zUser;
  char *zPw;
  char *zPrompt;
  char *zHttpAuth = 0;
  if( !isatty(fileno(stdin)) ) return 0;
  zPrompt = mprintf("\n%s authorization required by\n%s\n",
    g.url.isHttps==1 ? "Encrypted HTTPS" : "Unencrypted HTTP", g.url.canonical);
  fossil_print(zPrompt);
  free(zPrompt);
  if ( g.url.user && g.url.passwd && use_fossil_creds_for_httpauth_prompt() ){
    zHttpAuth = mprintf("%s:%s", g.url.user, g.url.passwd);
  }else{
    prompt_user("Basic Authorization user: ", &x);
    zUser = mprintf("%b", &x);
    zPrompt = mprintf("HTTP password for %b: ", &x);
    blob_reset(&x);
    prompt_for_password(zPrompt, &x, 1);
    zPw = mprintf("%b", &x);
    zHttpAuth = mprintf("%s:%s", zUser, zPw);
    free(zUser);
    free(zPw);
    free(zPrompt);
    blob_reset(&x);
  }
  if( save_httpauth_prompt() ){
    set_httpauth(zHttpAuth);
  }
  return zHttpAuth;
}

/*
** Sign the content in pSend, compress it, and send it to the server
** via HTTP or HTTPS.  Get a reply, uncompress the reply, and store the reply
** in pRecv.  pRecv is assumed to be uninitialized when
** this routine is called - this routine will initialize it.
**
** The server address is contain in the "g" global structure.  The







|
|




|
|
|

|
|



>

|

|








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124





























































125
126
127
128
129
130
131
** the complete payload (including the login card) already compressed.
*/
static void http_build_header(Blob *pPayload, Blob *pHdr){
  int i;
  const char *zSep;

  blob_zero(pHdr);
  i = strlen(g.urlPath);
  if( i>0 && g.urlPath[i-1]=='/' ){
    zSep = "";
  }else{
    zSep = "/";
  }
  blob_appendf(pHdr, "POST %s%sxfer/xfer HTTP/1.0\r\n", g.urlPath, zSep);
  if( g.urlProxyAuth ){
    blob_appendf(pHdr, "Proxy-Authorization: %s\r\n", g.urlProxyAuth);
  }
  if( g.urlPasswd && g.urlUser && g.urlPasswd[0]=='#' ){
    char *zCredentials = mprintf("%s:%s", g.urlUser, &g.urlPasswd[1]);
    char *zEncoded = encode64(zCredentials, -1);
    blob_appendf(pHdr, "Authorization: Basic %s\r\n", zEncoded);
    fossil_free(zEncoded);
    fossil_free(zCredentials);
  }
  blob_appendf(pHdr, "Host: %s\r\n", g.urlHostname);
  blob_appendf(pHdr, "User-Agent: %s\r\n", get_user_agent());
  if( g.urlIsSsh ) blob_appendf(pHdr, "X-Fossil-Transport: SSH\r\n");
  if( g.fHttpTrace ){
    blob_appendf(pHdr, "Content-Type: application/x-fossil-debug\r\n");
  }else{
    blob_appendf(pHdr, "Content-Type: application/x-fossil\r\n");
  }
  blob_appendf(pHdr, "Content-Length: %d\r\n\r\n", blob_size(pPayload));
}






























































/*
** Sign the content in pSend, compress it, and send it to the server
** via HTTP or HTTPS.  Get a reply, uncompress the reply, and store the reply
** in pRecv.  pRecv is assumed to be uninitialized when
** this routine is called - this routine will initialize it.
**
** The server address is contain in the "g" global structure.  The
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
  int rc = 0;           /* Result code */
  int iHttpVersion;     /* Which version of HTTP protocol server uses */
  char *zLine;          /* A single line of the reply header */
  int i;                /* Loop counter */
  int isError = 0;      /* True if the reply is an error message */
  int isCompressed = 1; /* True if the reply is compressed */

  if( transport_open(&g.url) ){
    fossil_warning(transport_errmsg(&g.url));
    return 1;
  }

  /* Construct the login card and prepare the complete payload */
  blob_zero(&login);
  if( useLogin ) http_build_login_card(pSend, &login);
  if( g.fHttpTrace ){







|
|







141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
  int rc = 0;           /* Result code */
  int iHttpVersion;     /* Which version of HTTP protocol server uses */
  char *zLine;          /* A single line of the reply header */
  int i;                /* Loop counter */
  int isError = 0;      /* True if the reply is an error message */
  int isCompressed = 1; /* True if the reply is compressed */

  if( transport_open(GLOBAL_URL()) ){
    fossil_warning(transport_errmsg(GLOBAL_URL()));
    return 1;
  }

  /* Construct the login card and prepare the complete payload */
  blob_zero(&login);
  if( useLogin ) http_build_login_card(pSend, &login);
  if( g.fHttpTrace ){
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
    transport_log(out);
    free(zOutFile);
  }

  /*
  ** Send the request to the server.
  */
  transport_send(&g.url, &hdr);
  transport_send(&g.url, &payload);
  blob_reset(&hdr);
  blob_reset(&payload);
  transport_flip(&g.url);
  
  /*
  ** Read and interpret the server reply
  */
  closeConnection = 1;
  iLength = -1;
  while( (zLine = transport_receive_line(&g.url))!=0 && zLine[0]!=0 ){
    /* printf("[%s]\n", zLine); fflush(stdout); */
    if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){
      if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;
      if( rc==401 ){
        if( fSeenHttpAuth++ < MAX_HTTP_AUTH ){
          if( g.zHttpAuth ){
            if( g.zHttpAuth ) free(g.zHttpAuth);
          }
          g.zHttpAuth = prompt_for_httpauth_creds();
          transport_close(&g.url);
          return http_exchange(pSend, pReply, useLogin, maxRedirect);
        }
      }
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_warning("server says: %s", &zLine[ii]);
        goto write_err;
      }
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }
    }else if( g.url.isSsh && fossil_strnicmp(zLine, "status:", 7)==0 ){
      if( sscanf(zLine, "Status: %d", &rc)!=1 ) goto write_err;
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_warning("server says: %s", &zLine[ii]);
        goto write_err;







|
|


|






|



<
<
<
<
<
<
<
<
<
<












|







188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209










210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    transport_log(out);
    free(zOutFile);
  }

  /*
  ** Send the request to the server.
  */
  transport_send(GLOBAL_URL(), &hdr);
  transport_send(GLOBAL_URL(), &payload);
  blob_reset(&hdr);
  blob_reset(&payload);
  transport_flip(GLOBAL_URL());
  
  /*
  ** Read and interpret the server reply
  */
  closeConnection = 1;
  iLength = -1;
  while( (zLine = transport_receive_line(GLOBAL_URL()))!=0 && zLine[0]!=0 ){
    /* printf("[%s]\n", zLine); fflush(stdout); */
    if( fossil_strnicmp(zLine, "http/1.", 7)==0 ){
      if( sscanf(zLine, "HTTP/1.%d %d", &iHttpVersion, &rc)!=2 ) goto write_err;










      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_warning("server says: %s", &zLine[ii]);
        goto write_err;
      }
      if( iHttpVersion==0 ){
        closeConnection = 1;
      }else{
        closeConnection = 0;
      }
    }else if( g.urlIsSsh && fossil_strnicmp(zLine, "status:", 7)==0 ){
      if( sscanf(zLine, "Status: %d", &rc)!=1 ) goto write_err;
      if( rc!=200 && rc!=302 ){
        int ii;
        for(ii=7; zLine[ii] && zLine[ii]!=' '; ii++){}
        while( zLine[ii]==' ' ) ii++;
        fossil_warning("server says: %s", &zLine[ii]);
        goto write_err;
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
      j = strlen(zLine) - 1; 
      while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){
         j -= 4;
         zLine[j] = 0;
      }
      fossil_print("redirect to %s\n", &zLine[i]);
      url_parse(&zLine[i], 0);
      fSeenHttpAuth = 0;
      if( g.zHttpAuth ) free(g.zHttpAuth);
      g.zHttpAuth = get_httpauth();
      transport_close(&g.url);
      return http_exchange(pSend, pReply, useLogin, maxRedirect);
    }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
      if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
        isCompressed = 0;
      }else if( fossil_strnicmp(&zLine[14], 
                          "application/x-fossil-uncompressed", -1)==0 ){
        isCompressed = 0;







<
<
<
|







252
253
254
255
256
257
258



259
260
261
262
263
264
265
266
      j = strlen(zLine) - 1; 
      while( j>4 && fossil_strcmp(&zLine[j-4],"/xfer")==0 ){
         j -= 4;
         zLine[j] = 0;
      }
      fossil_print("redirect to %s\n", &zLine[i]);
      url_parse(&zLine[i], 0);



      transport_close(GLOBAL_URL());
      return http_exchange(pSend, pReply, useLogin, maxRedirect);
    }else if( fossil_strnicmp(zLine, "content-type: ", 14)==0 ){
      if( fossil_strnicmp(&zLine[14], "application/x-fossil-debug", -1)==0 ){
        isCompressed = 0;
      }else if( fossil_strnicmp(&zLine[14], 
                          "application/x-fossil-uncompressed", -1)==0 ){
        isCompressed = 0;
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
  }

  /*
  ** Extract the reply payload that follows the header
  */
  blob_zero(pReply);
  blob_resize(pReply, iLength);
  iLength = transport_receive(&g.url, blob_buffer(pReply), iLength);
  blob_resize(pReply, iLength);
  if( isError ){
    char *z;
    int i, j;
    z = blob_str(pReply);
    for(i=j=0; z[i]; i++, j++){
      if( z[i]=='<' ){







|







279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
  }

  /*
  ** Extract the reply payload that follows the header
  */
  blob_zero(pReply);
  blob_resize(pReply, iLength);
  iLength = transport_receive(GLOBAL_URL(), blob_buffer(pReply), iLength);
  blob_resize(pReply, iLength);
  if( isError ){
    char *z;
    int i, j;
    z = blob_str(pReply);
    for(i=j=0; z[i]; i++, j++){
      if( z[i]=='<' ){
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
  **
  ** FIXME:  There is some bug in the lower layers that prevents the
  ** connection from remaining open.  The easiest fix for now is to
  ** simply close and restart the connection for each round-trip.
  **
  ** For SSH we will leave the connection open.
  */
  if( ! g.url.isSsh ) closeConnection = 1; /* FIX ME */
  if( closeConnection ){
    transport_close(&g.url);
  }else{
    transport_rewind(&g.url);
  }
  return 0;

  /* 
  ** Jump to here if an error is seen.
  */
write_err:
  transport_close(&g.url);
  return 1;  
}







|

|

|







|


306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  **
  ** FIXME:  There is some bug in the lower layers that prevents the
  ** connection from remaining open.  The easiest fix for now is to
  ** simply close and restart the connection for each round-trip.
  **
  ** For SSH we will leave the connection open.
  */
  if( ! g.urlIsSsh ) closeConnection = 1; /* FIX ME */
  if( closeConnection ){
    transport_close(GLOBAL_URL());
  }else{
    transport_rewind(GLOBAL_URL());
  }
  return 0;

  /* 
  ** Jump to here if an error is seen.
  */
write_err:
  transport_close(GLOBAL_URL());
  return 1;  
}

Changes to src/http_socket.c.

122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#endif
    iSocket = -1;
  }
}

/*
** Open a socket connection.  The identify of the server is determined
** by pUrlData
**
**    pUrlDAta->name       Name of the server.  Ex: www.fossil-scm.org
**    pUrlDAta->port       TCP/IP port to use.  Ex: 80
**
** Return the number of errors.
*/
int socket_open(UrlData *pUrlData){
  static struct sockaddr_in addr;  /* The server address */
  static int addrIsInit = 0;       /* True once addr is initialized */








|

|
|







122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#endif
    iSocket = -1;
  }
}

/*
** Open a socket connection.  The identify of the server is determined
** by global variables that are set using url_parse():
**
**    g.urlName       Name of the server.  Ex: www.fossil-scm.org
**    g.urlPort       TCP/IP port to use.  Ex: 80
**
** Return the number of errors.
*/
int socket_open(UrlData *pUrlData){
  static struct sockaddr_in addr;  /* The server address */
  static int addrIsInit = 0;       /* True once addr is initialized */

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
    N -= (size_t)got;
    pContent = (void*)&((char*)pContent)[got];
  }
  return total;
}

/*
** Attempt to resolve pUrlData->name to an IP address and setup g.zIpAddr
** so rcvfrom gets populated. For hostnames with more than one IP (or
** if overridden in ~/.ssh/config) the rcvfrom may not match the host
** to which we connect.
*/
void socket_ssh_resolve_addr(UrlData *pUrlData){
  struct hostent *pHost;        /* Used to make best effort for rcvfrom */
  struct sockaddr_in addr;

  memset(&addr, 0, sizeof(addr));
  pHost = gethostbyname(pUrlData->name);
  if( pHost!=0 ){
    memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
    g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
  }
}







|
|
|
<












210
211
212
213
214
215
216
217
218
219

220
221
222
223
224
225
226
227
228
229
230
231
    N -= (size_t)got;
    pContent = (void*)&((char*)pContent)[got];
  }
  return total;
}

/*
** Attempt to resolve g.urlName to IP and setup g.zIpAddr so rcvfrom gets
** populated. For hostnames with more than one IP (or if overridden in
** ~/.ssh/config) the rcvfrom may not match the host to which we connect.

*/
void socket_ssh_resolve_addr(UrlData *pUrlData){
  struct hostent *pHost;        /* Used to make best effort for rcvfrom */
  struct sockaddr_in addr;

  memset(&addr, 0, sizeof(addr));
  pHost = gethostbyname(pUrlData->name);
  if( pHost!=0 ){
    memcpy(&addr.sin_addr,pHost->h_addr_list[0],pHost->h_length);
    g.zIpAddr = mprintf("%s", inet_ntoa(addr.sin_addr));
  }
}

Changes to src/http_ssl.c.

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229

230
231
232
233
234
235
236
237
238
239
void ssl_close(void){
  if( iBio!=NULL ){
    (void)BIO_reset(iBio);
    BIO_free_all(iBio);
  }
}

/* See RFC2817 for details */
static int establish_proxy_tunnel(UrlData *pUrlData, BIO *bio){
  int rc, httpVerMin;
  char *bbuf;
  Blob snd, reply;
  int done=0,end=0;
  blob_zero(&snd);
  blob_appendf(&snd, "CONNECT %s:%d HTTP/1.1\r\n", pUrlData->hostname,
      pUrlData->proxyOrigPort);
  blob_appendf(&snd, "Host: %s:%d\r\n", pUrlData->hostname, pUrlData->proxyOrigPort);
  if( pUrlData->proxyAuth ){
    blob_appendf(&snd, "Proxy-Authorization: %s\r\n", pUrlData->proxyAuth);
  }
  blob_append(&snd, "Proxy-Connection: keep-alive\r\n", -1);
  blob_appendf(&snd, "User-Agent: %s\r\n", get_user_agent());
  blob_append(&snd, "\r\n", 2);
  BIO_write(bio, blob_buffer(&snd), blob_size(&snd));
  blob_reset(&snd);

  /* Wait for end of reply */
  blob_zero(&reply);
  do{
    int len;
    char buf[256];
    len = BIO_read(bio, buf, sizeof(buf));
    blob_append(&reply, buf, len);

    bbuf = blob_buffer(&reply);
    len = blob_size(&reply);
    while(end < len) {
      if(bbuf[end] == '\r') {
        if(len - end < 4) {
          /* need more data */
          break;
        }
        if(memcmp(&bbuf[end], "\r\n\r\n", 4) == 0) {
          done = 1;
          break;
        }
      }
      end++;
    }
  }while(!done);
  sscanf(bbuf, "HTTP/1.%d %d", &httpVerMin, &rc);
  blob_reset(&reply);
  return rc;
}

/*
** Open an SSL connection.  The identify of the server is determined
** as follows:

**
**    g.url.name      Name of the server.  Ex: www.fossil-scm.org
**    pUrlData->port  TCP/IP port to use.  Ex: 80
**
** Return the number of errors.
*/
int ssl_open(UrlData *pUrlData){
  X509 *cert;
  int hasSavedCertificate = 0;
  int trusted = 0;







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


<
>

|
|







172
173
174
175
176
177
178
















































179
180

181
182
183
184
185
186
187
188
189
190
191
void ssl_close(void){
  if( iBio!=NULL ){
    (void)BIO_reset(iBio);
    BIO_free_all(iBio);
  }
}

















































/*
** Open an SSL connection.  The identify of the server is determined

** by global variables that are set using url_parse():
**
**    g.urlName       Name of the server.  Ex: www.fossil-scm.org
**    g.urlPort       TCP/IP port to use.  Ex: 80
**
** Return the number of errors.
*/
int ssl_open(UrlData *pUrlData){
  X509 *cert;
  int hasSavedCertificate = 0;
  int trusted = 0;
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294




295
296
297
298

299
300
301

302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
  cert = ssl_get_certificate(pUrlData, &trusted);
  if ( cert!=NULL ){
    X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
    X509_free(cert);
    hasSavedCertificate = 1;
  }

  if( pUrlData->useProxy ){
    int rc;
    BIO *sBio;
    char *connStr;
    connStr = mprintf("%s:%d", g.url.name, pUrlData->port);
    sBio = BIO_new_connect(connStr);
    free(connStr);
    if( BIO_do_connect(sBio)<=0 ){
      ssl_set_errmsg("SSL: cannot connect to proxy %s:%d (%s)",
            pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));
      ssl_close();
      return 1;
    }
    rc = establish_proxy_tunnel(pUrlData, sBio);
    if( rc<200||rc>299 ){
      ssl_set_errmsg("SSL: proxy connect failed with HTTP status code %d", rc);
      return 1;
    }

    pUrlData->path = pUrlData->proxyUrlPath;

    iBio = BIO_new_ssl(sslCtx, 1);
    BIO_push(iBio, sBio);
  }else{
    iBio = BIO_new_ssl_connect(sslCtx);
  }
  if( iBio==NULL ) {
    ssl_set_errmsg("SSL: cannot open SSL (%s)", 
                    ERR_reason_error_string(ERR_get_error()));
    return 1;
  }
  BIO_get_ssl(iBio, &ssl);

#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
  if( !SSL_set_tlsext_host_name(ssl, (pUrlData->useProxy?pUrlData->hostname:pUrlData->name)) ){
    fossil_warning("WARNING: failed to set server name indication (SNI), "
                  "continuing without it.\n");
  }
#endif

  SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);





  if( !pUrlData->useProxy ){
    BIO_set_conn_hostname(iBio, pUrlData->name);
    BIO_set_conn_int_port(iBio, &pUrlData->port);

    if( BIO_do_connect(iBio)<=0 ){
      ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", 
          pUrlData->name, pUrlData->port, ERR_reason_error_string(ERR_get_error()));

      ssl_close();
      return 1;
    }
  }
  
  if( BIO_do_handshake(iBio)<=0 ) {
    ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", 
        pUrlData->useProxy?pUrlData->hostname:pUrlData->name,
        pUrlData->useProxy?pUrlData->proxyOrigPort:pUrlData->port,
        ERR_reason_error_string(ERR_get_error()));
    ssl_close();
    return 1;
  }
  /* Check if certificate is valid */
  cert = SSL_get_peer_certificate(ssl);








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<



|






>
>
>
>
|
|
|
|
>
|
|
|
>
|
|
<




|
<







199
200
201
202
203
204
205
























206






207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

232
233
234
235
236

237
238
239
240
241
242
243
  cert = ssl_get_certificate(pUrlData, &trusted);
  if ( cert!=NULL ){
    X509_STORE_add_cert(SSL_CTX_get_cert_store(sslCtx), cert);
    X509_free(cert);
    hasSavedCertificate = 1;
  }

























  iBio = BIO_new_ssl_connect(sslCtx);






  BIO_get_ssl(iBio, &ssl);

#if (SSLEAY_VERSION_NUMBER >= 0x00908070) && !defined(OPENSSL_NO_TLSEXT)
  if( !SSL_set_tlsext_host_name(ssl, pUrlData->name) ){
    fossil_warning("WARNING: failed to set server name indication (SNI), "
                  "continuing without it.\n");
  }
#endif

  SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
  if( iBio==NULL ) {
    ssl_set_errmsg("SSL: cannot open SSL (%s)", 
                    ERR_reason_error_string(ERR_get_error()));
    return 1;
  }

  BIO_set_conn_hostname(iBio, pUrlData->name);
  BIO_set_conn_int_port(iBio, &pUrlData->port);
  
  if( BIO_do_connect(iBio)<=0 ){
    ssl_set_errmsg("SSL: cannot connect to host %s:%d (%s)", 
        pUrlData->name, pUrlData->port,
        ERR_reason_error_string(ERR_get_error()));
    ssl_close();
    return 1;

  }
  
  if( BIO_do_handshake(iBio)<=0 ) {
    ssl_set_errmsg("Error establishing SSL connection %s:%d (%s)", 
        pUrlData->name, pUrlData->port,

        ERR_reason_error_string(ERR_get_error()));
    ssl_close();
    return 1;
  }
  /* Check if certificate is valid */
  cert = SSL_get_peer_certificate(ssl);

355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
        "SHA1 fingerprint above\n"
        " * use the global ssl-ca-location setting to specify your CA root\n"
        "   certificates list\n\n"
        "If you are not expecting this message, answer no and "
        "contact your server\nadministrator.\n\n"
        "Accept certificate for host %s (a=always/y/N)? ",
        X509_verify_cert_error_string(e), desc, warning,
        pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
    BIO_free(mem);

    prompt_user(prompt, &ans);
    free(prompt);
    cReply = blob_str(&ans)[0];
    blob_reset(&ans);
    if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {







|







281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
        "SHA1 fingerprint above\n"
        " * use the global ssl-ca-location setting to specify your CA root\n"
        "   certificates list\n\n"
        "If you are not expecting this message, answer no and "
        "contact your server\nadministrator.\n\n"
        "Accept certificate for host %s (a=always/y/N)? ",
        X509_verify_cert_error_string(e), desc, warning,
        pUrlData->name);
    BIO_free(mem);

    prompt_user(prompt, &ans);
    free(prompt);
    cReply = blob_str(&ans)[0];
    blob_reset(&ans);
    if( cReply!='y' && cReply!='Y' && cReply!='a' && cReply!='A') {
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
  BIO *mem;
  char *zCert, *zHost;

  mem = BIO_new(BIO_s_mem());
  PEM_write_bio_X509(mem, cert);
  BIO_write(mem, "", 1); /* nul-terminate mem buffer */
  BIO_get_mem_data(mem, &zCert);
  zHost = mprintf("cert:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
  db_set(zHost, zCert, 1);
  free(zHost);
  zHost = mprintf("trusted:%s", pUrlData->useProxy?pUrlData->hostname:pUrlData->name);
  db_set_int(zHost, trusted, 1);
  free(zHost);
  BIO_free(mem);  
}

/*
** Get certificate for pUrlData->urlName from global config.
** Return NULL if no certificate found.
*/
X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
  char *zHost, *zCert;
  BIO *mem;
  X509 *cert;

  zHost = mprintf("cert:%s",
      pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
  zCert = db_get(zHost, NULL);
  free(zHost);
  if ( zCert==NULL )
    return NULL;

  if ( pTrusted!=0 ){
    zHost = mprintf("trusted:%s",
             pUrlData->useProxy ? pUrlData->hostname : pUrlData->name);
    *pTrusted = db_get_int(zHost, 0);
    free(zHost);
  }

  mem = BIO_new(BIO_s_mem());
  BIO_puts(mem, zCert);
  cert = PEM_read_bio_X509(mem, NULL, 0, NULL);







|


|






|







|
<






|
<







331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356

357
358
359
360
361
362
363

364
365
366
367
368
369
370
  BIO *mem;
  char *zCert, *zHost;

  mem = BIO_new(BIO_s_mem());
  PEM_write_bio_X509(mem, cert);
  BIO_write(mem, "", 1); /* nul-terminate mem buffer */
  BIO_get_mem_data(mem, &zCert);
  zHost = mprintf("cert:%s", pUrlData->name);
  db_set(zHost, zCert, 1);
  free(zHost);
  zHost = mprintf("trusted:%s", pUrlData->name);
  db_set_int(zHost, trusted, 1);
  free(zHost);
  BIO_free(mem);  
}

/*
** Get certificate for g.urlName from global config.
** Return NULL if no certificate found.
*/
X509 *ssl_get_certificate(UrlData *pUrlData, int *pTrusted){
  char *zHost, *zCert;
  BIO *mem;
  X509 *cert;

  zHost = mprintf("cert:%s", pUrlData->name);

  zCert = db_get(zHost, NULL);
  free(zHost);
  if ( zCert==NULL )
    return NULL;

  if ( pTrusted!=0 ){
    zHost = mprintf("trusted:%s", pUrlData->name);

    *pTrusted = db_get_int(zHost, 0);
    free(zHost);
  }

  mem = BIO_new(BIO_s_mem());
  BIO_puts(mem, zCert);
  cert = PEM_read_bio_X509(mem, NULL, 0, NULL);

Changes to src/http_transport.c.

136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  }
  blob_reset(&zCmd);
  return sshPid==0;
}

/*
** Open a connection to the server.  The server is defined by the following
** variables:
**
**   pUrlData->name        Name of the server.  Ex: www.fossil-scm.org
**   pUrlData->port        TCP/IP port.  Ex: 80
**   pUrlData->isHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(UrlData *pUrlData){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( pUrlData->isSsh ){







|

|
|
|







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  }
  blob_reset(&zCmd);
  return sshPid==0;
}

/*
** Open a connection to the server.  The server is defined by the following
** global variables:
**
**   g.urlName        Name of the server.  Ex: www.fossil-scm.org
**   g.urlPort        TCP/IP port.  Ex: 80
**   g.urlIsHttps     Use TLS for the connection
**
** Return the number of errors.
*/
int transport_open(UrlData *pUrlData){
  int rc = 0;
  if( transport.isOpen==0 ){
    if( pUrlData->isSsh ){

Changes to src/import.c.

573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
    if( memcmp(zLine, "mark ", 5)==0 ){
      trim_newline(&zLine[5]);
      fossil_free(gg.zMark);
      gg.zMark = fossil_strdup(&zLine[5]);
    }else
    if( memcmp(zLine, "tagger ", 7)==0 || memcmp(zLine, "committer ",10)==0 ){
      sqlite3_int64 secSince1970;
      int hastz;
      char tzdir;
      int tz;
      for(i=0; zLine[i] && zLine[i]!='<'; i++){}
      if( zLine[i]==0 ) goto malformed_line;
      z = &zLine[i+1];
      for(i=i+1; zLine[i] && zLine[i]!='>'; i++){}
      if( zLine[i]==0 ) goto malformed_line;
      zLine[i] = 0;
      fossil_free(gg.zUser);
      gg.zUser = fossil_strdup(z);
      secSince1970 = 0;

      /* We don't use sscanf here because of int64 portability issues. */
      for(i=i+2; fossil_isdigit(zLine[i]); i++){
        secSince1970 = secSince1970*10 + zLine[i] - '0';
      }

      /* Read in optional timezone modifier (we don't know if it's strictly
       * optional, but better to be sure). */
      tzdir = '+';
      tz = 0;
      hastz = sscanf(&zLine[i], " %c%d", &tzdir, &tz);
      if ((hastz == 1) || (hastz > 2)) goto malformed_line;
      secSince1970 += ((tzdir == '-') ? -1 : 1) *
        ((tz/100)*3600 + (tz%100)*60);

      fossil_free(gg.zDate);
      gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", secSince1970);
      gg.zDate[10] = 'T';
    }else
    if( memcmp(zLine, "from ", 5)==0 ){
      trim_newline(&zLine[5]);
      fossil_free(gg.zFromMark);







<
<
<









<
<



<
<
<
<
<
<
<
<
<
<







573
574
575
576
577
578
579



580
581
582
583
584
585
586
587
588


589
590
591










592
593
594
595
596
597
598
    if( memcmp(zLine, "mark ", 5)==0 ){
      trim_newline(&zLine[5]);
      fossil_free(gg.zMark);
      gg.zMark = fossil_strdup(&zLine[5]);
    }else
    if( memcmp(zLine, "tagger ", 7)==0 || memcmp(zLine, "committer ",10)==0 ){
      sqlite3_int64 secSince1970;



      for(i=0; zLine[i] && zLine[i]!='<'; i++){}
      if( zLine[i]==0 ) goto malformed_line;
      z = &zLine[i+1];
      for(i=i+1; zLine[i] && zLine[i]!='>'; i++){}
      if( zLine[i]==0 ) goto malformed_line;
      zLine[i] = 0;
      fossil_free(gg.zUser);
      gg.zUser = fossil_strdup(z);
      secSince1970 = 0;


      for(i=i+2; fossil_isdigit(zLine[i]); i++){
        secSince1970 = secSince1970*10 + zLine[i] - '0';
      }










      fossil_free(gg.zDate);
      gg.zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", secSince1970);
      gg.zDate[10] = 'T';
    }else
    if( memcmp(zLine, "from ", 5)==0 ){
      trim_newline(&zLine[5]);
      fossil_free(gg.zFromMark);

Changes to src/info.c.

107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
  }
  if( zUuid ){
    fossil_print("%-13s ", "leaf:");
    if(is_a_leaf(rid)){
      if(db_int(0, "SELECT 1 FROM tagxref AS tx"
                " WHERE tx.rid=%d"
                " AND tx.tagid=%d"
                " AND tx.tagtype>0",
                rid, TAG_CLOSED)){
        fossil_print("%s\n", "closed");
      }else{
        fossil_print("%s\n", "open");
      }
    }else{
      fossil_print("no\n");
    }
  }
  zTags = info_tags_of_checkin(rid, 0);
  if( zTags && zTags[0] ){
    fossil_print("tags:         %s\n", zTags);
  }
  free(zTags);
  if( zComment ){
    fossil_print("comment:      ");







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







107
108
109
110
111
112
113
















114
115
116
117
118
119
120
        db_column_int(&q, 1)
      );
      fossil_print("%-13s %s %s\n", zType, zUuid, zDate);
      free(zDate);
    }
    db_finalize(&q);
  }
















  zTags = info_tags_of_checkin(rid, 0);
  if( zTags && zTags[0] ){
    fossil_print("tags:         %s\n", zTags);
  }
  free(zTags);
  if( zComment ){
    fossil_print("comment:      ");
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
      @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
      @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
    }
    if( diffFlags ){
      append_diff(zOld, zNew, diffFlags, pRe);
    }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
      @ &nbsp;&nbsp;
      @ %z(href("%R/fdiff?v1=%s&v2=%s&sbs=1",zOld,zNew))[diff]</a>
    }
  }
}

/*
** Generate javascript to enhance HTML diffs.
*/







|







381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
      @ <p>Added %z(href("%R/finfo?name=%T",zName))%h(zName)</a>
      @ version %z(href("%R/artifact/%s",zNew))[%S(zNew)]</a>
    }
    if( diffFlags ){
      append_diff(zOld, zNew, diffFlags, pRe);
    }else if( zOld && zNew && fossil_strcmp(zOld,zNew)!=0 ){
      @ &nbsp;&nbsp;
      @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a>
    }
  }
}

/*
** Generate javascript to enhance HTML diffs.
*/
441
442
443
444
445
446
447
448
449


450
451
452
453
454
455
456
457
458

459
460
461
462
463
464
465
466
467
468
469
470
471
472
473

474
475
476
477
478
479
480
}

/*
** Construct an appropriate diffFlag for text_diff() based on query
** parameters and the to boolean arguments.
*/
u64 construct_diff_flags(int verboseFlag, int sideBySide){
  u64 diffFlags = 0;  /* Zero means do not show any diff */
  if( verboseFlag!=0 ){


    int x;
    if( sideBySide ){
      diffFlags = DIFF_SIDEBYSIDE;

      /* "dw" query parameter determines width of each column */
      x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1);
      if( x<0 || x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK;
      diffFlags += x;
    }


    if( P("w") ){
      diffFlags |= DIFF_IGNORE_ALLWS;
    }
    /* "dc" query parameter determines lines of context */
    x = atoi(PD("dc","7"));
    if( x<0 || x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK;
    diffFlags += x;

    /* The "noopt" parameter disables diff optimization */
    if( PD("noopt",0)!=0 ) diffFlags |= DIFF_NOOPT;
    diffFlags |= DIFF_STRIP_EOLCR;
  }
  return diffFlags;
}


/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL:  /ci?name=RID|ARTIFACTID
**
** Display information about a particular check-in.







|
|
>
>


|





|
>
|
<
<
|







<



>







425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446


447
448
449
450
451
452
453
454

455
456
457
458
459
460
461
462
463
464
465
}

/*
** Construct an appropriate diffFlag for text_diff() based on query
** parameters and the to boolean arguments.
*/
u64 construct_diff_flags(int verboseFlag, int sideBySide){
  u64 diffFlags;
  if( verboseFlag==0 ){
    diffFlags = 0;  /* Zero means do not show any diff */
  }else{
    int x;
    if( sideBySide ){
      diffFlags = DIFF_SIDEBYSIDE | DIFF_IGNORE_EOLWS;

      /* "dw" query parameter determines width of each column */
      x = atoi(PD("dw","80"))*(DIFF_CONTEXT_MASK+1);
      if( x<0 || x>DIFF_WIDTH_MASK ) x = DIFF_WIDTH_MASK;
      diffFlags += x;
    }else{
      diffFlags = DIFF_INLINE | DIFF_IGNORE_EOLWS;
    }



    /* "dc" query parameter determines lines of context */
    x = atoi(PD("dc","7"));
    if( x<0 || x>DIFF_CONTEXT_MASK ) x = DIFF_CONTEXT_MASK;
    diffFlags += x;

    /* The "noopt" parameter disables diff optimization */
    if( PD("noopt",0)!=0 ) diffFlags |= DIFF_NOOPT;

  }
  return diffFlags;
}


/*
** WEBPAGE: vinfo
** WEBPAGE: ci
** URL:  /ci?name=RID|ARTIFACTID
**
** Display information about a particular check-in.
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
  int sideBySide;      /* True for side-by-side diffs */
  u64 diffFlags;       /* Flag parameter for text_diff() */
  const char *zName;   /* Name of the checkin to be displayed */
  const char *zUuid;   /* UUID of zName */
  const char *zParent; /* UUID of the parent checkin (if any) */
  const char *zRe;     /* regex parameter */
  ReCompiled *pRe = 0; /* regex */
  const char *zW;      /* URL param for ignoring whitespace */
  const char *zPage = "vinfo";  /* Page that shows diffs */
  const char *zPageHide = "ci"; /* Page that hides diffs */

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  rid = name_to_rid_www("name");
  if( rid==0 ){
    style_header("Check-in Information Error");







<
<
<







480
481
482
483
484
485
486



487
488
489
490
491
492
493
  int sideBySide;      /* True for side-by-side diffs */
  u64 diffFlags;       /* Flag parameter for text_diff() */
  const char *zName;   /* Name of the checkin to be displayed */
  const char *zUuid;   /* UUID of zName */
  const char *zParent; /* UUID of the parent checkin (if any) */
  const char *zRe;     /* regex parameter */
  ReCompiled *pRe = 0; /* regex */




  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  zName = P("name");
  rid = name_to_rid_www("name");
  if( rid==0 ){
    style_header("Check-in Information Error");
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
      zPJ = blob_str(&projName);
      for(jj=0; zPJ[jj]; jj++){
        if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
          zPJ[jj] = '_';
        }
      }
      @ <tr><th>Timelines:</th><td>
      @   %z(href("%R/timeline?f=%s&unhide",zUuid))family</a>
      if( zParent ){
        @ | %z(href("%R/timeline?p=%s&unhide",zUuid))ancestors</a>
      }
      if( !isLeaf ){
        @ | %z(href("%R/timeline?d=%s&unhide",zUuid))descendants</a>
      }
      if( zParent && !isLeaf ){
        @ | %z(href("%R/timeline?dp=%s&unhide",zUuid))both</a>
      }
      db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
                     " WHERE rid=%d AND tagtype>0 "
                     "   AND tag.tagid=tagxref.tagid "
                     "   AND +tag.tagname GLOB 'sym-*'", rid);
      while( db_step(&q2)==SQLITE_ROW ){
        const char *zTagName = db_column_text(&q2, 0);







|

|


|


|







592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
      zPJ = blob_str(&projName);
      for(jj=0; zPJ[jj]; jj++){
        if( (zPJ[jj]>0 && zPJ[jj]<' ') || strchr("\"*/:<>?\\|", zPJ[jj]) ){
          zPJ[jj] = '_';
        }
      }
      @ <tr><th>Timelines:</th><td>
      @   %z(href("%R/timeline?f=%S&unhide",zUuid))family</a>
      if( zParent ){
        @ | %z(href("%R/timeline?p=%S&unhide",zUuid))ancestors</a>
      }
      if( !isLeaf ){
        @ | %z(href("%R/timeline?d=%S&unhide",zUuid))descendants</a>
      }
      if( zParent && !isLeaf ){
        @ | %z(href("%R/timeline?dp=%S&unhide",zUuid))both</a>
      }
      db_prepare(&q2,"SELECT substr(tag.tagname,5) FROM tagxref, tag "
                     " WHERE rid=%d AND tagtype>0 "
                     "   AND tag.tagid=tagxref.tagid "
                     "   AND +tag.tagname GLOB 'sym-*'", rid);
      while( db_step(&q2)==SQLITE_ROW ){
        const char *zTagName = db_column_text(&q2, 0);
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703






704
705






706
























707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724

725
726
727
728
729
730
731
732
733

734
735
736
737
738
739
740
        @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
        @         ZIP archive</a>
        fossil_free(zUrl);
      }
      @ </td></tr>
      @ <tr><th>Other&nbsp;Links:</th>
      @   <td>
      @     %z(href("%R/tree?ci=%s",zUuid))files</a>
      @   | %z(href("%R/fileage?name=%s",zUuid))file ages</a>
      @   | %z(href("%R/tree?ci=%s&nofiles",zUuid))folders</a>
      @   | %z(href("%R/artifact/%s",zUuid))manifest</a>
      if( g.perm.Write ){
        @   | %z(href("%R/ci_edit?r=%s",zUuid))edit</a>
      }
      @   </td>
      @ </tr>
      blob_reset(&projName);
    }
    @ </table>
  }else{
    style_header("Check-in Information");
    login_anonymous_available();
  }
  db_finalize(&q1);
  showTags(rid, "");
  @ <div class="section">Changes</div>
  @ <div class="sectionmenu">
  verboseFlag = g.zPath[0]!='c';
  if( db_get_boolean("show-version-diffs", 0)==0 ){
    verboseFlag = !verboseFlag;
    zPage = "ci";
    zPageHide = "vinfo";
  }
  diffFlags = construct_diff_flags(verboseFlag, sideBySide);
  zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( verboseFlag ){
    @ %z(xhref("class='button'","%R/%s/%T",zPageHide,zName))
    @ Hide&nbsp;Diffs</a>
    if( sideBySide ){
      @ %z(xhref("class='button'","%R/%s/%T?sbs=0%s",zPage,zName,zW))
      @ Unified&nbsp;Diffs</a>
    }else{
      @ %z(xhref("class='button'","%R/%s/%T?sbs=1%s",zPage,zName,zW))
      @ Side-by-Side&nbsp;Diffs</a>
    }
    if( *zW ){
      @ %z(xhref("class='button'","%R/%s/%T?sbs=%d",zPage,zName,sideBySide))
      @ Show&nbsp;Whitespace&nbsp;Changes</a>
    }else{
      @ %z(xhref("class='button'","%R/%s/%T?sbs=%d&w",zPage,zName,sideBySide))
      @ Ignore&nbsp;Whitespace</a>
    }
  }else{
    @ %z(xhref("class='button'","%R/%s/%T?sbs=0",zPage,zName))
    @ Show&nbsp;Unified&nbsp;Diffs</a>
    @ %z(xhref("class='button'","%R/%s/%T?sbs=1",zPage,zName))
    @ Show&nbsp;Side-by-Side&nbsp;Diffs</a>
  }
  if( zParent ){






    @ %z(xhref("class='button'","%R/vpatch?from=%s&to=%s",zParent,zUuid))
    @ Patch</a>






  }
























  @</div>
  if( pRe ){
    @ <p><b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b></p>
  }
  db_prepare(&q3,
    "SELECT name,"
    "       mperm,"
    "       (SELECT uuid FROM blob WHERE rid=mlink.pid),"
    "       (SELECT uuid FROM blob WHERE rid=mlink.fid),"
    "       (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
    "  FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
    " WHERE mlink.mid=%d"
    "   AND (mlink.fid>0"
           " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
    " ORDER BY name /*sort*/",
    rid, rid
  );

  while( db_step(&q3)==SQLITE_ROW ){
    const char *zName = db_column_text(&q3,0);
    int mperm = db_column_int(&q3, 1);
    const char *zOld = db_column_text(&q3,2);
    const char *zNew = db_column_text(&q3,3);
    const char *zOldName = db_column_text(&q3, 4);
    append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
  }
  db_finalize(&q3);

  append_diff_javascript(sideBySide);
  style_footer();
}

/*
** WEBPAGE: winfo
** URL:  /winfo?name=UUID







|
|
|
|

|












<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

>
>
>
>
>
>
|
|
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
>







627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651

































652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
        @ | %z(href("%R/zip/%t-%S.zip?uuid=%s",zPJ,zUuid,zUuid))
        @         ZIP archive</a>
        fossil_free(zUrl);
      }
      @ </td></tr>
      @ <tr><th>Other&nbsp;Links:</th>
      @   <td>
      @     %z(href("%R/tree?ci=%S",zUuid))files</a>
      @   | %z(href("%R/fileage?name=%S",zUuid))file ages</a>
      @   | %z(href("%R/tree?ci=%S&nofiles",zUuid))folders</a>
      @   | %z(href("%R/artifact/%S",zUuid))manifest</a>
      if( g.perm.Write ){
        @   | %z(href("%R/ci_edit?r=%S",zUuid))edit</a>
      }
      @   </td>
      @ </tr>
      blob_reset(&projName);
    }
    @ </table>
  }else{
    style_header("Check-in Information");
    login_anonymous_available();
  }
  db_finalize(&q1);
  showTags(rid, "");

































  if( zParent ){
    @ <div class="section">Changes</div>
    @ <div class="sectionmenu">
    verboseFlag = g.zPath[0]!='c';
    if( db_get_boolean("show-version-diffs", 0)==0 ){
      verboseFlag = !verboseFlag;
      if( verboseFlag ){
        @ %z(xhref("class='button'","%R/vinfo/%T",zName))
        @ hide&nbsp;diffs</a>
        if( sideBySide ){
          @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
          @ unified&nbsp;diffs</a>
        }else{
          @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
          @ side-by-side&nbsp;diffs</a>
        }
      }else{
        @ %z(xhref("class='button'","%R/ci/%T?sbs=0",zName))
        @ show&nbsp;unified&nbsp;diffs</a>
        @ %z(xhref("class='button'","%R/ci/%T?sbs=1",zName))
        @ show&nbsp;side-by-side&nbsp;diffs</a>
      }
    }else{
      if( verboseFlag ){
        @ %z(xhref("class='button'","%R/ci/%T",zName))hide&nbsp;diffs</a>
        if( sideBySide ){
          @ %z(xhref("class='button'","%R/info/%T?sbs=0",zName))
          @ unified&nbsp;diffs</a>
        }else{
          @ %z(xhref("class='button'","%R/info/%T?sbs=1",zName))
          @ side-by-side&nbsp;diffs</a>
        }
      }else{
        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=0",zName))
        @ show&nbsp;unified&nbsp;diffs</a>
        @ %z(xhref("class='button'","%R/vinfo/%T?sbs=1",zName))
        @ show&nbsp;side-by-side&nbsp;diffs</a>
      }
    }
    @ %z(xhref("class='button'","%R/vpatch?from=%S&to=%S",zParent,zUuid))
    @ patch</a></div>
    if( pRe ){
      @ <p><b>Only differences that match regular expression "%h(zRe)"
      @ are shown.</b></p>
    }
    db_prepare(&q3,
       "SELECT name,"
       "       mperm,"
       "       (SELECT uuid FROM blob WHERE rid=mlink.pid),"
       "       (SELECT uuid FROM blob WHERE rid=mlink.fid),"
       "       (SELECT name FROM filename WHERE filename.fnid=mlink.pfnid)"
       "  FROM mlink JOIN filename ON filename.fnid=mlink.fnid"
       " WHERE mlink.mid=%d"
       "   AND (mlink.fid>0"
              " OR mlink.fnid NOT IN (SELECT pfnid FROM mlink WHERE mid=%d))"
       " ORDER BY name /*sort*/",
       rid, rid
    );
    diffFlags = construct_diff_flags(verboseFlag, sideBySide);
    while( db_step(&q3)==SQLITE_ROW ){
      const char *zName = db_column_text(&q3,0);
      int mperm = db_column_int(&q3, 1);
      const char *zOld = db_column_text(&q3,2);
      const char *zNew = db_column_text(&q3,3);
      const char *zOldName = db_column_text(&q3, 4);
      append_file_change_line(zName, zOld, zNew, zOldName, diffFlags,pRe,mperm);
    }
    db_finalize(&q3);
  }
  append_diff_javascript(sideBySide);
  style_footer();
}

/*
** WEBPAGE: winfo
** URL:  /winfo?name=UUID
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
  style_header("Update of \"%h\"", pWiki->zWikiTitle);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate);
  style_submenu_element("Raw", "Raw", "artifact/%s", zUuid);
  style_submenu_element("History", "History", "whistory?name=%t",
                        pWiki->zWikiTitle);
  style_submenu_element("Page", "Page", "wiki?name=%t",
                        pWiki->zWikiTitle);
  login_anonymous_available();
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">







|







755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
    if( strcmp(zModAction,"approve")==0 ){
      moderation_approve(rid);
    }
  }
  style_header("Update of \"%h\"", pWiki->zWikiTitle);
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  zDate = db_text(0, "SELECT datetime(%.17g)", pWiki->rDate);
  style_submenu_element("Raw", "Raw", "artifact/%S", zUuid);
  style_submenu_element("History", "History", "whistory?name=%t",
                        pWiki->zWikiTitle);
  style_submenu_element("Page", "Page", "wiki?name=%t",
                        pWiki->zWikiTitle);
  login_anonymous_available();
  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
  @ <tr><th>Original&nbsp;User:</th><td>
  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->nParent>0 ){
    int i;
    @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
    for(i=0; i<pWiki->nParent; i++){
      char *zParent = pWiki->azParent[i];
      @ %z(href("info/%s",zParent))%s(zParent)</a>
    }
    @ </td></tr>
  }
  @ </table>

  if( g.perm.ModWiki && modPending ){
    @ <div class="section">Moderation</div>







|







783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
  @ <tr><th>Original&nbsp;User:</th><td>
  hyperlink_to_user(pWiki->zUser, zDate, "</td></tr>");
  if( pWiki->nParent>0 ){
    int i;
    @ <tr><th>Parent%s(pWiki->nParent==1?"":"s"):</th><td>
    for(i=0; i<pWiki->nParent; i++){
      char *zParent = pWiki->azParent[i];
      @ %z(href("info/%S",zParent))%s(zParent)</a>
    }
    @ </td></tr>
  }
  @ </table>

  if( g.perm.ModWiki && modPending ){
    @ <div class="section">Moderation</div>
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959

960
961
962

963
964
965
966
967
968
969

/*
** WEBPAGE: vdiff
** URL: /vdiff
**
** Query parameters:
**
**   from=TAG        Left side of the comparison
**   to=TAG          Right side of the comparison
**   branch=TAG      Show all changes on a particular branch
**   v=BOOLEAN       Default true.  If false, only list files that have changed
**   sbs=BOOLEAN     Side-by-side diff if true.  Unified diff if false
**   glob=STRING     only diff files matching this glob
**
**
** Show all differences between two checkins.
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int verboseFlag = 0;
  int sideBySide = 0;
  u64 diffFlags = 0;
  Manifest *pFrom, *pTo;
  ManifestFile *pFileFrom, *pFileTo;
  const char *zBranch;
  const char *zFrom;
  const char *zTo;
  const char *zRe;
  const char *zW;
  const char *zVerbose;
  const char *zGlob;
  ReCompiled *pRe = 0;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  login_anonymous_available();

  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  zBranch = P("branch");
  if( zBranch && zBranch[0] ){
    cgi_replace_parameter("from", mprintf("root:%s", zBranch));
    cgi_replace_parameter("to", zBranch);
  }







|
|
|
|
|
<















<

<

>



>







915
916
917
918
919
920
921
922
923
924
925
926

927
928
929
930
931
932
933
934
935
936
937
938
939
940
941

942

943
944
945
946
947
948
949
950
951
952
953
954
955

/*
** WEBPAGE: vdiff
** URL: /vdiff
**
** Query parameters:
**
**   from=TAG
**   to=TAG
**   branch=TAG
**   v=BOOLEAN
**   sbs=BOOLEAN

**
**
** Show all differences between two checkins.
*/
void vdiff_page(void){
  int ridFrom, ridTo;
  int verboseFlag = 0;
  int sideBySide = 0;
  u64 diffFlags = 0;
  Manifest *pFrom, *pTo;
  ManifestFile *pFileFrom, *pFileTo;
  const char *zBranch;
  const char *zFrom;
  const char *zTo;
  const char *zRe;

  const char *zVerbose;

  ReCompiled *pRe = 0;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  login_anonymous_available();

  zRe = P("regex");
  if( zRe ) re_compile(&pRe, zRe, 0);
  zBranch = P("branch");
  if( zBranch && zBranch[0] ){
    cgi_replace_parameter("from", mprintf("root:%s", zBranch));
    cgi_replace_parameter("to", zBranch);
  }
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053

1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076

1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
    zVerbose = P("verbose");
  }
  if( !zVerbose ){
    zVerbose = P("detail"); /* deprecated */
  }
  verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
  if( !verboseFlag && sideBySide ) verboseFlag = 1;
  zGlob = P("glob");
  zFrom = P("from");
  zTo = P("to");
  if(zGlob && !*zGlob){
    zGlob = NULL;
  }
  diffFlags = construct_diff_flags(verboseFlag, sideBySide);
  zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( sideBySide || verboseFlag ){
    style_submenu_element("Hide Diff", "hidediff",
                          "%R/vdiff?from=%T&to=%T&sbs=0%s%T%s",
                          zFrom, zTo,
                          zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
  }
  if( !sideBySide ){
    style_submenu_element("Side-by-Side Diff", "sbsdiff",
                          "%R/vdiff?from=%T&to=%T&sbs=1%s%T%s",
                          zFrom, zTo,
                          zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
  }
  if( sideBySide || !verboseFlag ) {
    style_submenu_element("Unified Diff", "udiff",
                          "%R/vdiff?from=%T&to=%T&sbs=0&v%s%T%s",
                          zFrom, zTo,
                          zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
  }
  style_submenu_element("Invert", "invert",
                        "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T%s", zTo, zFrom,
                        sideBySide, (verboseFlag && !sideBySide)?"&v":"",
                        zGlob ? "&glob=" : "", zGlob ? zGlob : "", zW);
  if( zGlob ){
    style_submenu_element("Clear glob", "clearglob",
                          "%R/vdiff?from=%T&to=%T&sbs=%d%s%s", zFrom, zTo,
                          sideBySide, (verboseFlag && !sideBySide)?"&v":"", zW);
  }else{
    style_submenu_element("Patch", "patch",
                          "%R/vpatch?from=%T&to=%T%s", zFrom, zTo, zW);
  }
  if( sideBySide || verboseFlag ){
    if( *zW ){
      style_submenu_element("Show Whitespace Differences", "whitespace",
                            "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T", zFrom, zTo,
                            sideBySide, (verboseFlag && !sideBySide)?"&v":"",
                            zGlob ? "&glob=" : "", zGlob ? zGlob : "");
    }else{
      style_submenu_element("Ignore Whitespace", "ignorews",
                            "%R/vdiff?from=%T&to=%T&sbs=%d%s%s%T&w", zFrom, zTo,
                            sideBySide, (verboseFlag && !sideBySide)?"&v":"",
                            zGlob ? "&glob=" : "", zGlob ? zGlob : "");
    }
  }
  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);
  @ </blockquote><h2>To:</h2><blockquote>
  checkin_description(ridTo);
  @ </blockquote>
  if( pRe ){
    @ <p><b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b></p>
  }
  if( zGlob ){
    @ <p><b>Only files matching the glob "%h(zGlob)" are shown.</b></p>
  }
  @<hr /><p>

  manifest_file_rewind(pFrom);
  pFileFrom = manifest_file_next(pFrom, 0);
  manifest_file_rewind(pTo);
  pFileTo = manifest_file_next(pTo, 0);

  while( pFileFrom || pFileTo ){
    int cmp;
    if( pFileFrom==0 ){
      cmp = +1;
    }else if( pFileTo==0 ){
      cmp = -1;
    }else{
      cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
    }
    if( cmp<0 ){
      if(!zGlob || strglob(zGlob, pFileFrom->zName)){
        append_file_change_line(pFileFrom->zName,
                                pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0);
      }
      pFileFrom = manifest_file_next(pFrom, 0);
    }else if( cmp>0 ){
      if(!zGlob || strglob(zGlob, pFileTo->zName)){
        append_file_change_line(pFileTo->zName,
                                0, pFileTo->zUuid, 0, diffFlags, pRe,
                                manifest_file_mperm(pFileTo));
      }
      pFileTo = manifest_file_next(pTo, 0);
    }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){

      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }else{
      if(!zGlob || (strglob(zGlob, pFileFrom->zName)
                || strglob(zGlob, pFileTo->zName))){
        append_file_change_line(pFileFrom->zName,
                                pFileFrom->zUuid,
                                pFileTo->zUuid, 0, diffFlags, pRe,
                                manifest_file_mperm(pFileTo));
      }
      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
  append_diff_javascript(sideBySide);







<


<
<
<
<
<


|
|
<


|
|
|
<



|
|
<


|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










<
<
<






>










<
|
|
<


<
|
|
|
<


>



<
<
|
|
|
|
<







963
964
965
966
967
968
969

970
971





972
973
974
975

976
977
978
979
980

981
982
983
984
985

986
987
988
989






















990
991
992
993
994
995
996
997
998
999



1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016

1017
1018

1019
1020

1021
1022
1023

1024
1025
1026
1027
1028
1029


1030
1031
1032
1033

1034
1035
1036
1037
1038
1039
1040
    zVerbose = P("verbose");
  }
  if( !zVerbose ){
    zVerbose = P("detail"); /* deprecated */
  }
  verboseFlag = (zVerbose!=0) && !is_false(zVerbose);
  if( !verboseFlag && sideBySide ) verboseFlag = 1;

  zFrom = P("from");
  zTo = P("to");





  if( sideBySide || verboseFlag ){
    style_submenu_element("Hide Diff", "hidediff",
                          "%R/vdiff?from=%T&to=%T&sbs=0",
                          zFrom, zTo);

  }
  if( !sideBySide ){
    style_submenu_element("Side-by-side Diff", "sbsdiff",
                          "%R/vdiff?from=%T&to=%T&sbs=1",
                          zFrom, zTo);

  }
  if( sideBySide || !verboseFlag ) {
    style_submenu_element("Unified Diff", "udiff",
                          "%R/vdiff?from=%T&to=%T&sbs=0&v",
                          zFrom, zTo);

  }
  style_submenu_element("Invert", "invert",
                        "%R/vdiff?from=%T&to=%T&sbs=%d%s", zTo, zFrom,
                        sideBySide, (verboseFlag && !sideBySide)?"&v":"");






















  style_header("Check-in Differences");
  @ <h2>Difference From:</h2><blockquote>
  checkin_description(ridFrom);
  @ </blockquote><h2>To:</h2><blockquote>
  checkin_description(ridTo);
  @ </blockquote>
  if( pRe ){
    @ <p><b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b></p>
  }



  @<hr /><p>

  manifest_file_rewind(pFrom);
  pFileFrom = manifest_file_next(pFrom, 0);
  manifest_file_rewind(pTo);
  pFileTo = manifest_file_next(pTo, 0);
  diffFlags = construct_diff_flags(verboseFlag, sideBySide);
  while( pFileFrom || pFileTo ){
    int cmp;
    if( pFileFrom==0 ){
      cmp = +1;
    }else if( pFileTo==0 ){
      cmp = -1;
    }else{
      cmp = fossil_strcmp(pFileFrom->zName, pFileTo->zName);
    }
    if( cmp<0 ){

      append_file_change_line(pFileFrom->zName,
                              pFileFrom->zUuid, 0, 0, diffFlags, pRe, 0);

      pFileFrom = manifest_file_next(pFrom, 0);
    }else if( cmp>0 ){

      append_file_change_line(pFileTo->zName,
                              0, pFileTo->zUuid, 0, diffFlags, pRe,
                              manifest_file_mperm(pFileTo));

      pFileTo = manifest_file_next(pTo, 0);
    }else if( fossil_strcmp(pFileFrom->zUuid, pFileTo->zUuid)==0 ){
      /* No changes */
      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }else{


      append_file_change_line(pFileFrom->zName,
                              pFileFrom->zUuid,
                              pFileTo->zUuid, 0, diffFlags, pRe,
                              manifest_file_mperm(pFileTo));

      pFileFrom = manifest_file_next(pFrom, 0);
      pFileTo = manifest_file_next(pTo, 0);
    }
  }
  manifest_destroy(pFrom);
  manifest_destroy(pTo);
  append_diff_javascript(sideBySide);
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
    hyperlink_to_uuid(zVers);
    if( zBr && zBr[0] ){
      @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
    }
    @ - %!w(zCom) (user:
    hyperlink_to_user(zUser,zDate,")");
    if( g.perm.Hyperlink ){
      @ %z(href("%R/finfo?name=%T&ci=%s",zName,zVers))[ancestry]</a>
      @ %z(href("%R/annotate?filename=%T&checkin=%s",zName,zVers))
      @ [annotate]</a>
      @ %z(href("%R/blame?filename=%T&checkin=%s",zName,zVers))
      @ [blame]</a>
    }
    cnt++;
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_append(pDownloadName, zName, -1);
    }
  }







|
|

|







1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
    hyperlink_to_uuid(zVers);
    if( zBr && zBr[0] ){
      @ on branch %z(href("%R/timeline?r=%T",zBr))%h(zBr)</a>
    }
    @ - %!w(zCom) (user:
    hyperlink_to_user(zUser,zDate,")");
    if( g.perm.Hyperlink ){
      @ %z(href("%R/finfo?name=%T&ci=%S",zName,zVers))[ancestry]</a>
      @ %z(href("%R/annotate?checkin=%S&filename=%T",zVers,zName))
      @ [annotate]</a>
      @ %z(href("%R/blame?checkin=%S&filename=%T",zVers,zName))
      @ [blame]</a>
    }
    cnt++;
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_append(pDownloadName, zName, -1);
    }
  }
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
      @ Also attachment "%h(zFilename)" to
    }else{
      @ Attachment "%h(zFilename)" to
    }
    objType |= OBJTYPE_ATTACHMENT;
    if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
      if( g.perm.Hyperlink && g.perm.RdTkt ){
        @ ticket [%z(href("%R/tktview?name=%s",zTarget))%S(zTarget)</a>]
      }else{
        @ ticket [%S(zTarget)]
      }
    }else{
      if( g.perm.Hyperlink && g.perm.RdWiki ){
        @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
      }else{







|







1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
      @ Also attachment "%h(zFilename)" to
    }else{
      @ Attachment "%h(zFilename)" to
    }
    objType |= OBJTYPE_ATTACHMENT;
    if( strlen(zTarget)==UUID_SIZE && validate16(zTarget,UUID_SIZE) ){
      if( g.perm.Hyperlink && g.perm.RdTkt ){
        @ ticket [%z(href("%R/tktview?name=%S",zTarget))%S(zTarget)</a>]
      }else{
        @ ticket [%S(zTarget)]
      }
    }else{
      if( g.perm.Hyperlink && g.perm.RdWiki ){
        @ wiki page [%z(href("%R/wiki?name=%t",zTarget))%h(zTarget)</a>]
      }else{
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
  db_finalize(&q);
  if( cnt==0 ){
    @ Control artifact.
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_appendf(pDownloadName, "%.10s.txt", zUuid);
    }
  }else if( linkToView && g.perm.Hyperlink ){
    @ %z(href("%R/artifact/%s",zUuid))[view]</a>
  }
  return objType;
}


/*
** WEBPAGE: fdiff
** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN&regex=REGEX
**
** Two arguments, v1 and v2, identify the files to be diffed.  Show the
** difference between the two artifacts.  Show diff side by side unless sbs
** is 0.  Generate plaintext if "patch" is present.
*/
void diff_page(void){
  int v1, v2;
  int isPatch;
  int sideBySide;
  char *zV1;
  char *zV2;
  const char *zRe;
  const char *zW;      /* URL param for ignoring whitespace */
  ReCompiled *pRe = 0;
  u64 diffFlags;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  v1 = name_to_rid_www("v1");
  v2 = name_to_rid_www("v2");







|




















<







1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300

1301
1302
1303
1304
1305
1306
1307
  db_finalize(&q);
  if( cnt==0 ){
    @ Control artifact.
    if( pDownloadName && blob_size(pDownloadName)==0 ){
      blob_appendf(pDownloadName, "%.10s.txt", zUuid);
    }
  }else if( linkToView && g.perm.Hyperlink ){
    @ %z(href("%R/artifact/%S",zUuid))[view]</a>
  }
  return objType;
}


/*
** WEBPAGE: fdiff
** URL: fdiff?v1=UUID&v2=UUID&patch&sbs=BOOLEAN&regex=REGEX
**
** Two arguments, v1 and v2, identify the files to be diffed.  Show the
** difference between the two artifacts.  Show diff side by side unless sbs
** is 0.  Generate plaintext if "patch" is present.
*/
void diff_page(void){
  int v1, v2;
  int isPatch;
  int sideBySide;
  char *zV1;
  char *zV2;
  const char *zRe;

  ReCompiled *pRe = 0;
  u64 diffFlags;

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  v1 = name_to_rid_www("v1");
  v2 = name_to_rid_www("v2");
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422

  sideBySide = !is_false(PD("sbs","1"));
  zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
  zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
  diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;

  style_header("Diff");
  zW = (diffFlags&DIFF_IGNORE_ALLWS)?"&w":"";
  if( *zW ){
    style_submenu_element("Show Whitespace Changes", "Show Whitespace Changes",
                          "%s/fdiff?v1=%T&v2=%T&sbs=%d",
                          g.zTop, P("v1"), P("v2"), sideBySide);
  }else{
    style_submenu_element("Ignore Whitespace", "Ignore Whitespace",
                          "%s/fdiff?v1=%T&v2=%T&sbs=%d&w",
                          g.zTop, P("v1"), P("v2"), sideBySide);
  }
  style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
                        g.zTop, P("v1"), P("v2"));
  if( !sideBySide ){
    style_submenu_element("Side-by-Side Diff", "sbsdiff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=1%s",
                          g.zTop, P("v1"), P("v2"), zW);
  }else{
    style_submenu_element("Unified Diff", "udiff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=0%s",
                          g.zTop, P("v1"), P("v2"), zW);
  }

  if( P("smhdr")!=0 ){
    @ <h2>Differences From Artifact
    @ %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a> To
    @ %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>.</h2>
  }else{
    @ <h2>Differences From
    @ Artifact %z(href("%R/artifact/%s",zV1))[%S(zV1)]</a>:</h2>
    object_description(v1, 0, 0);
    @ <h2>To Artifact %z(href("%R/artifact/%s",zV2))[%S(zV2)]</a>:</h2>
    object_description(v2, 0, 0);
  }
  if( pRe ){
    @ <b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b>
  }
  @ <hr />







<
<
<
<
<
<
<
<
<
<



|
|
|


|
|




|
|


|

|







1324
1325
1326
1327
1328
1329
1330










1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358

  sideBySide = !is_false(PD("sbs","1"));
  zV1 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v1);
  zV2 = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", v2);
  diffFlags = construct_diff_flags(1, sideBySide) | DIFF_HTML;

  style_header("Diff");










  style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
                        g.zTop, P("v1"), P("v2"));
  if( !sideBySide ){
    style_submenu_element("Side-by-side Diff", "sbsdiff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=1",
                          g.zTop, P("v1"), P("v2"));
  }else{
    style_submenu_element("Unified Diff", "udiff",
                          "%s/fdiff?v1=%T&v2=%T&sbs=0",
                          g.zTop, P("v1"), P("v2"));
  }

  if( P("smhdr")!=0 ){
    @ <h2>Differences From Artifact
    @ %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a> To
    @ %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>.</h2>
  }else{
    @ <h2>Differences From
    @ Artifact %z(href("%R/artifact/%S",zV1))[%S(zV1)]</a>:</h2>
    object_description(v1, 0, 0);
    @ <h2>To Artifact %z(href("%R/artifact/%S",zV2))[%S(zV2)]</a>:</h2>
    object_description(v2, 0, 0);
  }
  if( pRe ){
    @ <b>Only differences that match regular expression "%h(zRe)"
    @ are shown.</b>
  }
  @ <hr />
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
  Blob content;

  rid = name_to_rid_www("name");
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( rid==0 ) fossil_redirect_home();
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( fossil_strcmp(P("name"), zUuid)==0 && login_is_nobody() ){
    g.isConst = 1;
  }
  free(zUuid);
  zMime = P("m");
  if( zMime==0 ){
    char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
                              " WHERE mlink.fid=%d"







|







1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
  Blob content;

  rid = name_to_rid_www("name");
  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if( rid==0 ) fossil_redirect_home();
  zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
  if( fossil_strcmp(P("name"), zUuid)==0 ){
    g.isConst = 1;
  }
  free(zUuid);
  zMime = P("m");
  if( zMime==0 ){
    char *zFName = db_text(0, "SELECT filename.name FROM mlink, filename"
                              " WHERE mlink.fid=%d"
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756

1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
        style_submenu_element("Html", "Html",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsHtml = 1;
        style_submenu_element("Text", "Text",
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0
           || fossil_strcmp(zMime, "text/x-markdown")==0 ){
      if( asText ){
        style_submenu_element("Wiki", "Wiki",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsWiki = 1;
        style_submenu_element("Text", "Text",
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }
  }
  if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
    style_submenu_element("Parsed", "Parsed", "%R/info/%s", zUuid);
  }
  @ <hr />
  content_get(rid, &content);
  if( renderAsWiki ){
    wiki_render_by_mimetype(&content, zMime);
  }else if( renderAsHtml ){
    @ <iframe src="%R/raw/%T(blob_str(&downloadName))?name=%s(zUuid)"
    @   width="100%%" frameborder="0" marginwidth="0" marginheight="0"
    @   sandbox="allow-same-origin"
    @   onload="this.height = this.contentDocument.documentElement.scrollHeight;">
    @ </iframe>
  }else{
    style_submenu_element("Hex","Hex", "%s/hexdump?name=%s", g.zTop, zUuid);
    blob_to_utf8_no_bom(&content, 0);
    zMime = mimetype_from_content(&content);
    @ <blockquote>
    if( zMime==0 ){
      const char *zLn = P("ln");
      const char *z;

      z = blob_str(&content);
      if( zLn ){
        output_text_with_line_numbers(z, zLn);
      }else{
        @ <pre>
        @ %h(z)
        @ </pre>
      }
    }else if( strncmp(zMime, "image/", 6)==0 ){
      @ <img src="%R/raw/%s(zUuid)?m=%s(zMime)" />
      style_submenu_element("Image", "Image",
                            "%R/raw/%s?m=%s", zUuid, zMime);
    }else{
      @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
    }
    @ </blockquote>
  }
  style_footer();
}







|
<
















|








<





>









|

|







1653
1654
1655
1656
1657
1658
1659
1660

1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685

1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
        style_submenu_element("Html", "Html",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsHtml = 1;
        style_submenu_element("Text", "Text",
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }else if( fossil_strcmp(zMime, "text/x-fossil-wiki")==0 ){

      if( asText ){
        style_submenu_element("Wiki", "Wiki",
                              "%s/artifact/%s", g.zTop, zUuid);
      }else{
        renderAsWiki = 1;
        style_submenu_element("Text", "Text",
                              "%s/artifact/%s?txt=1", g.zTop, zUuid);
      }
    }
  }
  if( (objType & (OBJTYPE_WIKI|OBJTYPE_TICKET))!=0 ){
    style_submenu_element("Parsed", "Parsed", "%R/info/%s", zUuid);
  }
  @ <hr />
  content_get(rid, &content);
  if( renderAsWiki ){
    wiki_convert(&content, 0, 0);
  }else if( renderAsHtml ){
    @ <iframe src="%R/raw/%T(blob_str(&downloadName))?name=%s(zUuid)"
    @   width="100%%" frameborder="0" marginwidth="0" marginheight="0"
    @   sandbox="allow-same-origin"
    @   onload="this.height = this.contentDocument.documentElement.scrollHeight;">
    @ </iframe>
  }else{
    style_submenu_element("Hex","Hex", "%s/hexdump?name=%s", g.zTop, zUuid);

    zMime = mimetype_from_content(&content);
    @ <blockquote>
    if( zMime==0 ){
      const char *zLn = P("ln");
      const char *z;
      blob_to_utf8_no_bom(&content, 0);
      z = blob_str(&content);
      if( zLn ){
        output_text_with_line_numbers(z, zLn);
      }else{
        @ <pre>
        @ %h(z)
        @ </pre>
      }
    }else if( strncmp(zMime, "image/", 6)==0 ){
      @ <img src="%R/raw/%S(zUuid)?m=%s(zMime)" />
      style_submenu_element("Image", "Image",
                            "%R/raw/%S?m=%s", zUuid, zMime);
    }else{
      @ <i>(file is %d(blob_size(&content)) bytes of binary data)</i>
    }
    @ </blockquote>
  }
  style_footer();
}
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
      moderation_approve(rid);
    }
  }
  zTktTitle = db_table_has_column( "ticket", "title" )
      ? db_text("(No title)", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName)
      : 0;
  style_header("Ticket Change Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%s", zUuid);
  style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
  style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
  style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
  if( P("plaintext") ){
    style_submenu_element("Formatted", "Formatted", "%R/info/%s", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/info/%s?plaintext", zUuid);
  }

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){







|




|


|







1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
      moderation_approve(rid);
    }
  }
  zTktTitle = db_table_has_column( "ticket", "title" )
      ? db_text("(No title)", "SELECT title FROM ticket WHERE tkt_uuid=%Q", zTktName)
      : 0;
  style_header("Ticket Change Details");
  style_submenu_element("Raw", "Raw", "%R/artifact/%S", zUuid);
  style_submenu_element("History", "History", "%R/tkthistory/%s", zTktName);
  style_submenu_element("Page", "Page", "%R/tktview/%t", zTktName);
  style_submenu_element("Timeline", "Timeline", "%R/tkttimeline/%t", zTktName);
  if( P("plaintext") ){
    style_submenu_element("Formatted", "Formatted", "%R/info/%S", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/info/%S?plaintext", zUuid);
  }

  @ <div class="section">Overview</div>
  @ <p><table class="label-value">
  @ <tr><th>Artifact&nbsp;ID:</th>
  @ <td>%z(href("%R/artifact/%s",zUuid))%s(zUuid)</a>
  if( g.perm.Setup ){
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
        blob_appendf(&ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid);
      }
    }
    db_finalize(&q);
    if( nChng>0 ){
      int nrid;
      Blob cksum;
      blob_appendf(&ctrl, "U %F\n", login_name());
      md5sum_blob(&ctrl, &cksum);
      blob_appendf(&ctrl, "Z %b\n", &cksum);
      db_begin_transaction();
      g.markPrivate = content_is_private(rid);
      nrid = content_put(&ctrl);
      manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
      assert( blob_is_reset(&ctrl) );







|







2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
        blob_appendf(&ctrl, "T %s%F %s\n", zPrefix, zTag, zUuid);
      }
    }
    db_finalize(&q);
    if( nChng>0 ){
      int nrid;
      Blob cksum;
      blob_appendf(&ctrl, "U %F\n", g.zLogin);
      md5sum_blob(&ctrl, &cksum);
      blob_appendf(&ctrl, "Z %b\n", &cksum);
      db_begin_transaction();
      g.markPrivate = content_is_private(rid);
      nrid = content_put(&ctrl);
      manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
      assert( blob_is_reset(&ctrl) );
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
    @ <hr />
    blob_reset(&suffix);
  }
  @ <p>Make changes to attributes of check-in
  @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
  form_begin(0, "%R/ci_edit");
  login_insert_csrf_secret();
  @ <div><input type="hidden" name="r" value="%s(zUuid)" />
  @ <table border="0" cellspacing="10">

  @ <tr><th align="right" valign="top">User:</th>
  @ <td valign="top">
  @   <input type="text" name="u" size="20" value="%h(zNewUser)" />
  @ </td></tr>








|







2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
    @ <hr />
    blob_reset(&suffix);
  }
  @ <p>Make changes to attributes of check-in
  @ [%z(href("%R/ci/%s",zUuid))%s(zUuid)</a>]:</p>
  form_begin(0, "%R/ci_edit");
  login_insert_csrf_secret();
  @ <div><input type="hidden" name="r" value="%S(zUuid)" />
  @ <table border="0" cellspacing="10">

  @ <tr><th align="right" valign="top">User:</th>
  @ <td valign="top">
  @   <input type="text" name="u" size="20" value="%h(zNewUser)" />
  @ </td></tr>

2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
  @ <input id="brname" type="text" style="width:15;" name="brname"
  @ value="%h(zNewBranch)"
  @ onkeyup="chgbn(this.value.trim(),'%h(zBranchName)')" /></td></tr>
  if( !fHasHidden ){
    @ <tr><th align="right" valign="top">Branch Hiding:</th>
    @ <td valign="top">
    @ <label><input type="checkbox" id="hidebr" name="hide"%s(zHideFlag) />
    @ Hide branch
    @ <span style="font-weight:bold" id="hbranch">%h(zBranchName)</span>
    @ from the timeline starting from this check-in</label>
    @ </td></tr>
  }
  if( !fHasClosed ){
    if( is_a_leaf(rid) ){
      @ <tr><th align="right" valign="top">Leaf Closure:</th>







|







2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
  @ <input id="brname" type="text" style="width:15;" name="brname"
  @ value="%h(zNewBranch)"
  @ onkeyup="chgbn(this.value.trim(),'%h(zBranchName)')" /></td></tr>
  if( !fHasHidden ){
    @ <tr><th align="right" valign="top">Branch Hiding:</th>
    @ <td valign="top">
    @ <label><input type="checkbox" id="hidebr" name="hide"%s(zHideFlag) />
    @ Hide branch 
    @ <span style="font-weight:bold" id="hbranch">%h(zBranchName)</span>
    @ from the timeline starting from this check-in</label>
    @ </td></tr>
  }
  if( !fHasClosed ){
    if( is_a_leaf(rid) ){
      @ <tr><th align="right" valign="top">Leaf Closure:</th>

Changes to src/json.c.

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
** See the API docs for that typedef (below) for the semantics of the callbacks.
**
**
*/
#include "VERSION.h"
#include "config.h"
#include "json.h"
#include <assert.h>
#include <time.h>

#if INTERFACE
#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
#endif







|
|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
** Here's how command/page dispatching works: json_page_top() (in HTTP mode) or
** json_cmd_top() (in CLI mode) catch the "json" path/command. Those functions then
** dispatch to a JSON-mode-specific command/page handler with the type fossil_json_f().
** See the API docs for that typedef (below) for the semantics of the callbacks.
**
**
*/
#include "config.h"
#include "VERSION.h"
#include "json.h"
#include <assert.h>
#include <time.h>

#if INTERFACE
#include "json_detail.h" /* workaround for apparent enum limitation in makeheaders */
#endif
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
  INT(g, xferPanic);
  INT(g, fullHttpReply);
  INT(g, xlinkClusterOnly);
  INT(g, fTimeFormat);
  INT(g, markPrivate);
  INT(g, clockSkewSeen);
  INT(g, isHTTP);
  INT(g.url, isFile);
  INT(g.url, isHttps);
  INT(g.url, isSsh);
  INT(g.url, port);
  INT(g.url, dfltPort);
  INT(g, useLocalauth);
  INT(g, noPswd);
  INT(g, userUid);
  INT(g, rcvid);
  INT(g, okCsrf);
  INT(g, thTrace);
  INT(g, isHome);
  INT(g, nAux);
  INT(g, allowSymlinks);

  CSTR(g, zMainDbType);
  CSTR(g, zConfigDbType);
  CSTR(g, zLocalRoot);
  CSTR(g, zPath);
  CSTR(g, zExtra);
  CSTR(g, zBaseURL);
  CSTR(g, zTop);
  CSTR(g, zContentType);
  CSTR(g, zErrMsg);
  CSTR(g.url, name);
  CSTR(g.url, hostname);
  CSTR(g.url, protocol);
  CSTR(g.url, path);
  CSTR(g.url, user);
  CSTR(g.url, passwd);
  CSTR(g.url, canonical);
  CSTR(g.url, proxyAuth);
  CSTR(g.url, fossil);
  CSTR(g, zLogin);
  CSTR(g, zSSLIdentity);
  CSTR(g, zIpAddr);
  CSTR(g, zNonce);
  CSTR(g, zCsrfToken);

  o = cson_new_object();







|
|
|
|
|



















|
|
|
|
|
|
|
|
|







1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
  INT(g, xferPanic);
  INT(g, fullHttpReply);
  INT(g, xlinkClusterOnly);
  INT(g, fTimeFormat);
  INT(g, markPrivate);
  INT(g, clockSkewSeen);
  INT(g, isHTTP);
  INT(g, urlIsFile);
  INT(g, urlIsHttps);
  INT(g, urlIsSsh);
  INT(g, urlPort);
  INT(g, urlDfltPort);
  INT(g, useLocalauth);
  INT(g, noPswd);
  INT(g, userUid);
  INT(g, rcvid);
  INT(g, okCsrf);
  INT(g, thTrace);
  INT(g, isHome);
  INT(g, nAux);
  INT(g, allowSymlinks);

  CSTR(g, zMainDbType);
  CSTR(g, zConfigDbType);
  CSTR(g, zLocalRoot);
  CSTR(g, zPath);
  CSTR(g, zExtra);
  CSTR(g, zBaseURL);
  CSTR(g, zTop);
  CSTR(g, zContentType);
  CSTR(g, zErrMsg);
  CSTR(g, urlName);
  CSTR(g, urlHostname);
  CSTR(g, urlProtocol);
  CSTR(g, urlPath);
  CSTR(g, urlUser);
  CSTR(g, urlPasswd);
  CSTR(g, urlCanonical);
  CSTR(g, urlProxyAuth);
  CSTR(g, urlFossil);
  CSTR(g, zLogin);
  CSTR(g, zSSLIdentity);
  CSTR(g, zIpAddr);
  CSTR(g, zNonce);
  CSTR(g, zCsrfToken);

  o = cson_new_object();

Changes to src/json_timeline.c.

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
}

/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){
  /* Field order MUST match that from json_timeline_query()!!! */
  static const char zSql[] =
    @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
    @   sortId INTEGER PRIMARY KEY,
    @   rid INTEGER,
    @   uuid TEXT,
    @   mtime INTEGER,
    @   timestampString TEXT,
    @   comment TEXT,







|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
}

/*
** Create a temporary table suitable for storing timeline data.
*/
static void json_timeline_temp_table(void){
  /* Field order MUST match that from json_timeline_query()!!! */
  static const char zSql[] = 
    @ CREATE TEMP TABLE IF NOT EXISTS json_timeline(
    @   sortId INTEGER PRIMARY KEY,
    @   rid INTEGER,
    @   uuid TEXT,
    @   mtime INTEGER,
    @   timestampString TEXT,
    @   comment TEXT,
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    @   bgcolor,
    @   event.type,
    @   (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags,
    @   tagid as tagId,
    @   brief as brief
    @  FROM event JOIN blob
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}

/*
** Internal helper to append query information if the







|







107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
    @   bgcolor,
    @   event.type,
    @   (SELECT group_concat(substr(tagname,5), ',') FROM tag, tagxref
    @     WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid
    @       AND tagxref.rid=blob.rid AND tagxref.tagtype>0) as tags,
    @   tagid as tagId,
    @   brief as brief
    @  FROM event JOIN blob 
    @ WHERE blob.rid=event.objid
  ;
  return zBaseSql;
}

/*
** Internal helper to append query information if the
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
** flags may optionally be a bitmask of json_get_changed_files flags,
** or 0 for defaults.
*/
cson_value * json_get_changed_files(int rid, int flags){
  cson_value * rowsV = NULL;
  cson_array * rows = NULL;
  Stmt q = empty_Stmt;
  db_prepare(&q,
           "SELECT (pid==0) AS isnew,"
           "       (fid==0) AS isdel,"
           "       (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
           "       blob.uuid as uuid,"
           "       (SELECT uuid FROM blob WHERE rid=pid) as parent,"
           "       blob.size as size"
           "  FROM mlink, blob"







|







314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
** flags may optionally be a bitmask of json_get_changed_files flags,
** or 0 for defaults.
*/
cson_value * json_get_changed_files(int rid, int flags){
  cson_value * rowsV = NULL;
  cson_array * rows = NULL;
  Stmt q = empty_Stmt;
  db_prepare(&q, 
           "SELECT (pid==0) AS isnew,"
           "       (fid==0) AS isdel,"
           "       (SELECT name FROM filename WHERE fnid=mlink.fnid) AS name,"
           "       blob.uuid as uuid,"
           "       (SELECT uuid FROM blob WHERE rid=pid) as parent,"
           "       blob.size as size"
           "  FROM mlink, blob"

Changes to src/json_wiki.c.

540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    goto manifest;
  }

  blob_init(&w1, pW1->zWiki, -1);
  blob_zero(&w2);
  blob_init(&w2, pW2->zWiki, -1);
  blob_zero(&d);
  diffFlags = DIFF_IGNORE_EOLWS | DIFF_STRIP_EOLCR;
  text_diff(&w2, &w1, &d, 0, diffFlags);
  blob_reset(&w1);
  blob_reset(&w2);

  pay = cson_new_object();
  
  zUuid = json_wiki_get_uuid_for_rid( pW1->rid );







|







540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
    goto manifest;
  }

  blob_init(&w1, pW1->zWiki, -1);
  blob_zero(&w2);
  blob_init(&w2, pW2->zWiki, -1);
  blob_zero(&d);
  diffFlags = DIFF_IGNORE_EOLWS | DIFF_INLINE;
  text_diff(&w2, &w1, &d, 0, diffFlags);
  blob_reset(&w1);
  blob_reset(&w2);

  pay = cson_new_object();
  
  zUuid = json_wiki_get_uuid_for_rid( pW1->rid );

Deleted src/loadctrl.c.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/*
** Copyright (c) 2014 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)

** This program is distributed in the hope that it will be useful,
** but without any warranty; without even the implied warranty of
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to check the host load-average and abort
** CPU-intensive operations if the load-average is too high.
*/
#include "config.h"
#include "loadctrl.h"
#include <assert.h>

/*
** Return the load average for the host processor
*/
double load_average(void){
#if !defined(_WIN32) && !defined(FOSSIL_OMIT_LOAD_AVERAGE)
  double a[3];
  if( getloadavg(a, 3)>0 ){
    return a[0];
  }
#endif
  return 0.0;
}

/*
** COMMAND: test-loadavg
** %fossil test-loadavg
**
** Print the load average on the host machine.
*/
void loadavg_test_cmd(void){
  fossil_print("load-average: %f\n", load_average());
}

/*
** Abort the current operation of the load average of the host computer
** is too high.
*/
void load_control(void){
  double mxLoad = atof(db_get("max-loadavg", "0"));
  if( mxLoad<=0.0 || mxLoad>=load_average() ) return;

  style_header("Server Overload");
  @ <h2>The server load is currently too high.
  @ Please try again later.</h2>
  @ <p>Current load average: %f(load_average()).<br />
  @ Load average limit: %f(mxLoad)</p>
  style_footer();
  cgi_set_status(503,"Server Overload");
  cgi_reply();
  exit(0);
}
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


































































































































Changes to src/login.c.

39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
** not really the point.  The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#if defined(_WIN32)
#  include <windows.h>           /* for Sleep */
#  if defined(__MINGW32__) || defined(_MSC_VER)
#    define sleep Sleep            /* windows does not have sleep, but Sleep */
#  endif
#endif
#include <time.h>








|







39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
** not really the point.  The anonymous login keeps search-engine
** crawlers and site download tools like wget from walking change
** logs and downloading diffs of very version of the archive that
** has ever existed, and things like that.
*/
#include "config.h"
#include "login.h"
#if defined(_WIN32)  
#  include <windows.h>           /* for Sleep */
#  if defined(__MINGW32__) || defined(_MSC_VER)
#    define sleep Sleep            /* windows does not have sleep, but Sleep */
#  endif
#endif
#include <time.h>

110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  }else{
    fossil_redirect_home();
  }
}

/*
** The IP address of the client is stored as part of login cookies.
** But some clients are behind firewalls that shift the IP address
** with each HTTP request.  To allow such (broken) clients to log in,
** extract just a prefix of the IP address.
*/
static char *ipPrefix(const char *zIP){
  int i, j;
  static int ip_prefix_terms = -1;
  if( ip_prefix_terms<0 ){
    ip_prefix_terms = db_get_int("ip-prefix-terms",2);
  }







|
|
|







110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
  }else{
    fossil_redirect_home();
  }
}

/*
** The IP address of the client is stored as part of login cookies.
** But some clients are behind firewalls that shift the IP address 
** with each HTTP request.  To allow such (broken) clients to log in, 
** extract just a prefix of the IP address.  
*/
static char *ipPrefix(const char *zIP){
  int i, j;
  static int ip_prefix_terms = -1;
  if( ip_prefix_terms<0 ){
    ip_prefix_terms = db_get_int("ip-prefix-terms",2);
  }
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
**
** This is a no-op if g.userUid is 0.
*/
void login_clear_login_data(){
  if(!g.userUid){
    return;
  }else{
    char const * cookie = login_cookie_name();
    /* To logout, change the cookie value to an empty string */
    cgi_set_cookie(cookie, "",
                   login_cookie_path(), -86400);
    db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
                  "  cexpire=0 WHERE uid=%d"
                  "  AND login NOT IN ('anonymous','nobody',"
                  "  'developer','reader')", g.userUid);







|







342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
**
** This is a no-op if g.userUid is 0.
*/
void login_clear_login_data(){
  if(!g.userUid){
    return;
  }else{
    char const * cookie = login_cookie_name(); 
    /* To logout, change the cookie value to an empty string */
    cgi_set_cookie(cookie, "",
                   login_cookie_path(), -86400);
    db_multi_exec("UPDATE user SET cookie=NULL, ipaddr=NULL, "
                  "  cexpire=0 WHERE uid=%d"
                  "  AND login NOT IN ('anonymous','nobody',"
                  "  'developer','reader')", g.userUid);
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
   && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
  ){
    /* The user requests a password change */
    zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d"
                  " AND (constant_time_cmp(pw,%Q)=0"
                  "      OR constant_time_cmp(pw,%Q)=0)",
                  g.userUid, zSha1Pw, zPasswd) ){
      sleep(1);
      zErrMsg =
         @ <p><span class="loginError">
         @ You entered an incorrect old password while attempting to change
         @ your password.  Your password is unchanged.
         @ </span></p>
      ;
    }else if( fossil_strcmp(zNew1,zNew2)!=0 ){
      zErrMsg =
         @ <p><span class="loginError">
         @ The two copies of your new passwords do not match.
         @ Your password is unchanged.
         @ </span></p>
      ;
    }else{
      char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);







|


|






|







490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
   && (zNew1 = P("n1"))!=0 && (zNew2 = P("n2"))!=0
  ){
    /* The user requests a password change */
    zSha1Pw = sha1_shared_secret(zPasswd, g.zLogin, 0);
    if( db_int(1, "SELECT 0 FROM user"
                  " WHERE uid=%d"
                  " AND (constant_time_cmp(pw,%Q)=0"
                  "      OR constant_time_cmp(pw,%Q)=0)", 
                  g.userUid, zSha1Pw, zPasswd) ){
      sleep(1);
      zErrMsg = 
         @ <p><span class="loginError">
         @ You entered an incorrect old password while attempting to change
         @ your password.  Your password is unchanged.
         @ </span></p>
      ;
    }else if( fossil_strcmp(zNew1,zNew2)!=0 ){
      zErrMsg = 
         @ <p><span class="loginError">
         @ The two copies of your new passwords do not match.
         @ Your password is unchanged.
         @ </span></p>
      ;
    }else{
      char *zNewPw = sha1_shared_secret(zNew1, g.zLogin, 0);
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);
      zErrMsg =
         @ <p><span class="loginError">
         @ You entered an unknown user or an incorrect password.
         @ </span></p>
      ;
      record_login_attempt(zUsername, zIpAddr, 0);
    }else{
      /* Non-anonymous login is successful.  Set a cookie of the form:







|







544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
  }
  if( zUsername!=0 && zPasswd!=0 && zPasswd[0]!=0 ){
    /* Attempting to log in as a user other than anonymous.
    */
    uid = login_search_uid(zUsername, zPasswd);
    if( uid<=0 ){
      sleep(1);
      zErrMsg = 
         @ <p><span class="loginError">
         @ You entered an unknown user or an incorrect password.
         @ </span></p>
      ;
      record_login_attempt(zUsername, zIpAddr, 0);
    }else{
      /* Non-anonymous login is successful.  Set a cookie of the form:
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
  }
  @ <tr>
  @   <td></td>
  @   <td><input type="submit" name="in" value="Login"
  @        onClick="chngAction(this.form)" /></td>
  @ </tr>
  @ </table>
  @ <script>
  @   gebi('u').focus()
  @   function chngAction(form){
  if( g.sslNotAvailable==0
   && strncmp(g.zBaseURL,"https:",6)!=0
   && db_get_boolean("https-login",0)
  ){
     char *zSSL = mprintf("https:%s", &g.zBaseURL[5]);







|







597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
  }
  @ <tr>
  @   <td></td>
  @   <td><input type="submit" name="in" value="Login"
  @        onClick="chngAction(this.form)" /></td>
  @ </tr>
  @ </table>
  @ <script type="text/JavaScript">
  @   gebi('u').focus()
  @   function chngAction(form){
  if( g.sslNotAvailable==0
   && strncmp(g.zBaseURL,"https:",6)!=0
   && db_get_boolean("https-login",0)
  ){
     char *zSSL = mprintf("https:%s", &g.zBaseURL[5]);
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
    @ <p>To change your login to a different user, enter
  }
  @ your user-id and password at the left and press the
  @ "Login" button.  Your user name will be stored in a browser cookie.
  @ You must configure your web browser to accept cookies in order for
  @ the login to take.</p>
  if( db_get_boolean("self-register", 0) ){
    @ <p>If you do not have an account, you can
    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
  }
  if( zAnonPw ){
    unsigned int uSeed = captcha_seed();
    char const *zDecoded = captcha_decode(uSeed);
    int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
    char *zCaptcha = captcha_render(zDecoded);







|







622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
    @ <p>To change your login to a different user, enter
  }
  @ your user-id and password at the left and press the
  @ "Login" button.  Your user name will be stored in a browser cookie.
  @ You must configure your web browser to accept cookies in order for
  @ the login to take.</p>
  if( db_get_boolean("self-register", 0) ){
    @ <p>If you do not have an account, you can 
    @ <a href="%s(g.zTop)/register?g=%T(P("G"))">create one</a>.
  }
  if( zAnonPw ){
    unsigned int uSeed = captcha_seed();
    char const *zDecoded = captcha_decode(uSeed);
    int bAutoCaptcha = db_get_boolean("auto-captcha", 0);
    char *zCaptcha = captcha_render(zDecoded);
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
    @ </form>
  }
  style_footer();
}

/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode.  Transfer those credentials to the local
** repository.
**
** Return true if a transfer was made and false if not.
*/
static int login_transfer_credentials(
  const char *zLogin,          /* Login we are looking for */
  const char *zCode,           /* Project code of peer repository */
  const char *zHash,           /* HASH from login cookie HASH/CODE/LOGIN */
  const char *zRemoteAddr      /* Request comes from here */
){
  sqlite3 *pOther = 0;         /* The other repository */
  sqlite3_stmt *pStmt;         /* Query against the other repository */
  char *zSQL;                  /* SQL of the query against other repo */
  char *zOtherRepo;            /* Filename of the other repository */
  int rc;                      /* Result code from SQLite library functions */
  int nXfer = 0;               /* Number of credentials transferred */

  zOtherRepo = db_text(0,
       "SELECT value FROM config WHERE name='peer-repo-%q'",
       zCode
  );
  if( zOtherRepo==0 ) return 0;  /* No such peer repository */

  rc = sqlite3_open_v2(
       zOtherRepo, &pOther,







|

















|







674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
    @ </form>
  }
  style_footer();
}

/*
** Attempt to find login credentials for user zLogin on a peer repository
** with project code zCode.  Transfer those credentials to the local 
** repository.
**
** Return true if a transfer was made and false if not.
*/
static int login_transfer_credentials(
  const char *zLogin,          /* Login we are looking for */
  const char *zCode,           /* Project code of peer repository */
  const char *zHash,           /* HASH from login cookie HASH/CODE/LOGIN */
  const char *zRemoteAddr      /* Request comes from here */
){
  sqlite3 *pOther = 0;         /* The other repository */
  sqlite3_stmt *pStmt;         /* Query against the other repository */
  char *zSQL;                  /* SQL of the query against other repo */
  char *zOtherRepo;            /* Filename of the other repository */
  int rc;                      /* Result code from SQLite library functions */
  int nXfer = 0;               /* Number of credentials transferred */

  zOtherRepo = db_text(0, 
       "SELECT value FROM config WHERE name='peer-repo-%q'",
       zCode
  );
  if( zOtherRepo==0 ) return 0;  /* No such peer repository */

  rc = sqlite3_open_v2(
       zOtherRepo, &pOther,
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
  const char *zRemoteAddr        /* Abbreviated IP address for valid login */
){
  int uid;
  if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0;
  if( fossil_strcmp(zLogin, "nobody")==0 ) return 0;
  if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
  if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
  uid = db_int(0,
    "SELECT uid FROM user"
    " WHERE login=%Q"
    "   AND ipaddr=%Q"
    "   AND cexpire>julianday('now')"
    "   AND length(cap)>0"
    "   AND length(pw)>0"
    "   AND constant_time_cmp(cookie,%Q)=0",







|







754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
  const char *zRemoteAddr        /* Abbreviated IP address for valid login */
){
  int uid;
  if( fossil_strcmp(zLogin, "anonymous")==0 ) return 0;
  if( fossil_strcmp(zLogin, "nobody")==0 ) return 0;
  if( fossil_strcmp(zLogin, "developer")==0 ) return 0;
  if( fossil_strcmp(zLogin, "reader")==0 ) return 0;
  uid = db_int(0, 
    "SELECT uid FROM user"
    " WHERE login=%Q"
    "   AND ipaddr=%Q"
    "   AND cexpire>julianday('now')"
    "   AND length(cap)>0"
    "   AND length(pw)>0"
    "   AND constant_time_cmp(cookie,%Q)=0",
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
  /* Only run this check once.  */
  if( g.userUid!=0 ) return;

  sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
                  constant_time_cmp_function, 0, 0);

  /* If the HTTP connection is coming over 127.0.0.1 and if
  ** local login is disabled and if we are using HTTP and not HTTPS,
  ** then there is no need to check user credentials.
  **
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
  if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||







|







794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
  /* Only run this check once.  */
  if( g.userUid!=0 ) return;

  sqlite3_create_function(g.db, "constant_time_cmp", 2, SQLITE_UTF8, 0,
                  constant_time_cmp_function, 0, 0);

  /* If the HTTP connection is coming over 127.0.0.1 and if
  ** local login is disabled and if we are using HTTP and not HTTPS, 
  ** then there is no need to check user credentials.
  **
  ** This feature allows the "fossil ui" command to give the user
  ** full access rights without having to log in.
  */
  zRemoteAddr = ipPrefix(zIpAddr = PD("REMOTE_ADDR","nil"));
  if( ( fossil_strcmp(zIpAddr, "127.0.0.1")==0 ||
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must not be
      ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
      ** SECRET is the "captcha-secret" value in the repository.
      */
      double rTime = atof(zArg);
      Blob b;
      blob_zero(&b);
      blob_appendf(&b, "%s/%s/%s",
                   zArg, zRemoteAddr, db_get("captcha-secret",""));
      sha1sum_blob(&b, &b);
      if( fossil_strcmp(zHash, blob_str(&b))==0 ){
        uid = db_int(0,
            "SELECT uid FROM user WHERE login='anonymous'"
            " AND length(cap)>0"
            " AND length(pw)>0"
            " AND %.17g+0.25>julianday('now')",
            rTime
        );
      }







|



|







849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
      /* Cookies of the form "HASH/TIME/anonymous".  The TIME must not be
      ** too old and the sha1 hash of TIME/IPADDR/SECRET must match HASH.
      ** SECRET is the "captcha-secret" value in the repository.
      */
      double rTime = atof(zArg);
      Blob b;
      blob_zero(&b);
      blob_appendf(&b, "%s/%s/%s", 
                   zArg, zRemoteAddr, db_get("captcha-secret",""));
      sha1sum_blob(&b, &b);
      if( fossil_strcmp(zHash, blob_str(&b))==0 ){
        uid = db_int(0, 
            "SELECT uid FROM user WHERE login='anonymous'"
            " AND length(cap)>0"
            " AND length(pw)>0"
            " AND %.17g+0.25>julianday('now')",
            rTime
        );
      }
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
    return;
  }
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
      case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
                           g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
                           g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone =
                           g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
                           g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt =
                           g.perm.ModWiki = g.perm.ModTkt = 1;
                           /* Fall thru into Read/Write */
      case 'i':   g.perm.Read = g.perm.Write = 1;                     break;
      case 'o':   g.perm.Read = 1;                                 break;
      case 'z':   g.perm.Zip = 1;                                  break;







|







1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
    return;
  }
  for(i=0; zCap[i]; i++){
    switch( zCap[i] ){
      case 's':   g.perm.Setup = 1;  /* Fall thru into Admin */
      case 'a':   g.perm.Admin = g.perm.RdTkt = g.perm.WrTkt = g.perm.Zip =
                           g.perm.RdWiki = g.perm.WrWiki = g.perm.NewWiki =
                           g.perm.ApndWiki = g.perm.Hyperlink = g.perm.Clone = 
                           g.perm.NewTkt = g.perm.Password = g.perm.RdAddr =
                           g.perm.TktFmt = g.perm.Attach = g.perm.ApndTkt =
                           g.perm.ModWiki = g.perm.ModTkt = 1;
                           /* Fall thru into Read/Write */
      case 'i':   g.perm.Read = g.perm.Write = 1;                     break;
      case 'o':   g.perm.Read = 1;                                 break;
      case 'z':   g.perm.Zip = 1;                                  break;
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
      case 'm':   g.perm.ApndWiki = 1;                             break;
      case 'f':   g.perm.NewWiki = 1;                              break;
      case 'l':   g.perm.ModWiki = 1;                              break;

      case 'e':   g.perm.RdAddr = 1;                               break;
      case 'r':   g.perm.RdTkt = 1;                                break;
      case 'n':   g.perm.NewTkt = 1;                               break;
      case 'w':   g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt =
                  g.perm.ApndTkt = 1;                              break;
      case 'c':   g.perm.ApndTkt = 1;                              break;
      case 'q':   g.perm.ModTkt = 1;                               break;
      case 't':   g.perm.TktFmt = 1;                               break;
      case 'b':   g.perm.Attach = 1;                               break;
      case 'x':   g.perm.Private = 1;                              break;

      /* The "u" privileges is a little different.  It recursively
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zUser;
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser, flags | LOGIN_IGNORE_UV);
        }
        break;
      }

      /* The "v" privileges is a little different.  It recursively
      ** inherits all privileges of the user named "developer" */
      case 'v': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zDev;
          zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
          login_set_capabilities(zDev, flags | LOGIN_IGNORE_UV);
        }







|







|










|







1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
      case 'm':   g.perm.ApndWiki = 1;                             break;
      case 'f':   g.perm.NewWiki = 1;                              break;
      case 'l':   g.perm.ModWiki = 1;                              break;

      case 'e':   g.perm.RdAddr = 1;                               break;
      case 'r':   g.perm.RdTkt = 1;                                break;
      case 'n':   g.perm.NewTkt = 1;                               break;
      case 'w':   g.perm.WrTkt = g.perm.RdTkt = g.perm.NewTkt = 
                  g.perm.ApndTkt = 1;                              break;
      case 'c':   g.perm.ApndTkt = 1;                              break;
      case 'q':   g.perm.ModTkt = 1;                               break;
      case 't':   g.perm.TktFmt = 1;                               break;
      case 'b':   g.perm.Attach = 1;                               break;
      case 'x':   g.perm.Private = 1;                              break;

      /* The "u" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "reader" */
      case 'u': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zUser;
          zUser = db_text("", "SELECT cap FROM user WHERE login='reader'");
          login_set_capabilities(zUser, flags | LOGIN_IGNORE_UV);
        }
        break;
      }

      /* The "v" privileges is a little different.  It recursively 
      ** inherits all privileges of the user named "developer" */
      case 'v': {
        if( (flags & LOGIN_IGNORE_UV)==0 ){
          const char *zDev;
          zDev = db_text("", "SELECT cap FROM user WHERE login='developer'");
          login_set_capabilities(zDev, flags | LOGIN_IGNORE_UV);
        }
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168

  /* Set the capabilities */
  login_set_capabilities(zCap, 0);
  login_anon_once = 1;
  login_set_anon_nobody_capabilities();
}

/*
** Return true if the user is "nobody"
*/
int login_is_nobody(void){
  return g.zLogin==0 || g.zLogin[0]==0 || fossil_strcmp(g.zLogin,"nobody")==0;
}

/*
** Return the login name.  If no login name is specified, return "nobody".
*/
const char *login_name(void){
  return (g.zLogin && g.zLogin[0]) ? g.zLogin : "nobody";
}

/*
** Call this routine when the credential check fails.  It causes
** a redirect to the "login" page.
*/
void login_needed(void){
#ifdef FOSSIL_ENABLE_JSON
  if(g.json.isJsonMode){







<
<
<
<
<
<
<
<
<
<
<
<
<
<







1141
1142
1143
1144
1145
1146
1147














1148
1149
1150
1151
1152
1153
1154

  /* Set the capabilities */
  login_set_capabilities(zCap, 0);
  login_anon_once = 1;
  login_set_anon_nobody_capabilities();
}















/*
** Call this routine when the credential check fails.  It causes
** a redirect to the "login" page.
*/
void login_needed(void){
#ifdef FOSSIL_ENABLE_JSON
  if(g.json.isJsonMode){
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
  Stmt q;                  /* Query of all peer-* entries in CONFIG */

  if( zPrefix==0 ) zPrefix = "";
  if( zSuffix==0 ) zSuffix = "";
  if( pzErrorMsg ) *pzErrorMsg = 0;
  zSelfCode = abbreviated_project_code(db_get("project-code", "x"));
  blob_zero(&err);
  db_prepare(&q,
    "SELECT name, value FROM config"
    " WHERE name GLOB 'peer-repo-*'"
    "   AND name <> 'peer-repo-%q'"
    " ORDER BY +value",
    zSelfCode
  );
  while( db_step(&q)==SQLITE_ROW ){







|







1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
  Stmt q;                  /* Query of all peer-* entries in CONFIG */

  if( zPrefix==0 ) zPrefix = "";
  if( zSuffix==0 ) zSuffix = "";
  if( pzErrorMsg ) *pzErrorMsg = 0;
  zSelfCode = abbreviated_project_code(db_get("project-code", "x"));
  blob_zero(&err);
  db_prepare(&q, 
    "SELECT name, value FROM config"
    " WHERE name GLOB 'peer-repo-*'"
    "   AND name <> 'peer-repo-%q'"
    " ORDER BY +value",
    zSelfCode
  );
  while( db_step(&q)==SQLITE_ROW ){
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
  char *zSelfProjCode;       /* Our project-code */
  char *zSql;                /* SQL to run on all peers */
  const char *zSelf;         /* The ATTACH name of our repository */

  *pzErrMsg = 0;   /* Default to no errors */
  zSelf = db_name("repository");

  /* Get the full pathname of the other repository */
  file_canonical_name(zRepo, &fullName, 0);
  zRepo = mprintf(blob_str(&fullName));
  blob_reset(&fullName);

  /* Get the full pathname for our repository.  Also the project code
  ** and project name for ourself. */
  file_canonical_name(g.zRepositoryName, &fullName, 0);







|







1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
  char *zSelfProjCode;       /* Our project-code */
  char *zSql;                /* SQL to run on all peers */
  const char *zSelf;         /* The ATTACH name of our repository */

  *pzErrMsg = 0;   /* Default to no errors */
  zSelf = db_name("repository");

  /* Get the full pathname of the other repository */  
  file_canonical_name(zRepo, &fullName, 0);
  zRepo = mprintf(blob_str(&fullName));
  blob_reset(&fullName);

  /* Get the full pathname for our repository.  Also the project code
  ** and project name for ourself. */
  file_canonical_name(g.zRepositoryName, &fullName, 0);

Changes to src/main.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This module codes the main() procedure that runs first when the
** program is invoked.
*/
#include "VERSION.h"
#include "config.h"
#include "main.h"
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>







<







14
15
16
17
18
19
20

21
22
23
24
25
26
27
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This module codes the main() procedure that runs first when the
** program is invoked.
*/

#include "config.h"
#include "main.h"
#include <string.h>
#include <time.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
110
111
112
113
114
115
116





117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
  void *xPreEval;        /* Optional, called before Tcl_Eval*(). */
  void *pPreContext;     /* Optional, provided to xPreEval(). */
  void *xPostEval;       /* Optional, called after Tcl_Eval*(). */
  void *pPostContext;    /* Optional, provided to xPostEval(). */
};
#endif






struct Global {
  int argc; char **argv;  /* Command-line arguments to the program */
  char *nameOfExe;        /* Full path of executable. */
  const char *zErrlog;    /* Log errors to this file, if not NULL */
  int isConst;            /* True if the output is unchanging & cacheable */
  const char *zVfsName;   /* The VFS to use for database connections */
  sqlite3 *db;            /* The connection to the databases */
  sqlite3 *dbConfig;      /* Separate connection for global_config table */
  int useAttach;          /* True if global_config is attached to repository */
  const char *zConfigDbName;/* Path of the config database. NULL if not open */
  sqlite3_int64 now;      /* Seconds since 1970 */
  int repositoryOpen;     /* True if the main repository database is open */
  char *zRepositoryName;  /* Name of the repository database */
  const char *zMainDbType;/* "configdb", "localdb", or "repository" */
  const char *zConfigDbType;  /* "configdb", "localdb", or "repository" */
  int localOpen;          /* True if the local database is open */
  char *zLocalRoot;       /* The directory holding the  local database */
  int minPrefix;          /* Number of digits needed for a distinct UUID */
  int fSqlTrace;          /* True if --sqltrace flag is present */
  int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fQuiet;             /* True if -quiet flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */
  char *zHttpAuth;        /* HTTP Authorization user:pass information */
  int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
  int fSshTrace;          /* Trace the SSH setup traffic */
  int fSshClient;         /* HTTP client flags for SSH client */
  char *zSshCmd;          /* SSH command string */
  int fNoSync;            /* Do not do an autosync ever.  --nosync */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */







>
>
>
>
>




|


















<







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
  void *xPreEval;        /* Optional, called before Tcl_Eval*(). */
  void *pPreContext;     /* Optional, provided to xPreEval(). */
  void *xPostEval;       /* Optional, called after Tcl_Eval*(). */
  void *pPostContext;    /* Optional, provided to xPostEval(). */
};
#endif

/*
** All global variables are in this structure.
*/
#define GLOBAL_URL()      ((UrlData *)(&g.urlIsFile))

struct Global {
  int argc; char **argv;  /* Command-line arguments to the program */
  char *nameOfExe;        /* Full path of executable. */
  const char *zErrlog;    /* Log errors to this file, if not NULL */
  int isConst;            /* True if the output is unchanging */
  const char *zVfsName;   /* The VFS to use for database connections */
  sqlite3 *db;            /* The connection to the databases */
  sqlite3 *dbConfig;      /* Separate connection for global_config table */
  int useAttach;          /* True if global_config is attached to repository */
  const char *zConfigDbName;/* Path of the config database. NULL if not open */
  sqlite3_int64 now;      /* Seconds since 1970 */
  int repositoryOpen;     /* True if the main repository database is open */
  char *zRepositoryName;  /* Name of the repository database */
  const char *zMainDbType;/* "configdb", "localdb", or "repository" */
  const char *zConfigDbType;  /* "configdb", "localdb", or "repository" */
  int localOpen;          /* True if the local database is open */
  char *zLocalRoot;       /* The directory holding the  local database */
  int minPrefix;          /* Number of digits needed for a distinct UUID */
  int fSqlTrace;          /* True if --sqltrace flag is present */
  int fSqlStats;          /* True if --sqltrace or --sqlstats are present */
  int fSqlPrint;          /* True if -sqlprint flag is present */
  int fQuiet;             /* True if -quiet flag is present */
  int fHttpTrace;         /* Trace outbound HTTP requests */

  int fSystemTrace;       /* Trace calls to fossil_system(), --systemtrace */
  int fSshTrace;          /* Trace the SSH setup traffic */
  int fSshClient;         /* HTTP client flags for SSH client */
  char *zSshCmd;          /* SSH command string */
  int fNoSync;            /* Do not do an autosync ever.  --nosync */
  char *zPath;            /* Name of webpage being served */
  char *zExtra;           /* Extra path information past the webpage name */
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */
  int clockSkewSeen;      /* True if clocks on client and server out of sync */
  int wikiFlags;          /* Wiki conversion flags applied to %w and %W */
  char isHTTP;            /* True if server/CGI modes, else assume CLI. */
  char javascriptHyperlink; /* If true, set href= using script, not HTML */
  Blob httpHeader;        /* Complete text of the HTTP request header */
  UrlData url;            /* Information about current URL */
#if 0
  /*
  ** NOTE: These members MUST be kept in sync with those in the "UrlData"
  **       structure defined in "url.c".
  */
  int urlIsFile;          /* True if a "file:" url */
  int urlIsHttps;         /* True if a "https:" url */
  int urlIsSsh;           /* True if an "ssh:" url */
  char *urlName;          /* Hostname for http: or filename for file: */
  char *urlHostname;      /* The HOST: parameter on http headers */
  char *urlProtocol;      /* "http" or "https" */
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  char *urlFossil;        /* The fossil query parameter on ssh: */
  unsigned urlFlags;      /* Boolean flags controlling URL processing */
  int useProxy;           /* Used to remember that a proxy is in use */
  char *proxyUrlPath;
  int proxyOrigPort;      /* Tunneled port number for https through proxy */
#endif

  const char *zLogin;     /* Login name.  NULL or "" if not logged in. */
  const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
                             ** SSL client identity */
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */
  int isHuman;            /* True if access by a human, not a spider or bot */








|
<



















<
<
<
<

|







167
168
169
170
171
172
173
174

175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193




194
195
196
197
198
199
200
201
202
  int *aCommitFile;       /* Array of files to be committed */
  int markPrivate;        /* All new artifacts are private if true */
  int clockSkewSeen;      /* True if clocks on client and server out of sync */
  int wikiFlags;          /* Wiki conversion flags applied to %w and %W */
  char isHTTP;            /* True if server/CGI modes, else assume CLI. */
  char javascriptHyperlink; /* If true, set href= using script, not HTML */
  Blob httpHeader;        /* Complete text of the HTTP request header */


  /*
  ** NOTE: These members MUST be kept in sync with those in the "UrlData"
  **       structure defined in "url.c".
  */
  int urlIsFile;          /* True if a "file:" url */
  int urlIsHttps;         /* True if a "https:" url */
  int urlIsSsh;           /* True if an "ssh:" url */
  char *urlName;          /* Hostname for http: or filename for file: */
  char *urlHostname;      /* The HOST: parameter on http headers */
  char *urlProtocol;      /* "http" or "https" */
  int urlPort;            /* TCP port number for http: or https: */
  int urlDfltPort;        /* The default port for the given protocol */
  char *urlPath;          /* Pathname for http: */
  char *urlUser;          /* User id for http: */
  char *urlPasswd;        /* Password for http: */
  char *urlCanonical;     /* Canonical representation of the URL */
  char *urlProxyAuth;     /* Proxy-Authorizer: string */
  char *urlFossil;        /* The fossil query parameter on ssh: */
  unsigned urlFlags;      /* Boolean flags controlling URL processing */





  const char *zLogin;     /* Login name.  "" if not logged in. */
  const char *zSSLIdentity;  /* Value of --ssl-identity option, filename of
                             ** SSL client identity */
  int useLocalauth;       /* No login required if from 127.0.0.1 */
  int noPswd;             /* Logged in without password (on 127.0.0.1) */
  int userUid;            /* Integer user id */
  int isHuman;            /* True if access by a human, not a spider or bot */

568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
#endif
int main(int argc, char **argv)
#endif
{
  const char *zCmdName = "unknown";
  int idx;
  int rc;
  if( sqlite3_libversion_number()<3008003 ){
    fossil_fatal("Unsuitable SQLite version %s, must be at least 3.8.3",
                 sqlite3_libversion());
  }
  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
  sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
  memset(&g, 0, sizeof(g));
  g.now = time(0);
  g.httpHeader = empty_blob;







|
|







566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
#endif
int main(int argc, char **argv)
#endif
{
  const char *zCmdName = "unknown";
  int idx;
  int rc;
  if( sqlite3_libversion_number()<3007017 ){
    fossil_fatal("Unsuitable SQLite version %s, must be at least 3.7.17",
                 sqlite3_libversion());
  }
  sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
  sqlite3_config(SQLITE_CONFIG_LOG, fossil_sqlite_log, 0);
  memset(&g, 0, sizeof(g));
  g.now = time(0);
  g.httpHeader = empty_blob;
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
    if( g.fSqlTrace ) g.fSqlStats = 1;
    g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;
    g.zHttpAuth = 0;
    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;
    if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
    if( zChdir && file_chdir(zChdir, 0) ){
      fossil_fatal("unable to change directories to %s", zChdir);







<







641
642
643
644
645
646
647

648
649
650
651
652
653
654
    g.fSystemTrace = find_option("systemtrace", 0, 0)!=0;
    g.fSshTrace = find_option("sshtrace", 0, 0)!=0;
    g.fSshClient = 0;
    g.zSshCmd = 0;
    if( g.fSqlTrace ) g.fSqlStats = 1;
    g.fSqlPrint = find_option("sqlprint", 0, 0)!=0;
    g.fHttpTrace = find_option("httptrace", 0, 0)!=0;

    g.zLogin = find_option("user", "U", 1);
    g.zSSLIdentity = find_option("ssl-identity", 0, 1);
    g.zErrlog = find_option("errorlog", 0, 1);
    if( find_option("utc",0,0) ) g.fTimeFormat = 1;
    if( find_option("localtime",0,0) ) g.fTimeFormat = 2;
    if( zChdir && file_chdir(zChdir, 0) ){
      fossil_fatal("unable to change directories to %s", zChdir);
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

    @ <h1>Available web UI pages:</h1>
    @ (Only pages with help text are linked.)
    @ <table border="0"><tr>
    for(i=j=0; i<count(aCommand); i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;
      j++;
    }







|







1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
      }
    }
    if( j>0 ){
      @ </ul></td>
    }
    @ </tr></table>

    @ <h1>Available pages:</h1>
    @ (Only pages with help text are linked.)
    @ <table border="0"><tr>
    for(i=j=0; i<count(aCommand); i++){
      const char *z = aCommand[i].zName;
      if( '/'!=*z ) continue;
      j++;
    }
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
        for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
        if( zAltRepo[jj]==':' ){
          zAltRepo[jj] = 0;
          zAltRepo += jj+1;
        }else{
          zUser = "nobody";
        }
        if( g.zLogin==0 || g.zLogin[0]==0 ) zUser = "nobody";
        if( zAltRepo[0]!='/' ){
          zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
          file_simplify_name(zAltRepo, -1, 0);
        }
        db_close(1);
        db_open_repository(zAltRepo);
        login_as_user(zUser);







|







1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
        for(jj=0; zAltRepo[jj] && zAltRepo[jj]!=':'; jj++){}
        if( zAltRepo[jj]==':' ){
          zAltRepo[jj] = 0;
          zAltRepo += jj+1;
        }else{
          zUser = "nobody";
        }
        if( g.zLogin==0 ) zUser = "nobody";
        if( zAltRepo[0]!='/' ){
          zAltRepo = mprintf("%s/../%s", g.zRepositoryName, zAltRepo);
          file_simplify_name(zAltRepo, -1, 0);
        }
        db_close(1);
        db_open_repository(zAltRepo);
        login_as_user(zUser);
1840
1841
1842
1843
1844
1845
1846

1847
1848
1849
1850
1851
1852
1853
  process_one_web_page(zNotFound, glob_create(zFileGlob));
}

/*
** Process all requests in a single SSH connection if possible.
*/
void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){

  do{
    cgi_handle_ssh_http_request(zIpAddr);
    process_one_web_page(0, FileGlob);
    blob_reset(&g.cgiIn);
  } while ( g.fSshClient & CGI_SSH_FOSSIL ||
          g.fSshClient & CGI_SSH_COMPAT );
}







>







1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
  process_one_web_page(zNotFound, glob_create(zFileGlob));
}

/*
** Process all requests in a single SSH connection if possible.
*/
void ssh_request_loop(const char *zIpAddr, Glob *FileGlob){
  blob_zero(&g.cgiIn);
  do{
    cgi_handle_ssh_http_request(zIpAddr);
    process_one_web_page(0, FileGlob);
    blob_reset(&g.cgiIn);
  } while ( g.fSshClient & CGI_SSH_FOSSIL ||
          g.fSshClient & CGI_SSH_COMPAT );
}

Changes to src/main.mk.

64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \
  $(SRCDIR)/loadctrl.c \
  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \







<







64
65
66
67
68
69
70

71
72
73
74
75
76
77
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \

  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \
  $(OBJDIR)/loadctrl_.c \
  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \







<







174
175
176
177
178
179
180

181
182
183
184
185
186
187
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \

  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \
 $(OBJDIR)/loadctrl.o \
 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \







<







284
285
286
287
288
289
290

291
292
293
294
295
296
297
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \

 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \
398
399
400
401
402
403
404







405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
# to 1. If it is set to 1, then there is no need to build or link
# the sqlite3.o object. Instead, the system sqlite will be linked
# using -lsqlite3.
SQLITE3_OBJ.1 = 
SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)








EXTRAOBJ =  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE))  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/th_tcl.o  $(OBJDIR)/cson_amalgamation.o

$(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)

# This rule prevents make from using its default rules to try build
# an executable named "manifest" out of the file named "manifest.c"
#
$(SRCDIR)/../manifest:	
	# noop

clean:	
	rm -rf $(OBJDIR)/* $(APPNAME)


$(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex
	$(OBJDIR)/mkindex $(TRANS_SRC) >$@
$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
	$(OBJDIR)/makeheaders  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_dir_.c:$(OBJDIR)/json_dir.h $(OBJDIR)/json_finfo_.c:$(OBJDIR)/json_finfo.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h $(OBJDIR)/util_.c:$(OBJDIR)/util.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
	touch $(OBJDIR)/headers
$(OBJDIR)/headers: Makefile
$(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/json_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h
Makefile:
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c








>
>
>
>
>
>
>
|

















|







395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
# to 1. If it is set to 1, then there is no need to build or link
# the sqlite3.o object. Instead, the system sqlite will be linked
# using -lsqlite3.
SQLITE3_OBJ.1 = 
SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)

# The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1.
# If it is set to 1, then we need to build the Tcl integration code and
# link to the Tcl library.
TCL_OBJ.0 = 
TCL_OBJ.1 = $(OBJDIR)/th_tcl.o
TCL_OBJ. = $(TCL_OBJ.0)

EXTRAOBJ =  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE))  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(TCL_OBJ.$(FOSSIL_ENABLE_TCL))  $(OBJDIR)/cson_amalgamation.o

$(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)

# This rule prevents make from using its default rules to try build
# an executable named "manifest" out of the file named "manifest.c"
#
$(SRCDIR)/../manifest:	
	# noop

clean:	
	rm -rf $(OBJDIR)/* $(APPNAME)


$(OBJDIR)/page_index.h: $(TRANS_SRC) $(OBJDIR)/mkindex
	$(OBJDIR)/mkindex $(TRANS_SRC) >$@
$(OBJDIR)/headers:	$(OBJDIR)/page_index.h $(OBJDIR)/makeheaders $(OBJDIR)/VERSION.h
	$(OBJDIR)/makeheaders  $(OBJDIR)/add_.c:$(OBJDIR)/add.h $(OBJDIR)/allrepo_.c:$(OBJDIR)/allrepo.h $(OBJDIR)/attach_.c:$(OBJDIR)/attach.h $(OBJDIR)/bag_.c:$(OBJDIR)/bag.h $(OBJDIR)/bisect_.c:$(OBJDIR)/bisect.h $(OBJDIR)/blob_.c:$(OBJDIR)/blob.h $(OBJDIR)/branch_.c:$(OBJDIR)/branch.h $(OBJDIR)/browse_.c:$(OBJDIR)/browse.h $(OBJDIR)/captcha_.c:$(OBJDIR)/captcha.h $(OBJDIR)/cgi_.c:$(OBJDIR)/cgi.h $(OBJDIR)/checkin_.c:$(OBJDIR)/checkin.h $(OBJDIR)/checkout_.c:$(OBJDIR)/checkout.h $(OBJDIR)/clearsign_.c:$(OBJDIR)/clearsign.h $(OBJDIR)/clone_.c:$(OBJDIR)/clone.h $(OBJDIR)/comformat_.c:$(OBJDIR)/comformat.h $(OBJDIR)/configure_.c:$(OBJDIR)/configure.h $(OBJDIR)/content_.c:$(OBJDIR)/content.h $(OBJDIR)/db_.c:$(OBJDIR)/db.h $(OBJDIR)/delta_.c:$(OBJDIR)/delta.h $(OBJDIR)/deltacmd_.c:$(OBJDIR)/deltacmd.h $(OBJDIR)/descendants_.c:$(OBJDIR)/descendants.h $(OBJDIR)/diff_.c:$(OBJDIR)/diff.h $(OBJDIR)/diffcmd_.c:$(OBJDIR)/diffcmd.h $(OBJDIR)/doc_.c:$(OBJDIR)/doc.h $(OBJDIR)/encode_.c:$(OBJDIR)/encode.h $(OBJDIR)/event_.c:$(OBJDIR)/event.h $(OBJDIR)/export_.c:$(OBJDIR)/export.h $(OBJDIR)/file_.c:$(OBJDIR)/file.h $(OBJDIR)/finfo_.c:$(OBJDIR)/finfo.h $(OBJDIR)/glob_.c:$(OBJDIR)/glob.h $(OBJDIR)/graph_.c:$(OBJDIR)/graph.h $(OBJDIR)/gzip_.c:$(OBJDIR)/gzip.h $(OBJDIR)/http_.c:$(OBJDIR)/http.h $(OBJDIR)/http_socket_.c:$(OBJDIR)/http_socket.h $(OBJDIR)/http_ssl_.c:$(OBJDIR)/http_ssl.h $(OBJDIR)/http_transport_.c:$(OBJDIR)/http_transport.h $(OBJDIR)/import_.c:$(OBJDIR)/import.h $(OBJDIR)/info_.c:$(OBJDIR)/info.h $(OBJDIR)/json_.c:$(OBJDIR)/json.h $(OBJDIR)/json_artifact_.c:$(OBJDIR)/json_artifact.h $(OBJDIR)/json_branch_.c:$(OBJDIR)/json_branch.h $(OBJDIR)/json_config_.c:$(OBJDIR)/json_config.h $(OBJDIR)/json_diff_.c:$(OBJDIR)/json_diff.h $(OBJDIR)/json_dir_.c:$(OBJDIR)/json_dir.h $(OBJDIR)/json_finfo_.c:$(OBJDIR)/json_finfo.h $(OBJDIR)/json_login_.c:$(OBJDIR)/json_login.h $(OBJDIR)/json_query_.c:$(OBJDIR)/json_query.h $(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h $(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h $(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h $(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h $(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h $(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h $(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h $(OBJDIR)/login_.c:$(OBJDIR)/login.h $(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h $(OBJDIR)/main_.c:$(OBJDIR)/main.h $(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h $(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h $(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h $(OBJDIR)/md5_.c:$(OBJDIR)/md5.h $(OBJDIR)/merge_.c:$(OBJDIR)/merge.h $(OBJDIR)/merge3_.c:$(OBJDIR)/merge3.h $(OBJDIR)/moderate_.c:$(OBJDIR)/moderate.h $(OBJDIR)/name_.c:$(OBJDIR)/name.h $(OBJDIR)/path_.c:$(OBJDIR)/path.h $(OBJDIR)/pivot_.c:$(OBJDIR)/pivot.h $(OBJDIR)/popen_.c:$(OBJDIR)/popen.h $(OBJDIR)/pqueue_.c:$(OBJDIR)/pqueue.h $(OBJDIR)/printf_.c:$(OBJDIR)/printf.h $(OBJDIR)/rebuild_.c:$(OBJDIR)/rebuild.h $(OBJDIR)/regexp_.c:$(OBJDIR)/regexp.h $(OBJDIR)/report_.c:$(OBJDIR)/report.h $(OBJDIR)/rss_.c:$(OBJDIR)/rss.h $(OBJDIR)/schema_.c:$(OBJDIR)/schema.h $(OBJDIR)/search_.c:$(OBJDIR)/search.h $(OBJDIR)/setup_.c:$(OBJDIR)/setup.h $(OBJDIR)/sha1_.c:$(OBJDIR)/sha1.h $(OBJDIR)/shun_.c:$(OBJDIR)/shun.h $(OBJDIR)/skins_.c:$(OBJDIR)/skins.h $(OBJDIR)/sqlcmd_.c:$(OBJDIR)/sqlcmd.h $(OBJDIR)/stash_.c:$(OBJDIR)/stash.h $(OBJDIR)/stat_.c:$(OBJDIR)/stat.h $(OBJDIR)/style_.c:$(OBJDIR)/style.h $(OBJDIR)/sync_.c:$(OBJDIR)/sync.h $(OBJDIR)/tag_.c:$(OBJDIR)/tag.h $(OBJDIR)/tar_.c:$(OBJDIR)/tar.h $(OBJDIR)/th_main_.c:$(OBJDIR)/th_main.h $(OBJDIR)/timeline_.c:$(OBJDIR)/timeline.h $(OBJDIR)/tkt_.c:$(OBJDIR)/tkt.h $(OBJDIR)/tktsetup_.c:$(OBJDIR)/tktsetup.h $(OBJDIR)/undo_.c:$(OBJDIR)/undo.h $(OBJDIR)/unicode_.c:$(OBJDIR)/unicode.h $(OBJDIR)/update_.c:$(OBJDIR)/update.h $(OBJDIR)/url_.c:$(OBJDIR)/url.h $(OBJDIR)/user_.c:$(OBJDIR)/user.h $(OBJDIR)/utf8_.c:$(OBJDIR)/utf8.h $(OBJDIR)/util_.c:$(OBJDIR)/util.h $(OBJDIR)/verify_.c:$(OBJDIR)/verify.h $(OBJDIR)/vfile_.c:$(OBJDIR)/vfile.h $(OBJDIR)/wiki_.c:$(OBJDIR)/wiki.h $(OBJDIR)/wikiformat_.c:$(OBJDIR)/wikiformat.h $(OBJDIR)/winfile_.c:$(OBJDIR)/winfile.h $(OBJDIR)/winhttp_.c:$(OBJDIR)/winhttp.h $(OBJDIR)/wysiwyg_.c:$(OBJDIR)/wysiwyg.h $(OBJDIR)/xfer_.c:$(OBJDIR)/xfer.h $(OBJDIR)/xfersetup_.c:$(OBJDIR)/xfersetup.h $(OBJDIR)/zip_.c:$(OBJDIR)/zip.h $(SRCDIR)/sqlite3.h $(SRCDIR)/th.h $(OBJDIR)/VERSION.h
	touch $(OBJDIR)/headers
$(OBJDIR)/headers: Makefile
$(OBJDIR)/json.o $(OBJDIR)/json_artifact.o $(OBJDIR)/json_branch.o $(OBJDIR)/json_config.o $(OBJDIR)/json_diff.o $(OBJDIR)/json_dir.o $(OBJDIR)/json_finfo.o $(OBJDIR)/json_login.o $(OBJDIR)/json_query.o $(OBJDIR)/json_report.o $(OBJDIR)/json_status.o $(OBJDIR)/json_tag.o $(OBJDIR)/json_timeline.o $(OBJDIR)/json_user.o $(OBJDIR)/json_wiki.o : $(SRCDIR)/json_detail.h
Makefile:
$(OBJDIR)/add_.c:	$(SRCDIR)/add.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/add.c >$(OBJDIR)/add_.c

799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
$(OBJDIR)/leaf_.c:	$(SRCDIR)/leaf.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers
$(OBJDIR)/loadctrl_.c:	$(SRCDIR)/loadctrl.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/loadctrl.c >$(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.o:	$(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.h:	$(OBJDIR)/headers
$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<







803
804
805
806
807
808
809







810
811
812
813
814
815
816
$(OBJDIR)/leaf_.c:	$(SRCDIR)/leaf.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers







$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(OBJDIR)/translate $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers

Changes to src/makemake.tcl.

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  json_report
  json_status
  json_tag
  json_timeline
  json_user
  json_wiki
  leaf
  loadctrl
  login
  lookslike
  main
  manifest
  markdown
  markdown_html
  md5







<







67
68
69
70
71
72
73

74
75
76
77
78
79
80
  json_report
  json_status
  json_tag
  json_timeline
  json_user
  json_wiki
  leaf

  login
  lookslike
  main
  manifest
  markdown
  markdown_html
  md5
266
267
268
269
270
271
272







273
274
275
276
277
278
279
280
281
282
283
284
285
# to 1. If it is set to 1, then there is no need to build or link
# the sqlite3.o object. Instead, the system sqlite will be linked
# using -lsqlite3.
SQLITE3_OBJ.1 = 
SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)








EXTRAOBJ = \
  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \
  $(OBJDIR)/shell.o \
  $(OBJDIR)/th.o \
  $(OBJDIR)/th_lang.o \
  $(OBJDIR)/th_tcl.o \
  $(OBJDIR)/cson_amalgamation.o

$(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)

# This rule prevents make from using its default rules to try build
# an executable named "manifest" out of the file named "manifest.c"







>
>
>
>
>
>
>





|







265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# to 1. If it is set to 1, then there is no need to build or link
# the sqlite3.o object. Instead, the system sqlite will be linked
# using -lsqlite3.
SQLITE3_OBJ.1 = 
SQLITE3_OBJ.0 = $(OBJDIR)/sqlite3.o
SQLITE3_OBJ.  = $(SQLITE3_OBJ.0)

# The FOSSIL_ENABLE_TCL variable may be undefined, set to 0, or set to 1.
# If it is set to 1, then we need to build the Tcl integration code and
# link to the Tcl library.
TCL_OBJ.0 = 
TCL_OBJ.1 = $(OBJDIR)/th_tcl.o
TCL_OBJ. = $(TCL_OBJ.0)

EXTRAOBJ = \
  $(SQLITE3_OBJ.$(USE_SYSTEM_SQLITE)) \
  $(OBJDIR)/shell.o \
  $(OBJDIR)/th.o \
  $(OBJDIR)/th_lang.o \
  $(TCL_OBJ.$(FOSSIL_ENABLE_TCL)) \
  $(OBJDIR)/cson_amalgamation.o

$(APPNAME):	$(OBJDIR)/headers $(OBJ) $(EXTRAOBJ)
	$(TCC) -o $(APPNAME) $(OBJ) $(EXTRAOBJ) $(LIB)

# This rule prevents make from using its default rules to try build
# an executable named "manifest" out of the file named "manifest.c"
715
716
717
718
719
720
721
722
723




724
725
726
727
728
729
730
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ = \
  $(OBJDIR)/sqlite3.o \
  $(OBJDIR)/shell.o \
  $(OBJDIR)/th.o \
  $(OBJDIR)/th_lang.o \
  $(OBJDIR)/th_tcl.o \
  $(OBJDIR)/cson_amalgamation.o





zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean








<

>
>
>
>







721
722
723
724
725
726
727

728
729
730
731
732
733
734
735
736
737
738
739
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ = \
  $(OBJDIR)/sqlite3.o \
  $(OBJDIR)/shell.o \
  $(OBJDIR)/th.o \
  $(OBJDIR)/th_lang.o \

  $(OBJDIR)/cson_amalgamation.o

ifdef FOSSIL_ENABLE_TCL
EXTRAOBJ +=  $(OBJDIR)/th_tcl.o
endif

zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean

812
813
814
815
816
817
818

819
820

821
822
823
824
825
826
827

writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n"

writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n"


writeln "\$(OBJDIR)/th_tcl.o:\t\$(SRCDIR)/th_tcl.c"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th_tcl.c -o \$(OBJDIR)/th_tcl.o\n"


close $output_file
#
# End of the win/Makefile.mingw output
##############################################################################
##############################################################################
##############################################################################







>
|
|
>







821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838

writeln "\$(OBJDIR)/th.o:\t\$(SRCDIR)/th.c"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th.c -o \$(OBJDIR)/th.o\n"

writeln "\$(OBJDIR)/th_lang.o:\t\$(SRCDIR)/th_lang.c"
writeln "\t\$(XTCC) -c \$(SRCDIR)/th_lang.c -o \$(OBJDIR)/th_lang.o\n"

writeln {ifdef FOSSIL_ENABLE_TCL
$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o
endif}

close $output_file
#
# End of the win/Makefile.mingw output
##############################################################################
##############################################################################
##############################################################################
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133

1134

1135
1136
1137
1138
1139
1140
1141

# Uncomment to enable JSON API
# FOSSIL_ENABLE_JSON = 1

# Uncomment to enable SSL support
# FOSSIL_ENABLE_SSL = 1

# Uncomment to enable Tcl support
# FOSSIL_ENABLE_TCL = 1

!ifdef FOSSIL_ENABLE_SSL
SSLINCDIR = $(B)\compat\openssl-1.0.1g\include
SSLLIBDIR = $(B)\compat\openssl-1.0.1g\out32
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib
!endif

!ifdef FOSSIL_ENABLE_TCL
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)
TCLINCDIR = $(TCLSRCDIR)\generic
!endif

# zlib options
ZINCDIR   = $(B)\compat\zlib
ZLIBDIR   = $(B)\compat\zlib
ZLIB      = zlib.lib

INCL      = /I. /I$(SRCDIR) /I$B\win\include /I$(ZINCDIR)

!ifdef FOSSIL_ENABLE_SSL
INCL      = $(INCL) /I$(SSLINCDIR)
!endif

!ifdef FOSSIL_ENABLE_TCL
INCL      = $(INCL) /I$(TCLINCDIR)
!endif

CFLAGS    = /nologo
LDFLAGS   = /NODEFAULTLIB:msvcrt /MANIFEST:NO

!ifdef DEBUG
CFLAGS    = $(CFLAGS) /Zi /MTd /Od
LDFLAGS   = $(LDFLAGS) /DEBUG
!else
CFLAGS    = $(CFLAGS) /MT /O2
!endif

BCC       = $(CC) $(CFLAGS)
TCC       = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL)
RCC       = rc /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL)
LIBS      = $(ZLIB) ws2_32.lib advapi32.lib
LIBDIR    = /LIBPATH:$(ZLIBDIR)

!ifdef FOSSIL_ENABLE_JSON
TCC       = $(TCC) /DFOSSIL_ENABLE_JSON=1
RCC       = $(RCC) /DFOSSIL_ENABLE_JSON=1
!endif

!ifdef FOSSIL_ENABLE_SSL
TCC       = $(TCC) /DFOSSIL_ENABLE_SSL=1
RCC       = $(RCC) /DFOSSIL_ENABLE_SSL=1
LIBS      = $(LIBS) $(SSLLIB)
LIBDIR    = $(LIBDIR) /LIBPATH:$(SSLLIBDIR)
!endif

!ifdef FOSSIL_ENABLE_TCL
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL=1
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL_STUBS=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL_STUBS=1
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1
TCC       = $(TCC) /DUSE_TCL_STUBS=1
RCC       = $(RCC) /DUSE_TCL_STUBS=1
!endif
}
regsub -all {[-]D} [join $SQLITE_OPTIONS { }] {/D} MSC_SQLITE_OPTIONS
set j " \\\n                 "
writeln "SQLITE_OPTIONS = [join $MSC_SQLITE_OPTIONS $j]\n"

regsub -all {[-]D} [join $SHELL_WIN32_OPTIONS { }] {/D} MSC_SHELL_OPTIONS
set j " \\\n                "
writeln "SHELL_OPTIONS = [join $MSC_SHELL_OPTIONS $j]\n"

writeln -nonewline "SRC   = "
set i 0
foreach s [lsort $src] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  writeln -nonewline "${s}_.c"; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang th_tcl cson_amalgamation]
writeln -nonewline "OBJ   = "
set i 0
foreach s [lsort [concat $src $AdditionalObj]] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  writeln -nonewline "\$(OX)\\$s\$O"; incr i
}
writeln " \\"
writeln -nonewline "        \$(OX)\\fossil.res\n\n"
writeln {
APPNAME = $(OX)\fossil$(E)
PDBNAME = $(OX)\fossil$(P)

all: $(OX) $(APPNAME)

zlib:
	@echo Building zlib from "$(ZLIBDIR)"...
	@pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd

$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib
	cd $(OX) 
	link $(LDFLAGS) /OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts

$(OX)\linkopts: $B\win\Makefile.msc}
set redir {>}
foreach s [lsort [concat $src $AdditionalObj]] {
  writeln "\techo \$(OX)\\$s.obj $redir \$@"
  set redir {>>}
}
set redir {>>}
writeln "\techo \$(LIBS) $redir \$@"

writeln {

$(OX):
	@-mkdir $@

translate$E: $(SRCDIR)\translate.c
	$(BCC) $**

makeheaders$E: $(SRCDIR)\makeheaders.c







<
<
<






<
<
<
<
<
<





|


|


<
<
<
<
|



|


|



|
|

|


|
|



|
|

|
<
<
<
<
<
<
<
<
<
<
<




















|










|












|







<
|
>

>







1017
1018
1019
1020
1021
1022
1023



1024
1025
1026
1027
1028
1029






1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040




1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066











1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118

1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129

# Uncomment to enable JSON API
# FOSSIL_ENABLE_JSON = 1

# Uncomment to enable SSL support
# FOSSIL_ENABLE_SSL = 1




!ifdef FOSSIL_ENABLE_SSL
SSLINCDIR = $(B)\compat\openssl-1.0.1g\include
SSLLIBDIR = $(B)\compat\openssl-1.0.1g\out32
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib
!endif







# zlib options
ZINCDIR   = $(B)\compat\zlib
ZLIBDIR   = $(B)\compat\zlib
ZLIB      = zlib.lib

INCL      = -I. -I$(SRCDIR) -I$B\win\include -I$(ZINCDIR)

!ifdef FOSSIL_ENABLE_SSL
INCL      = $(INCL) -I$(SSLINCDIR)
!endif





CFLAGS    = -nologo
LDFLAGS   = /NODEFAULTLIB:msvcrt /MANIFEST:NO

!ifdef DEBUG
CFLAGS    = $(CFLAGS) -Zi -MTd -Od
LDFLAGS   = $(LDFLAGS) /DEBUG
!else
CFLAGS    = $(CFLAGS) -MT -O2
!endif

BCC       = $(CC) $(CFLAGS)
TCC       = $(CC) -c $(CFLAGS) $(MSCDEF) $(INCL)
RCC       = rc -D_WIN32 -D_MSC_VER $(MSCDEF) $(INCL)
LIBS      = $(ZLIB) ws2_32.lib advapi32.lib
LIBDIR    = -LIBPATH:$(ZLIBDIR)

!ifdef FOSSIL_ENABLE_JSON
TCC       = $(TCC) -DFOSSIL_ENABLE_JSON=1
RCC       = $(RCC) -DFOSSIL_ENABLE_JSON=1
!endif

!ifdef FOSSIL_ENABLE_SSL
TCC       = $(TCC) -DFOSSIL_ENABLE_SSL=1
RCC       = $(RCC) -DFOSSIL_ENABLE_SSL=1
LIBS      = $(LIBS) $(SSLLIB)
LIBDIR    = $(LIBDIR) -LIBPATH:$(SSLLIBDIR)











!endif
}
regsub -all {[-]D} [join $SQLITE_OPTIONS { }] {/D} MSC_SQLITE_OPTIONS
set j " \\\n                 "
writeln "SQLITE_OPTIONS = [join $MSC_SQLITE_OPTIONS $j]\n"

regsub -all {[-]D} [join $SHELL_WIN32_OPTIONS { }] {/D} MSC_SHELL_OPTIONS
set j " \\\n                "
writeln "SHELL_OPTIONS = [join $MSC_SHELL_OPTIONS $j]\n"

writeln -nonewline "SRC   = "
set i 0
foreach s [lsort $src] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  writeln -nonewline "${s}_.c"; incr i
}
writeln "\n"
set AdditionalObj [list shell sqlite3 th th_lang cson_amalgamation]
writeln -nonewline "OBJ   = "
set i 0
foreach s [lsort [concat $src $AdditionalObj]] {
  if {$i > 0} {
    writeln " \\"
    writeln -nonewline "        "
  }
  writeln -nonewline "\$(OX)\\$s\$O"; incr i
}
writeln " \\"
writeln -nonewline "        \$(OX)\\fossil.res\n"
writeln {
APPNAME = $(OX)\fossil$(E)
PDBNAME = $(OX)\fossil$(P)

all: $(OX) $(APPNAME)

zlib:
	@echo Building zlib from "$(ZLIBDIR)"...
	@pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd

$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib
	cd $(OX) 
	link $(LDFLAGS) -OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts

$(OX)\linkopts: $B\win\Makefile.msc}
set redir {>}
foreach s [lsort [concat $src $AdditionalObj]] {
  writeln "\techo \$(OX)\\$s.obj $redir \$@"
  set redir {>>}
}

writeln "\techo \$(LIBS) >> \$@\n\n"

writeln {

$(OX):
	@-mkdir $@

translate$E: $(SRCDIR)\translate.c
	$(BCC) $**

makeheaders$E: $(SRCDIR)\makeheaders.c
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175

$(OX)\th$O : $(SRCDIR)\th.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_lang$O : $(SRCDIR)\th_lang.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_tcl$O : $(SRCDIR)\th_tcl.c
	$(TCC) /Fo$@ -c $**

VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
	$** > $@
$(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c
	$(TCC) /Fo$@ /c $**

page_index.h: mkindex$E $(SRC) 
	$** > $@

clean:
	-del $(OX)\*.obj
	-del *.obj







<
<
<



|







1143
1144
1145
1146
1147
1148
1149



1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160

$(OX)\th$O : $(SRCDIR)\th.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_lang$O : $(SRCDIR)\th_lang.c
	$(TCC) /Fo$@ -c $**




VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
	$** > $@
$(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c
	$(TCC) /Fo$@ -c $**

page_index.h: mkindex$E $(SRC) 
	$** > $@

clean:
	-del $(OX)\*.obj
	-del *.obj
1200
1201
1202
1203
1204
1205
1206

1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h

}
foreach s [lsort $src] {
  writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h"
  writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n"
  writeln "${s}_.c : \$(SRCDIR)\\$s.c"
  writeln "\ttranslate\$E \$** > \$@\n"
}

writeln "fossil.res : \$B\\win\\fossil.rc"
writeln "\t\$(RCC)  /fo \$@ \$**\n"

writeln "headers: makeheaders\$E page_index.h VERSION.h"
writeln -nonewline "\tmakeheaders\$E "
set i 0
foreach s [lsort $src] {
  if {$i > 0} {
    writeln " \\"







>









|







1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h

}
foreach s [lsort $src] {
  writeln "\$(OX)\\$s\$O : ${s}_.c ${s}.h"
  writeln "\t\$(TCC) /Fo\$@ -c ${s}_.c\n"
  writeln "${s}_.c : \$(SRCDIR)\\$s.c"
  writeln "\ttranslate\$E \$** > \$@\n"
}

writeln "fossil.res : \$B\\win\\fossil.rc"
writeln "\t\$(RCC)  -fo \$@ \$**"

writeln "headers: makeheaders\$E page_index.h VERSION.h"
writeln -nonewline "\tmakeheaders\$E "
set i 0
foreach s [lsort $src] {
  if {$i > 0} {
    writeln " \\"

Changes to src/manifest.c.

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
*/
#define MC_NONE           0  /*  default handling           */
#define MC_PERMIT_HOOKS   1  /*  permit hooks to execute    */

/*
** A single F-card within a manifest
*/
struct ManifestFile {
  char *zName;           /* Name of a file */
  char *zUuid;           /* UUID of the file */
  char *zPerm;           /* File permissions */
  char *zPrior;          /* Prior name if the name was changed */
};









|







49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
*/
#define MC_NONE           0  /*  default handling           */
#define MC_PERMIT_HOOKS   1  /*  permit hooks to execute    */

/*
** A single F-card within a manifest
*/
struct ManifestFile { 
  char *zName;           /* Name of a file */
  char *zUuid;           /* UUID of the file */
  char *zPerm;           /* File permissions */
  char *zPrior;          /* Prior name if the name was changed */
};


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  int nFileAlloc;       /* Slots allocated in aFile[] */
  int iFile;            /* Index of current file in iterator */
  ManifestFile *aFile;  /* One entry for each F-card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* UUIDs of parents.  One for each P card argument */
  int nCherrypick;      /* Number of entries in aCherrypick[] */
  struct {
    char *zCPTarget;    /* UUID of cherry-picked version w/ +|- prefix */
    char *zCPBase;      /* UUID of cherry-pick baseline. NULL for singletons */
  } *aCherrypick;
  int nCChild;          /* Number of cluster children */
  int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
  char **azCChild;      /* UUIDs of referenced objects in a cluster. M cards */
  int nTag;             /* Number of T Cards */
  int nTagAlloc;        /* Slots allocated in aTag[] */
  struct TagType {
    char *zName;           /* Name of the tag */
    char *zUuid;           /* UUID that the tag is applied to */
    char *zValue;          /* Value if the tag is really a property */
  } *aTag;              /* One for each T card */
  int nField;           /* Number of J cards */
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct {
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;            /* One for each J card */
};
#endif

/*







|















|







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
  int nFileAlloc;       /* Slots allocated in aFile[] */
  int iFile;            /* Index of current file in iterator */
  ManifestFile *aFile;  /* One entry for each F-card */
  int nParent;          /* Number of parents. */
  int nParentAlloc;     /* Slots allocated in azParent[] */
  char **azParent;      /* UUIDs of parents.  One for each P card argument */
  int nCherrypick;      /* Number of entries in aCherrypick[] */
  struct {            
    char *zCPTarget;    /* UUID of cherry-picked version w/ +|- prefix */
    char *zCPBase;      /* UUID of cherry-pick baseline. NULL for singletons */
  } *aCherrypick;
  int nCChild;          /* Number of cluster children */
  int nCChildAlloc;     /* Number of closts allocated in azCChild[] */
  char **azCChild;      /* UUIDs of referenced objects in a cluster. M cards */
  int nTag;             /* Number of T Cards */
  int nTagAlloc;        /* Slots allocated in aTag[] */
  struct TagType {
    char *zName;           /* Name of the tag */
    char *zUuid;           /* UUID that the tag is applied to */
    char *zValue;          /* Value if the tag is really a property */
  } *aTag;              /* One for each T card */
  int nField;           /* Number of J cards */
  int nFieldAlloc;      /* Slots allocated in aField[] */
  struct { 
    char *zName;           /* Key or field name */
    char *zValue;          /* Value of the field */
  } *aField;            /* One for each J card */
};
#endif

/*
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

/*
** Verify the Z-card checksum on the artifact, if there is such a
** checksum.  Return 0 if there is no Z-card.  Return 1 if the Z-card
** exists and is correct.  Return 2 if the Z-card exists and has the wrong
** value.
**
**   0123456789 123456789 123456789 123456789
**   Z aea84f4f863865a8d59d0384e4d2a41c
*/
static int verify_z_card(const char *z, int n){
  if( n<35 ) return 0;
  if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0;
  md5sum_init();
  md5sum_step_text(z, n-35);







|







249
250
251
252
253
254
255
256
257
258
259
260
261
262
263

/*
** Verify the Z-card checksum on the artifact, if there is such a
** checksum.  Return 0 if there is no Z-card.  Return 1 if the Z-card
** exists and is correct.  Return 2 if the Z-card exists and has the wrong
** value.
**
**   0123456789 123456789 123456789 123456789 
**   Z aea84f4f863865a8d59d0384e4d2a41c
*/
static int verify_z_card(const char *z, int n){
  if( n<35 ) return 0;
  if( z[n-35]!='Z' || z[n-34]!=' ' ) return 0;
  md5sum_init();
  md5sum_step_text(z, n-35);
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
      */
      case 'A': {
        char *zName, *zTarget, *zSrc;
        int nTarget = 0, nSrc = 0;
        zName = next_token(&x, 0);
        zTarget = next_token(&x, &nTarget);
        zSrc = next_token(&x, &nSrc);
        if( zName==0 || zTarget==0 ) goto manifest_syntax_error;
        if( p->zAttachName!=0 ) goto manifest_syntax_error;
        defossilize(zName);
        if( !file_is_simple_pathname(zName, 0) ){
          SYNTAX("invalid filename on A-card");
        }
        defossilize(zTarget);
        if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))







|







433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
      */
      case 'A': {
        char *zName, *zTarget, *zSrc;
        int nTarget = 0, nSrc = 0;
        zName = next_token(&x, 0);
        zTarget = next_token(&x, &nTarget);
        zSrc = next_token(&x, &nSrc);
        if( zName==0 || zTarget==0 ) goto manifest_syntax_error;      
        if( p->zAttachName!=0 ) goto manifest_syntax_error;
        defossilize(zName);
        if( !file_is_simple_pathname(zName, 0) ){
          SYNTAX("invalid filename on A-card");
        }
        defossilize(zTarget);
        if( (nTarget!=UUID_SIZE || !validate16(zTarget, UUID_SIZE))
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        if( p->rDate<=0.0 ) SYNTAX("cannot parse date on D-card");
        break;
      }

      /*
      **     E <timestamp> <uuid>
      **
      ** An "event" card that contains the timestamp of the event in the
      ** format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event.
      ** The event timestamp is distinct from the D timestamp.  The D
      ** timestamp is when the artifact was created whereas the E timestamp
      ** is when the specific event is said to occur.
      */
      case 'E': {
        if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");







|







502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
        if( p->rDate<=0.0 ) SYNTAX("cannot parse date on D-card");
        break;
      }

      /*
      **     E <timestamp> <uuid>
      **
      ** An "event" card that contains the timestamp of the event in the 
      ** format YYYY-MM-DDtHH:MM:SS and a unique identifier for the event.
      ** The event timestamp is distinct from the D timestamp.  The D
      ** timestamp is when the artifact was created whereas the E timestamp
      ** is when the specific event is said to occur.
      */
      case 'E': {
        if( p->rEventDate>0.0 ) SYNTAX("more than one E-card");
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
          defossilize(zPriorName);
          if( !file_is_simple_pathname(zPriorName, 0) ){
            SYNTAX("F-card old filename is not a simple path");
          }
        }
        if( p->nFile>=p->nFileAlloc ){
          p->nFileAlloc = p->nFileAlloc*2 + 10;
          p->aFile = fossil_realloc(p->aFile,
                                    p->nFileAlloc*sizeof(p->aFile[0]) );
        }
        i = p->nFile++;
        p->aFile[i].zName = zName;
        p->aFile[i].zUuid = zUuid;
        p->aFile[i].zPerm = zPerm;
        p->aFile[i].zPrior = zPriorName;







|







549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
          defossilize(zPriorName);
          if( !file_is_simple_pathname(zPriorName, 0) ){
            SYNTAX("F-card old filename is not a simple path");
          }
        }
        if( p->nFile>=p->nFileAlloc ){
          p->nFileAlloc = p->nFileAlloc*2 + 10;
          p->aFile = fossil_realloc(p->aFile, 
                                    p->nFileAlloc*sizeof(p->aFile[0]) );
        }
        i = p->nFile++;
        p->aFile[i].zName = zName;
        p->aFile[i].zUuid = zUuid;
        p->aFile[i].zPerm = zPerm;
        p->aFile[i].zPrior = zPriorName;
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
      ** Create or cancel a tag or property.  The tagname is fossil-encoded.
      ** The first character of the name must be either "+" to create a
      ** singleton tag, "*" to create a propagating tag, or "-" to create
      ** anti-tag that undoes a prior "+" or blocks propagation of of
      ** a "*".
      **
      ** The tag is applied to <uuid>.  If <uuid> is "*" then the tag is
      ** applied to the current manifest.  If <value> is provided then
      ** the tag is really a property with the given value.
      **
      ** Tags are not allowed in clusters.  Multiple T lines are allowed.
      */
      case 'T': {
        char *zName, *zValue;
        zName = next_token(&x, 0);







|







741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
      ** Create or cancel a tag or property.  The tagname is fossil-encoded.
      ** The first character of the name must be either "+" to create a
      ** singleton tag, "*" to create a propagating tag, or "-" to create
      ** anti-tag that undoes a prior "+" or blocks propagation of of
      ** a "*".
      **
      ** The tag is applied to <uuid>.  If <uuid> is "*" then the tag is
      ** applied to the current manifest.  If <value> is provided then 
      ** the tag is really a property with the given value.
      **
      ** Tags are not allowed in clusters.  Multiple T lines are allowed.
      */
      case 'T': {
        char *zName, *zValue;
        zName = next_token(&x, 0);
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
    if( p->pBaseline==0 ){
      if( !throwError ){
        db_multi_exec(
           "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
           p->rid, rid
        );
        return 1;
      }
      fossil_fatal("cannot access baseline manifest %S", p->zBaseline);
    }
  }
  return 0;
}

/*







|







1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
    if( p->pBaseline==0 ){
      if( !throwError ){
        db_multi_exec(
           "INSERT OR IGNORE INTO orphan(rid, baseline) VALUES(%d,%d)",
           p->rid, rid
        );
        return 1;
      }    
      fossil_fatal("cannot access baseline manifest %S", p->zBaseline);
    }
  }
  return 0;
}

/*
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
/*
** Advance to the next manifest-file.
**
** Return NULL for end-of-records or if there is an error.  If an error
** occurs and pErr!=0 then store 1 in *pErr.
*/
ManifestFile *manifest_file_next(
  Manifest *p,
  int *pErr
){
  ManifestFile *pOut = 0;
  if( pErr ) *pErr = 0;
  if( p->pBaseline==0 ){
    /* Manifest p is a baseline-manifest.  Just scan down the list
    ** of files. */







|







1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
/*
** Advance to the next manifest-file.
**
** Return NULL for end-of-records or if there is an error.  If an error
** occurs and pErr!=0 then store 1 in *pErr.
*/
ManifestFile *manifest_file_next(
  Manifest *p,   
  int *pErr
){
  ManifestFile *pOut = 0;
  if( pErr ) *pErr = 0;
  if( p->pBaseline==0 ){
    /* Manifest p is a baseline-manifest.  Just scan down the list
    ** of files. */
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
  db_exec(&s1);
  if( pid && fid ){
    content_deltify(pid, fid, 0);
  }
}

/*
** Do a binary search to find a file in the p->aFile[] array.
**
** As an optimization, guess that the file we seek is at index p->iFile.
** That will usually be the case.  If it is not found there, then do the
** actual binary search.
**
** Update p->iFile to be the index of the file that is found.
*/







|







1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
  db_exec(&s1);
  if( pid && fid ){
    content_deltify(pid, fid, 0);
  }
}

/*
** Do a binary search to find a file in the p->aFile[] array.  
**
** As an optimization, guess that the file we seek is at index p->iFile.
** That will usually be the case.  If it is not found there, then do the
** actual binary search.
**
** Update p->iFile to be the index of the file that is found.
*/
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
}

/*
** Locate a file named zName in the aFile[] array of the given manifest.
** Return a pointer to the appropriate ManifestFile object.  Return NULL
** if not found.
**
** This routine works even if p is a delta-manifest.  The pointer
** returned might be to the baseline.
**
** We assume that filenames are in sorted order and use a binary search.
*/
ManifestFile *manifest_file_seek(Manifest *p, const char *zName){
  ManifestFile *pFile;

  pFile = manifest_file_seek_base(p, zName);
  if( pFile && pFile->zUuid==0 ) return 0;
  if( pFile==0 && p->zBaseline ){
    fetch_baseline(p, 1);
    pFile = manifest_file_seek_base(p->pBaseline, zName);
  }
  return pFile;







|






|







1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
}

/*
** Locate a file named zName in the aFile[] array of the given manifest.
** Return a pointer to the appropriate ManifestFile object.  Return NULL
** if not found.
**
** This routine works even if p is a delta-manifest.  The pointer 
** returned might be to the baseline.
**
** We assume that filenames are in sorted order and use a binary search.
*/
ManifestFile *manifest_file_seek(Manifest *p, const char *zName){
  ManifestFile *pFile;
  
  pFile = manifest_file_seek_base(p, zName);
  if( pFile && pFile->zUuid==0 ) return 0;
  if( pFile==0 && p->zBaseline ){
    fetch_baseline(p, 1);
    pFile = manifest_file_seek_base(p->pBaseline, zName);
  }
  return pFile;
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
  if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){
    manifest_destroy(*ppOther);
    return;
  }
  isPublic = !content_is_private(cid);

  /* Try to make the parent manifest a delta from the child, if that
  ** is an appropriate thing to do.  For a new baseline, make the
  ** previous baseline a delta from the current baseline.
  */
  if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){
    content_deltify(pid, cid, 0);
  }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){
    content_deltify(pParent->pBaseline->rid, cid, 0);
  }

  /* Remember all children less than a few seconds younger than their parent,
  ** as we might want to fudge the times for those children.
  */
  if( pChild->rDate<pParent->rDate+AGE_FUDGE_WINDOW
      && manifest_crosslink_busy
  ){
    db_multi_exec(
       "INSERT OR REPLACE INTO time_fudge VALUES(%d, %.17g, %d, %.17g);",
       pParent->rid, pParent->rDate, pChild->rid, pChild->rDate
    );
  }

  /* First look at all files in pChild, ignoring its baseline.  This
  ** is where most of the changes will be found.
  */
  for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){
    int mperm = manifest_file_mperm(pChildFile);
    if( pChildFile->zPrior ){
       pParentFile = manifest_file_seek(pParent, pChildFile->zPrior);
       if( pParentFile ){
         /* File with name change */
         add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,







|



|


















|







1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
  if( fetch_baseline(pParent, 0) || fetch_baseline(pChild, 0) ){
    manifest_destroy(*ppOther);
    return;
  }
  isPublic = !content_is_private(cid);

  /* Try to make the parent manifest a delta from the child, if that
  ** is an appropriate thing to do.  For a new baseline, make the 
  ** previous baseline a delta from the current baseline.
  */
  if( (pParent->zBaseline==0)==(pChild->zBaseline==0) ){
    content_deltify(pid, cid, 0); 
  }else if( pChild->zBaseline==0 && pParent->zBaseline!=0 ){
    content_deltify(pParent->pBaseline->rid, cid, 0);
  }

  /* Remember all children less than a few seconds younger than their parent,
  ** as we might want to fudge the times for those children.
  */
  if( pChild->rDate<pParent->rDate+AGE_FUDGE_WINDOW
      && manifest_crosslink_busy
  ){
    db_multi_exec(
       "INSERT OR REPLACE INTO time_fudge VALUES(%d, %.17g, %d, %.17g);",
       pParent->rid, pParent->rDate, pChild->rid, pChild->rDate
    );
  }

  /* First look at all files in pChild, ignoring its baseline.  This
  ** is where most of the changes will be found.
  */  
  for(i=0, pChildFile=pChild->aFile; i<pChild->nFile; i++, pChildFile++){
    int mperm = manifest_file_mperm(pChildFile);
    if( pChildFile->zPrior ){
       pParentFile = manifest_file_seek(pParent, pChildFile->zPrior);
       if( pParentFile ){
         /* File with name change */
         add_one_mlink(cid, pParentFile->zUuid, pChildFile->zUuid,
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
  }else if( pChild->zBaseline==0 ){
    /* pChild is a baseline.  Look for files that are present in pParent
    ** but are missing from pChild and mark them as having been deleted. */
    manifest_file_rewind(pParent);
    while( (pParentFile = manifest_file_next(pParent,0))!=0 ){
      pChildFile = manifest_file_seek(pChild, pParentFile->zName);
      if( pChildFile==0 && pParentFile->zUuid!=0 ){
        add_one_mlink(cid, pParentFile->zUuid, 0, pParentFile->zName, 0,
                      isPublic, 0);
      }
    }
  }
  manifest_cache_insert(*ppOther);
}








|







1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
  }else if( pChild->zBaseline==0 ){
    /* pChild is a baseline.  Look for files that are present in pParent
    ** but are missing from pChild and mark them as having been deleted. */
    manifest_file_rewind(pParent);
    while( (pParentFile = manifest_file_next(pParent,0))!=0 ){
      pChildFile = manifest_file_seek(pChild, pParentFile->zName);
      if( pChildFile==0 && pParentFile->zUuid!=0 ){
        add_one_mlink(cid, pParentFile->zUuid, 0, pParentFile->zName, 0, 
                      isPublic, 0);
      }
    }
  }
  manifest_cache_insert(*ppOther);
}

1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
  blob_zero(&comment);
  blob_zero(&brief);
  if( once ){
    once = 0;
    zTitleExpr = db_get("ticket-title-expr", "title");
    zStatusColumn = db_get("ticket-status-column", "status");
  }
  zTitle = db_text("unknown",
    "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
    zTitleExpr, pManifest->zTicketUuid
  );
  if( !isNew ){
    for(i=0; i<pManifest->nField; i++){
      if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
        zNewStatus = pManifest->aField[i].zValue;
      }
    }
    if( zNewStatus ){
      blob_appendf(&comment, "%h ticket [%s|%.10s]: <i>%h</i>",
         zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
      );
      if( pManifest->nField>1 ){
        blob_appendf(&comment, " plus %d other change%s",
          pManifest->nField-1, pManifest->nField==2 ? "" : "s");
      }
      blob_appendf(&brief, "%h ticket [%s|%.10s].",
                   zNewStatus, pManifest->zTicketUuid, pManifest->zTicketUuid);
    }else{
      zNewStatus = db_text("unknown",
         "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
         zStatusColumn, pManifest->zTicketUuid
      );
      blob_appendf(&comment, "Ticket [%s|%.10s] <i>%h</i> status still %h with "
           "%d other change%s",
           pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle, zNewStatus,
           pManifest->nField, pManifest->nField==1 ? "" : "s"
      );
      free(zNewStatus);
      blob_appendf(&brief, "Ticket [%s|%.10s]: %d change%s",
           pManifest->zTicketUuid, pManifest->zTicketUuid, pManifest->nField,
           pManifest->nField==1 ? "" : "s"
      );
    }
  }else{
    blob_appendf(&comment, "New ticket [%s|%.10s] <i>%h</i>.",
      pManifest->zTicketUuid, pManifest->zTicketUuid, zTitle
    );
    blob_appendf(&brief, "New ticket [%s|%.10s].", pManifest->zTicketUuid,
        pManifest->zTicketUuid);
  }
  free(zTitle);
  db_multi_exec(
    "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
    "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)",
    tktTagId, pManifest->rDate, rid, pManifest->zUser,
    blob_str(&comment), blob_str(&brief)







|










|
|





|
|

|



|

|
|


|
|




|
|

|
<







1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623

1624
1625
1626
1627
1628
1629
1630
  blob_zero(&comment);
  blob_zero(&brief);
  if( once ){
    once = 0;
    zTitleExpr = db_get("ticket-title-expr", "title");
    zStatusColumn = db_get("ticket-status-column", "status");
  }
  zTitle = db_text("unknown", 
    "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
    zTitleExpr, pManifest->zTicketUuid
  );
  if( !isNew ){
    for(i=0; i<pManifest->nField; i++){
      if( fossil_strcmp(pManifest->aField[i].zName, zStatusColumn)==0 ){
        zNewStatus = pManifest->aField[i].zValue;
      }
    }
    if( zNewStatus ){
      blob_appendf(&comment, "%h ticket [%.10s]: <i>%h</i>",
         zNewStatus, pManifest->zTicketUuid, zTitle
      );
      if( pManifest->nField>1 ){
        blob_appendf(&comment, " plus %d other change%s",
          pManifest->nField-1, pManifest->nField==2 ? "" : "s");
      }
      blob_appendf(&brief, "%h ticket [%.10s].",
                   zNewStatus, pManifest->zTicketUuid);
    }else{
      zNewStatus = db_text("unknown", 
         "SELECT %s FROM ticket WHERE tkt_uuid='%s'",
         zStatusColumn, pManifest->zTicketUuid
      );
      blob_appendf(&comment, "Ticket [%.10s] <i>%h</i> status still %h with "
           "%d other change%s",
           pManifest->zTicketUuid, zTitle, zNewStatus, pManifest->nField,
           pManifest->nField==1 ? "" : "s"
      );
      free(zNewStatus);
      blob_appendf(&brief, "Ticket [%.10s]: %d change%s",
           pManifest->zTicketUuid, pManifest->nField,
           pManifest->nField==1 ? "" : "s"
      );
    }
  }else{
    blob_appendf(&comment, "New ticket [%.10s] <i>%h</i>.",
      pManifest->zTicketUuid, zTitle
    );
    blob_appendf(&brief, "New ticket [%.10s].", pManifest->zTicketUuid);

  }
  free(zTitle);
  db_multi_exec(
    "REPLACE INTO event(type,tagid,mtime,objid,user,comment,brief)"
    "VALUES('t',%d,%.17g,%d,%Q,%Q,%Q)",
    tktTagId, pManifest->rDate, rid, pManifest->zUser,
    blob_str(&comment), blob_str(&brief)
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
**      *  Attachment
**      *  Event
**
** If the input is a control artifact, then make appropriate entries
** in the auxiliary tables of the database in order to crosslink the
** artifact.
**
** If global variable g.xlinkClusterOnly is true, then ignore all
** control artifacts other than clusters.
**
** This routine always resets the pContent blob before returning.
**
** Historical note:  This routine original processed manifests only.
** Processing for other control artifacts was added later.  The name
** of the routine, "manifest_crosslink", and the name of this source







|







1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
**      *  Attachment
**      *  Event
**
** If the input is a control artifact, then make appropriate entries
** in the auxiliary tables of the database in order to crosslink the
** artifact.
**
** If global variable g.xlinkClusterOnly is true, then ignore all 
** control artifacts other than clusters.
**
** This routine always resets the pContent blob before returning.
**
** Historical note:  This routine original processed manifests only.
** Processing for other control artifacts was added later.  The name
** of the routine, "manifest_crosslink", and the name of this source
1702
1703
1704
1705
1706
1707
1708
1709
1710

1711
1712
1713
1714
1715
1716
1717
    fossil_error(1, "cannot fetch baseline manifest");
    return 0;
  }
  db_begin_transaction();
  if( p->type==CFTYPE_MANIFEST ){
    if( permitHooks ){
      zScript = xfer_commit_code();
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    }

    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
      char *zCom;
      for(i=0; i<p->nParent; i++){
        int pid = uuid_to_rid(p->azParent[i], 1);
        db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate);
        if( i==0 ){







<

>







1701
1702
1703
1704
1705
1706
1707

1708
1709
1710
1711
1712
1713
1714
1715
1716
    fossil_error(1, "cannot fetch baseline manifest");
    return 0;
  }
  db_begin_transaction();
  if( p->type==CFTYPE_MANIFEST ){
    if( permitHooks ){
      zScript = xfer_commit_code();

    }
    zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    if( !db_exists("SELECT 1 FROM mlink WHERE mid=%d", rid) ){
      char *zCom;
      for(i=0; i<p->nParent; i++){
        int pid = uuid_to_rid(p->azParent[i], 1);
        db_multi_exec("INSERT OR IGNORE INTO plink(pid, cid, isprim, mtime)"
                      "VALUES(%d, %d, %d, %.17g)", pid, rid, i==0, p->rDate);
        if( i==0 ){
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
        "    %.17g"
        "  ),"
        "  %d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),%.17g);",
        TAG_DATE, rid, p->rDate,
        rid, p->zUser, p->zComment,
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid, p->rDate
      );
      zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
                        " WHERE rowid=last_insert_rowid()");
      wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE);







|







1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
        "    %.17g"
        "  ),"
        "  %d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>0),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),%.17g);",
        TAG_DATE, rid, p->rDate,
        rid, p->zUser, p->zComment, 
        TAG_BGCOLOR, rid,
        TAG_USER, rid,
        TAG_COMMENT, rid, p->rDate
      );
      zCom = db_text(0, "SELECT coalesce(ecomment, comment) FROM event"
                        " WHERE rowid=last_insert_rowid()");
      wiki_extract_links(zCom, rid, 0, p->rDate, 1, WIKI_INLINE);
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
          case '-':  type = 0;  break;  /* Cancel prior occurrences */
          case '+':  type = 1;  break;  /* Apply to target only */
          case '*':  type = 2;  break;  /* Propagate to descendants */
          default:
            fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
            return 0;
        }
        tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue,
                   rid, p->rDate, tid);
      }
    }
    if( parentid ){
      tag_propagate_all(parentid);
    }
  }







|







1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
          case '-':  type = 0;  break;  /* Cancel prior occurrences */
          case '+':  type = 1;  break;  /* Apply to target only */
          case '*':  type = 2;  break;  /* Propagate to descendants */
          default:
            fossil_error(1, "unknown tag type in manifest: %s", p->aTag);
            return 0;
        }
        tag_insert(&p->aTag[i].zName[1], type, p->aTag[i].zValue, 
                   rid, p->rDate, tid);
      }
    }
    if( parentid ){
      tag_propagate_all(parentid);
    }
  }
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
      p->rDate, rid, p->zUser, zComment,
      TAG_BGCOLOR, rid,
      TAG_BGCOLOR, rid,
      TAG_USER, rid,
      TAG_COMMENT, rid
    );
    free(zComment);
  }







|







1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
    db_multi_exec(
      "REPLACE INTO event(type,mtime,objid,user,comment,"
      "                  bgcolor,euser,ecomment)"
      "VALUES('w',%.17g,%d,%Q,%Q,"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d AND tagtype>1),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d),"
      "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
      p->rDate, rid, p->zUser, zComment, 
      TAG_BGCOLOR, rid,
      TAG_BGCOLOR, rid,
      TAG_USER, rid,
      TAG_COMMENT, rid
    );
    free(zComment);
  }
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
    if( subsequent ){
      content_deltify(rid, subsequent, 0);
    }else{
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
        "VALUES('e',%.17g,%d,%d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        p->rEventDate, rid, tagid, p->zUser, p->zComment,
        TAG_BGCOLOR, rid
      );
    }
  }
  if( p->type==CFTYPE_TICKET ){
    char *zTag;








|







1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
    if( subsequent ){
      content_deltify(rid, subsequent, 0);
    }else{
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,tagid,user,comment,bgcolor)"
        "VALUES('e',%.17g,%d,%d,%Q,%Q,"
        "  (SELECT value FROM tagxref WHERE tagid=%d AND rid=%d));",
        p->rEventDate, rid, tagid, p->zUser, p->zComment, 
        TAG_BGCOLOR, rid
      );
    }
  }
  if( p->type==CFTYPE_TICKET ){
    char *zTag;

1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
          "(SELECT max(mtime) FROM attachment"
          "  WHERE target=%Q AND filename=%Q))"
       " WHERE target=%Q AND filename=%Q",
       p->zAttachTarget, p->zAttachName,
       p->zAttachTarget, p->zAttachName
    );
    if( strlen(p->zAttachTarget)!=UUID_SIZE
     || !validate16(p->zAttachTarget, UUID_SIZE)
    ){
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
             "Add attachment [/artifact/%s|%h] to wiki page [%h]",
             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
             "Add attachment [/artifact/%s|%h] to ticket [%s|%.10s]",
             p->zAttachSrc, p->zAttachName, p->zAttachTarget, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from ticket [%s|%.10s]",
             p->zAttachName, p->zAttachTarget, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);







|




|















|
|

|
|







1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
          "(SELECT max(mtime) FROM attachment"
          "  WHERE target=%Q AND filename=%Q))"
       " WHERE target=%Q AND filename=%Q",
       p->zAttachTarget, p->zAttachName,
       p->zAttachTarget, p->zAttachName
    );
    if( strlen(p->zAttachTarget)!=UUID_SIZE
     || !validate16(p->zAttachTarget, UUID_SIZE) 
    ){
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
             "Add attachment [/artifact/%S|%h] to wiki page [%h]",
             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from wiki page [%h]",
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('w',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
    }else{
      char *zComment;
      if( p->zAttachSrc && p->zAttachSrc[0] ){
        zComment = mprintf(
             "Add attachment [/artifact/%S|%h] to ticket [%S]",
             p->zAttachSrc, p->zAttachName, p->zAttachTarget);
      }else{
        zComment = mprintf("Delete attachment \"%h\" from ticket [%.10s]",
             p->zAttachName, p->zAttachTarget);
      }
      db_multi_exec(
        "REPLACE INTO event(type,mtime,objid,user,comment)"
        "VALUES('t',%.17g,%d,%Q,%Q)",
        p->rDate, rid, p->zUser, zComment
      );
      free(zComment);
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989

1990

1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
    /* Next loop expects tags to be sorted on UUID, so sort it. */
    qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
    for(i=0; i<p->nTag; i++){
      zTagUuid = p->aTag[i].zUuid;
      if( !zTagUuid ) continue;
      if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
        blob_appendf(&comment,
           " Edit [%s|%.10s]:",
           zTagUuid, zTagUuid);
        branchMove = 0;
        if( permitHooks && db_exists("SELECT 1 FROM event, blob"
            " WHERE event.type='ci' AND event.objid=blob.rid"
            " AND blob.uuid='%s'", zTagUuid) ){

          zScript = xfer_commit_code();

          zUuid = zTagUuid;
        }
      }
      zName = p->aTag[i].zName;
      zValue = p->aTag[i].zValue;
      if( strcmp(zName, "*branch")==0 ){
        blob_appendf(&comment,
           " Move to branch [/timeline?r=%h&nd&dp=%s&unhide | %h].",
           zValue, zTagUuid, zValue);
        branchMove = 1;
        continue;
      }else if( strcmp(zName, "*bgcolor")==0 ){
        blob_appendf(&comment,
           " Change branch background color to \"%h\".", zValue);
        continue;







|
|

|


>
|
>







|







1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
    /* Next loop expects tags to be sorted on UUID, so sort it. */
    qsort(p->aTag, p->nTag, sizeof(p->aTag[0]), tag_compare);
    for(i=0; i<p->nTag; i++){
      zTagUuid = p->aTag[i].zUuid;
      if( !zTagUuid ) continue;
      if( i==0 || fossil_strcmp(zTagUuid, p->aTag[i-1].zUuid)!=0 ){
        blob_appendf(&comment,
           " Edit [%S]:",
           zTagUuid);
        branchMove = 0;
        if( db_exists("SELECT 1 FROM event, blob"
            " WHERE event.type='ci' AND event.objid=blob.rid"
            " AND blob.uuid='%s'", zTagUuid) ){
          if( permitHooks ){
            zScript = xfer_commit_code();
          }
          zUuid = zTagUuid;
        }
      }
      zName = p->aTag[i].zName;
      zValue = p->aTag[i].zValue;
      if( strcmp(zName, "*branch")==0 ){
        blob_appendf(&comment,
           " Move to branch [/timeline?r=%h&nd&dp=%S&unhide | %h].",
           zValue, zTagUuid, zValue);
        branchMove = 1;
        continue;
      }else if( strcmp(zName, "*bgcolor")==0 ){
        blob_appendf(&comment,
           " Change branch background color to \"%h\".", zValue);
        continue;

Changes to src/markdown.c.

159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
  int work_active;
  struct Blob *work;
};


/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  const char *text;
  int size;
};



/********************
 * GLOBAL VARIABLES *
 ********************/

/* block_tags -- recognised block tags, sorted by cmp_html_tag */
static const struct html_tag block_tags[] = {
  { "p",            1 },
  { "dl",           2 },
  { "h1",           2 },
  { "h2",           2 },
  { "h3",           2 },
  { "h4",           2 },
  { "h5",           2 },







|










|







159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
  int work_active;
  struct Blob *work;
};


/* html_tag -- structure for quick HTML tag search (inspired from discount) */
struct html_tag {
  char *text;
  int size;
};



/********************
 * GLOBAL VARIABLES *
 ********************/

/* block_tags -- recognised block tags, sorted by cmp_html_tag */
static struct html_tag block_tags[] = {
  { "p",            1 },
  { "dl",           2 },
  { "h1",           2 },
  { "h2",           2 },
  { "h3",           2 },
  { "h4",           2 },
  { "h5",           2 },
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  const struct html_tag *htb = b;
  if( hta->size!=htb->size ) return hta->size-htb->size;
  return fossil_strnicmp(hta->text, htb->text, hta->size);
}


/* find_block_tag -- returns the current block tag */
static const struct html_tag *find_block_tag(const char *data, size_t size){
  size_t i = 0;
  struct html_tag key;

  /* looking for the word end */
  while( i<size
   && ((data[i]>='0' && data[i]<='9')
       || (data[i]>='A' && data[i]<='Z')







|







274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
  const struct html_tag *htb = b;
  if( hta->size!=htb->size ) return hta->size-htb->size;
  return fossil_strnicmp(hta->text, htb->text, hta->size);
}


/* find_block_tag -- returns the current block tag */
static struct html_tag *find_block_tag(char *data, size_t size){
  size_t i = 0;
  struct html_tag key;

  /* looking for the word end */
  while( i<size
   && ((data[i]>='0' && data[i]<='9')
       || (data[i]>='A' && data[i]<='Z')
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082


/*********************************
 * BLOCK-LEVEL PARSING FUNCTIONS *
 *********************************/

/* is_empty -- returns the line length when it is empty, 0 otherwise */
static size_t is_empty(const char *data, size_t size){
  size_t i;
  for(i=0; i<size && data[i]!='\n'; i++){
    if( data[i]!=' ' && data[i]!='\t' ) return 0;
  }
  return i+1;
}








|







1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082


/*********************************
 * BLOCK-LEVEL PARSING FUNCTIONS *
 *********************************/

/* is_empty -- returns the line length when it is empty, 0 otherwise */
static size_t is_empty(char *data, size_t size){
  size_t i;
  for(i=0; i<size && data[i]!='\n'; i++){
    if( data[i]!=' ' && data[i]!='\t' ) return 0;
  }
  return i+1;
}

1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
  }
  return skip;
}


/* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
/*  returns the length on match, 0 otherwise */
static size_t htmlblock_end(const struct html_tag *tag, const char *data, size_t size){
  size_t i, w;

  /* assuming data[0]=='<' && data[1]=='/' already tested */

  /* checking tag is a match */
  if( (tag->size+3)>=size
    || fossil_strnicmp(data+2, tag->text, tag->size)







|







1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
  }
  return skip;
}


/* htmlblock_end -- checking end of HTML block : </tag>[ \t]*\n[ \t*]\n */
/*  returns the length on match, 0 otherwise */
static size_t htmlblock_end(struct html_tag *tag, char *data, size_t size){
  size_t i, w;

  /* assuming data[0]=='<' && data[1]=='/' already tested */

  /* checking tag is a match */
  if( (tag->size+3)>=size
    || fossil_strnicmp(data+2, tag->text, tag->size)
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
static size_t parse_htmlblock(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t size
){
  size_t i, j = 0;
  const struct html_tag *curtag;
  int found;
  size_t work_size = 0;
  struct Blob work = BLOB_INITIALIZER;

  /* identification of the opening tag */
  if( size<2 || data[0]!='<' ) return 0;
  curtag = find_block_tag(data+1, size-1);







|







1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
static size_t parse_htmlblock(
  struct Blob *ob,
  struct render *rndr,
  char *data,
  size_t size
){
  size_t i, j = 0;
  struct html_tag *curtag;
  int found;
  size_t work_size = 0;
  struct Blob work = BLOB_INITIALIZER;

  /* identification of the opening tag */
  if( size<2 || data[0]!='<' ) return 0;
  curtag = find_block_tag(data+1, size-1);

Changes to src/merge.c.

256
257
258
259
260
261
262
263







264
265

266
267
268
269
270
271
272
    fossil_fatal("not a version: record #%d", pid);
  }
  if( !forceFlag && mid==pid ){
    fossil_print("Merge skipped because it is a no-op. "
                 " Use --force to override.\n");
    return;
  }
  if( integrateFlag && !is_a_leaf(mid)){







    fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]);
    integrateFlag = 0;

  }
  if( verboseFlag ){
    print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_begin_transaction();







|
>
>
>
>
>
>
>
|
|
>







256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
    fossil_fatal("not a version: record #%d", pid);
  }
  if( !forceFlag && mid==pid ){
    fossil_print("Merge skipped because it is a no-op. "
                 " Use --force to override.\n");
    return;
  }
  if( integrateFlag ){
    if( db_exists("SELECT 1 FROM vmerge WHERE id=-4")) {
      /* Fossil earlier than [55cacfcace] cannot handle this,
       * therefore disallow it. */
      fossil_fatal("Integration of another branch already in progress."
        "  Commit or Undo needed first", g.argv[2]);
    }
    if( !is_a_leaf(mid) ){
      fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]);
      integrateFlag = 0;
    }
  }
  if( verboseFlag ){
    print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:");
    print_checkin_description(pid, 12, "baseline:");
  }
  vfile_check_signature(vid, CKSIG_ENOTFILE);
  db_begin_transaction();

Changes to src/merge3.c.

362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
** that the "Xbase.c" is an exact copy of the last imported "Xup.c".
** Then to import the latest "Xup.c" while preserving all the local changes:
**
**      fossil 3-way-merge Xbase.c Xlocal.c Xup.c Xlocal.c
**      cp Xup.c Xbase.c
**      # Verify that everything still works
**      fossil commit
**
*/
void delta_3waymerge_cmd(void){
  Blob pivot, v1, v2, merged;
  if( g.argc!=6 ){
    usage("PIVOT V1 V2 MERGED");
  }
  if( blob_read_from_file(&pivot, g.argv[2])<0 ){







|







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
** that the "Xbase.c" is an exact copy of the last imported "Xup.c".
** Then to import the latest "Xup.c" while preserving all the local changes:
**
**      fossil 3-way-merge Xbase.c Xlocal.c Xup.c Xlocal.c
**      cp Xup.c Xbase.c
**      # Verify that everything still works
**      fossil commit
** 
*/
void delta_3waymerge_cmd(void){
  Blob pivot, v1, v2, merged;
  if( g.argc!=6 ){
    usage("PIVOT V1 V2 MERGED");
  }
  if( blob_read_from_file(&pivot, g.argv[2])<0 ){

Changes to src/mkversion.c.

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
        z[0] = '\0';
        break;
      }
    }
    printf("#define RELEASE_RESOURCE_VERSION %s", vx);
    while( d<3 ){ printf(",0"); d++; }
    printf("\n");
#if defined(__DMC__)            /* e.g. 0x857 */
    d = (__DMC__ & 0xF00) >> 8; /* major */
    x = (__DMC__ & 0x0F0) >> 4; /* minor */
    i = (__DMC__ & 0x00F);      /* revision */
    printf("#define COMPILER_VERSION \"%d.%d.%d\"\n", d, x, i);
#elif defined(__POCC__)   /* e.g. 700 */
    d = (__POCC__ / 100); /* major */
    x = (__POCC__ % 100); /* minor */
    printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x);
#elif defined(_MSC_VER)   /* e.g. 1800 */
    d = (_MSC_VER / 100); /* major */
    x = (_MSC_VER % 100); /* minor */
    printf("#define COMPILER_VERSION \"%d.%02d\"\n", d, x);
#endif
    return 0;
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<


67
68
69
70
71
72
73














74
75
        z[0] = '\0';
        break;
      }
    }
    printf("#define RELEASE_RESOURCE_VERSION %s", vx);
    while( d<3 ){ printf(",0"); d++; }
    printf("\n");














    return 0;
}

Changes to src/moderate.c.

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  return rc;
}

/*
** Check to see if the object identified by RID is used for anything.
*/
static int object_used(int rid){
  static const char *const aTabField[] = {
     "modreq",     "attachRid",
     "mlink",      "mid",
     "mlink",      "fid",
     "tagxref",    "srcid",
     "tagxref",    "rid",
  };
  int i;







|







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  return rc;
}

/*
** Check to see if the object identified by RID is used for anything.
*/
static int object_used(int rid){
  static const char *aTabField[] = {
     "modreq",     "attachRid",
     "mlink",      "mid",
     "mlink",      "fid",
     "tagxref",    "srcid",
     "tagxref",    "rid",
  };
  int i;

Changes to src/name.c.

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
**
*******************************************************************************
**
** This file contains code used to convert user-supplied object names into
** canonical UUIDs.
**
** A user-supplied object name is any unique prefix of a valid UUID but
** not necessarily in canonical form.
*/
#include "config.h"
#include "name.h"
#include <assert.h>

/*
** Return TRUE if the string begins with something that looks roughly







|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
**
*******************************************************************************
**
** This file contains code used to convert user-supplied object names into
** canonical UUIDs.
**
** A user-supplied object name is any unique prefix of a valid UUID but
** not necessarily in canonical form.  
*/
#include "config.h"
#include "name.h"
#include <assert.h>

/*
** Return TRUE if the string begins with something that looks roughly
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
** Convert a symbolic name into a RID.  Acceptable forms:
**
**   *  SHA1 hash
**   *  SHA1 hash prefix of at least 4 characters
**   *  Symbolic Name
**   *  "tag:" + symbolic name
**   *  Date or date-time
**   *  "date:" + Date or date-time
**   *  symbolic-name ":" date-time
**   *  "tip"
**
** The following additional forms are available in local checkouts:
**
**   *  "current"
**   *  "prev" or "previous"
**   *  "next"
**
** Return the RID of the matching artifact.  Or return 0 if the name does not
** match any known object.  Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g.
** If zType is NULL or "" or "*" then any type of artifact will serve.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
  int vid;
  int rid = 0;







|













|







48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
** Convert a symbolic name into a RID.  Acceptable forms:
**
**   *  SHA1 hash
**   *  SHA1 hash prefix of at least 4 characters
**   *  Symbolic Name
**   *  "tag:" + symbolic name
**   *  Date or date-time 
**   *  "date:" + Date or date-time
**   *  symbolic-name ":" date-time
**   *  "tip"
**
** The following additional forms are available in local checkouts:
**
**   *  "current"
**   *  "prev" or "previous"
**   *  "next"
**
** Return the RID of the matching artifact.  Or return 0 if the name does not
** match any known object.  Or return -1 if the name is ambiguous.
**
** The zType parameter specifies the type of artifact: ci, t, w, e, g. 
** If zType is NULL or "" or "*" then any type of artifact will serve.
** zType is "ci" in most use cases since we are usually searching for
** a check-in.
*/
int symbolic_name_to_rid(const char *zTag, const char *zType){
  int vid;
  int rid = 0;
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    if( rid ) return rid;
  }

  /* special keywords: "prev", "previous", "current", and "next" */
  if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){
    if( fossil_strcmp(zTag, "current")==0 ){
      rid = vid;
    }else if( fossil_strcmp(zTag, "prev")==0
              || fossil_strcmp(zTag, "previous")==0 ){
      rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid);
    }else if( fossil_strcmp(zTag, "next")==0 ){
      rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
                      "  ORDER BY isprim DESC, mtime DESC", vid);
    }
    if( rid ) return rid;
  }

  /* Date and times */
  if( memcmp(zTag, "date:", 5)==0 ){
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[5], zType);
    return rid;
  }
  if( fossil_isdate(zTag) ){
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      zTag, zType);
    if( rid) return rid;
  }

  /* Deprecated date & time formats:   "local:" + date-time and
  ** "utc:" + date-time */
  if( memcmp(zTag, "local:", 6)==0 ){
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[6], zType);
    return rid;
  }
  if( memcmp(zTag, "utc:", 4)==0 ){
    rid = db_int(0,
      "SELECT objid FROM event"
      " WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[4], zType);
    return rid;
  }

  /* "tag:" + symbolic-name */
  if( memcmp(zTag, "tag:", 4)==0 ){
    rid = db_int(0,
       "SELECT event.objid, max(event.mtime)"
       "  FROM tag, tagxref, event"
       " WHERE tag.tagname='sym-%q' "
       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
       "   AND event.objid=tagxref.rid "
       "   AND event.type GLOB '%q'",
       &zTag[4], zType
    );
    return rid;
  }

  /* root:TAG -> The origin of the branch */
  if( memcmp(zTag, "root:", 5)==0 ){
    Stmt q;
    int rc;
    char *zBr;
    rid = symbolic_name_to_rid(zTag+5, zType);
    zBr = db_text("trunk","SELECT value FROM tagxref"







|











|







|










|







|




















|







91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    if( rid ) return rid;
  }

  /* special keywords: "prev", "previous", "current", and "next" */
  if( g.localOpen && (vid=db_lget_int("checkout",0))!=0 ){
    if( fossil_strcmp(zTag, "current")==0 ){
      rid = vid;
    }else if( fossil_strcmp(zTag, "prev")==0 
              || fossil_strcmp(zTag, "previous")==0 ){
      rid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", vid);
    }else if( fossil_strcmp(zTag, "next")==0 ){
      rid = db_int(0, "SELECT cid FROM plink WHERE pid=%d"
                      "  ORDER BY isprim DESC, mtime DESC", vid);
    }
    if( rid ) return rid;
  }

  /* Date and times */
  if( memcmp(zTag, "date:", 5)==0 ){
    rid = db_int(0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[5], zType);
    return rid;
  }
  if( fossil_isdate(zTag) ){
    rid = db_int(0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q,'utc') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      zTag, zType);
    if( rid) return rid;
  }

  /* Deprecated date & time formats:   "local:" + date-time and
  ** "utc:" + date-time */
  if( memcmp(zTag, "local:", 6)==0 ){
    rid = db_int(0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday(%Q) AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[6], zType);
    return rid;
  }
  if( memcmp(zTag, "utc:", 4)==0 ){
    rid = db_int(0, 
      "SELECT objid FROM event"
      " WHERE mtime<=julianday('%qz') AND type GLOB '%q'"
      " ORDER BY mtime DESC LIMIT 1",
      &zTag[4], zType);
    return rid;
  }

  /* "tag:" + symbolic-name */
  if( memcmp(zTag, "tag:", 4)==0 ){
    rid = db_int(0,
       "SELECT event.objid, max(event.mtime)"
       "  FROM tag, tagxref, event"
       " WHERE tag.tagname='sym-%q' "
       "   AND tagxref.tagid=tag.tagid AND tagxref.tagtype>0 "
       "   AND event.objid=tagxref.rid "
       "   AND event.type GLOB '%q'",
       &zTag[4], zType
    );
    return rid;
  }
  
  /* root:TAG -> The origin of the branch */
  if( memcmp(zTag, "root:", 5)==0 ){
    Stmt q;
    int rc;
    char *zBr;
    rid = symbolic_name_to_rid(zTag+5, zType);
    zBr = db_text("trunk","SELECT value FROM tagxref"
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  if( memcmp(zTag, "rid:", 4)==0 ){
    zTag += 4;
    for(i=0; fossil_isdigit(zTag[i]); i++){}
    if( zTag[i]==0 ){
      if( strcmp(zType,"*")==0 ){
        rid = atoi(zTag);
      }else{
        rid = db_int(0,
          "SELECT event.objid"
          "  FROM event"
          " WHERE event.objid=%s"
          "   AND event.type GLOB '%q'", zTag, zType);
      }
    }
  }







|







253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
  if( memcmp(zTag, "rid:", 4)==0 ){
    zTag += 4;
    for(i=0; fossil_isdigit(zTag[i]); i++){}
    if( zTag[i]==0 ){
      if( strcmp(zType,"*")==0 ){
        rid = atoi(zTag);
      }else{
        rid = db_int(0, 
          "SELECT event.objid"
          "  FROM event"
          " WHERE event.objid=%s"
          "   AND event.type GLOB '%q'", zTag, zType);
      }
    }
  }
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag.  If multiple tags match, pick the latest.
** If the input name matches "tag:*" then always resolve as a tag.
**
** If the input is not a tag, then try to match it as an ISO-8601 date
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
** If the input is of the form "date:*" then always resolve the name as
** a date. The forms "utc:*" and "local:" are deprecated.
**
** Return 0 on success.  Return 1 if the name cannot be resolved.
** Return 2 name is ambiguous.
*/
int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
  char *zName = blob_str(pName);
  int rid = symbolic_name_to_rid(zName, zType);







|
|







276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
**
** If the input is not a UUID or a UUID prefix, then try to resolve
** the name as a tag.  If multiple tags match, pick the latest.
** If the input name matches "tag:*" then always resolve as a tag.
**
** If the input is not a tag, then try to match it as an ISO-8601 date
** string YYYY-MM-DD HH:MM:SS and pick the nearest check-in to that date.
** If the input is of the form "date:*" or "localtime:*" or "utc:*" then
** always resolve the name as a date.
**
** Return 0 on success.  Return 1 if the name cannot be resolved.
** Return 2 name is ambiguous.
*/
int name_to_uuid(Blob *pName, int iErrPriority, const char *zType){
  char *zName = blob_str(pName);
  int rid = symbolic_name_to_rid(zName, zType);
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
int name_to_rid(const char *zName){
  return name_to_typed_rid(zName, "*");
}

/*
** WEBPAGE: ambiguous
** URL: /ambiguous?name=UUID&src=WEBPAGE
**
** The UUID given by the name parameter is ambiguous.  Display a page
** that shows all possible choices and let the user select between them.
*/
void ambiguous_page(void){
  Stmt q;
  const char *zName = P("name");
  const char *zSrc = P("src");
  char *z;

  if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
    fossil_redirect_home();
  }
  style_header("Ambiguous Artifact ID");
  @ <p>The artifact id <b>%h(zName)</b> is ambiguous and might
  @ mean any of the following:
  @ <ol>
  z = mprintf("%s", zName);
  canonical16(z, strlen(z));
  db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%s(zUuid)">
    @ %s(zUuid)</a> -
    object_description(rid, 0, 0);
    @ </p></li>
  }
  @ </ol>
  db_finalize(&q);
  style_footer();
}

/*
** Convert the name in CGI parameter zParamName into a rid and return that
** rid.  If the CGI parameter is missing or is not a valid artifact tag,
** return 0.  If the CGI parameter is ambiguous, redirect to a page that







|





|


|













|
|




<







377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412

413
414
415
416
417
418
419
int name_to_rid(const char *zName){
  return name_to_typed_rid(zName, "*");
}

/*
** WEBPAGE: ambiguous
** URL: /ambiguous?name=UUID&src=WEBPAGE
** 
** The UUID given by the name parameter is ambiguous.  Display a page
** that shows all possible choices and let the user select between them.
*/
void ambiguous_page(void){
  Stmt q;
  const char *zName = P("name");  
  const char *zSrc = P("src");
  char *z;
  
  if( zName==0 || zName[0]==0 || zSrc==0 || zSrc[0]==0 ){
    fossil_redirect_home();
  }
  style_header("Ambiguous Artifact ID");
  @ <p>The artifact id <b>%h(zName)</b> is ambiguous and might
  @ mean any of the following:
  @ <ol>
  z = mprintf("%s", zName);
  canonical16(z, strlen(z));
  db_prepare(&q, "SELECT uuid, rid FROM blob WHERE uuid GLOB '%q*'", z);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    @ <li><p><a href="%s(g.zTop)/%T(zSrc)/%S(zUuid)">
    @ %S(zUuid)</a> -
    object_description(rid, 0, 0);
    @ </p></li>
  }
  @ </ol>

  style_footer();
}

/*
** Convert the name in CGI parameter zParamName into a rid and return that
** rid.  If the CGI parameter is missing or is not a valid artifact tag,
** return 0.  If the CGI parameter is ambiguous, redirect to a page that
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614












615








616


617















618
619
620
621
622
623
624
625
626
627


628
629









630





631
632
633
634

635
  if( rid<0 ){
    cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath);
    rid = 0;
  }
  return rid;
}

/*
** Generate a description of artifact "rid"
*/
static void whatis_rid(int rid, int verboseFlag){
  Stmt q;
  int cnt;

  /* Basic information about the object. */
  db_prepare(&q,
     "SELECT uuid, size, datetime(mtime%s), ipaddr"
     "  FROM blob, rcvfrom"
     " WHERE rid=%d"
     "   AND rcvfrom.rcvid=blob.rcvid",
     timeline_utc(), rid);
  if( db_step(&q)==SQLITE_ROW ){
    if( verboseFlag ){
      fossil_print("artifact:   %s (%d)\n", db_column_text(&q,0), rid);
      fossil_print("size:       %d bytes\n", db_column_int(&q,1));
      fossil_print("received:   %s from %s\n",
         db_column_text(&q, 2),
         db_column_text(&q, 3));
    }else{
      fossil_print("artifact:   %s\n", db_column_text(&q,0));
      fossil_print("size:       %d bytes\n", db_column_int(&q,1));
    }
  }
  db_finalize(&q);

  /* Report any symbolic tags on this artifact */
  db_prepare(&q,
    "SELECT substr(tagname,5)"
    "  FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
    " WHERE tagxref.rid=%d"
    "   AND tagname GLOB 'sym-*'"
    " ORDER BY 1",
    rid
  );
  cnt = 0;
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPrefix = cnt++ ? ", " : "tags:       ";
    fossil_print("%s%s", zPrefix, db_column_text(&q,0));
  }
  if( cnt ) fossil_print("\n");
  db_finalize(&q);

  /* Report any HIDDEN, PRIVATE, CLUSTER, or CLOSED tags on this artifact */
  db_prepare(&q,
    "SELECT tagname"
    "  FROM tag JOIN tagxref ON tag.tagid=tagxref.tagid"
    " WHERE tagxref.rid=%d"
    "   AND tag.tagid IN (5,6,7,9)"
    " ORDER BY 1",
    rid, rid
  );
  cnt = 0;
  while( db_step(&q)==SQLITE_ROW ){
    const char *zPrefix = cnt++ ? ", " : "raw-tags:   ";
    fossil_print("%s%s", zPrefix, db_column_text(&q,0));
  }
  if( cnt ) fossil_print("\n");
  db_finalize(&q);

  /* Check for entries on the timeline that reference this object */
  db_prepare(&q,
     "SELECT type, datetime(mtime%s),"
     "       coalesce(euser,user), coalesce(ecomment,comment)"
     "  FROM event WHERE objid=%d", timeline_utc(), rid);
  if( db_step(&q)==SQLITE_ROW ){
    const char *zType;
    switch( db_column_text(&q,0)[0] ){
      case 'c':  zType = "Check-in";       break;
      case 'w':  zType = "Wiki-edit";      break;
      case 'e':  zType = "Event";          break;
      case 't':  zType = "Ticket-change";  break;
      case 'g':  zType = "Tag-change";     break;
      default:   zType = "Unknown";        break;
    }
    fossil_print("type:       %s by %s on %s\n", zType, db_column_text(&q,2),
                 db_column_text(&q, 1));
    fossil_print("comment:    ");
    comment_print(db_column_text(&q,3), 12, 78);
  }
  db_finalize(&q);

  /* Check to see if this object is used as a file in a check-in */
  db_prepare(&q,
    "SELECT filename.name, blob.uuid, datetime(event.mtime%s),"
    "       coalesce(euser,user), coalesce(ecomment,comment)"
    "  FROM mlink, filename, blob, event"
    " WHERE mlink.fid=%d"
    "   AND filename.fnid=mlink.fnid"
    "   AND event.objid=mlink.mid"
    "   AND blob.rid=mlink.mid"
    " ORDER BY event.mtime DESC /*sort*/",
    timeline_utc(), rid);
  while( db_step(&q)==SQLITE_ROW ){
    fossil_print("file:       %s\n", db_column_text(&q,0));
    fossil_print("            part of [%.10s] by %s on %s\n",
      db_column_text(&q, 1),
      db_column_text(&q, 3),
      db_column_text(&q, 2));
    fossil_print("            ");
    comment_print(db_column_text(&q,4), 12, 78);
  }
  db_finalize(&q);

  /* Check to see if this object is used as an attachment */
  db_prepare(&q,
    "SELECT attachment.filename,"
    "       attachment.comment,"
    "       attachment.user,"
    "       datetime(attachment.mtime%s),"
    "       attachment.target,"
    "       CASE WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('tkt-'||target))"
    "            THEN 'ticket'"
    "       WHEN EXISTS(SELECT 1 FROM tag WHERE tagname=('wiki-'||target))"
    "            THEN 'wiki' END,"
    "       attachment.attachid,"
    "       (SELECT uuid FROM blob WHERE rid=attachid)"
    "  FROM attachment JOIN blob ON attachment.src=blob.uuid"
    " WHERE blob.rid=%d",
    timeline_utc(), rid
  );
  while( db_step(&q)==SQLITE_ROW ){
    fossil_print("attachment: %s\n", db_column_text(&q,0));
    fossil_print("            attached to %s %s\n",
                 db_column_text(&q,5), db_column_text(&q,4));
    if( verboseFlag ){
      fossil_print("            via %s (%d)\n",
                   db_column_text(&q,7), db_column_int(&q,6));
    }else{
      fossil_print("            via %s\n",
                   db_column_text(&q,7));
    }
    fossil_print("            by user %s on %s\n",
                 db_column_text(&q,2), db_column_text(&q,3));
    fossil_print("            ");
    comment_print(db_column_text(&q,1), 12, 78);
  }
  db_finalize(&q);
}

/*
** COMMAND: whatis*
** Usage: %fossil whatis NAME
**
** Resolve the symbol NAME into its canonical 40-character SHA1-hash
** artifact name and provide a description of what role that artifact
** plays.
*/
void whatis_cmd(void){
  int rid;
  const char *zName;
  int verboseFlag;
  db_find_and_open_repository(0,0);
  verboseFlag = find_option("verbose","v",0)!=0;
  if( g.argc!=3 ) usage("whatis NAME");
  zName = g.argv[2];
  rid = symbolic_name_to_rid(zName, 0);
  if( rid<0 ){
    Stmt q;
    int cnt = 0;
    fossil_print("Ambiguous artifact name prefix: %s\n", zName);
    db_prepare(&q,
       "SELECT rid FROM blob WHERE uuid>=lower(%Q) AND uuid<(lower(%Q)||'z')",
       zName, zName
    );
    while( db_step(&q)==SQLITE_ROW ){
      if( cnt++ ) fossil_print("%.79c\n", '-');
      whatis_rid(db_column_int(&q, 0), verboseFlag);
    }
    db_finalize(&q);
  }else if( rid==0 ){
    fossil_print("Unknown artifact: %s\n", zName);
  }else{












    whatis_rid(rid, verboseFlag);








  }


}
















/*
** COMMAND: test-whatis-all
** Usage: %fossil test-whatis-all
**
** Show "whatis" information about every artifact in the repository
*/
void test_whatis_all_cmd(void){
  Stmt q;
  int cnt = 0;


  db_find_and_open_repository(0,0);
  db_prepare(&q, "SELECT rid FROM blob ORDER BY rid");









  while( db_step(&q)==SQLITE_ROW ){





    if( cnt++ ) fossil_print("%.79c\n", '-');
    whatis_rid(db_column_int(&q,0), 1);
  }
  db_finalize(&q);

}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<


















<
<

<
<
<
<
<
<
<
<
<



>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
<
|
<
<
<
<
|
|
>
>
|
|
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
|
|
|
|
>

432
433
434
435
436
437
438














































































































































439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456


457









458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501


502




503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
  if( rid<0 ){
    cgi_redirectf("%s/ambiguous/%T?src=%t", g.zTop, zName, g.zPath);
    rid = 0;
  }
  return rid;
}















































































































































/*
** COMMAND: whatis*
** Usage: %fossil whatis NAME
**
** Resolve the symbol NAME into its canonical 40-character SHA1-hash
** artifact name and provide a description of what role that artifact
** plays.
*/
void whatis_cmd(void){
  int rid;
  const char *zName;
  int verboseFlag;
  db_find_and_open_repository(0,0);
  verboseFlag = find_option("verbose","v",0)!=0;
  if( g.argc!=3 ) usage("whatis NAME");
  zName = g.argv[2];
  rid = symbolic_name_to_rid(zName, 0);
  if( rid<0 ){


    fossil_print("Ambiguous artifact name prefix: %s\n", zName);









  }else if( rid==0 ){
    fossil_print("Unknown artifact: %s\n", zName);
  }else{
    Stmt q;
    db_prepare(&q,
       "SELECT uuid, size, datetime(mtime%s), ipaddr,"
       "       (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref"
       "         WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid"
       "           AND tagxref.rid=blob.rid AND tagxref.tagtype>0)"
       "  FROM blob, rcvfrom"
       " WHERE rid=%d"
       "   AND rcvfrom.rcvid=blob.rcvid",
       timeline_utc(), rid);
    if( db_step(&q)==SQLITE_ROW ){
      const char *zTagList = db_column_text(&q, 4);
      if( verboseFlag ){
        fossil_print("artifact: %s (%d)\n", db_column_text(&q,0), rid);
        fossil_print("size:     %d bytes\n", db_column_int(&q,1));
        fossil_print("received: %s from %s\n",
           db_column_text(&q, 2),
           db_column_text(&q, 3));
      }else{
        fossil_print("artifact: %s\n", db_column_text(&q,0));
        fossil_print("size:     %d bytes\n", db_column_int(&q,1));
      }
      if( zTagList && zTagList[0] ){
        fossil_print("tags:     %s\n", zTagList);
      }
    }
    db_finalize(&q);
    db_prepare(&q,
       "SELECT type, datetime(mtime%s),"
       "       coalesce(euser,user), coalesce(ecomment,comment)"
       "  FROM event WHERE objid=%d", timeline_utc(), rid);
    if( db_step(&q)==SQLITE_ROW ){
      const char *zType;
      switch( db_column_text(&q,0)[0] ){
        case 'c':  zType = "Check-in";       break;
        case 'w':  zType = "Wiki-edit";      break;
        case 'e':  zType = "Event";          break;
        case 't':  zType = "Ticket-change";  break;
        case 'g':  zType = "Tag-change";     break;
        default:   zType = "Unknown";        break;
      }


      fossil_print("type:     %s by %s on %s\n", zType, db_column_text(&q,2),




                   db_column_text(&q, 1));
      fossil_print("comment:  ");
      comment_print(db_column_text(&q,3), 10, 78);
    }
    db_finalize(&q);
    db_prepare(&q,
      "SELECT filename.name, blob.uuid, datetime(event.mtime%s),"
      "       coalesce(euser,user), coalesce(ecomment,comment)"
      "  FROM mlink, filename, blob, event"
      " WHERE mlink.fid=%d"
      "   AND filename.fnid=mlink.fnid"
      "   AND event.objid=mlink.mid"
      "   AND blob.rid=mlink.mid"
      " ORDER BY event.mtime DESC /*sort*/",
      timeline_utc(), rid);
    while( db_step(&q)==SQLITE_ROW ){
      fossil_print("file:     %s\n", db_column_text(&q,0));
      fossil_print("          part of [%.10s] by %s on %s\n",
        db_column_text(&q, 1),
        db_column_text(&q, 3),
        db_column_text(&q, 2));
      fossil_print("          ");
      comment_print(db_column_text(&q,4), 10, 78);
    }
    db_finalize(&q);
  }
}

Changes to src/popen.c.

202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218

219
220
221
    return 0;
  }
#endif
}

/*
** Close the connection to a child process previously created using
** popen2().
*/
void pclose2(int fdIn, FILE *pOut, int childPid){
#ifdef _WIN32
  /* Not implemented, yet */
  close(fdIn);
  fclose(pOut);
#else
  close(fdIn);
  fclose(pOut);

  while( waitpid(0, 0, WNOHANG)>0 ) {}
#endif
}







|









>



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
    return 0;
  }
#endif
}

/*
** Close the connection to a child process previously created using
** popen2().  Kill off the child process, then close the pipes.
*/
void pclose2(int fdIn, FILE *pOut, int childPid){
#ifdef _WIN32
  /* Not implemented, yet */
  close(fdIn);
  fclose(pOut);
#else
  close(fdIn);
  fclose(pOut);
  kill(childPid, SIGINT);
  while( waitpid(0, 0, WNOHANG)>0 ) {}
#endif
}

Changes to src/printf.c.

857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
#endif
  assert( toStdErr==0 || toStdErr==1 );
  fwrite(z, 1, n, toStdErr ? stderr : stdout);
  fflush(toStdErr ? stderr : stdout);
}

/*
** Force the standard output cursor to move to the beginning
** of a line, if it is not there already.
*/
void fossil_force_newline(void){
  if( g.cgiOutput==0 && stdoutAtBOL==0 ) fossil_puts("\n", 0);
}

/*







|







857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
#endif
  assert( toStdErr==0 || toStdErr==1 );
  fwrite(z, 1, n, toStdErr ? stderr : stdout);
  fflush(toStdErr ? stderr : stdout);
}

/*
** Force the standard output cursor to move to the beginning 
** of a line, if it is not there already.
*/
void fossil_force_newline(void){
  if( g.cgiOutput==0 && stdoutAtBOL==0 ) fossil_puts("\n", 0);
}

/*
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
static void fossil_errorlog(const char *zFormat, ...){
  struct tm *pNow;
  time_t now;
  FILE *out;
  const char *z;
  int i;
  va_list ap;
  static const char *const azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT",
      "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
      "REQUEST_URI", "SCRIPT_NAME" };
  if( g.zErrlog==0 ) return;
  out = fossil_fopen(g.zErrlog, "a");
  if( out==0 ) return;
  now = time(0);
  pNow = gmtime(&now);







|







916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
static void fossil_errorlog(const char *zFormat, ...){
  struct tm *pNow;
  time_t now;
  FILE *out;
  const char *z;
  int i;
  va_list ap;
  static const char *azEnv[] = { "HTTP_HOST", "HTTP_USER_AGENT",
      "PATH_INFO", "QUERY_STRING", "REMOTE_ADDR", "REQUEST_METHOD",
      "REQUEST_URI", "SCRIPT_NAME" };
  if( g.zErrlog==0 ) return;
  out = fossil_fopen(g.zErrlog, "a");
  if( out==0 ) return;
  now = time(0);
  pNow = gmtime(&now);

Changes to src/rebuild.c.

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@ CREATE TABLE IF NOT EXISTS shun(
@   uuid UNIQUE,          -- UUID of artifact to be shunned. Canonical form
@   mtime INTEGER,        -- When added.  Seconds since 1970
@   scom TEXT             -- Optional text explaining why the shun occurred
@ );
@
@ -- Artifacts that should not be pushed are stored in the "private"
@ -- table.
@ --
@ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY);
@
@ -- Some ticket content (such as the originators email address or contact
@ -- information) needs to be obscured to protect privacy.  This is achieved
@ -- by storing an SHA1 hash of the content.  For display, the hash is
@ -- mapped back into the original text using this table.
@ --
@ -- This table contains sensitive information and should not be shared
@ -- with unauthorized users.
@ --
@ CREATE TABLE IF NOT EXISTS concealed(
@   hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
@   mtime INTEGER,            -- Time created.  Seconds since 1970







|






|







45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@ CREATE TABLE IF NOT EXISTS shun(
@   uuid UNIQUE,          -- UUID of artifact to be shunned. Canonical form
@   mtime INTEGER,        -- When added.  Seconds since 1970
@   scom TEXT             -- Optional text explaining why the shun occurred
@ );
@
@ -- Artifacts that should not be pushed are stored in the "private"
@ -- table.  
@ --
@ CREATE TABLE IF NOT EXISTS private(rid INTEGER PRIMARY KEY);
@
@ -- Some ticket content (such as the originators email address or contact
@ -- information) needs to be obscured to protect privacy.  This is achieved
@ -- by storing an SHA1 hash of the content.  For display, the hash is
@ -- mapped back into the original text using this table.  
@ --
@ -- This table contains sensitive information and should not be shared
@ -- with unauthorized users.
@ --
@ CREATE TABLE IF NOT EXISTS concealed(
@   hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
@   mtime INTEGER,            -- Time created.  Seconds since 1970
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
                 " WHERE name='concealed' AND sql GLOB '* mtime *'");
  if( rc==0 ){
    db_multi_exec(
      "ALTER TABLE concealed ADD COLUMN mtime INTEGER;"
      "UPDATE concealed SET mtime=now();"
    );
  }
}

/*
** Variables used to store state information about an on-going "rebuild"
** or "deconstruct".
*/
static int totalSize;       /* Total number of artifacts to process */
static int processCnt;      /* Number processed so far */







|







151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
                 " WHERE name='concealed' AND sql GLOB '* mtime *'");
  if( rc==0 ){
    db_multi_exec(
      "ALTER TABLE concealed ADD COLUMN mtime INTEGER;"
      "UPDATE concealed SET mtime=now();"
    );
  }
}  

/*
** Variables used to store state information about an on-going "rebuild"
** or "deconstruct".
*/
static int totalSize;       /* Total number of artifacts to process */
static int processCnt;      /* Number processed so far */
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

    /* Fix up the "blob.size" field if needed. */
    if( size!=blob_size(pBase) ){
      db_multi_exec(
         "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid
      );
    }

    /* Find all children of artifact rid */
    db_static_prepare(&q1, "SELECT rid FROM delta WHERE srcid=:rid");
    db_bind_int(&q1, ":rid", rid);
    bag_init(&children);
    while( db_step(&q1)==SQLITE_ROW ){
      int cid = db_column_int(&q1, 0);
      if( !bag_find(&bagDone, cid) ){
        bag_insert(&children, cid);
      }
    }
    nChild = bag_count(&children);
    db_reset(&q1);

    /* Crosslink the artifact */
    if( nChild==0 ){
      pUse = pBase;
    }else{
      blob_copy(&copy, pBase);
      pUse = &copy;
    }







|












|







225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

    /* Fix up the "blob.size" field if needed. */
    if( size!=blob_size(pBase) ){
      db_multi_exec(
         "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid
      );
    }
  
    /* Find all children of artifact rid */
    db_static_prepare(&q1, "SELECT rid FROM delta WHERE srcid=:rid");
    db_bind_int(&q1, ":rid", rid);
    bag_init(&children);
    while( db_step(&q1)==SQLITE_ROW ){
      int cid = db_column_int(&q1, 0);
      if( !bag_find(&bagDone, cid) ){
        bag_insert(&children, cid);
      }
    }
    nChild = bag_count(&children);
    db_reset(&q1);
  
    /* Crosslink the artifact */
    if( nChild==0 ){
      pUse = pBase;
    }else{
      blob_copy(&copy, pBase);
      pUse = &copy;
    }
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
      blob_write_to_file(pUse,zFile);
      free(zFile);
      free(zUuid);
      blob_reset(pUse);
    }
    assert( blob_is_reset(pUse) );
    rebuild_step_done(rid);

    /* Call all children recursively */
    rid = 0;
    for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){
      static Stmt q2;
      int sz;
      db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid");
      db_bind_int(&q2, ":rid", cid);







|







260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
      blob_write_to_file(pUse,zFile);
      free(zFile);
      free(zUuid);
      blob_reset(pUse);
    }
    assert( blob_is_reset(pUse) );
    rebuild_step_done(rid);
  
    /* Call all children recursively */
    rid = 0;
    for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){
      static Stmt q2;
      int sz;
      db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid");
      db_bind_int(&q2, ":rid", cid);
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
      fossil_print("done\n");
    }
    if( activateWal ){
      db_multi_exec("PRAGMA journal_mode=WAL;");
    }
  }
  if( showStats ){
    static const struct { int idx; const char *zLabel; } aStat[] = {
       { CFTYPE_ANY,       "Artifacts:" },
       { CFTYPE_MANIFEST,  "Manifests:" },
       { CFTYPE_CLUSTER,   "Clusters:" },
       { CFTYPE_CONTROL,   "Tags:" },
       { CFTYPE_WIKI,      "Wikis:" },
       { CFTYPE_TICKET,    "Tickets:" },
       { CFTYPE_ATTACHMENT,"Attachments:" },







|







624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
      fossil_print("done\n");
    }
    if( activateWal ){
      db_multi_exec("PRAGMA journal_mode=WAL;");
    }
  }
  if( showStats ){
    static struct { int idx; const char *zLabel; } aStat[] = {
       { CFTYPE_ANY,       "Artifacts:" },
       { CFTYPE_MANIFEST,  "Manifests:" },
       { CFTYPE_CLUSTER,   "Clusters:" },
       { CFTYPE_CONTROL,   "Tags:" },
       { CFTYPE_WIKI,      "Wikis:" },
       { CFTYPE_TICKET,    "Tickets:" },
       { CFTYPE_ATTACHMENT,"Attachments:" },
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
      usage("?REPOSITORY-FILENAME?");
    }
    db_close(1);
    db_open_repository(g.zRepositoryName);
  }
  db_begin_transaction();
  create_cluster();
  db_end_transaction(0);
}

/*
** COMMAND: test-clusters
**
** Verify that all non-private and non-shunned artifacts are accessible
** through the cluster chain.
*/
void test_clusters_cmd(void){
  Bag pending;
  Stmt q;
  int n;

  db_find_and_open_repository(0, 2);
  bag_init(&pending);
  db_multi_exec(
    "CREATE TEMP TABLE xdone(x INTEGER PRIMARY KEY);"
    "INSERT INTO xdone SELECT rid FROM unclustered;"
    "INSERT OR IGNORE INTO xdone SELECT rid FROM private;"
    "INSERT OR IGNORE INTO xdone"







|












|







685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
      usage("?REPOSITORY-FILENAME?");
    }
    db_close(1);
    db_open_repository(g.zRepositoryName);
  }
  db_begin_transaction();
  create_cluster();
  db_end_transaction(0);  
}

/*
** COMMAND: test-clusters
**
** Verify that all non-private and non-shunned artifacts are accessible
** through the cluster chain.
*/
void test_clusters_cmd(void){
  Bag pending;
  Stmt q;
  int n;
  
  db_find_and_open_repository(0, 2);
  bag_init(&pending);
  db_multi_exec(
    "CREATE TEMP TABLE xdone(x INTEGER PRIMARY KEY);"
    "INSERT INTO xdone SELECT rid FROM unclustered;"
    "INSERT OR IGNORE INTO xdone SELECT rid FROM private;"
    "INSERT OR IGNORE INTO xdone"
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
    bag_insert(&pending, db_column_int(&q, 0));
  }
  db_finalize(&q);
  while( bag_count(&pending)>0 ){
    Manifest *p;
    int rid = bag_first(&pending);
    int i;

    bag_remove(&pending, rid);
    p = manifest_get(rid, CFTYPE_CLUSTER, 0);
    if( p==0 ){
      fossil_fatal("bad cluster: rid=%d", rid);
    }
    for(i=0; i<p->nCChild; i++){
      const char *zUuid = p->azCChild[i];







|







720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
    bag_insert(&pending, db_column_int(&q, 0));
  }
  db_finalize(&q);
  while( bag_count(&pending)>0 ){
    Manifest *p;
    int rid = bag_first(&pending);
    int i;
    
    bag_remove(&pending, rid);
    p = manifest_get(rid, CFTYPE_CLUSTER, 0);
    if( p==0 ){
      fossil_fatal("bad cluster: rid=%d", rid);
    }
    for(i=0; i<p->nCChild; i++){
      const char *zUuid = p->azCChild[i];
791
792
793
794
795
796
797

798
799
800
801
802
803
804
  int bNeedRebuild = 0;
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
  db_close(1);
  db_open_repository(g.zRepositoryName);
  if( !bForce ){
    Blob ans;
    char cReply;

    prompt_user(
         "Scrubbing the repository will permanently delete information.\n"
         "Changes cannot be undone.  Continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }







>







791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
  int bNeedRebuild = 0;
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 2);
  db_close(1);
  db_open_repository(g.zRepositoryName);
  if( !bForce ){
    Blob ans;
    char cReply;
    blob_zero(&ans);
    prompt_user(
         "Scrubbing the repository will permanently delete information.\n"
         "Changes cannot be undone.  Continue (y/N)? ", &ans);
    cReply = blob_str(&ans)[0];
    if( cReply!='y' && cReply!='Y' ){
      fossil_exit(1);
    }
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
  reconstruct_private_table();

  /* Skip the verify_before_commit() step on a reconstruct.  Most artifacts
  ** will have been changed and verification therefore takes a really, really
  ** long time.
  */
  verify_cancel();

  db_end_transaction(0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  fossil_print("server-id: %s\n", db_get("server-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}

/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
**
**
** This command exports all artifacts of a given repository and
** writes all artifacts to the file system. The DESTINATION directory
** will be populated with subdirectories AA and files AA/BBBBBBBBB.., where
** AABBBBBBBBB.. is the 40 character artifact ID, AA the first 2 characters.
** If -L|--prefixlength is given, the length (default 2) of the directory
** prefix can be set to 0,1,..,9 characters.
**
** Options:
**   -R|--repository REPOSITORY  deconstruct given REPOSITORY
**   -L|--prefixlength N         set the length of the names of the DESTINATION
**                               subdirectories to N
**   --private                   Include private artifacts.
**
** See also: rebuild, reconstruct







|



















|







927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
  reconstruct_private_table();

  /* Skip the verify_before_commit() step on a reconstruct.  Most artifacts
  ** will have been changed and verification therefore takes a really, really
  ** long time.
  */
  verify_cancel();
  
  db_end_transaction(0);
  fossil_print("project-id: %s\n", db_get("project-code", 0));
  fossil_print("server-id: %s\n", db_get("server-code", 0));
  zPassword = db_text(0, "SELECT pw FROM user WHERE login=%Q", g.zLogin);
  fossil_print("admin-user: %s (initial password is \"%s\")\n", g.zLogin, zPassword);
}

/*
** COMMAND: deconstruct*
**
** Usage %fossil deconstruct ?OPTIONS? DESTINATION
**
**
** This command exports all artifacts of a given repository and
** writes all artifacts to the file system. The DESTINATION directory
** will be populated with subdirectories AA and files AA/BBBBBBBBB.., where
** AABBBBBBBBB.. is the 40 character artifact ID, AA the first 2 characters.
** If -L|--prefixlength is given, the length (default 2) of the directory
** prefix can be set to 0,1,..,9 characters.
** 
** Options:
**   -R|--repository REPOSITORY  deconstruct given REPOSITORY
**   -L|--prefixlength N         set the length of the names of the DESTINATION
**                               subdirectories to N
**   --private                   Include private artifacts.
**
** See also: rebuild, reconstruct

Changes to src/report.c.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** Code to generate the ticket listings
*/
#include "config.h"
#include <time.h>
#include "report.h"
#include <assert.h>

/* Forward references to static routines */
static void report_format_hints(void);

#ifndef SQLITE_RECURSIVE
#  define SQLITE_RECURSIVE            33
#endif

/*
** WEBPAGE: /reportlist
*/
void view_list(void){
  const char *zScript;
  Blob ril;   /* Report Item List */
  Stmt q;
  int rn = 0;
  int cnt = 0;

  login_check_credentials();
  if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }
  style_header("Ticket Main Menu");
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
  zScript = ticket_reportlist_code();
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);

  blob_zero(&ril);
  ticket_init();

  db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTitle = db_column_text(&q, 1);
    const char *zOwner = db_column_text(&q, 2);







|










<
<
<
<
















|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27




28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
** merchantability or fitness for a particular purpose.
**
** Author contact information:
**   drh@hwaci.com
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**  
** Code to generate the ticket listings
*/
#include "config.h"
#include <time.h>
#include "report.h"
#include <assert.h>

/* Forward references to static routines */
static void report_format_hints(void);





/*
** WEBPAGE: /reportlist
*/
void view_list(void){
  const char *zScript;
  Blob ril;   /* Report Item List */
  Stmt q;
  int rn = 0;
  int cnt = 0;

  login_check_credentials();
  if( !g.perm.RdTkt && !g.perm.NewTkt ){ login_needed(); return; }
  style_header("Ticket Main Menu");
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST<br />\n", -1);
  zScript = ticket_reportlist_code();
  if( g.thTrace ) Th_Trace("BEGIN_REPORTLIST_SCRIPT<br />\n", -1);
  
  blob_zero(&ril);
  ticket_init();

  db_prepare(&q, "SELECT rn, title, owner FROM reportfmt ORDER BY title");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zTitle = db_column_text(&q, 1);
    const char *zOwner = db_column_text(&q, 2);
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
    if( g.perm.Write && zOwner && zOwner[0] ){
      blob_appendf(&ril, "(by <i>%h</i>) ", zOwner);
    }
    if( g.perm.TktFmt ){
      blob_appendf(&ril, "[%zcopy</a>] ",
                   href("%R/rptedit?rn=%d&copy=1", rn));
    }
    if( g.perm.Admin
     || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0)
    ){
      blob_appendf(&ril, "[%zedit</a>]",
                         href("%R/rptedit?rn=%d", rn));
    }
    if( g.perm.TktFmt ){
      blob_appendf(&ril, "[%zsql</a>]",
                         href("%R/rptsql?rn=%d", rn));
    }
    blob_appendf(&ril, "</li>\n");
  }
  db_finalize(&q);

  Th_Store("report_items", blob_str(&ril));

  Th_Render(zScript);

  blob_reset(&ril);
  if( g.thTrace ) Th_Trace("END_REPORTLIST<br />\n", -1);

  style_footer();
}

/*







|


|











|

|







64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    if( g.perm.Write && zOwner && zOwner[0] ){
      blob_appendf(&ril, "(by <i>%h</i>) ", zOwner);
    }
    if( g.perm.TktFmt ){
      blob_appendf(&ril, "[%zcopy</a>] ",
                   href("%R/rptedit?rn=%d&copy=1", rn));
    }
    if( g.perm.Admin 
     || (g.perm.WrTkt && zOwner && fossil_strcmp(g.zLogin,zOwner)==0)
    ){
      blob_appendf(&ril, "[%zedit</a>]", 
                         href("%R/rptedit?rn=%d", rn));
    }
    if( g.perm.TktFmt ){
      blob_appendf(&ril, "[%zsql</a>]",
                         href("%R/rptsql?rn=%d", rn));
    }
    blob_appendf(&ril, "</li>\n");
  }
  db_finalize(&q);

  Th_Store("report_items", blob_str(&ril));
  
  Th_Render(zScript);
  
  blob_reset(&ril);
  if( g.thTrace ) Th_Trace("END_REPORTLIST<br />\n", -1);

  style_footer();
}

/*
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
        *(char**)pError = mprintf("access to table \"%s\" is restricted",zArg1);
        rc = SQLITE_DENY;
      }else if( !g.perm.RdAddr && strncmp(zArg2, "private_", 8)==0 ){
        rc = SQLITE_IGNORE;
      }
      break;
    }
    case SQLITE_RECURSIVE: {
      *(char**)pError = mprintf("recursive queries are not allowed");
      rc = SQLITE_DENY;
      break;
    }
    default: {
      *(char**)pError = mprintf("only SELECT statements are allowed");
      rc = SQLITE_DENY;
      break;
    }
  }
  return rc;







<
<
<
<
<







195
196
197
198
199
200
201





202
203
204
205
206
207
208
        *(char**)pError = mprintf("access to table \"%s\" is restricted",zArg1);
        rc = SQLITE_DENY;
      }else if( !g.perm.RdAddr && strncmp(zArg2, "private_", 8)==0 ){
        rc = SQLITE_IGNORE;
      }
      break;
    }





    default: {
      *(char**)pError = mprintf("only SELECT statements are allowed");
      rc = SQLITE_DENY;
      break;
    }
  }
  return rc;
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
        ** was found. We don't actually check what's after that.
        */
        return mprintf("Semi-colon detected! "
                       "Only a single SQL statement is allowed");
      }
    }
  }

  /* Compile the statement and check for illegal accesses or syntax errors. */
  report_restrict_sql(&zErr);
  rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, &zTail);
  if( rc!=SQLITE_OK ){
    zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
  }
  if( !sqlite3_stmt_readonly(pStmt) ){
    zErr = mprintf("SQL must not modify the database");
  }
  if( pStmt ){







|


|







251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
        ** was found. We don't actually check what's after that.
        */
        return mprintf("Semi-colon detected! "
                       "Only a single SQL statement is allowed");
      }
    }
  }
  
  /* Compile the statement and check for illegal accesses or syntax errors. */
  report_restrict_sql(&zErr);
  rc = sqlite3_prepare(g.db, zSql, -1, &pStmt, &zTail);
  if( rc!=SQLITE_OK ){
    zErr = mprintf("Syntax error: %s", sqlite3_errmsg(g.db));
  }
  if( !sqlite3_stmt_readonly(pStmt) ){
    zErr = mprintf("SQL must not modify the database");
  }
  if( pStmt ){
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
  rn = atoi(PD("rn","0"));
  db_prepare(&q, "SELECT title, sqlcode, owner, cols "
                   "FROM reportfmt WHERE rn=%d",rn);
  style_header("SQL For Report Format Number %d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    @ <p>Unknown report number: %d(rn)</p>
    style_footer();
    db_finalize(&q);
    return;
  }
  zTitle = db_column_text(&q, 0);
  zSQL = db_column_text(&q, 1);
  zOwner = db_column_text(&q, 2);
  zClrKey = db_column_text(&q, 3);
  @ <table cellpadding=0 cellspacing=0 border=0>







<







291
292
293
294
295
296
297

298
299
300
301
302
303
304
  rn = atoi(PD("rn","0"));
  db_prepare(&q, "SELECT title, sqlcode, owner, cols "
                   "FROM reportfmt WHERE rn=%d",rn);
  style_header("SQL For Report Format Number %d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    @ <p>Unknown report number: %d(rn)</p>
    style_footer();

    return;
  }
  zTitle = db_column_text(&q, 0);
  zSQL = db_column_text(&q, 1);
  zOwner = db_column_text(&q, 2);
  zClrKey = db_column_text(&q, 3);
  @ <table cellpadding=0 cellspacing=0 border=0>
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
  @ </pre></td>
  @ <td width=15></td><td valign="top">
  output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3");
  @ </td>
  @ </tr></table>
  report_format_hints();
  style_footer();
  db_finalize(&q);
}

/*
** WEBPAGE: /rptnew
** WEBPAGE: /rptedit
*/
void view_edit(void){







<







312
313
314
315
316
317
318

319
320
321
322
323
324
325
  @ </pre></td>
  @ <td width=15></td><td valign="top">
  output_color_key(zClrKey, 0, "border=0 cellspacing=0 cellpadding=3");
  @ </td>
  @ </tr></table>
  report_format_hints();
  style_footer();

}

/*
** WEBPAGE: /rptnew
** WEBPAGE: /rptedit
*/
void view_edit(void){
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
    cgi_redirect("reportlist");
    return;
  }
  if( zTitle && zSQL ){
    if( zSQL[0]==0 ){
      zErr = "Please supply an SQL query statement";
    }else if( (zTitle = trim_string(zTitle))[0]==0 ){
      zErr = "Please supply a title";
    }else{
      zErr = verify_sql_statement(zSQL);
    }
    if( zErr==0
     && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d",
                  zTitle, rn)
    ){







|







372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
    cgi_redirect("reportlist");
    return;
  }
  if( zTitle && zSQL ){
    if( zSQL[0]==0 ){
      zErr = "Please supply an SQL query statement";
    }else if( (zTitle = trim_string(zTitle))[0]==0 ){
      zErr = "Please supply a title"; 
    }else{
      zErr = verify_sql_statement(zSQL);
    }
    if( zErr==0
     && db_exists("SELECT 1 FROM reportfmt WHERE title=%Q and rn<>%d",
                  zTitle, rn)
    ){
614
615
616
617
618
619
620













621
622
623
624
625
626
627
  @    priority AS 'Pri',
  @    title AS 'Title',
  @    description AS '_Description',  -- When the column name begins with '_'
  @    remarks AS '_Remarks'           -- content is rendered as wiki
  @  FROM ticket
  @ </pre></blockquote>
  @













}

/*
** The state of the report generation.
*/
struct GenerateHTML {
  int rn;          /* Report number */







>
>
>
>
>
>
>
>
>
>
>
>
>







603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
  @    priority AS 'Pri',
  @    title AS 'Title',
  @    description AS '_Description',  -- When the column name begins with '_'
  @    remarks AS '_Remarks'           -- content is rendered as wiki
  @  FROM ticket
  @ </pre></blockquote>
  @
  @ <p>Or, to see part of the description on the same row, use the
  @ <b>wiki()</b> function with some string manipulation. Using the
  @ <b>tkt()</b> function on the ticket number will also generate a linked
  @ field, but without the extra <i>edit</i> column:
  @ </p>
  @ <blockquote><pre>
  @  SELECT
  @    tkt(tn) AS '',
  @    title AS 'Title',
  @    wiki(substr(description,0,80)) AS 'Description'
  @  FROM ticket
  @ </pre></blockquote>
  @
}

/*
** The state of the report generation.
*/
struct GenerateHTML {
  int rn;          /* Report number */
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657

/*
** The callback function for db_query
*/
static int generate_html(
  void *pUser,     /* Pointer to output state */
  int nArg,        /* Number of columns in this result row */
  const char **azArg, /* Text of data in all columns */
  const char **azName /* Names of the columns */
){
  struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
  int i;
  const char *zTid;  /* Ticket UUID.  (value of column named '#') */
  const char *zBg = 0; /* Use this background color */

  /* Do initialization
  */
  if( pState->nCount==0 ){
    /* Turn off the authorizer.  It is no longer doing anything since the
    ** query has already been prepared.
    */







|
|




|







639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659

/*
** The callback function for db_query
*/
static int generate_html(
  void *pUser,     /* Pointer to output state */
  int nArg,        /* Number of columns in this result row */
  char **azArg,    /* Text of data in all columns */
  char **azName    /* Names of the columns */
){
  struct GenerateHTML *pState = (struct GenerateHTML*)pUser;
  int i;
  const char *zTid;  /* Ticket UUID.  (value of column named '#') */
  char *zBg = 0;     /* Use this background color */

  /* Do initialization
  */
  if( pState->nCount==0 ){
    /* Turn off the authorizer.  It is no longer doing anything since the
    ** query has already been prepared.
    */
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
    }

    /* The first time this routine is called, output a table header
    */
    @ <thead><tr>
    zTid = 0;
    for(i=0; i<nArg; i++){
      const char *zName = azName[i];
      if( i==pState->iBg ) continue;
      if( pState->iNewRow>=0 && i>=pState->iNewRow ){
        if( g.perm.Write && zTid ){
          @ <th>&nbsp;</th>
          zTid = 0;
        }
        if( zName[0]=='_' ) zName++;







|







699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
    }

    /* The first time this routine is called, output a table header
    */
    @ <thead><tr>
    zTid = 0;
    for(i=0; i<nArg; i++){
      char *zName = azName[i];
      if( i==pState->iBg ) continue;
      if( pState->iNewRow>=0 && i>=pState->iNewRow ){
        if( g.perm.Write && zTid ){
          @ <th>&nbsp;</th>
          zTid = 0;
        }
        if( zName[0]=='_' ) zName++;
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
  /* Output the data for this entry from the database
  */
  zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
  if( zBg==0 ) zBg = "white";
  @ <tr style="background-color:%h(zBg)">
  zTid = 0;
  for(i=0; i<nArg; i++){
    const char *zData;
    if( i==pState->iBg ) continue;
    zData = azArg[i];
    if( zData==0 ) zData = "";
    if( pState->iNewRow>=0 && i>=pState->iNewRow ){
      if( zTid && g.perm.Write ){
        @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
        zTid = 0;







|







742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
  /* Output the data for this entry from the database
  */
  zBg = pState->iBg>=0 ? azArg[pState->iBg] : 0;
  if( zBg==0 ) zBg = "white";
  @ <tr style="background-color:%h(zBg)">
  zTid = 0;
  for(i=0; i<nArg; i++){
    char *zData;
    if( i==pState->iBg ) continue;
    zData = azArg[i];
    if( zData==0 ) zData = "";
    if( pState->iNewRow>=0 && i>=pState->iNewRow ){
      if( zTid && g.perm.Write ){
        @ <td valign="top">%z(href("%R/tktedit/%h",zTid))edit</a></td>
        zTid = 0;
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817

/*
** Output a row as a tab-separated line of text.
*/
static int output_tab_separated(
  void *pUser,     /* Pointer to row-count integer */
  int nArg,        /* Number of columns in this result row */
  const char **azArg, /* Text of data in all columns */
  const char **azName /* Names of the columns */
){
  int *pCount = (int*)pUser;
  int i;

  if( *pCount==0 ){
    for(i=0; i<nArg; i++){
      output_no_tabs(azName[i]);







|
|







804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

/*
** Output a row as a tab-separated line of text.
*/
static int output_tab_separated(
  void *pUser,     /* Pointer to row-count integer */
  int nArg,        /* Number of columns in this result row */
  char **azArg,    /* Text of data in all columns */
  char **azName    /* Names of the columns */
){
  int *pCount = (int*)pUser;
  int i;

  if( *pCount==0 ){
    for(i=0; i<nArg; i++){
      output_no_tabs(azName[i]);
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
}

/*
** Generate HTML that describes a color key.
*/
void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){
  int i, j, k;
  const char *zSafeKey;
  char *zToFree;
  while( fossil_isspace(*zClrKey) ) zClrKey++;
  if( zClrKey[0]==0 ) return;
  @ <table %s(zTabArgs)>
  if( horiz ){
    @ <tr>
  }
  zSafeKey = zToFree = mprintf("%h", zClrKey);
  while( zSafeKey[0] ){
    while( fossil_isspace(*zSafeKey) ) zSafeKey++;
    for(i=0; zSafeKey[i] && !fossil_isspace(zSafeKey[i]); i++){}
    for(j=i; fossil_isspace(zSafeKey[j]); j++){}
    for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){}
    if( !horiz ){
      cgi_printf("<tr style=\"background-color: %.*s;\"><td>%.*s</td></tr>\n",







|
<






|







829
830
831
832
833
834
835
836

837
838
839
840
841
842
843
844
845
846
847
848
849
850
}

/*
** Generate HTML that describes a color key.
*/
void output_color_key(const char *zClrKey, int horiz, char *zTabArgs){
  int i, j, k;
  char *zSafeKey, *zToFree;

  while( fossil_isspace(*zClrKey) ) zClrKey++;
  if( zClrKey[0]==0 ) return;
  @ <table %s(zTabArgs)>
  if( horiz ){
    @ <tr>
  }
  zToFree = zSafeKey = mprintf("%h", zClrKey);
  while( zSafeKey[0] ){
    while( fossil_isspace(*zSafeKey) ) zSafeKey++;
    for(i=0; zSafeKey[i] && !fossil_isspace(zSafeKey[i]); i++){}
    for(j=i; fossil_isspace(zSafeKey[j]); j++){}
    for(k=j; zSafeKey[k] && zSafeKey[k]!='\n' && zSafeKey[k]!='\r'; k++){}
    if( !horiz ){
      cgi_printf("<tr style=\"background-color: %.*s;\"><td>%.*s</td></tr>\n",
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
  @ </table>
}

/*
** Execute a single read-only SQL statement.  Invoke xCallback() on each
** row.
*/
static int db_exec_readonly(
  sqlite3 *db,                /* The database on which the SQL executes */
  const char *zSql,           /* The SQL to be executed */
  int (*xCallback)(void*,int,const char**, const char**),
                              /* Invoke this callback routine */
  void *pArg,                 /* First argument to xCallback() */
  char **pzErrMsg             /* Write error messages here */
){
  int rc = SQLITE_OK;         /* Return code */
  const char *zLeftover;      /* Tail of unprocessed SQL */
  sqlite3_stmt *pStmt = 0;    /* The current SQL statement */
  const char **azCols = 0;    /* Names of result columns */
  int nCol;                   /* Number of columns of output */
  const char **azVals = 0;    /* Text of all output columns */
  int i;                      /* Loop counter */

  pStmt = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
  assert( rc==SQLITE_OK || pStmt==0 );
  if( rc!=SQLITE_OK ){
    return rc;







|


<
|






|

|







862
863
864
865
866
867
868
869
870
871

872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
  @ </table>
}

/*
** Execute a single read-only SQL statement.  Invoke xCallback() on each
** row.
*/
int sqlite3_exec_readonly(
  sqlite3 *db,                /* The database on which the SQL executes */
  const char *zSql,           /* The SQL to be executed */

  sqlite3_callback xCallback, /* Invoke this callback routine */
  void *pArg,                 /* First argument to xCallback() */
  char **pzErrMsg             /* Write error messages here */
){
  int rc = SQLITE_OK;         /* Return code */
  const char *zLeftover;      /* Tail of unprocessed SQL */
  sqlite3_stmt *pStmt = 0;    /* The current SQL statement */
  char **azCols = 0;          /* Names of result columns */
  int nCol;                   /* Number of columns of output */
  char **azVals = 0;          /* Text of all output columns */
  int i;                      /* Loop counter */

  pStmt = 0;
  rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zLeftover);
  assert( rc==SQLITE_OK || pStmt==0 );
  if( rc!=SQLITE_OK ){
    return rc;
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926

  nCol = sqlite3_column_count(pStmt);
  azVals = fossil_malloc(2*nCol*sizeof(const char*) + 1);
  while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
    if( azCols==0 ){
      azCols = &azVals[nCol];
      for(i=0; i<nCol; i++){
        azCols[i] = sqlite3_column_name(pStmt, i);
      }
    }
    for(i=0; i<nCol; i++){
      azVals[i] = (const char *)sqlite3_column_text(pStmt, i);
    }
    if( xCallback(pArg, nCol, azVals, azCols) ){
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  fossil_free((void *)azVals);
  return rc;
}

/*
** Output Javascript code that will enables sorting of the table with
** the id zTableId by clicking.
**







|



|






|







901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926

  nCol = sqlite3_column_count(pStmt);
  azVals = fossil_malloc(2*nCol*sizeof(const char*) + 1);
  while( (rc = sqlite3_step(pStmt))==SQLITE_ROW ){
    if( azCols==0 ){
      azCols = &azVals[nCol];
      for(i=0; i<nCol; i++){
        azCols[i] = (char *)sqlite3_column_name(pStmt, i);
      }
    }
    for(i=0; i<nCol; i++){
      azVals[i] = (char *)sqlite3_column_text(pStmt, i);
    }
    if( xCallback(pArg, nCol, azVals, azCols) ){
      break;
    }
  }
  rc = sqlite3_finalize(pStmt);
  fossil_free(azVals);
  return rc;
}

/*
** Output Javascript code that will enables sorting of the table with
** the id zTableId by clicking.
**
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
  }
  tabs = P("tablist")!=0;
  /* view_add_functions(tabs); */
  db_prepare(&q,
    "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    cgi_redirect("reportlist");
    db_finalize(&q);
    return;
  }
  zTitle = db_column_malloc(&q, 0);
  zSql = db_column_malloc(&q, 1);
  zOwner = db_column_malloc(&q, 2);
  zClrKey = db_column_malloc(&q, 3);
  db_finalize(&q);







<







1026
1027
1028
1029
1030
1031
1032

1033
1034
1035
1036
1037
1038
1039
  }
  tabs = P("tablist")!=0;
  /* view_add_functions(tabs); */
  db_prepare(&q,
    "SELECT title, sqlcode, owner, cols FROM reportfmt WHERE rn=%d", rn);
  if( db_step(&q)!=SQLITE_ROW ){
    cgi_redirect("reportlist");

    return;
  }
  zTitle = db_column_malloc(&q, 0);
  zSql = db_column_malloc(&q, 1);
  zOwner = db_column_malloc(&q, 2);
  zClrKey = db_column_malloc(&q, 3);
  db_finalize(&q);
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
  }

  count = 0;
  if( !tabs ){
    struct GenerateHTML sState;

    db_multi_exec("PRAGMA empty_result_callbacks=ON");
    style_submenu_element("Raw", "Raw",
      "rptview?tablist=1&%h", PD("QUERY_STRING",""));
    if( g.perm.Admin
       || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
      style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn);
    }
    if( g.perm.TktFmt ){
      style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
    }
    if( g.perm.NewTkt ){
      style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
    }
    style_header(zTitle);
    output_color_key(zClrKey, 1,
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report"
    @  id="reportTable">
    sState.rn = rn;
    sState.nCount = 0;
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2);
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    output_table_sorting_javascript("reportTable","");
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    db_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }
}

/*
** report number for full table ticket export







|

|











|






|











|







1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
  }

  count = 0;
  if( !tabs ){
    struct GenerateHTML sState;

    db_multi_exec("PRAGMA empty_result_callbacks=ON");
    style_submenu_element("Raw", "Raw", 
      "rptview?tablist=1&%h", PD("QUERY_STRING",""));
    if( g.perm.Admin 
       || (g.perm.TktFmt && g.zLogin && fossil_strcmp(g.zLogin,zOwner)==0) ){
      style_submenu_element("Edit", "Edit", "rptedit?rn=%d", rn);
    }
    if( g.perm.TktFmt ){
      style_submenu_element("SQL", "SQL", "rptsql?rn=%d",rn);
    }
    if( g.perm.NewTkt ){
      style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
    }
    style_header(zTitle);
    output_color_key(zClrKey, 1, 
        "border=\"0\" cellpadding=\"3\" cellspacing=\"0\" class=\"report\"");
    @ <table border="1" cellpadding="2" cellspacing="0" class="report"
    @  id="reportTable">
    sState.rn = rn;
    sState.nCount = 0;
    report_restrict_sql(&zErr1);
    sqlite3_exec_readonly(g.db, zSql, generate_html, &sState, &zErr2);
    report_unrestrict_sql();
    @ </tbody></table>
    if( zErr1 ){
      @ <p class="reportError">Error: %h(zErr1)</p>
    }else if( zErr2 ){
      @ <p class="reportError">Error: %h(zErr2)</p>
    }
    output_table_sorting_javascript("reportTable","");
    style_footer();
  }else{
    report_restrict_sql(&zErr1);
    sqlite3_exec_readonly(g.db, zSql, output_tab_separated, &count, &zErr2);
    report_unrestrict_sql();
    cgi_set_content_type("text/plain");
  }
}

/*
** report number for full table ticket export
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
        }
        for(j=i; fossil_isspace(z[j]); j++){}
        if( j>i ){
          fossil_print("%*s", j-i, "");
        }
        z += j;
      }
      break;
  }
}

/*
** Output a row as a tab-separated line of text.
*/
int output_separated_file(
  void *pUser,     /* Pointer to row-count integer */
  int nArg,        /* Number of columns in this result row */
  const char **azArg, /* Text of data in all columns */
  const char **azName /* Names of the columns */
){
  int *pCount = (int*)pUser;
  int i;

  if( *pCount==0 ){
    for(i=0; i<nArg; i++){
      output_no_tabs_file(azName[i]);







|









|
|







1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
        }
        for(j=i; fossil_isspace(z[j]); j++){}
        if( j>i ){
          fossil_print("%*s", j-i, "");
        }
        z += j;
      }
      break; 
  }
}

/*
** Output a row as a tab-separated line of text.
*/
int output_separated_file(
  void *pUser,     /* Pointer to row-count integer */
  int nArg,        /* Number of columns in this result row */
  char **azArg,    /* Text of data in all columns */
  char **azName    /* Names of the columns */
){
  int *pCount = (int*)pUser;
  int i;

  if( *pCount==0 ){
    for(i=0; i<nArg; i++){
      output_no_tabs_file(azName[i]);
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
}

/*
** Generate a report.  The rn query parameter is the report number.
** The output is written to stdout as flat file. The zFilter parameter
** is a full WHERE-condition.
*/
void rptshow(
    const char *zRep,
    const char *zSepIn,
    const char *zFilter,
    tTktShowEncoding enc
){
  Stmt q;
  char *zSql;







|







1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
}

/*
** Generate a report.  The rn query parameter is the report number.
** The output is written to stdout as flat file. The zFilter parameter
** is a full WHERE-condition.
*/
void rptshow( 
    const char *zRep,
    const char *zSepIn,
    const char *zFilter,
    tTktShowEncoding enc
){
  Stmt q;
  char *zSql;
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
  if( zFilter ){
    zSql = mprintf("SELECT * FROM (%s) WHERE %s",zSql,zFilter);
  }
  count = 0;
  tktEncode = enc;
  zSep = zSepIn;
  report_restrict_sql(&zErr1);
  db_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2);
  report_unrestrict_sql();
  if( zFilter ){
    free(zSql);
  }
}







|





1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
  if( zFilter ){
    zSql = mprintf("SELECT * FROM (%s) WHERE %s",zSql,zFilter);
  }
  count = 0;
  tktEncode = enc;
  zSep = zSepIn;
  report_restrict_sql(&zErr1);
  sqlite3_exec_readonly(g.db, zSql, output_separated_file, &count, &zErr2);
  report_unrestrict_sql();
  if( zFilter ){
    free(zSql);
  }
}

Changes to src/rss.c.

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  @     <link>%s(g.zBaseURL)</link>
  @     <description>%h(zProjectDescr)</description>
  @     <pubDate>%s(zPubDate)</pubDate>
  @     <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
  free(zPubDate);
  db_prepare(&q, blob_str(&bSQL));
  blob_reset( &bSQL );
  while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
    const char *zId = db_column_text(&q, 1);
    const char *zCom = db_column_text(&q, 3);
    const char *zAuthor = db_column_text(&q, 4);
    char *zPrefix = "";
    char *zDate;
    int nChild = db_column_int(&q, 5);
    int nParent = db_column_int(&q, 6);







|







158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
  @     <link>%s(g.zBaseURL)</link>
  @     <description>%h(zProjectDescr)</description>
  @     <pubDate>%s(zPubDate)</pubDate>
  @     <generator>Fossil version %s(MANIFEST_VERSION) %s(MANIFEST_DATE)</generator>
  free(zPubDate);
  db_prepare(&q, blob_str(&bSQL));
  blob_reset( &bSQL );
  while( db_step(&q)==SQLITE_ROW && nLine<=nLimit ){
    const char *zId = db_column_text(&q, 1);
    const char *zCom = db_column_text(&q, 3);
    const char *zAuthor = db_column_text(&q, 4);
    char *zPrefix = "";
    char *zDate;
    int nChild = db_column_int(&q, 5);
    int nParent = db_column_int(&q, 6);
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
    nLine++;
  }

  db_finalize(&q);
  @   </channel>
  @ </rss>

  if( zFreeProjectName != 0 ){
    free( zFreeProjectName );
  }
}

/*
** COMMAND: rss
**
** The CLI variant of the /timeline.rss page, this produces an RSS
** feed of the timeline to stdout. Options:
**
** -type|y FLAG
**    may be: all (default), ci (show checkins only), t (show tickets only),
**    w (show wiki only). LIMIT is the number of items to show.
**
** -tkt UUID
**    Filters for only those events for the specified ticket.
**
** -tag TAG
**    filters for a tag
**
** -wiki NAME
**   Filters on a specific wiki page.
**
** Only one of -tkt, -tag, or -wiki may be used.
**
** -name FILENAME
**   filters for a specific file. This may be combined with one of the other
**   filters (useful for looking at a specific branch).
**
** -url STRING
**   Sets the RSS feed's root URL to the given string. The default is
** "URL-PLACEHOLDER" (without quotes).
*/
void cmd_timeline_rss(void){
  Stmt q;
  int nLine=0;
  char *zPubDate, *zProjectName, *zProjectDescr, *zFreeProjectName=0;
  Blob bSQL;
  const char *zType = find_option("type","y",1); /* Type of events.  All if NULL */
  const char *zTicketUuid = find_option("tkt",NULL,1);
  const char *zTag = find_option("tag",NULL,1);
  const char *zFilename = find_option("name",NULL,1);
  const char *zWiki = find_option("wiki",NULL,1);
  const char *zLimit = find_option("limit", "n",1);
  const char *zBaseURL = find_option("url", NULL, 1);
  int nLimit = atoi( (zLimit && *zLimit) ? zLimit : "20" );
  int nTagId;
  const char zSQL1[] =
    @ SELECT
    @   blob.rid,
    @   uuid,
    @   event.mtime,
    @   coalesce(ecomment,comment),
    @   coalesce(euser,user),
    @   (SELECT count(*) FROM plink WHERE pid=blob.rid AND isprim),
    @   (SELECT count(*) FROM plink WHERE cid=blob.rid)
    @ FROM event, blob
    @ WHERE blob.rid=event.objid
  ;
  if(!zType || !*zType){
    zType = "all";
  }
  if(!zBaseURL || !*zBaseURL){
    zBaseURL = "URL-PLACEHOLDER";
  }

  db_find_and_open_repository(0, 0);

  blob_zero(&bSQL);
  blob_append( &bSQL, zSQL1, -1 );

  if( zType[0]!='a' ){
    blob_appendf(&bSQL, " AND event.type=%Q", zType);
  }

  if( zTicketUuid ){
    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
      zTicketUuid);
    if ( nTagId==0 ){
      nTagId = -1;
    }
  }else if( zTag ){
    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'sym-%q*'",
      zTag);
    if ( nTagId==0 ){
      nTagId = -1;
    }
  }else if( zWiki ){
    nTagId = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'wiki-%q*'",
      zWiki);
    if ( nTagId==0 ){
      nTagId = -1;
    }
  }else{
    nTagId = 0;
  }

  if( nTagId==-1 ){
    blob_appendf(&bSQL, " AND 0");
  }else if( nTagId!=0 ){
    blob_appendf(&bSQL, " AND (EXISTS(SELECT 1 FROM tagxref"
      " WHERE tagid=%d AND tagtype>0 AND rid=blob.rid))", nTagId);
  }

  if( zFilename ){
    blob_appendf(&bSQL,
      " AND (SELECT mlink.fnid FROM mlink WHERE event.objid=mlink.mid) IN (SELECT fnid FROM filename WHERE name=%Q %s)",
        zFilename, filename_collation()
    );
  }

  blob_append( &bSQL, " ORDER BY event.mtime DESC", -1 );

  zProjectName = db_get("project-name", 0);
  if( zProjectName==0 ){
    zFreeProjectName = zProjectName = mprintf("Fossil source repository for: %s",
      zBaseURL);
  }
  zProjectDescr = db_get("project-description", 0);
  if( zProjectDescr==0 ){
    zProjectDescr = zProjectName;
  }

  zPubDate = cgi_rfc822_datestamp(time(NULL));

  fossil_print("<?xml version=\"1.0\"?>");
  fossil_print("<rss xmlns:dc=\"http://purl.org/dc/elements/1.1/\" version=\"2.0\">");
  fossil_print("<channel>\n");
  fossil_print("<title>%h</title>\n", zProjectName);
  fossil_print("<link>%s</link>\n", zBaseURL);
  fossil_print("<description>%h</description>\n", zProjectDescr);
  fossil_print("<pubDate>%s</pubDate>\n", zPubDate);
  fossil_print("<generator>Fossil version %s %s</generator>\n",
               MANIFEST_VERSION, MANIFEST_DATE);
  free(zPubDate);
  db_prepare(&q, blob_str(&bSQL));
  blob_reset( &bSQL );
  while( db_step(&q)==SQLITE_ROW && nLine<nLimit ){
    const char *zId = db_column_text(&q, 1);
    const char *zCom = db_column_text(&q, 3);
    const char *zAuthor = db_column_text(&q, 4);
    char *zPrefix = "";
    char *zDate;
    int nChild = db_column_int(&q, 5);
    int nParent = db_column_int(&q, 6);
    time_t ts;

    ts = (time_t)((db_column_double(&q,2) - 2440587.5)*86400.0);
    zDate = cgi_rfc822_datestamp(ts);

    if( nParent>1 && nChild>1 ){
      zPrefix = "*MERGE/FORK* ";
    }else if( nParent>1 ){
      zPrefix = "*MERGE* ";
    }else if( nChild>1 ){
      zPrefix = "*FORK* ";
    }

    fossil_print("<item>");
    fossil_print("<title>%s%h</title>\n", zPrefix, zCom);
    fossil_print("<link>%s/info/%s</link>\n", zBaseURL, zId);
    fossil_print("<description>%s%h</description>\n", zPrefix, zCom);
    fossil_print("<pubDate>%s</pubDate>\n", zDate);
    fossil_print("<dc:creator>%h</dc:creator>\n", zAuthor);
    fossil_print("<guid>%s/info/%s</guid>\n", g.zBaseURL, zId);
    fossil_print("</item>\n");
    free(zDate);
    nLine++;
  }

  db_finalize(&q);
  fossil_print("</channel>\n");
  fossil_print("</rss>\n");

  if( zFreeProjectName != 0 ){
    free( zFreeProjectName );
  }
}











<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
195
196
197
198
199
200
201
202
203
204
205















































































































































































    nLine++;
  }

  db_finalize(&q);
  @   </channel>
  @ </rss>

  if( zFreeProjectName != 0 ){
    free( zFreeProjectName );
  }
}















































































































































































Changes to src/schema.c.

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
*/
#include "config.h"
#include "schema.h"

/*
** The database schema for the ~/.fossil configuration database.
*/
const char zConfigSchema[] =
@ -- This file contains the schema for the database that is kept in the
@ -- ~/.fossil file and that stores information about the users setup.
@ --
@ CREATE TABLE global_config(
@   name TEXT PRIMARY KEY,
@   value TEXT
@ );







|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
*/
#include "config.h"
#include "schema.h"

/*
** The database schema for the ~/.fossil configuration database.
*/
const char zConfigSchema[] = 
@ -- This file contains the schema for the database that is kept in the
@ -- ~/.fossil file and that stores information about the users setup.
@ --
@ CREATE TABLE global_config(
@   name TEXT PRIMARY KEY,
@   value TEXT
@ );
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#define CONTENT_SCHEMA  "2"
#define AUX_SCHEMA      "2011-04-25 19:50"

#endif /* INTERFACE */


/*
** The schema for a repository database.
**
** Schema1[] contains parts of the schema that are fixed and unchanging
** across versions.  Schema2[] contains parts of the schema that can
** change from one version to the next.  The information in Schema2[]
** is reconstructed from the information in Schema1[] by the "rebuild"
** operation.
*/
const char zRepositorySchema1[] =
@ -- The BLOB and DELTA tables contain all records held in the repository.
@ --
@ -- The BLOB.CONTENT column is always compressed using zlib.  This
@ -- column might hold the full text of the record or it might hold
@ -- a delta that is able to reconstruct the record from some other
@ -- record.  If BLOB.CONTENT holds a delta, then a DELTA table entry
@ -- will exist for the record and that entry will point to another
@ -- entry that holds the source of the delta.  Deltas can be chained.
@ --
@ -- The blob and delta tables collectively hold the "global state" of
@ -- a Fossil repository.
@ --
@ CREATE TABLE blob(
@   rid INTEGER PRIMARY KEY,        -- Record ID
@   rcvid INTEGER,                  -- Origin of this record
@   size INTEGER,                   -- Size of content. -1 for a phantom.
@   uuid TEXT UNIQUE NOT NULL,      -- SHA1 hash of the content
@   content BLOB,                   -- Compressed content of this record
@   CHECK( length(uuid)==40 AND rid>0 )
@ );
@ CREATE TABLE delta(
@   rid INTEGER PRIMARY KEY,                 -- Record ID
@   srcid INTEGER NOT NULL REFERENCES blob   -- Record holding source document
@ );
@ CREATE INDEX delta_i1 ON delta(srcid);
@
@ -------------------------------------------------------------------------
@ -- The BLOB and DELTA tables above hold the "global state" of a Fossil
@ -- project; the stuff that is normally exchanged during "sync".  The
@ -- "local state" of a repository is contained in the remaining tables of
@ -- the zRepositorySchema1 string.
@ -------------------------------------------------------------------------
@
@ -- Whenever new blobs are received into the repository, an entry
@ -- in this table records the source of the blob.
@ --
@ CREATE TABLE rcvfrom(
@   rcvid INTEGER PRIMARY KEY,      -- Received-From ID







|







|










|



















|







48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#define CONTENT_SCHEMA  "2"
#define AUX_SCHEMA      "2011-04-25 19:50"

#endif /* INTERFACE */


/*
** The schema for a repository database.  
**
** Schema1[] contains parts of the schema that are fixed and unchanging
** across versions.  Schema2[] contains parts of the schema that can
** change from one version to the next.  The information in Schema2[]
** is reconstructed from the information in Schema1[] by the "rebuild"
** operation.
*/
const char zRepositorySchema1[] = 
@ -- The BLOB and DELTA tables contain all records held in the repository.
@ --
@ -- The BLOB.CONTENT column is always compressed using zlib.  This
@ -- column might hold the full text of the record or it might hold
@ -- a delta that is able to reconstruct the record from some other
@ -- record.  If BLOB.CONTENT holds a delta, then a DELTA table entry
@ -- will exist for the record and that entry will point to another
@ -- entry that holds the source of the delta.  Deltas can be chained.
@ --
@ -- The blob and delta tables collectively hold the "global state" of
@ -- a Fossil repository.  
@ --
@ CREATE TABLE blob(
@   rid INTEGER PRIMARY KEY,        -- Record ID
@   rcvid INTEGER,                  -- Origin of this record
@   size INTEGER,                   -- Size of content. -1 for a phantom.
@   uuid TEXT UNIQUE NOT NULL,      -- SHA1 hash of the content
@   content BLOB,                   -- Compressed content of this record
@   CHECK( length(uuid)==40 AND rid>0 )
@ );
@ CREATE TABLE delta(
@   rid INTEGER PRIMARY KEY,                 -- Record ID
@   srcid INTEGER NOT NULL REFERENCES blob   -- Record holding source document
@ );
@ CREATE INDEX delta_i1 ON delta(srcid);
@
@ -------------------------------------------------------------------------
@ -- The BLOB and DELTA tables above hold the "global state" of a Fossil
@ -- project; the stuff that is normally exchanged during "sync".  The
@ -- "local state" of a repository is contained in the remaining tables of
@ -- the zRepositorySchema1 string.  
@ -------------------------------------------------------------------------
@
@ -- Whenever new blobs are received into the repository, an entry
@ -- in this table records the source of the blob.
@ --
@ CREATE TABLE rcvfrom(
@   rcvid INTEGER PRIMARY KEY,      -- Received-From ID
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
@    cols TEXT,               -- A color-key specification
@    sqlcode TEXT             -- An SQL SELECT statement for this report
@ );
@
@ -- Some ticket content (such as the originators email address or contact
@ -- information) needs to be obscured to protect privacy.  This is achieved
@ -- by storing an SHA1 hash of the content.  For display, the hash is
@ -- mapped back into the original text using this table.
@ --
@ -- This table contains sensitive information and should not be shared
@ -- with unauthorized users.
@ --
@ CREATE TABLE concealed(
@   hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
@   mtime DATE,               -- Time created.  Seconds since 1970
@   content TEXT              -- Content intended to be concealed
@ );
@
@ -- The application ID helps the unix "file" command to identify the
@ -- database as a fossil repository.
@ PRAGMA application_id=252006673;
;

/*
** The default reportfmt entry for the schema. This is in an extra
** script so that (configure reset) can install the default report.
*/
const char zRepositorySchemaDefaultReports[] =
@ INSERT INTO reportfmt(title,mtime,cols,sqlcode)
@ VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key:
@ #f2dcdc Active
@ #e8e8e8 Review
@ #cfe8bd Fixed
@ #bde5d6 Tested
@ #cacae5 Deferred
@ #c8c8c8 Closed','SELECT







|




















|







170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
@    cols TEXT,               -- A color-key specification
@    sqlcode TEXT             -- An SQL SELECT statement for this report
@ );
@
@ -- Some ticket content (such as the originators email address or contact
@ -- information) needs to be obscured to protect privacy.  This is achieved
@ -- by storing an SHA1 hash of the content.  For display, the hash is
@ -- mapped back into the original text using this table.  
@ --
@ -- This table contains sensitive information and should not be shared
@ -- with unauthorized users.
@ --
@ CREATE TABLE concealed(
@   hash TEXT PRIMARY KEY,    -- The SHA1 hash of content
@   mtime DATE,               -- Time created.  Seconds since 1970
@   content TEXT              -- Content intended to be concealed
@ );
@
@ -- The application ID helps the unix "file" command to identify the
@ -- database as a fossil repository.
@ PRAGMA application_id=252006673;
;

/*
** The default reportfmt entry for the schema. This is in an extra
** script so that (configure reset) can install the default report.
*/
const char zRepositorySchemaDefaultReports[] =
@ INSERT INTO reportfmt(title,mtime,cols,sqlcode) 
@ VALUES('All Tickets',julianday('1970-01-01'),'#ffffff Key:
@ #f2dcdc Active
@ #e8e8e8 Review
@ #cfe8bd Fixed
@ #bde5d6 Tested
@ #cacae5 Deferred
@ #c8c8c8 Closed','SELECT
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
@ --
@ CREATE TABLE unsent(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );
@
@ -- Each baseline or manifest can have one or more tags.  A tag
@ -- is defined by a row in the next table.
@ --
@ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of
@ -- the wiki page.  Tickets changes are tagged with "ticket-UUID" where
@ -- UUID is the indentifier of the ticket.  Tags used to assign symbolic
@ -- names to baselines are branches are of the form "sym-NAME" where
@ -- NAME is the symbolic name.
@ --
@ CREATE TABLE tag(
@   tagid INTEGER PRIMARY KEY,       -- Numeric tag ID
@   tagname TEXT UNIQUE              -- Tag name.







|

|







319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
@ --
@ CREATE TABLE unsent(
@   rid INTEGER PRIMARY KEY         -- Record ID of the phantom
@ );
@
@ -- Each baseline or manifest can have one or more tags.  A tag
@ -- is defined by a row in the next table.
@ -- 
@ -- Wiki pages are tagged with "wiki-NAME" where NAME is the name of
@ -- the wiki page.  Tickets changes are tagged with "ticket-UUID" where 
@ -- UUID is the indentifier of the ticket.  Tags used to assign symbolic
@ -- names to baselines are branches are of the form "sym-NAME" where
@ -- NAME is the symbolic name.
@ --
@ CREATE TABLE tag(
@   tagid INTEGER PRIMARY KEY,       -- Numeric tag ID
@   tagname TEXT UNIQUE              -- Tag name.

Changes to src/search.c.

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    while( !isBoundary[zDoc[i]&0xff] ){ i++; }
  }

  /* Every term must be seen or else the score is zero */
  for(j=0; j<p->nTerm; j++){
    if( !seen[j] ) return 0;
  }

  return score;
}

/*
** This is an SQLite function that scores its input using
** a pre-computed pattern.
*/







|







133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    while( !isBoundary[zDoc[i]&0xff] ){ i++; }
  }

  /* Every term must be seen or else the score is zero */
  for(j=0; j<p->nTerm; j++){
    if( !seen[j] ) return 0;
  }
      
  return score;
}

/*
** This is an SQLite function that scores its input using
** a pre-computed pattern.
*/

Changes to src/setup.c.

247
248
249
250
251
252
253
254



255
256
257
258
259
260
261
     @   <td><i>Developer:</i> Inherit privileges of
     @   user <tt>developer</tt></td></tr>
     @ <tr><th valign="top">w</th>
     @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
     @ <tr><th valign="top">x</th>
     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
     @ <tr><th valign="top">z</th>
     @   <td><i>Zip download:</i> Download a ZIP archive or tarball</td></tr>



  @ </table>
  @ </li>
  @
  @ <li><p>
  @ Every user, logged in or not, inherits the privileges of
  @ <span class="usertype">nobody</span>.
  @ </p></li>







|
>
>
>







247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
     @   <td><i>Developer:</i> Inherit privileges of
     @   user <tt>developer</tt></td></tr>
     @ <tr><th valign="top">w</th>
     @   <td><i>Write-Tkt:</i> Edit tickets</td></tr>
     @ <tr><th valign="top">x</th>
     @   <td><i>Private:</i> Push and/or pull private branches</td></tr>
     @ <tr><th valign="top">z</th>
     @   <td><i>Zip download:</i> Download a baseline via the
     @   <tt>/zip</tt> URL even without
     @    check<span class="capability">o</span>ut
     @    and <span class="capability">h</span>istory permissions</td></tr>
  @ </table>
  @ </li>
  @
  @ <li><p>
  @ Every user, logged in or not, inherits the privileges of
  @ <span class="usertype">nobody</span>.
  @ </p></li>
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ </ol>
  @ </td></tr></table>
  style_footer();
  db_finalize(&s);
}

/*
** Return true if zPw is a valid password string.  A valid
** password string is:
**
**  (1)  A zero-length string, or







<







278
279
280
281
282
283
284

285
286
287
288
289
290
291
  @ <span class="usertype">anonymous</span>, and
  @ <span class="usertype">nobody</span>.
  @ </p></li>
  @
  @ </ol>
  @ </td></tr></table>
  style_footer();

}

/*
** Return true if zPw is a valid password string.  A valid
** password string is:
**
**  (1)  A zero-length string, or
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
  }
  @ </table>
  @ </div></form>
  @ </div>
  @ <h2>Privileges And Capabilities:</h2>
  @ <ul>
  if( higherUser ){
    @ <li><p class="missingPriv">
    @ User %h(zLogin) has Setup privileges and you only have Admin privileges
    @ so you are not permitted to make changes to %h(zLogin).
    @ </p></li>
    @
  }
  @ <li><p>
  @ The <span class="capability">Setup</span> user can make arbitrary







|







598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
  }
  @ </table>
  @ </div></form>
  @ </div>
  @ <h2>Privileges And Capabilities:</h2>
  @ <ul>
  if( higherUser ){
    @ <li><p class=missingPriv">
    @ User %h(zLogin) has Setup privileges and you only have Admin privileges
    @ so you are not permitted to make changes to %h(zLogin).
    @ </p></li>
    @
  }
  @ <li><p>
  @ The <span class="capability">Setup</span> user can make arbitrary
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
*/
static void multiple_choice_attribute(
  const char *zLabel,   /* The text label on the menu */
  const char *zVar,     /* The corresponding row in the VAR table */
  const char *zQP,      /* The query parameter */
  const char *zDflt,    /* Default value if VAR table entry does not exist */
  int nChoice,          /* Number of choices */
  const char *const *azChoice /* Choices. 2 per choice: (VAR value, Display) */
){
  const char *z = db_get(zVar, (char*)zDflt);
  const char *zQ = P(zQP);
  int i;
  if( zQ && fossil_strcmp(zQ,z)!=0){
    login_verify_csrf_secret();
    db_set(zVar, zQ, 0);
    z = zQ;
  }
  @ <select size="1" name="%s(zQP)" id="id%s(zQP)">
  for(i=0; i<nChoice*2; i+=2){
    const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
    @ <option value="%h(azChoice[i])"%s(zSel)>%h(azChoice[i+1])</option>
  }
  @ </select> <b>%h(zLabel)</b>
}


/*
** WEBPAGE: setup_access
*/
void setup_access(void){







|














|







857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
*/
static void multiple_choice_attribute(
  const char *zLabel,   /* The text label on the menu */
  const char *zVar,     /* The corresponding row in the VAR table */
  const char *zQP,      /* The query parameter */
  const char *zDflt,    /* Default value if VAR table entry does not exist */
  int nChoice,          /* Number of choices */
  const char **azChoice /* Choices. 2 per choice: (VAR value, Display) */
){
  const char *z = db_get(zVar, (char*)zDflt);
  const char *zQ = P(zQP);
  int i;
  if( zQ && fossil_strcmp(zQ,z)!=0){
    login_verify_csrf_secret();
    db_set(zVar, zQ, 0);
    z = zQ;
  }
  @ <select size="1" name="%s(zQP)" id="id%s(zQP)">
  for(i=0; i<nChoice*2; i+=2){
    const char *zSel = fossil_strcmp(azChoice[i],z)==0 ? " selected" : "";
    @ <option value="%h(azChoice[i])"%s(zSel)>%h(azChoice[i+1])</option>
  }
  @ </select>
}


/*
** WEBPAGE: setup_access
*/
void setup_access(void){
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
                  "30", 0);

  @ <p>Fossil tries to spend less than this many seconds gathering
  @ the out-bound data of sync, clone, and pull packets.
  @ If the client request takes longer, a partial reply is given similar
  @ to the download packet limit. 30s is a reasonable default.</p>

  @ <hr />
  entry_attribute("Server Load Average Limit", 11, "max-loadavg", "mxldavg",
                  "0.0", 0);
  @ <p>Some expensive operations (such as computing tarballs, zip archives,
  @ or annotation/blame pages) are prohibited if the load average on the host
  @ computer is too large.  Set the threshold for disallowing expensive
  @ computations here.  Set this to 0.0 to disable the load average limit.
  @ This limit is only enforced on Unix servers.  On Linux systems,
  @ access to the /proc virtual filesystem is required, which means this limit
  @ might not work inside a chroot() jail.</p>

  @ <hr />
  onoff_attribute(
      "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
      "auto-hyperlink", "autohyperlink", 1, 0);
  @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
  @ including user "nobody", as long as (1) the User-Agent string in the
  @ HTTP header indicates that the request is coming from an actual human
  @ being and not a a robot or spider and (2) the user agent is able to
  @ run Javascript in order to set the href= attribute of hyperlinks.  Bots
  @ and spiders can forge a User-Agent string that makes them seem to be a
  @ normal browser and they can run javascript just like browsers.  But most
  @ bots do not go to that much trouble so this is normally an effective
  @ defense.</p>
  @
  @ <p>You do not normally want a bot to walk your entire repository because
  @ if it does, your server will end up computing diffs and annotations for
  @ every historical version of every file and creating ZIPs and tarballs of
  @ every historical check-in, which can use a lot of CPU and bandwidth
  @ even for relatively small projects.</p>
  @
  @ <p>Additional parameters that control this behavior:</p>
  @ <blockquote>
  onoff_attribute("Require mouse movement before enabling hyperlinks",
                  "auto-hyperlink-mouseover", "ahmo", 0, 0);
  @ <br>
  entry_attribute("Delay before enabling hyperlinks (milliseconds)", 5,
                  "auto-hyperlink-delay", "ah-delay", "10", 0);







<
<
<
<
<
<
<
<
<
<
<










|
|
<






|







963
964
965
966
967
968
969











970
971
972
973
974
975
976
977
978
979
980
981

982
983
984
985
986
987
988
989
990
991
992
993
994
995
                  "30", 0);

  @ <p>Fossil tries to spend less than this many seconds gathering
  @ the out-bound data of sync, clone, and pull packets.
  @ If the client request takes longer, a partial reply is given similar
  @ to the download packet limit. 30s is a reasonable default.</p>












  @ <hr />
  onoff_attribute(
      "Enable hyperlinks for \"nobody\" based on User-Agent and Javascript",
      "auto-hyperlink", "autohyperlink", 1, 0);
  @ <p>Enable hyperlinks (the equivalent of the "h" permission) for all users
  @ including user "nobody", as long as (1) the User-Agent string in the
  @ HTTP header indicates that the request is coming from an actual human
  @ being and not a a robot or spider and (2) the user agent is able to
  @ run Javascript in order to set the href= attribute of hyperlinks.  Bots
  @ and spiders can forge a User-Agent string that makes them seem to be a
  @ normal browser and they can run javascript just like browsers.  But most 
  @ bots do not go to that much trouble so this is normally an effective defense.</p>

  @
  @ <p>You do not normally want a bot to walk your entire repository because
  @ if it does, your server will end up computing diffs and annotations for
  @ every historical version of every file and creating ZIPs and tarballs of
  @ every historical check-in, which can use a lot of CPU and bandwidth
  @ even for relatively small projects.</p>
  @ 
  @ <p>Additional parameters that control this behavior:</p>
  @ <blockquote>
  onoff_attribute("Require mouse movement before enabling hyperlinks",
                  "auto-hyperlink-mouseover", "ahmo", 0, 0);
  @ <br>
  entry_attribute("Delay before enabling hyperlinks (milliseconds)", 5,
                  "auto-hyperlink-delay", "ah-delay", "10", 0);
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173

/*
** WEBPAGE: setup_timeline
*/
void setup_timeline(void){
  double tmDiff;
  char zTmDiff[20];
  static const char *const azTimeFormats[] = {
      "0", "HH:MM",
      "1", "HH:MM:SS",
      "2", "YYYY-MM-DD HH:MM",
      "3", "YYMMDD HH:MM"
  };
  login_check_credentials();
  if( !g.perm.Setup ){







|







1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163

/*
** WEBPAGE: setup_timeline
*/
void setup_timeline(void){
  double tmDiff;
  char zTmDiff[20];
  static const char *azTimeFormats[] = {
      "0", "HH:MM",
      "1", "HH:MM:SS",
      "2", "YYYY-MM-DD HH:MM",
      "3", "YYMMDD HH:MM"
  };
  login_check_credentials();
  if( !g.perm.Setup ){
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
  @ <p>In timeline displays, check-in comments can be displayed with or
  @ without block markup (paragraphs, tables, etc.)</p>

  @ <hr />
  onoff_attribute("Plaintext comments on timelines",
                  "timeline-plaintext", "tpt", 0, 0);
  @ <p>In timeline displays, check-in comments are displayed literally,
  @ without any wiki or HTML interpretation.  (Note: Use CSS to change
  @ display formatting features such as fonts and line-wrapping behavior.)</p>

  @ <hr />
  onoff_attribute("Use Universal Coordinated Time (UTC)",
                  "timeline-utc", "utc", 1, 0);
  @ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
  @ Zulu) instead of in local time.  On this server, local time is currently
  tmDiff = db_double(0.0, "SELECT julianday('now')");







|
<







1175
1176
1177
1178
1179
1180
1181
1182

1183
1184
1185
1186
1187
1188
1189
  @ <p>In timeline displays, check-in comments can be displayed with or
  @ without block markup (paragraphs, tables, etc.)</p>

  @ <hr />
  onoff_attribute("Plaintext comments on timelines",
                  "timeline-plaintext", "tpt", 0, 0);
  @ <p>In timeline displays, check-in comments are displayed literally,
  @ without any wiki or HTML interpretation.</p>


  @ <hr />
  onoff_attribute("Use Universal Coordinated Time (UTC)",
                  "timeline-utc", "utc", 1, 0);
  @ <p>Show times as UTC (also sometimes called Greenwich Mean Time (GMT) or
  @ Zulu) instead of in local time.  On this server, local time is currently
  tmDiff = db_double(0.0, "SELECT julianday('now')");
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
  @ <input type="submit" name="submit" value="Apply Changes" />
  @ <input type="submit" name="clear" value="Revert To Default" />
  @ </div></form>
  @ <hr />
  @ The default header is shown below for reference.  Other examples
  @ of headers can be seen on the <a href="setup_skin">skins page</a>.
  @ See also the <a href="setup_editcss">CSS</a> and
  @ <a href="setup_footer">footer</a> editing screens.
  @ <blockquote><pre>
  @ %h(zDefaultHeader)
  @ </pre></blockquote>
  style_footer();
  db_end_transaction(0);
}








|







1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
  @ <input type="submit" name="submit" value="Apply Changes" />
  @ <input type="submit" name="clear" value="Revert To Default" />
  @ </div></form>
  @ <hr />
  @ The default header is shown below for reference.  Other examples
  @ of headers can be seen on the <a href="setup_skin">skins page</a>.
  @ See also the <a href="setup_editcss">CSS</a> and
  @ <a href="setup_footer">footer</a> editing screeens.
  @ <blockquote><pre>
  @ %h(zDefaultHeader)
  @ </pre></blockquote>
  style_footer();
  db_end_transaction(0);
}

Changes to src/shell.c.

1145
1146
1147
1148
1149
1150
1151

1152
1153

1154
1155
1156
1157
1158
1159
1160
  if( pArg && pArg->out && db && pArg->pStmt ){
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset);
    fprintf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
    fprintf(pArg->out, "Sort Operations:                     %d\n", iCur);
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX, bReset);
    fprintf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);

    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
    fprintf(pArg->out, "Virtual Machine Steps:               %d\n", iCur);

  }

  return 0;
}

/*
** Parameter azArray points to a zero-terminated array of strings. zStr







>
|
|
>







1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
  if( pArg && pArg->out && db && pArg->pStmt ){
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_FULLSCAN_STEP, bReset);
    fprintf(pArg->out, "Fullscan Steps:                      %d\n", iCur);
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_SORT, bReset);
    fprintf(pArg->out, "Sort Operations:                     %d\n", iCur);
    iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_AUTOINDEX, bReset);
    fprintf(pArg->out, "Autoindex Inserts:                   %d\n", iCur);
    if( sqlite3_libversion_number()>=3008000 ){
      iCur = sqlite3_stmt_status(pArg->pStmt, SQLITE_STMTSTATUS_VM_STEP, bReset);
      fprintf(pArg->out, "Virtual Machine Steps:               %d\n", iCur);
    }
  }

  return 0;
}

/*
** Parameter azArray points to a zero-terminated array of strings. zStr
2126
2127
2128
2129
2130
2131
2132

2133
2134
2135
2136

2137
2138
2139
2140
2141
2142
2143
    return;
  }
  rc = sqlite3_open(zNewDb, &newDb);
  if( rc ){
    fprintf(stderr, "Cannot create output database: %s\n",
            sqlite3_errmsg(newDb));
  }else{

    sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
    tryToCloneSchema(p, newDb, "type='table'", tryToCloneData);
    tryToCloneSchema(p, newDb, "type!='table'", 0);
    sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);

  }
  sqlite3_close(newDb);
}

/*
** If an input line begins with "." then invoke this routine to
** process that line.







>




>







2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
    return;
  }
  rc = sqlite3_open(zNewDb, &newDb);
  if( rc ){
    fprintf(stderr, "Cannot create output database: %s\n",
            sqlite3_errmsg(newDb));
  }else{
    sqlite3_exec(p->db, "PRAGMA writable_schema=ON;", 0, 0, 0);
    sqlite3_exec(newDb, "BEGIN EXCLUSIVE;", 0, 0, 0);
    tryToCloneSchema(p, newDb, "type='table'", tryToCloneData);
    tryToCloneSchema(p, newDb, "type!='table'", 0);
    sqlite3_exec(newDb, "COMMIT;", 0, 0, 0);
    sqlite3_exec(p->db, "PRAGMA writable_schema=OFF;", 0, 0, 0);
  }
  sqlite3_close(newDb);
}

/*
** If an input line begins with "." then invoke this routine to
** process that line.
3021
3022
3023
3024
3025
3026
3027

3028
3029
3030
3031
3032
3033
3034
      { "pending_byte",          SQLITE_TESTCTRL_PENDING_BYTE           },
      { "assert",                SQLITE_TESTCTRL_ASSERT                 },
      { "always",                SQLITE_TESTCTRL_ALWAYS                 },
      { "reserve",               SQLITE_TESTCTRL_RESERVE                },
      { "optimizations",         SQLITE_TESTCTRL_OPTIMIZATIONS          },
      { "iskeyword",             SQLITE_TESTCTRL_ISKEYWORD              },
      { "scratchmalloc",         SQLITE_TESTCTRL_SCRATCHMALLOC          },

    };
    int testctrl = -1;
    int rc = 0;
    int i, n;
    open_db(p, 0);

    /* convert testctrl text option to value. allow any unique prefix







>







3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
      { "pending_byte",          SQLITE_TESTCTRL_PENDING_BYTE           },
      { "assert",                SQLITE_TESTCTRL_ASSERT                 },
      { "always",                SQLITE_TESTCTRL_ALWAYS                 },
      { "reserve",               SQLITE_TESTCTRL_RESERVE                },
      { "optimizations",         SQLITE_TESTCTRL_OPTIMIZATIONS          },
      { "iskeyword",             SQLITE_TESTCTRL_ISKEYWORD              },
      { "scratchmalloc",         SQLITE_TESTCTRL_SCRATCHMALLOC          },
      { "byteorder",             SQLITE_TESTCTRL_BYTEORDER              },
    };
    int testctrl = -1;
    int rc = 0;
    int i, n;
    open_db(p, 0);

    /* convert testctrl text option to value. allow any unique prefix
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070

3071
3072
3073
3074
3075
3076
3077
          } else {
            fprintf(stderr,"Error: testctrl %s takes a single int option\n",
                    azArg[1]);
          }
          break;

        /* sqlite3_test_control(int) */
        case SQLITE_TESTCTRL_PRNG_SAVE:           
        case SQLITE_TESTCTRL_PRNG_RESTORE:        
        case SQLITE_TESTCTRL_PRNG_RESET:

          if( nArg==2 ){
            rc = sqlite3_test_control(testctrl);
            fprintf(p->out, "%d (0x%08x)\n", rc, rc);
          } else {
            fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]);
          }
          break;







|
|

>







3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
          } else {
            fprintf(stderr,"Error: testctrl %s takes a single int option\n",
                    azArg[1]);
          }
          break;

        /* sqlite3_test_control(int) */
        case SQLITE_TESTCTRL_PRNG_SAVE:
        case SQLITE_TESTCTRL_PRNG_RESTORE:
        case SQLITE_TESTCTRL_PRNG_RESET:
        case SQLITE_TESTCTRL_BYTEORDER:
          if( nArg==2 ){
            rc = sqlite3_test_control(testctrl);
            fprintf(p->out, "%d (0x%08x)\n", rc, rc);
          } else {
            fprintf(stderr,"Error: testctrl %s takes no options\n", azArg[1]);
          }
          break;

Changes to src/shun.c.

67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    }
  }
  style_header("Shunned Artifacts");
  if( zUuid && P("sub") ){
    login_verify_csrf_secret();
    db_multi_exec("DELETE FROM shun WHERE uuid='%s'", zUuid);
    if( db_exists("SELECT 1 FROM blob WHERE uuid='%s'", zUuid) ){
      @ <p class="noMoreShun">Artifact
      @ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> is no
      @ longer being shunned.</p>
    }else{
      @ <p class="noMoreShun">Artifact %s(zUuid) will no longer
      @ be shunned.  But it does not exist in the repository.  It
      @ may be necessary to rebuild the repository using the
      @ <b>fossil rebuild</b> command-line before the artifact content







|







67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    }
  }
  style_header("Shunned Artifacts");
  if( zUuid && P("sub") ){
    login_verify_csrf_secret();
    db_multi_exec("DELETE FROM shun WHERE uuid='%s'", zUuid);
    if( db_exists("SELECT 1 FROM blob WHERE uuid='%s'", zUuid) ){
      @ <p class="noMoreShun">Artifact 
      @ <a href="%s(g.zTop)/artifact/%s(zUuid)">%s(zUuid)</a> is no
      @ longer being shunned.</p>
    }else{
      @ <p class="noMoreShun">Artifact %s(zUuid) will no longer
      @ be shunned.  But it does not exist in the repository.  It
      @ may be necessary to rebuild the repository using the
      @ <b>fossil rebuild</b> command-line before the artifact content
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
      db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
    }
  }
  @ <p>A shunned artifact will not be pushed nor accepted in a pull and the
  @ artifact content will be purged from the repository the next time the
  @ repository is rebuilt.  A list of shunned artifacts can be seen at the
  @ bottom of this page.</p>
  @
  @ <a name="addshun"></a>
  @ <p>To shun an artifact, enter its artifact ID (the 40-character SHA1
  @ hash of the artifact) in the
  @ following box and press the "Shun" button.  This will cause the artifact
  @ to be removed from the repository and will prevent the artifact from being
  @ readded to the repository by subsequent sync operation.</p>
  @
  @ <p>Note that you must enter the full 40-character artifact ID, not
  @ an abbreviation or a symbolic tag.</p>
  @
  @ <p>Warning:  Shunning should only be used to remove inappropriate content
  @ from the repository.  Inappropriate content includes such things as
  @ spam added to Wiki, files that violate copyright or patent agreements,
  @ or artifacts that by design or accident interfere with the processing
  @ of the repository.  Do not shun artifacts merely to remove them from
  @ sight - set the "hidden" tag on such artifacts instead.</p>
  @
  @ <blockquote>
  @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
  login_insert_csrf_secret();
  @ <input type="text" name="uuid" value="%h(PD("shun",""))" size="50" />
  @ <input type="submit" name="add" value="Shun" />
  @ </div></form>
  @ </blockquote>







|
















|







105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
      db_multi_exec("DELETE FROM tagxref WHERE tagid=%d", tagid);
    }
  }
  @ <p>A shunned artifact will not be pushed nor accepted in a pull and the
  @ artifact content will be purged from the repository the next time the
  @ repository is rebuilt.  A list of shunned artifacts can be seen at the
  @ bottom of this page.</p>
  @ 
  @ <a name="addshun"></a>
  @ <p>To shun an artifact, enter its artifact ID (the 40-character SHA1
  @ hash of the artifact) in the
  @ following box and press the "Shun" button.  This will cause the artifact
  @ to be removed from the repository and will prevent the artifact from being
  @ readded to the repository by subsequent sync operation.</p>
  @
  @ <p>Note that you must enter the full 40-character artifact ID, not
  @ an abbreviation or a symbolic tag.</p>
  @
  @ <p>Warning:  Shunning should only be used to remove inappropriate content
  @ from the repository.  Inappropriate content includes such things as
  @ spam added to Wiki, files that violate copyright or patent agreements,
  @ or artifacts that by design or accident interfere with the processing
  @ of the repository.  Do not shun artifacts merely to remove them from
  @ sight - set the "hidden" tag on such artifacts instead.</p>
  @ 
  @ <blockquote>
  @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
  login_insert_csrf_secret();
  @ <input type="text" name="uuid" value="%h(PD("shun",""))" size="50" />
  @ <input type="submit" name="add" value="Shun" />
  @ </div></form>
  @ </blockquote>
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
  @
  @ <blockquote>
  @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
  login_insert_csrf_secret();
  @ <input type="submit" name="rebuild" value="Rebuild" />
  @ </div></form>
  @ </blockquote>
  @
  @ <hr /><p>Shunned Artifacts:</p>
  @ <blockquote><p>
  db_prepare(&q,
     "SELECT uuid, EXISTS(SELECT 1 FROM blob WHERE blob.uuid=shun.uuid)"
     "  FROM shun ORDER BY uuid");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int stillExists = db_column_int(&q, 1);
    cnt++;
    if( stillExists ){







|


|







157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
  @
  @ <blockquote>
  @ <form method="post" action="%s(g.zTop)/%s(g.zPath)"><div>
  login_insert_csrf_secret();
  @ <input type="submit" name="rebuild" value="Rebuild" />
  @ </div></form>
  @ </blockquote>
  @ 
  @ <hr /><p>Shunned Artifacts:</p>
  @ <blockquote><p>
  db_prepare(&q, 
     "SELECT uuid, EXISTS(SELECT 1 FROM blob WHERE blob.uuid=shun.uuid)"
     "  FROM shun ORDER BY uuid");
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    int stillExists = db_column_int(&q, 1);
    cnt++;
    if( stillExists ){
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    login_needed();
  }
  style_header("Content Sources");
  if( ofst>0 ){
    style_submenu_element("Newer", "Newer", "rcvfromlist?ofst=%d",
                           ofst>30 ? ofst-30 : 0);
  }
  db_prepare(&q,
    "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr"
    "  FROM rcvfrom LEFT JOIN user USING(uid)"
    " ORDER BY rcvid DESC LIMIT 31 OFFSET %d",
    ofst
  );
  @ <p>Whenever new artifacts are added to the repository, either by
  @ push or using the web interface, an entry is made in the RCVFROM table







|







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    login_needed();
  }
  style_header("Content Sources");
  if( ofst>0 ){
    style_submenu_element("Newer", "Newer", "rcvfromlist?ofst=%d",
                           ofst>30 ? ofst-30 : 0);
  }
  db_prepare(&q, 
    "SELECT rcvid, login, datetime(rcvfrom.mtime), rcvfrom.ipaddr"
    "  FROM rcvfrom LEFT JOIN user USING(uid)"
    " ORDER BY rcvid DESC LIMIT 31 OFFSET %d",
    ofst
  );
  @ <p>Whenever new artifacts are added to the repository, either by
  @ push or using the web interface, an entry is made in the RCVFROM table
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
  Stmt q;

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed();
  }
  style_header("Content Source %d", rcvid);
  db_prepare(&q,
    "SELECT login, datetime(rcvfrom.mtime), rcvfrom.ipaddr"
    "  FROM rcvfrom LEFT JOIN user USING(uid)"
    " WHERE rcvid=%d",
    rcvid
  );
  @ <table cellspacing="15" cellpadding="0" border="0">
  @ <tr><th valign="top" align="right">rcvid:</th>







|







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
  Stmt q;

  login_check_credentials();
  if( !g.perm.Admin ){
    login_needed();
  }
  style_header("Content Source %d", rcvid);
  db_prepare(&q, 
    "SELECT login, datetime(rcvfrom.mtime), rcvfrom.ipaddr"
    "  FROM rcvfrom LEFT JOIN user USING(uid)"
    " WHERE rcvid=%d",
    rcvid
  );
  @ <table cellspacing="15" cellpadding="0" border="0">
  @ <tr><th valign="top" align="right">rcvid:</th>

Changes to src/sqlcmd.c.

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  zName = (const char*)sqlite3_value_text(argv[0]);
  if( zName==0 ) return;
  g.db = sqlite3_context_db_handle(context);
  g.repositoryOpen = 1;
  rid = name_to_rid(zName);
  if( rid==0 ) return;
  if( content_get(rid, &cx) ){
    sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx),
                                 SQLITE_TRANSIENT);
    blob_reset(&cx);
  }
}

/*
** Implementation of the "compress(X)" SQL function.  The input X is







|







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
  zName = (const char*)sqlite3_value_text(argv[0]);
  if( zName==0 ) return;
  g.db = sqlite3_context_db_handle(context);
  g.repositoryOpen = 1;
  rid = name_to_rid(zName);
  if( rid==0 ) return;
  if( content_get(rid, &cx) ){
    sqlite3_result_blob(context, blob_buffer(&cx), blob_size(&cx), 
                                 SQLITE_TRANSIENT);
    blob_reset(&cx);
  }
}

/*
** Implementation of the "compress(X)" SQL function.  The input X is
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
** If DATABASE is omitted, then the repository that serves the working
** directory is opened.
**
** WARNING:  Careless use of this command can corrupt a Fossil repository
** in ways that are unrecoverable.  Be sure you know what you are doing before
** running any SQL commands that modifies the repository database.
*/
void cmd_sqlite3(void){
  extern int sqlite3_shell(int, char**);
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
  db_close(1);
  sqlite3_shutdown();
  sqlite3_shell(g.argc-1, g.argv+1);
  g.db = 0;
}







|







135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
** If DATABASE is omitted, then the repository that serves the working
** directory is opened.
**
** WARNING:  Careless use of this command can corrupt a Fossil repository
** in ways that are unrecoverable.  Be sure you know what you are doing before
** running any SQL commands that modifies the repository database.
*/
void sqlite3_cmd(void){
  extern int sqlite3_shell(int, char**);
  db_find_and_open_repository(OPEN_ANY_SCHEMA, 0);
  db_close(1);
  sqlite3_shutdown();
  sqlite3_shell(g.argc-1, g.argv+1);
  g.db = 0;
}

Changes to src/sqlite3.c.

6234
6235
6236
6237
6238
6239
6240

6241
6242
6243
6244
6245
6246
6247
6248
#define SQLITE_TESTCTRL_OPTIMIZATIONS           15
#define SQLITE_TESTCTRL_ISKEYWORD               16
#define SQLITE_TESTCTRL_SCRATCHMALLOC           17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21

#define SQLITE_TESTCTRL_LAST                    21

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various
** highwater marks.  ^The first argument is an integer code for







>
|







6234
6235
6236
6237
6238
6239
6240
6241
6242
6243
6244
6245
6246
6247
6248
6249
#define SQLITE_TESTCTRL_OPTIMIZATIONS           15
#define SQLITE_TESTCTRL_ISKEYWORD               16
#define SQLITE_TESTCTRL_SCRATCHMALLOC           17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21
#define SQLITE_TESTCTRL_BYTEORDER               22
#define SQLITE_TESTCTRL_LAST                    22

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various
** highwater marks.  ^The first argument is an integer code for

Changes to src/sqlite3.h.

6119
6120
6121
6122
6123
6124
6125

6126
6127
6128
6129
6130
6131
6132
6133
#define SQLITE_TESTCTRL_OPTIMIZATIONS           15
#define SQLITE_TESTCTRL_ISKEYWORD               16
#define SQLITE_TESTCTRL_SCRATCHMALLOC           17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21

#define SQLITE_TESTCTRL_LAST                    21

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various
** highwater marks.  ^The first argument is an integer code for







>
|







6119
6120
6121
6122
6123
6124
6125
6126
6127
6128
6129
6130
6131
6132
6133
6134
#define SQLITE_TESTCTRL_OPTIMIZATIONS           15
#define SQLITE_TESTCTRL_ISKEYWORD               16
#define SQLITE_TESTCTRL_SCRATCHMALLOC           17
#define SQLITE_TESTCTRL_LOCALTIME_FAULT         18
#define SQLITE_TESTCTRL_EXPLAIN_STMT            19
#define SQLITE_TESTCTRL_NEVER_CORRUPT           20
#define SQLITE_TESTCTRL_VDBE_COVERAGE           21
#define SQLITE_TESTCTRL_BYTEORDER               22
#define SQLITE_TESTCTRL_LAST                    22

/*
** CAPI3REF: SQLite Runtime Status
**
** ^This interface is used to retrieve runtime status information
** about the performance of SQLite, and optionally to reset various
** highwater marks.  ^The first argument is an integer code for

Changes to src/stash.c.

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    blob_init(&prompt, (const char *) bom, bomSize);
#else
    blob_zero(&prompt);
#endif
    blob_append(&prompt,
       "\n"
       "# Enter a description of what is being stashed.  Lines beginning\n"
       "# with \"#\" are ignored.  Stash comments are plain text except\n"
       "# newlines are not preserved.\n",
       -1);
    prompt_for_user_comment(&comment, &prompt);
    blob_reset(&prompt);
    zComment = blob_str(&comment);
  }
  stashid = db_lget_int("stash-next", 1);







|







166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
    blob_init(&prompt, (const char *) bom, bomSize);
#else
    blob_zero(&prompt);
#endif
    blob_append(&prompt,
       "\n"
       "# Enter a description of what is being stashed.  Lines beginning\n"
       "# with \"#\" are ignored.  Stash comments are plain text except.\n"
       "# newlines are not preserved.\n",
       -1);
    prompt_for_user_comment(&comment, &prompt);
    blob_reset(&prompt);
    zComment = blob_str(&comment);
  }
  stashid = db_lget_int("stash-next", 1);
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230

231
232
233
234
235
236
237
  return stashid;
}

/*
** Apply a stash to the current check-out.
*/
static void stash_apply(int stashid, int nConflict){
  int vid;
  Stmt q;
  db_prepare(&q,
     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile WHERE stashid=%d",
     stashid
  );
  vid = db_lget_int("checkout",0);
  db_multi_exec("CREATE TEMP TABLE sfile(x TEXT PRIMARY KEY %s)",
                filename_collation());
  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    int isExec = db_column_int(&q, 2);
    int isLink = db_column_int(&q, 3);
    const char *zOrig = db_column_text(&q, 4);
    const char *zNew = db_column_text(&q, 5);
    char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
    char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew);
    Blob delta;
    undo_save(zNew);
    blob_zero(&delta);
    if( rid==0 ){
      db_multi_exec("INSERT OR IGNORE INTO sfile(x) VALUES(%Q)", zNew);
      db_ephemeral_blob(&q, 6, &delta);
      blob_write_to_file(&delta, zNPath);
      file_wd_setexe(zNPath, isExec);

    }else if( isRemoved ){
      fossil_print("DELETE %s\n", zOrig);
      file_delete(zOPath);
    }else{
      Blob a, b, out, disk;
      int isNewLink = file_wd_islink(zOPath);
      db_ephemeral_blob(&q, 6, &delta);







<






<
<
<













<



>







197
198
199
200
201
202
203

204
205
206
207
208
209



210
211
212
213
214
215
216
217
218
219
220
221
222

223
224
225
226
227
228
229
230
231
232
233
  return stashid;
}

/*
** Apply a stash to the current check-out.
*/
static void stash_apply(int stashid, int nConflict){

  Stmt q;
  db_prepare(&q,
     "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
     "  FROM stashfile WHERE stashid=%d",
     stashid
  );



  while( db_step(&q)==SQLITE_ROW ){
    int rid = db_column_int(&q, 0);
    int isRemoved = db_column_int(&q, 1);
    int isExec = db_column_int(&q, 2);
    int isLink = db_column_int(&q, 3);
    const char *zOrig = db_column_text(&q, 4);
    const char *zNew = db_column_text(&q, 5);
    char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
    char *zNPath = mprintf("%s%s", g.zLocalRoot, zNew);
    Blob delta;
    undo_save(zNew);
    blob_zero(&delta);
    if( rid==0 ){

      db_ephemeral_blob(&q, 6, &delta);
      blob_write_to_file(&delta, zNPath);
      file_wd_setexe(zNPath, isExec);
      fossil_print("ADD %s\n", zNew);
    }else if( isRemoved ){
      fossil_print("DELETE %s\n", zOrig);
      file_delete(zOPath);
    }else{
      Blob a, b, out, disk;
      int isNewLink = file_wd_islink(zOPath);
      db_ephemeral_blob(&q, 6, &delta);
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
    }
    blob_reset(&delta);
    if( fossil_strcmp(zOrig,zNew)!=0 ){
      undo_save(zOrig);
      file_delete(zOPath);
    }
  }
  stash_add_files_in_sfile(vid);
  db_finalize(&q);
  if( nConflict ){
    fossil_print(
      "WARNING: %d merge conflicts - see messages above for details.\n",
      nConflict);
  }
}







<







274
275
276
277
278
279
280

281
282
283
284
285
286
287
    }
    blob_reset(&delta);
    if( fossil_strcmp(zOrig,zNew)!=0 ){
      undo_save(zOrig);
      file_delete(zOPath);
    }
  }

  db_finalize(&q);
  if( nConflict ){
    fossil_print(
      "WARNING: %d merge conflicts - see messages above for details.\n",
      nConflict);
  }
}
572
573
574
575
576
577
578

579
580
581
582
583
584
585
    if( n==0 ) fossil_print("empty stash\n");
  }else
  if( memcmp(zCmd, "drop", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 ){
    int allFlag = find_option("all", "a", 0)!=0;
    if( allFlag ){
      Blob ans;
      char cReply;

      prompt_user("This action is not undoable.  Continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply=='y' || cReply=='Y' ){
        db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;");
      }
    }else if( g.argc>=4 ){
      int i;







>







567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
    if( n==0 ) fossil_print("empty stash\n");
  }else
  if( memcmp(zCmd, "drop", nCmd)==0 || memcmp(zCmd, "rm", nCmd)==0 ){
    int allFlag = find_option("all", "a", 0)!=0;
    if( allFlag ){
      Blob ans;
      char cReply;
      blob_zero(&ans);
      prompt_user("This action is not undoable.  Continue (y/N)? ", &ans);
      cReply = blob_str(&ans)[0];
      if( cReply=='y' || cReply=='Y' ){
        db_multi_exec("DELETE FROM stash; DELETE FROM stashfile;");
      }
    }else if( g.argc>=4 ){
      int i;

Changes to src/stat.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the stat web page
**
*/
#include "VERSION.h"
#include "config.h"
#include <string.h>
#include "stat.h"

/*
** For a sufficiently large integer, provide an alternative
** representation as MB or GB or TB.







<







14
15
16
17
18
19
20

21
22
23
24
25
26
27
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the stat web page
**
*/

#include "config.h"
#include <string.h>
#include "stat.h"

/*
** For a sufficiently large integer, provide an alternative
** representation as MB or GB or TB.

Changes to src/style.c.

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the basic web page look and feel.
**
*/
#include "VERSION.h"
#include "config.h"
#include "style.h"


/*
** Elements of the submenu are collected into the following
** structure and displayed below the main menu by style_header().







<







14
15
16
17
18
19
20

21
22
23
24
25
26
27
**   http://www.hwaci.com/drh/
**
*******************************************************************************
**
** This file contains code to implement the basic web page look and feel.
**
*/

#include "config.h"
#include "style.h"


/*
** Elements of the submenu are collected into the following
** structure and displayed below the main menu by style_header().
162
163
164
165
166
167
168
169

170
171
172
173
174
175
176
** Generate javascript that will set the href= attribute on all anchors.
*/
void style_resolve_href(void){
  int i;
  int nDelay = db_get_int("auto-hyperlink-delay",10);
  if( !g.perm.Hyperlink ) return;
  if( nHref==0 && nFormAction==0 ) return;
  @ <script>

  @ function setAllHrefs(){
  if( g.javascriptHyperlink ){
    for(i=0; i<nHref; i++){
      @ gebi("a%d(i+1)").href="%s(aHref[i])";
    }
  }
  for(i=0; i<nFormAction; i++){







|
>







161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
** Generate javascript that will set the href= attribute on all anchors.
*/
void style_resolve_href(void){
  int i;
  int nDelay = db_get_int("auto-hyperlink-delay",10);
  if( !g.perm.Hyperlink ) return;
  if( nHref==0 && nFormAction==0 ) return;
  @ <script type="text/JavaScript">
  @ /* <![CDATA[ */
  @ function setAllHrefs(){
  if( g.javascriptHyperlink ){
    for(i=0; i<nHref; i++){
      @ gebi("a%d(i+1)").href="%s(aHref[i])";
    }
  }
  for(i=0; i<nFormAction; i++){
190
191
192
193
194
195
196

197
198
199
200
201
202
203
    @   setTimeout("setAllHrefs();",%d(nDelay));
    @   this.onmousemove = null;
    @ }
  }else{
    /* Active hyperlinks right away */
    @ setTimeout("setAllHrefs();",%d(nDelay));
  }

  @ </script>
}

/*
** Add a new element to the submenu
*/
void style_submenu_element(







>







190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
    @   setTimeout("setAllHrefs();",%d(nDelay));
    @   this.onmousemove = null;
    @ }
  }else{
    /* Active hyperlinks right away */
    @ setTimeout("setAllHrefs();",%d(nDelay));
  }
  @ /* ]]> */
  @ </script>
}

/*
** Add a new element to the submenu
*/
void style_submenu_element(
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
  Th_Store("release_version", RELEASE_VERSION);
  Th_Store("manifest_version", MANIFEST_VERSION);
  Th_Store("manifest_date", MANIFEST_DATE);
  Th_Store("compiler_name", COMPILER_NAME);
  url_var("stylesheet", "css", "style.css");
  image_url_var("logo");
  image_url_var("background");
  if( !login_is_nobody() ){
    Th_Store("login", g.zLogin);
  }
  if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
  Th_Render(zHeader);
  if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1);
  Th_Unstore("title");   /* Avoid collisions with ticket field names */
  cgi_destination(CGI_BODY);







|







307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
  Th_Store("release_version", RELEASE_VERSION);
  Th_Store("manifest_version", MANIFEST_VERSION);
  Th_Store("manifest_date", MANIFEST_DATE);
  Th_Store("compiler_name", COMPILER_NAME);
  url_var("stylesheet", "css", "style.css");
  image_url_var("logo");
  image_url_var("background");
  if( g.zLogin ){
    Th_Store("login", g.zLogin);
  }
  if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1);
  Th_Render(zHeader);
  if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1);
  Th_Unstore("title");   /* Avoid collisions with ticket field names */
  cgi_destination(CGI_BODY);
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
** Append ad unit text if appropriate.
*/
static void style_ad_unit(void){
  const char *zAd;
  if( g.perm.Admin && db_get_boolean("adunit-omit-if-admin",0) ){
    return;
  }
  if( !login_is_nobody()
   && fossil_strcmp(g.zLogin,"anonymous")!=0
   && db_get_boolean("adunit-omit-if-user",0)
  ){
    return;
  }
  zAd = db_get("adunit", 0);
  if( zAd ) cgi_append_content(zAd, -1);
}

/*







<
|
|
<







345
346
347
348
349
350
351

352
353

354
355
356
357
358
359
360
** Append ad unit text if appropriate.
*/
static void style_ad_unit(void){
  const char *zAd;
  if( g.perm.Admin && db_get_boolean("adunit-omit-if-admin",0) ){
    return;
  }

  if( g.zLogin && strcmp(g.zLogin,"anonymous")!=0
      && db_get_boolean("adunit-omit-if-user",0) ){

    return;
  }
  zAd = db_get("adunit", 0);
  if( zAd ) cgi_append_content(zAd, -1);
}

/*
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
  },
  { ".filetree ul",
    "tree-view lists",
    @   margin: 0;
    @   padding: 0;
    @   list-style: none;
  },
  { ".filetree ul.collapsed",
    "tree-view collapsed list",
    @   display: none;
  },
  { ".filetree ul ul",
    "tree-view lists below the root",
    @   position: relative;
    @   margin: 0 0 0 21px;
  },
  { ".filetree li",
    "tree-view lists items",
    @   position: relative;
    @   margin: 0;
    @   padding: 0;
  },
  { ".filetree li li:before",
    "tree-view node lines",
    @   content: '';
    @   position: absolute;
    @   top: -.8em;
    @   left: -14px;
    @   width: 14px;
    @   height: 1.5em;
    @   border-left: 2px solid #aaa;
    @   border-bottom: 2px solid #aaa;
  },
  { ".filetree li > ul:before",
    "tree-view directory lines",
    @   content: '';
    @   position: absolute;
    @   top: -1.5em;
    @   bottom: 0;
    @   left: -35px;
    @   border-left: 2px solid #aaa;
  },
  { ".filetree li.last > ul:before",
    "hide lines for last-child directories",
    @   display: none;
  },
  { ".filetree a",
    "tree-view links",
    @   position: relative;
    @   z-index: 1;







<
<
<
<








<
<












|








|







780
781
782
783
784
785
786




787
788
789
790
791
792
793
794


795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
  },
  { ".filetree ul",
    "tree-view lists",
    @   margin: 0;
    @   padding: 0;
    @   list-style: none;
  },




  { ".filetree ul ul",
    "tree-view lists below the root",
    @   position: relative;
    @   margin: 0 0 0 21px;
  },
  { ".filetree li",
    "tree-view lists items",
    @   position: relative;


  },
  { ".filetree li li:before",
    "tree-view node lines",
    @   content: '';
    @   position: absolute;
    @   top: -.8em;
    @   left: -14px;
    @   width: 14px;
    @   height: 1.5em;
    @   border-left: 2px solid #aaa;
    @   border-bottom: 2px solid #aaa;
  },
  { ".filetree ul ul:before",
    "tree-view directory lines",
    @   content: '';
    @   position: absolute;
    @   top: -1.5em;
    @   bottom: 0;
    @   left: -35px;
    @   border-left: 2px solid #aaa;
  },
  { ".filetree li:last-child > ul:before",
    "hide lines for last-child directories",
    @   display: none;
  },
  { ".filetree a",
    "tree-view links",
    @   position: relative;
    @   z-index: 1;
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177




1178
1179
1180
1181
1182
1183
1184
  { "table.tale-value th",
    "The label/value pairs on (for example) the ci page",
    @   vertical-align: top;
    @   text-align: right;
    @   padding: 0.2ex 2ex;
  },
  { ".statistics-report-graph-line",
    "for the /reports views",
    @   background-color: #446979;
  },
  { ".statistics-report-table-events th",
    "",
    @   padding: 0 1em 0 1em;
  },
  { ".statistics-report-table-events td",
    "",
    @   padding: 0.1em 1em 0.1em 1em;
  },
  { ".statistics-report-row-year",
    "",
    @   text-align: left;




  },
  { ".statistics-report-week-number-label",
    "for the /stats_report views",
    @ text-align: right;
    @ font-size: 0.8em;
  },
  { ".statistics-report-week-of-year-list",







|













>
>
>
>







1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
  { "table.tale-value th",
    "The label/value pairs on (for example) the ci page",
    @   vertical-align: top;
    @   text-align: right;
    @   padding: 0.2ex 2ex;
  },
  { ".statistics-report-graph-line",
    "for the /stats_report views",
    @   background-color: #446979;
  },
  { ".statistics-report-table-events th",
    "",
    @   padding: 0 1em 0 1em;
  },
  { ".statistics-report-table-events td",
    "",
    @   padding: 0.1em 1em 0.1em 1em;
  },
  { ".statistics-report-row-year",
    "",
    @   text-align: left;
  },
  { ".statistics-report-graph-line",
    "for the /stats_report views",
    @   background-color: #446979;
  },
  { ".statistics-report-week-number-label",
    "for the /stats_report views",
    @ text-align: right;
    @ font-size: 0.8em;
  },
  { ".statistics-report-week-of-year-list",
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
** WEBPAGE: test_env
*/
void page_test_env(void){
  char c;
  int i;
  int showAll;
  char zCap[30];
  static const char *const azCgiVars[] = {
    "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
    "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST",
    "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
    "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT", "REQUEST_METHOD",
    "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
  };







|







1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
** WEBPAGE: test_env
*/
void page_test_env(void){
  char c;
  int i;
  int showAll;
  char zCap[30];
  static const char *azCgiVars[] = {
    "COMSPEC", "DOCUMENT_ROOT", "GATEWAY_INTERFACE",
    "HTTP_ACCEPT", "HTTP_ACCEPT_CHARSET", "HTTP_ACCEPT_ENCODING",
    "HTTP_ACCEPT_LANGUAGE", "HTTP_CONNECTION", "HTTP_HOST",
    "HTTP_USER_AGENT", "HTTP_REFERER", "PATH_INFO", "PATH_TRANSLATED",
    "QUERY_STRING", "REMOTE_ADDR", "REMOTE_PORT", "REQUEST_METHOD",
    "REQUEST_URI", "SCRIPT_FILENAME", "SCRIPT_NAME", "SERVER_PROTOCOL",
  };
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
    if( login_has_capability(&c, 1) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  @ g.userUid = %d(g.userUid)<br />
  @ g.zLogin = %h(g.zLogin)<br />
  @ g.isHuman = %d(g.isHuman)<br />
  @ capabilities = %s(zCap)<br />
  @ g.zRepositoryName = %h(g.zRepositoryName)<br />
  @ load_average() = %f(load_average())<br />
  @ <hr>
  P("HTTP_USER_AGENT");
  cgi_print_all(showAll);
  if( showAll && blob_size(&g.httpHeader)>0 ){
    @ <hr>
    @ <pre>
    @ %h(blob_str(&g.httpHeader))







<
<







1295
1296
1297
1298
1299
1300
1301


1302
1303
1304
1305
1306
1307
1308
    if( login_has_capability(&c, 1) ) zCap[i++] = c;
  }
  zCap[i] = 0;
  @ g.userUid = %d(g.userUid)<br />
  @ g.zLogin = %h(g.zLogin)<br />
  @ g.isHuman = %d(g.isHuman)<br />
  @ capabilities = %s(zCap)<br />


  @ <hr>
  P("HTTP_USER_AGENT");
  cgi_print_all(showAll);
  if( showAll && blob_size(&g.httpHeader)>0 ){
    @ <hr>
    @ <pre>
    @ %h(blob_str(&g.httpHeader))

Changes to src/sync.c.

46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
    if( is_false(zAutosync) ){
      return 0;   /* Autosync is completely off */
    }
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  url_parse(0, URL_REMEMBER);
  if( g.url.protocol==0 ) return 0;  
  if( g.url.user!=0 && g.url.passwd==0 ){
    g.url.passwd = unobscure(db_get("last-sync-pw", 0));
    g.url.flags |= URL_PROMPT_PW;
    url_prompt_for_password();
  }
  g.zHttpAuth = get_httpauth();
  url_remember();
#if 0 /* Disabled for now */
  if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
    /* When doing an automatic pull, also automatically pull shuns from
    ** the server if pull_shuns is enabled.
    **
    ** TODO:  What happens if the shun list gets really big? 
    ** Maybe the shunning list should only be pulled on every 10th
    ** autosync, or something?
    */
    configSync = CONFIGSET_SHUN;
  }
#endif
  if( find_option("verbose","v",0)!=0 ) flags |= SYNC_VERBOSE;
  fossil_print("Autosync:  %s\n", g.url.canonical);
  url_enable_proxy("via proxy: ");
  rc = client_sync(flags, configSync, 0);
  if( rc ) fossil_warning("Autosync failed");
  return rc;
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){
  const char *zUrl = 0;
  const char *zHttpAuth = 0;
  unsigned configSync = 0;
  unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW;
  int urlOptional = 0;
  if( find_option("autourl",0,0)!=0 ){
    urlOptional = 1;
    urlFlags = 0;
  }
  zHttpAuth = find_option("httpauth","B",1);
  if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER;
  if( find_option("private",0,0)!=0 ){
    *pSyncFlags |= SYNC_PRIVATE;
  }
  if( find_option("verbose","v",0)!=0 ){
    *pSyncFlags |= SYNC_VERBOSE;
  }







|
|
|
|


<














|














<







<







46
47
48
49
50
51
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87

88
89
90
91
92
93
94

95
96
97
98
99
100
101
    if( is_false(zAutosync) ){
      return 0;   /* Autosync is completely off */
    }
  }else{
    /* Autosync defaults on.  To make it default off, "return" here. */
  }
  url_parse(0, URL_REMEMBER);
  if( g.urlProtocol==0 ) return 0;  
  if( g.urlUser!=0 && g.urlPasswd==0 ){
    g.urlPasswd = unobscure(db_get("last-sync-pw", 0));
    g.urlFlags |= URL_PROMPT_PW;
    url_prompt_for_password();
  }

  url_remember();
#if 0 /* Disabled for now */
  if( (flags & AUTOSYNC_PULL)!=0 && db_get_boolean("auto-shun",1) ){
    /* When doing an automatic pull, also automatically pull shuns from
    ** the server if pull_shuns is enabled.
    **
    ** TODO:  What happens if the shun list gets really big? 
    ** Maybe the shunning list should only be pulled on every 10th
    ** autosync, or something?
    */
    configSync = CONFIGSET_SHUN;
  }
#endif
  if( find_option("verbose","v",0)!=0 ) flags |= SYNC_VERBOSE;
  fossil_print("Autosync:  %s\n", g.urlCanonical);
  url_enable_proxy("via proxy: ");
  rc = client_sync(flags, configSync, 0);
  if( rc ) fossil_warning("Autosync failed");
  return rc;
}

/*
** This routine processes the command-line argument for push, pull,
** and sync.  If a command-line argument is given, that is the URL
** of a server to sync against.  If no argument is given, use the
** most recently synced URL.  Remember the current URL for next time.
*/
static void process_sync_args(unsigned *pConfigFlags, unsigned *pSyncFlags){
  const char *zUrl = 0;

  unsigned configSync = 0;
  unsigned urlFlags = URL_REMEMBER | URL_PROMPT_PW;
  int urlOptional = 0;
  if( find_option("autourl",0,0)!=0 ){
    urlOptional = 1;
    urlFlags = 0;
  }

  if( find_option("once",0,0)!=0 ) urlFlags &= ~URL_REMEMBER;
  if( find_option("private",0,0)!=0 ){
    *pSyncFlags |= SYNC_PRIVATE;
  }
  if( find_option("verbose","v",0)!=0 ){
    *pSyncFlags |= SYNC_VERBOSE;
  }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( urlFlags & URL_REMEMBER ){
    clone_ssh_db_set_options();
  }
  url_parse(zUrl, urlFlags);
  remember_or_get_http_auth(zHttpAuth, urlFlags & URL_REMEMBER, zUrl);
  url_remember();
  if( g.url.protocol==0 ){
    if( urlOptional ) fossil_exit(0);
    usage("URL");
  }
  user_select();
  if( g.argc==2 ){
    if( ((*pSyncFlags) & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL) ){
      fossil_print("Sync with %s\n", g.url.canonical);
    }else if( (*pSyncFlags) & SYNC_PUSH ){
      fossil_print("Push to %s\n", g.url.canonical);
    }else if( (*pSyncFlags) & SYNC_PULL ){
      fossil_print("Pull from %s\n", g.url.canonical);
    }
  }
  url_enable_proxy("via proxy: ");
  *pConfigFlags |= configSync;
}

/*







<

|






|

|

|







114
115
116
117
118
119
120

121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
  }else if( g.argc==3 ){
    zUrl = g.argv[2];
  }
  if( urlFlags & URL_REMEMBER ){
    clone_ssh_db_set_options();
  }
  url_parse(zUrl, urlFlags);

  url_remember();
  if( g.urlProtocol==0 ){
    if( urlOptional ) fossil_exit(0);
    usage("URL");
  }
  user_select();
  if( g.argc==2 ){
    if( ((*pSyncFlags) & (SYNC_PUSH|SYNC_PULL))==(SYNC_PUSH|SYNC_PULL) ){
      fossil_print("Sync with %s\n", g.urlCanonical);
    }else if( (*pSyncFlags) & SYNC_PUSH ){
      fossil_print("Push to %s\n", g.urlCanonical);
    }else if( (*pSyncFlags) & SYNC_PULL ){
      fossil_print("Pull from %s\n", g.urlCanonical);
    }
  }
  url_enable_proxy("via proxy: ");
  *pConfigFlags |= configSync;
}

/*
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
  db_find_and_open_repository(0, 0);
  if( g.argc!=2 && g.argc!=3 ){
    usage("remote-url ?URL|off?");
  }
  if( g.argc==3 ){
    db_unset("last-sync-url", 0);
    db_unset("last-sync-pw", 0);
    db_unset("http-auth", 0);
    if( is_false(g.argv[2]) ) return;
    url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW);
  }
  url_remember();
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    fossil_print("off\n");
    return;
  }else{
    url_parse(zUrl, 0);
    fossil_print("%s\n", g.url.canonical);
  }
}







<










|


261
262
263
264
265
266
267

268
269
270
271
272
273
274
275
276
277
278
279
280
  db_find_and_open_repository(0, 0);
  if( g.argc!=2 && g.argc!=3 ){
    usage("remote-url ?URL|off?");
  }
  if( g.argc==3 ){
    db_unset("last-sync-url", 0);
    db_unset("last-sync-pw", 0);

    if( is_false(g.argv[2]) ) return;
    url_parse(g.argv[2], URL_REMEMBER|URL_PROMPT_PW|URL_ASK_REMEMBER_PW);
  }
  url_remember();
  zUrl = db_get("last-sync-url", 0);
  if( zUrl==0 ){
    fossil_print("off\n");
    return;
  }else{
    url_parse(zUrl, 0);
    fossil_print("%s\n", g.urlCanonical);
  }
}

Changes to src/tag.c.

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
  blob_appendf(&ctrl, "T %c%s%F %b",
               zTagtype[tagtype], zPrefix, zTagname, &uuid);
  if( tagtype>0 && zValue && zValue[0] ){
    blob_appendf(&ctrl, " %F\n", zValue);
  }else{
    blob_appendf(&ctrl, "\n");
  }
  blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : login_name());
  md5sum_blob(&ctrl, &cksum);
  blob_appendf(&ctrl, "Z %b\n", &cksum);
  nrid = content_put(&ctrl);
  manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
  assert( blob_is_reset(&ctrl) );
}








|







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
  blob_appendf(&ctrl, "T %c%s%F %b",
               zTagtype[tagtype], zPrefix, zTagname, &uuid);
  if( tagtype>0 && zValue && zValue[0] ){
    blob_appendf(&ctrl, " %F\n", zValue);
  }else{
    blob_appendf(&ctrl, "\n");
  }
  blob_appendf(&ctrl, "U %F\n", zUserOvrd ? zUserOvrd : g.zLogin);
  md5sum_blob(&ctrl, &cksum);
  blob_appendf(&ctrl, "Z %b\n", &cksum);
  nrid = content_put(&ctrl);
  manifest_crosslink(nrid, &ctrl, MC_PERMIT_HOOKS);
  assert( blob_is_reset(&ctrl) );
}

593
594
595
596
597
598
599






600
601
    "                                      WHERE tagname GLOB 'sym-*'))"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()
  );
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  @ <br />






  style_footer();
}







>
>
>
>
>
>


593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
    "                                      WHERE tagname GLOB 'sym-*'))"
    " ORDER BY event.mtime DESC",
    timeline_query_for_www()
  );
  www_print_timeline(&q, 0, 0, 0, 0);
  db_finalize(&q);
  @ <br />
  @ <script  type="text/JavaScript">
  @ function xin(id){
  @ }
  @ function xout(id){
  @ }
  @ </script>
  style_footer();
}

Changes to src/tar.c.

334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
  const char *zName,      /* Name of directory including final "/" */
  int nName,              /* Characters in zName */
  unsigned int mTime      /* Modification time */
){
  int i;
  for(i=nName-1; i>0 && zName[i]!='/'; i--){}
  if( i<=0 ) return;
  if( i<tball.nPrevDirAlloc 
   && strncmp(tball.zPrevDir, zName, i)==0
   && tball.zPrevDir[i]==0 ) return;
  db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName);
  if( sqlite3_changes(g.db)==0 ) return;
  tar_add_directory_of(zName, i-1, mTime);
  tar_add_header(zName, i, 0755, mTime, 0, '5');
  if( i >= tball.nPrevDirAlloc ){
    int nsize = tball.nPrevDirAlloc * 2;
    if(i+1 > nsize)







|
|
<







334
335
336
337
338
339
340
341
342

343
344
345
346
347
348
349
  const char *zName,      /* Name of directory including final "/" */
  int nName,              /* Characters in zName */
  unsigned int mTime      /* Modification time */
){
  int i;
  for(i=nName-1; i>0 && zName[i]!='/'; i--){}
  if( i<=0 ) return;
  if( i < tball.nPrevDirAlloc && tball.zPrevDir[i]==0 &&
        memcmp(tball.zPrevDir, zName, i)==0 ) return;

  db_multi_exec("INSERT OR IGNORE INTO dir VALUES('%#q')", i, zName);
  if( sqlite3_changes(g.db)==0 ) return;
  tar_add_directory_of(zName, i-1, mTime);
  tar_add_header(zName, i, 0755, mTime, 0, '5');
  if( i >= tball.nPrevDirAlloc ){
    int nsize = tball.nPrevDirAlloc * 2;
    if(i+1 > nsize)
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601

/*
** WEBPAGE: tarball
** URL: /tarball/RID.tar.gz
**
** Generate a compressed tarball for a checkin.
** Return that tarball as the HTTP reply content.
**
** Optional URL Parameters:
**
** - name=base name of the output file. Defaults to
** something project/version-specific.
**
** - uuid=the version to tar (may be a tag/branch name).
** Defaults to trunk.
**
*/
void tarball_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob tarball;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){
    /* Special case:  Remove the ".tar.gz" suffix.  */
    nName -= 7;







<
<
<
<
<
<
<
<
<









<







568
569
570
571
572
573
574









575
576
577
578
579
580
581
582
583

584
585
586
587
588
589
590

/*
** WEBPAGE: tarball
** URL: /tarball/RID.tar.gz
**
** Generate a compressed tarball for a checkin.
** Return that tarball as the HTTP reply content.









*/
void tarball_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob tarball;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(); return; }

  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  if( nName>7 && fossil_strcmp(&zName[nName-7], ".tar.gz")==0 ){
    /* Special case:  Remove the ".tar.gz" suffix.  */
    nName -= 7;

Changes to src/th.c.

126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
**
** results in nByte being set to 2.
*/
static int thNextCommand(Th_Interp*, const char *z, int n, int *pN);
static int thNextEscape (Th_Interp*, const char *z, int n, int *pN);
static int thNextVarname(Th_Interp*, const char *z, int n, int *pN);
static int thNextNumber (Th_Interp*, const char *z, int n, int *pN);
static int thNextInteger (Th_Interp*, const char *z, int n, int *pN);
static int thNextSpace  (Th_Interp*, const char *z, int n, int *pN);

/*
** Given that the input string (z, n) contains a language construct of
** the relevant type (a command enclosed in [], an escape sequence
** like "\xFF" or a variable reference like "${varname}", perform
** substitution on the string and store the resulting string in







<







126
127
128
129
130
131
132

133
134
135
136
137
138
139
**
** results in nByte being set to 2.
*/
static int thNextCommand(Th_Interp*, const char *z, int n, int *pN);
static int thNextEscape (Th_Interp*, const char *z, int n, int *pN);
static int thNextVarname(Th_Interp*, const char *z, int n, int *pN);
static int thNextNumber (Th_Interp*, const char *z, int n, int *pN);

static int thNextSpace  (Th_Interp*, const char *z, int n, int *pN);

/*
** Given that the input string (z, n) contains a language construct of
** the relevant type (a command enclosed in [], an escape sequence
** like "\xFF" or a variable reference like "${varname}", perform
** substitution on the string and store the resulting string in
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
  {"&",  OP_BITWISE_AND,    8, ARG_INTEGER},
  {"^",  OP_BITWISE_XOR,    9, ARG_INTEGER},
  {"|",  OP_BITWISE_OR,    10, ARG_INTEGER},

  {0,0,0,0}
};

/*
** The first part of the string (zInput,nInput) contains an integer.
** Set *pnVarname to the number of bytes in the numeric string.
*/
static int thNextInteger(
  Th_Interp *interp,
  const char *zInput,
  int nInput,
  int *pnLiteral
){
  int i;
  int (*isdigit)(char) = th_isdigit;
  char c;

  if( nInput<2) return TH_ERROR;
  assert(zInput[0]=='0');
  c = zInput[1];
  if( c>='A' && c<='Z' ) c += 'a' - 'A';
  if( c=='x' ){
    isdigit = th_ishexdig;
  }else if( c!='o' && c!='b' ){
    return TH_ERROR;
  }
  for(i=2; i<nInput; i++){
    c = zInput[i];
    if( !isdigit(c) ){
      break;
    }
  }
  *pnLiteral = i;
  return TH_OK;
}

/*
** The first part of the string (zInput,nInput) contains a number.
** Set *pnVarname to the number of bytes in the numeric string.
*/
static int thNextNumber(
  Th_Interp *interp,
  const char *zInput,
  int nInput,
  int *pnLiteral
){
  int i = 0;
  int seenDot = 0;
  for(; i<nInput; i++){
    char c = zInput[i];
    if( (seenDot || c!='.') && !th_isdigit(c) ) break;
    if( c=='.' ) seenDot = 1;
  }
  *pnLiteral = i;
  return TH_OK;
}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<










|

|







1865
1866
1867
1868
1869
1870
1871

































1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
  {"&",  OP_BITWISE_AND,    8, ARG_INTEGER},
  {"^",  OP_BITWISE_XOR,    9, ARG_INTEGER},
  {"|",  OP_BITWISE_OR,    10, ARG_INTEGER},

  {0,0,0,0}
};


































/*
** The first part of the string (zInput,nInput) contains a number.
** Set *pnVarname to the number of bytes in the numeric string.
*/
static int thNextNumber(
  Th_Interp *interp,
  const char *zInput,
  int nInput,
  int *pnLiteral
){
  int i;
  int seenDot = 0;
  for(i=0; i<nInput; i++){
    char c = zInput[i];
    if( (seenDot || c!='.') && !th_isdigit(c) ) break;
    if( c=='.' ) seenDot = 1;
  }
  *pnLiteral = i;
  return TH_OK;
}
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
        case OP_BITWISE_NOT:  iRes = ~iLeft;        break;
        case OP_LOGICAL_NOT:  iRes = !iLeft;        break;
        default: assert(!"Internal error");
      }
      Th_SetResultInt(interp, iRes);
    }else if( rc==TH_OK && eArgType==ARG_NUMBER ){
      switch( pExpr->pOp->eOp ) {
        case OP_MULTIPLY: Th_SetResultDouble(interp, fLeft*fRight);    break;
        case OP_DIVIDE:
          if( fRight==0.0 ){
            Th_ErrorMessage(interp, "Divide by 0:", zLeft, nLeft);
            rc = TH_ERROR;
            goto finish;
          }
          Th_SetResultDouble(interp, fLeft/fRight);
          break;
        case OP_ADD:         Th_SetResultDouble(interp, fLeft+fRight); break;
        case OP_SUBTRACT:    Th_SetResultDouble(interp, fLeft-fRight); break;
        case OP_LT:          Th_SetResultInt(interp, fLeft<fRight);    break;
        case OP_GT:          Th_SetResultInt(interp, fLeft>fRight);    break;
        case OP_LE:          Th_SetResultInt(interp, fLeft<=fRight);   break;
        case OP_GE:          Th_SetResultInt(interp, fLeft>=fRight);   break;
        case OP_EQ:          Th_SetResultInt(interp, fLeft==fRight);   break;
        case OP_NE:          Th_SetResultInt(interp, fLeft!=fRight);   break;
        case OP_UNARY_MINUS: Th_SetResultDouble(interp, -fLeft);       break;
        case OP_UNARY_PLUS:  Th_SetResultDouble(interp, +fLeft);       break;
        default: assert(!"Internal error");
      }
    }else if( rc==TH_OK ){
      int iEqual = 0;
      assert( eArgType==ARG_STRING );
      if( nRight==nLeft && 0==memcmp(zRight, zLeft, nRight) ){
        iEqual = 1;







|








|
|
|
|
|
|
|
|
<
<







2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024


2025
2026
2027
2028
2029
2030
2031
        case OP_BITWISE_NOT:  iRes = ~iLeft;        break;
        case OP_LOGICAL_NOT:  iRes = !iLeft;        break;
        default: assert(!"Internal error");
      }
      Th_SetResultInt(interp, iRes);
    }else if( rc==TH_OK && eArgType==ARG_NUMBER ){
      switch( pExpr->pOp->eOp ) {
        case OP_MULTIPLY: Th_SetResultDouble(interp, fLeft*fRight);  break;
        case OP_DIVIDE:
          if( fRight==0.0 ){
            Th_ErrorMessage(interp, "Divide by 0:", zLeft, nLeft);
            rc = TH_ERROR;
            goto finish;
          }
          Th_SetResultDouble(interp, fLeft/fRight);
          break;
        case OP_ADD:      Th_SetResultDouble(interp, fLeft+fRight);  break;
        case OP_SUBTRACT: Th_SetResultDouble(interp, fLeft-fRight);  break;
        case OP_LT:       Th_SetResultInt(interp, fLeft<fRight);  break;
        case OP_GT:       Th_SetResultInt(interp, fLeft>fRight);  break;
        case OP_LE:       Th_SetResultInt(interp, fLeft<=fRight); break;
        case OP_GE:       Th_SetResultInt(interp, fLeft>=fRight); break;
        case OP_EQ:       Th_SetResultInt(interp, fLeft==fRight); break;
        case OP_NE:       Th_SetResultInt(interp, fLeft!=fRight); break;


        default: assert(!"Internal error");
      }
    }else if( rc==TH_OK ){
      int iEqual = 0;
      assert( eArgType==ARG_STRING );
      if( nRight==nLeft && 0==memcmp(zRight, zLeft, nRight) ){
        iEqual = 1;
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
    if( th_isspace(c) ){                                /* White-space     */
      i++;
    }else{
      Expr *pNew = (Expr *)Th_Malloc(interp, sizeof(Expr));
      const char *z = &zExpr[i];

      switch (c) {
        case '0':
          if( thNextInteger(interp, z, nExpr-i, &pNew->nValue)==TH_OK ){
            break;
          }
          /* fall through */
        case '1': case '2': case '3': case '4': case '5':
        case '6': case '7': case '8': case '9':
          thNextNumber(interp, z, nExpr-i, &pNew->nValue);
          break;

        case '$':
          thNextVarname(interp, z, nExpr-i, &pNew->nValue);
          break;








<
<
<
<
<
|
|







2151
2152
2153
2154
2155
2156
2157





2158
2159
2160
2161
2162
2163
2164
2165
2166
    if( th_isspace(c) ){                                /* White-space     */
      i++;
    }else{
      Expr *pNew = (Expr *)Th_Malloc(interp, sizeof(Expr));
      const char *z = &zExpr[i];

      switch (c) {





        case '0': case '1': case '2': case '3': case '4':
        case '5': case '6': case '7': case '8': case '9':
          thNextNumber(interp, z, nExpr-i, &pNew->nValue);
          break;

        case '$':
          thNextVarname(interp, z, nExpr-i, &pNew->nValue);
          break;

2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
**     '\n'   0x0A
**     '\v'   0x0B
**     '\f'   0x0C
**     '\r'   0x0D
**
** Whitespace characters have the 0x01 flag set. Decimal digits have the
** 0x2 flag set. Single byte printable characters have the 0x4 flag set.
** Alphabet characters have the 0x8 bit set. Hexadecimal digits have the
** 0x20 flag set.
**
** The special list characters have the 0x10 flag set
**
**    { } [ ] \ ; ' "
**
**    " 0x22
**
*/
static unsigned char aCharProp[256] = {
  0,  0,  0,  0,  0,  0,  0,  0,     0,  1,  1,  1,  1,  1,  0,  0,   /* 0x0. */
  0,  0,  1,  1,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x1. */
  5,  4, 20,  4,  4,  4,  4,  4,     4,  4,  4,  4,  4,  4,  4,  4,   /* 0x2. */
 38, 38, 38, 38, 38, 38, 38, 38,    38, 38,  4, 20,  4,  4,  4,  4,   /* 0x3. */
  4, 44, 44, 44, 44, 44, 44, 12,    12, 12, 12, 12, 12, 12, 12, 12,   /* 0x4. */
 12, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 20, 20, 20,  4,  4,   /* 0x5. */
  4, 44, 44, 44, 44, 44, 44, 12,    12, 12, 12, 12, 12, 12, 12, 12,   /* 0x6. */
 12, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 20,  4, 20,  4,  4,   /* 0x7. */

  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x8. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x9. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xA. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xB. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xC. */







|
<












|
|

|







2407
2408
2409
2410
2411
2412
2413
2414

2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
**     '\n'   0x0A
**     '\v'   0x0B
**     '\f'   0x0C
**     '\r'   0x0D
**
** Whitespace characters have the 0x01 flag set. Decimal digits have the
** 0x2 flag set. Single byte printable characters have the 0x4 flag set.
** Alphabet characters have the 0x8 bit set.

**
** The special list characters have the 0x10 flag set
**
**    { } [ ] \ ; ' "
**
**    " 0x22
**
*/
static unsigned char aCharProp[256] = {
  0,  0,  0,  0,  0,  0,  0,  0,     0,  1,  1,  1,  1,  1,  0,  0,   /* 0x0. */
  0,  0,  1,  1,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x1. */
  5,  4, 20,  4,  4,  4,  4,  4,     4,  4,  4,  4,  4,  4,  4,  4,   /* 0x2. */
  6,  6,  6,  6,  6,  6,  6,  6,     6,  6,  4, 20,  4,  4,  4,  4,   /* 0x3. */
  4, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 12, 12, 12, 12, 12,   /* 0x4. */
 12, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 20, 20, 20,  4,  4,   /* 0x5. */
  4, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 12, 12, 12, 12, 12,   /* 0x6. */
 12, 12, 12, 12, 12, 12, 12, 12,    12, 12, 12, 20,  4, 20,  4,  4,   /* 0x7. */

  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x8. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0x9. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xA. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xB. */
  0,  0,  0,  0,  0,  0,  0,  0,     0,  0,  0,  0,  0,  0,  0,  0,   /* 0xC. */
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
}
int th_isspecial(char c){
  return (aCharProp[(unsigned char)c] & 0x11);
}
int th_isalnum(char c){
  return (aCharProp[(unsigned char)c] & 0x0A);
}
int th_isalpha(char c){
  return (aCharProp[(unsigned char)c] & 0x08);
}
int th_ishexdig(char c){
  return (aCharProp[(unsigned char)c] & 0x20);
}
int th_isoctdig(char c){
  return ((c|7) == '7');
}
int th_isbindig(char c){
  return ((c|1) == '1');
}

#ifndef LONGDOUBLE_TYPE
# define LONGDOUBLE_TYPE long double
#endif


/*







<
<
<
<
<
<
<
<
<
<
<
<







2451
2452
2453
2454
2455
2456
2457












2458
2459
2460
2461
2462
2463
2464
}
int th_isspecial(char c){
  return (aCharProp[(unsigned char)c] & 0x11);
}
int th_isalnum(char c){
  return (aCharProp[(unsigned char)c] & 0x0A);
}













#ifndef LONGDOUBLE_TYPE
# define LONGDOUBLE_TYPE long double
#endif


/*
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
** If the string cannot be converted to an integer, return TH_ERROR.
** If the interp argument is not NULL, leave an error message in the
** interpreter result too.
*/
int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){
  int i = 0;
  int iOut = 0;
  int base = 10;
  int (*isdigit)(char) = th_isdigit;

  if( n<0 ){
    n = th_strlen(z);
  }

  if( n>0 && (z[0]=='-' || z[0]=='+') ){
    i = 1;
  }
  if( n>2 ){
    if( z[i]=='0' ){
      if( z[i+1]=='x' || z[i+1]=='X' ){
        i += 2;
        base = 16;
        isdigit = th_ishexdig;
      }else if( z[i+1]=='o' || z[i+1]=='O' ){
        i += 2;
        base = 8;
        isdigit = th_isoctdig;
      }else if( z[i+1]=='b' || z[i+1]=='B' ){
        i += 2;
        base = 2;
        isdigit = th_isbindig;
      }
    }
  }
  for(; i<n; i++){
    char c = z[i];
    if( !isdigit(c) ){
      Th_ErrorMessage(interp, "expected integer, got: \"", z, n);
      return TH_ERROR;
    }
    if( c>='a' ){
      c -= 'a'-10;
    }else if( c>='A' ){
      c -= 'A'-10;
    }else{
      c -= '0';
    }
    iOut = iOut * base + c;
  }

  if( n>0 && z[0]=='-' ){
    iOut *= -1;
  }

  *piOut = iOut;







<
<








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<
|



<
<
<
<
<
<
<
|







2567
2568
2569
2570
2571
2572
2573


2574
2575
2576
2577
2578
2579
2580
2581

















2582

2583
2584
2585
2586







2587
2588
2589
2590
2591
2592
2593
2594
** If the string cannot be converted to an integer, return TH_ERROR.
** If the interp argument is not NULL, leave an error message in the
** interpreter result too.
*/
int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){
  int i = 0;
  int iOut = 0;



  if( n<0 ){
    n = th_strlen(z);
  }

  if( n>0 && (z[0]=='-' || z[0]=='+') ){
    i = 1;
  }

















  for(; i<n; i++){

    if( !th_isdigit(z[i]) ){
      Th_ErrorMessage(interp, "expected integer, got: \"", z, n);
      return TH_ERROR;
    }







    iOut = iOut * 10 + (z[i] - 48);
  }

  if( n>0 && z[0]=='-' ){
    iOut *= -1;
  }

  *piOut = iOut;
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
  char *z = &zBuf[32];

  if( iVal<0 ){
    isNegative = 1;
    iVal = iVal * -1;
  }
  *(--z) = '\0';
  *(--z) = (char)(48+((unsigned)iVal%10));
  while( (iVal = ((unsigned)iVal/10))>0 ){
    *(--z) = (char)(48+((unsigned)iVal%10));
    assert(z>zBuf);
  }
  if( isNegative ){
    *(--z) = '-';
  }

  return Th_SetResult(interp, z, -1);







|
|
|







2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
  char *z = &zBuf[32];

  if( iVal<0 ){
    isNegative = 1;
    iVal = iVal * -1;
  }
  *(--z) = '\0';
  *(--z) = (char)(48+(iVal%10));
  while( (iVal = (iVal/10))>0 ){
    *(--z) = (char)(48+(iVal%10));
    assert(z>zBuf);
  }
  if( isNegative ){
    *(--z) = '-';
  }

  return Th_SetResult(interp, z, -1);

Changes to src/th.h.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

17
18
19
20
21
22
23

/* This header file defines the external interface to the custom Scripting
** Language (TH) interpreter.  TH is very similar to TCL but is not an
** exact clone.
*/

/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/
typedef struct Th_Vtab Th_Vtab;
struct Th_Vtab {
  void *(*xMalloc)(unsigned int);
  void (*xFree)(void *);
};


/*
** Opaque handle for interpeter.
*/
typedef struct Th_Interp Th_Interp;

/*











<




>







1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19
20
21
22
23

/* This header file defines the external interface to the custom Scripting
** Language (TH) interpreter.  TH is very similar to TCL but is not an
** exact clone.
*/

/*
** Before creating an interpreter, the application must allocate and
** populate an instance of the following structure. It must remain valid
** for the lifetime of the interpreter.
*/

struct Th_Vtab {
  void *(*xMalloc)(unsigned int);
  void (*xFree)(void *);
};
typedef struct Th_Vtab Th_Vtab;

/*
** Opaque handle for interpeter.
*/
typedef struct Th_Interp Th_Interp;

/*
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
/*
** Drop in replacements for the corresponding standard library functions.
*/
int th_strlen(const char *);
int th_isdigit(char);
int th_isspace(char);
int th_isalnum(char);
int th_isalpha(char);
int th_isspecial(char);
int th_ishexdig(char);
int th_isoctdig(char);
int th_isbindig(char);
char *th_strdup(Th_Interp *interp, const char *z, int n);

/*
** Interfaces to register the language extensions.
*/
int th_register_language(Th_Interp *interp);            /* th_lang.c */
int th_register_sqlite(Th_Interp *interp);              /* th_sqlite.c */







<

<
<
<







143
144
145
146
147
148
149

150



151
152
153
154
155
156
157
/*
** Drop in replacements for the corresponding standard library functions.
*/
int th_strlen(const char *);
int th_isdigit(char);
int th_isspace(char);
int th_isalnum(char);

int th_isspecial(char);



char *th_strdup(Th_Interp *interp, const char *z, int n);

/*
** Interfaces to register the language extensions.
*/
int th_register_language(Th_Interp *interp);            /* th_lang.c */
int th_register_sqlite(Th_Interp *interp);              /* th_sqlite.c */
188
189
190
191
192
193
194
195
196
Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int);

/*
** Useful functions from th_lang.c.
*/
int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg);

typedef struct Th_SubCommand {const char *zName; Th_CommandProc xProc;} Th_SubCommand;
int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,const Th_SubCommand*);







|
|
184
185
186
187
188
189
190
191
192
Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int);

/*
** Useful functions from th_lang.c.
*/
int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg);

typedef struct Th_SubCommand {char *zName; Th_CommandProc xProc;} Th_SubCommand;
int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,Th_SubCommand*);

Changes to src/th_lang.c.

423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  int rc;
  const char *zName;

  ProcDefn *p;
  int nByte;
  int i;
  char *zSpace;

  char **azParam;







|







423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  int rc;
  char *zName;

  ProcDefn *p;
  int nByte;
  int i;
  char *zSpace;

  char **azParam;
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
  if( p->hasArgs ){
    Th_StringAppend(interp, &zUsage, &nUsage, (const char *)" ?args...?", -1);
  }
  p->zUsage = zUsage;
  p->nUsage = nUsage;

  /* Register the new command with the th1 interpreter. */
  zName = argv[1];
  rc = Th_CreateCommand(interp, zName, proc_call1, (void *)p, proc_del);
  if( rc==TH_OK ){
    Th_SetResult(interp, 0, 0);
  }

  Th_Free(interp, azParam);
  return TH_OK;







|







519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
  if( p->hasArgs ){
    Th_StringAppend(interp, &zUsage, &nUsage, (const char *)" ?args...?", -1);
  }
  p->zUsage = zUsage;
  p->nUsage = nUsage;

  /* Register the new command with the th1 interpreter. */
  zName = (char *)argv[1];
  rc = Th_CreateCommand(interp, zName, proc_call1, (void *)p, proc_del);
  if( rc==TH_OK ){
    Th_SetResult(interp, 0, 0);
  }

  Th_Free(interp, azParam);
  return TH_OK;
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904

int Th_CallSubCommand(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  const Th_SubCommand *aSub
){
  if( argc>1 ){
    int i;
    for(i=0; aSub[i].zName; i++){
      const char *zName = aSub[i].zName;
      if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
        return aSub[i].xProc(interp, ctx, argc, argv, argl);
      }
    }
  }
  if(argc<2){
    Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);







|




|







885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904

int Th_CallSubCommand(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl,
  Th_SubCommand *aSub
){
  if( argc>1 ){
    int i;
    for(i=0; aSub[i].zName; i++){
      char *zName = (char *)aSub[i].zName;
      if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){
        return aSub[i].xProc(interp, ctx, argc, argv, argl);
      }
    }
  }
  if(argc<2){
    Th_ErrorMessage(interp, "Expected sub-command for", argv[0], argl[0]);
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
static int string_command(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  static const Th_SubCommand aSub[] = {
    { "compare", string_compare_command },
    { "first",   string_first_command },
    { "is",      string_is_command },
    { "last",    string_last_command },
    { "length",  string_length_command },
    { "range",   string_range_command },
    { "repeat",  string_repeat_command },







|







922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
static int string_command(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  Th_SubCommand aSub[] = {
    { "compare", string_compare_command },
    { "first",   string_first_command },
    { "is",      string_is_command },
    { "last",    string_last_command },
    { "length",  string_length_command },
    { "range",   string_range_command },
    { "repeat",  string_repeat_command },
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
static int info_command(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  static const Th_SubCommand aSub[] = {
    { "exists",  info_exists_command },
    { 0, 0 }
  };
  return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
}

/*







|







950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
static int info_command(
  Th_Interp *interp,
  void *ctx,
  int argc,
  const char **argv,
  int *argl
){
  Th_SubCommand aSub[] = {
    { "exists",  info_exists_command },
    { 0, 0 }
  };
  return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub);
}

/*
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
    {"return",   return_command, 0},
    {"break",    simple_command, (void *)TH_BREAK},
    {"continue", simple_command, (void *)TH_CONTINUE},
    {"error",    simple_command, (void *)TH_ERROR},

    {0, 0, 0}
  };
  size_t i;

  /* Add the language commands. */
  for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
    void *ctx;
    if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
    ctx = aCommand[i].pContext;
    Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0);
  }

  return TH_OK;
}







|











1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
    {"return",   return_command, 0},
    {"break",    simple_command, (void *)TH_BREAK},
    {"continue", simple_command, (void *)TH_CONTINUE},
    {"error",    simple_command, (void *)TH_ERROR},

    {0, 0, 0}
  };
  int i;

  /* Add the language commands. */
  for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){
    void *ctx;
    if ( !aCommand[i].zName || !aCommand[i].xProc ) continue;
    ctx = aCommand[i].pContext;
    Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0);
  }

  return TH_OK;
}

Changes to src/timeline.c.

46
47
48
49
50
51
52
53
54
55
56
57













58
59
60
61
62
63
64
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
  char z[UUID_SIZE+1];
  shorten_uuid(z, zUuid);
  if( g.perm.Hyperlink ){
    @ %z(xhref("class='timelineHistLink'","%R/info/%s",zUuid))[%s(z)]</a>
  }else{
    @ <span class="timelineHistDsp">[%s(z)]</span>
  }
}














/*
** Generate a hyperlink to a date & time.
*/
void hyperlink_to_date(const char *zDate, const char *zSuffix){
  if( zSuffix==0 ) zSuffix = "";
  if( g.perm.Hyperlink ){







|




>
>
>
>
>
>
>
>
>
>
>
>
>







46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/*
** Generate a hyperlink to a version.
*/
void hyperlink_to_uuid(const char *zUuid){
  char z[UUID_SIZE+1];
  shorten_uuid(z, zUuid);
  if( g.perm.Hyperlink ){
    @ %z(xhref("class='timelineHistLink'","%R/info/%s",z))[%s(z)]</a>
  }else{
    @ <span class="timelineHistDsp">[%s(z)]</span>
  }
}

/*
** Generate a hyperlink to a diff between two versions.
*/
void hyperlink_to_diff(const char *zV1, const char *zV2){
  if( g.perm.Hyperlink ){
    if( zV2==0 ){
      @ %z(href("%R/diff?v2=%s",zV1))[diff]</a>
    }else{
      @ %z(href("%R/diff?v1=%s&v2=%s",zV1,zV2))[diff]</a>
    }
  }
}

/*
** Generate a hyperlink to a date & time.
*/
void hyperlink_to_date(const char *zDate, const char *zSuffix){
  if( zSuffix==0 ) zSuffix = "";
  if( g.perm.Hyperlink ){
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
  GraphContext *pGraph = 0;
  int prevWasDivider = 0;     /* True if previous output row was <hr> */
  int fchngQueryInit = 0;     /* True if fchngQuery is initialized */
  Stmt fchngQuery;            /* Query for file changes on check-ins */
  static Stmt qbranch;
  int pendingEndTr = 0;       /* True if a </td></tr> is needed */
  int vid = 0;                /* Current checkout version */
  int dateFormat = 0;         /* 0: HH:MM  1: HH:MM:SS
                                 2: YYYY-MM-DD HH:MM
                                 3: YYMMDD HH:MM */

  if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
    vid = db_lget_int("checkout", 0);
  }
  zPrevDate[0] = 0;
  mxWikiLen = db_get_int("timeline-max-comment", 0);
  dateFormat = db_get_int("timeline-date-format", 0);
  if( tmFlags & TIMELINE_GRAPH ){







|


|







245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
  GraphContext *pGraph = 0;
  int prevWasDivider = 0;     /* True if previous output row was <hr> */
  int fchngQueryInit = 0;     /* True if fchngQuery is initialized */
  Stmt fchngQuery;            /* Query for file changes on check-ins */
  static Stmt qbranch;
  int pendingEndTr = 0;       /* True if a </td></tr> is needed */
  int vid = 0;                /* Current checkout version */
  int dateFormat = 0;         /* 0: HH:MM  1: HH:MM:SS 
                                 2: YYYY-MM-DD HH:MM
                                 3: YYMMDD HH:MM */
  
  if( fossil_strcmp(g.zIpAddr, "127.0.0.1")==0 && db_open_local(0) ){
    vid = db_lget_int("checkout", 0);
  }
  zPrevDate[0] = 0;
  mxWikiLen = db_get_int("timeline-max-comment", 0);
  dateFormat = db_get_int("timeline-date-format", 0);
  if( tmFlags & TIMELINE_GRAPH ){
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
      @ (user: %z(href("%z",zLink))%h(zDispUser)</a>%s(zTagList?",":"\051")
    }else{
      @ (user: %h(zDispUser)%s(zTagList?",":"\051")
    }

    /* Generate a "detail" link for tags. */
    if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
      @ [%z(href("%R/info/%s",zUuid))details</a>]
    }

    /* Generate the "tags: TAGLIST" at the end of the comment, together
    ** with hyperlinks to the tag list.
    */
    if( zTagList ){
      if( g.perm.Hyperlink ){







|







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
      @ (user: %z(href("%z",zLink))%h(zDispUser)</a>%s(zTagList?",":"\051")
    }else{
      @ (user: %h(zDispUser)%s(zTagList?",":"\051")
    }

    /* Generate a "detail" link for tags. */
    if( (zType[0]=='g' || zType[0]=='w' || zType[0]=='t') && g.perm.Hyperlink ){
      @ [%z(href("%R/info/%S",zUuid))details</a>]
    }

    /* Generate the "tags: TAGLIST" at the end of the comment, together
    ** with hyperlinks to the tag list.
    */
    if( zTagList ){
      if( g.perm.Hyperlink ){
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
          if( !isNew && !isDel && zOldName!=0 ){
            @ <li> %h(zOldName) &rarr; %h(zFilename)
          }
          continue;
        }
        if( isNew ){
          @ <li> %h(zFilename) (new file) &nbsp;
          @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
        }else if( isDel ){
          @ <li> %h(zFilename) (deleted)</li>
        }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
          @ <li> %h(zOldName) &rarr; %h(zFilename)
          @ %z(href("%R/artifact/%s",zNew))[view]</a></li>
        }else{
          if( zOldName!=0 ){
            @ <li> %h(zOldName) &rarr; %h(zFilename)
          }else{
            @ <li> %h(zFilename) &nbsp;
          }
          @ %z(href("%R/fdiff?sbs=1&v1=%s&v2=%s",zOld,zNew))[diff]</a></li>
        }
      }
      db_reset(&fchngQuery);
      if( inUl ){
        @ </ul>
      }
    }







|




|






|







525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
          if( !isNew && !isDel && zOldName!=0 ){
            @ <li> %h(zOldName) &rarr; %h(zFilename)
          }
          continue;
        }
        if( isNew ){
          @ <li> %h(zFilename) (new file) &nbsp;
          @ %z(href("%R/artifact/%S",zNew))[view]</a></li>
        }else if( isDel ){
          @ <li> %h(zFilename) (deleted)</li>
        }else if( fossil_strcmp(zOld,zNew)==0 && zOldName!=0 ){
          @ <li> %h(zOldName) &rarr; %h(zFilename)
          @ %z(href("%R/artifact/%S",zNew))[view]</a></li>
        }else{
          if( zOldName!=0 ){
            @ <li> %h(zOldName) &rarr; %h(zFilename)
          }else{
            @ <li> %h(zFilename) &nbsp;
          }
          @ %z(href("%R/fdiff?v1=%S&v2=%S&sbs=1",zOld,zNew))[diff]</a></li>
        }
      }
      db_reset(&fchngQuery);
      if( inUl ){
        @ </ul>
      }
    }
576
577
578
579
580
581
582
583
584

585
586
587
588
589
590
591
  int omitDescenders,       /* True to omit descenders */
  int fileDiff              /* True for file diff.  False for check-in diff */
){
  if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
    GraphRow *pRow;
    int i;
    char cSep;

    @ <script>

    @ var railPitch=%d(pGraph->iRailPitch);

    /* the rowinfo[] array contains all the information needed to generate
    ** the graph.  Each entry contains information for a single row:
    **
    **   id:  The id of the <div> element for the row. This is an integer.
    **        to get an actual id, prepend "m" to the integer.  The top node







|
|
>







589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
  int omitDescenders,       /* True to omit descenders */
  int fileDiff              /* True for file diff.  False for check-in diff */
){
  if( pGraph && pGraph->nErr==0 && pGraph->nRow>0 ){
    GraphRow *pRow;
    int i;
    char cSep;
    
    @ <script  type="text/JavaScript">
    @ /* <![CDATA[ */
    @ var railPitch=%d(pGraph->iRailPitch);

    /* the rowinfo[] array contains all the information needed to generate
    ** the graph.  Each entry contains information for a single row:
    **
    **   id:  The id of the <div> element for the row. This is an integer.
    **        to get an actual id, prepend "m" to the integer.  The top node
856
857
858
859
860
861
862

863
864
865
866
867
868
869
    @   if( h!=lastY ){
    @     renderGraph();
    @     lastY = h;
    @   }
    @   setTimeout("checkHeight();", 1000);
    @ }
    @ checkHeight();

    @ </script>
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/







>







870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
    @   if( h!=lastY ){
    @     renderGraph();
    @     lastY = h;
    @   }
    @   setTimeout("checkHeight();", 1000);
    @ }
    @ checkHeight();
    @ /* ]]> */
    @ </script>
  }
}

/*
** Create a temporary table suitable for storing timeline data.
*/
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
      blob_appendf(&desc, "%d most recent %ss", n, zEType);
    }else{
      blob_appendf(&desc, "%d %ss", n, zEType);
    }
    if( zUses ){
      char *zFilenames = names_of_file(zUses);
      blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
                   href("%R/artifact/%s",zUses), zUses);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( renameOnly ){
      blob_appendf(&desc, " that contain filename changes");
      tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
    }
    if( zUser ){







|







1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
      blob_appendf(&desc, "%d most recent %ss", n, zEType);
    }else{
      blob_appendf(&desc, "%d %ss", n, zEType);
    }
    if( zUses ){
      char *zFilenames = names_of_file(zUses);
      blob_appendf(&desc, " using file %s version %z%S</a>", zFilenames,
                   href("%R/artifact/%S",zUses), zUses);
      tmFlags |= TIMELINE_DISJOINT;
    }
    if( renameOnly ){
      blob_appendf(&desc, " that contain filename changes");
      tmFlags |= TIMELINE_DISJOINT|TIMELINE_FRENAMES;
    }
    if( zUser ){
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
     "  FROM plink p, plink c, blob"
     " WHERE p.cid=c.pid  AND p.mtime>c.mtime"
     "   AND blob.rid=c.cid"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    @ <li>
    @ <a href="%s(g.zTop)/timeline?p=%s(zUuid)&amp;d=%s(zUuid)&amp;unhide">%S(zUuid)</a>
  }
  db_finalize(&q);
  style_footer();
}


/*







|







1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
     "  FROM plink p, plink c, blob"
     " WHERE p.cid=c.pid  AND p.mtime>c.mtime"
     "   AND blob.rid=c.cid"
  );
  while( db_step(&q)==SQLITE_ROW ){
    const char *zUuid = db_column_text(&q, 0);
    @ <li>
    @ <a href="%s(g.zTop)/timeline?p=%S(zUuid)&amp;d=%S(zUuid)&amp;unhide">%S(zUuid)</a>
  }
  db_finalize(&q);
  style_footer();
}


/*
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
      return "all types";
  }
}

/*
** A helper for the /reports family of pages which prints out a menu
** of links for the various type=XXX flags. zCurrentViewName must be
** the name/value of the 'view' parameter which is in effect at the
** time this is called. e.g. if called from the 'byuser' view then
** zCurrentViewName must be "byuser". Any URL parameters which need to
** be added to the generated URLs should be passed in zParam. The
** caller is expected to have already encoded any zParam in the %T or
** %t encoding.  */
static void stats_report_event_types_menu(char const * zCurrentViewName,
                                          char const * zParam){
  char * zTop;
  if(zParam && !*zParam){
    zParam = NULL;
  }
  zTop = mprintf("%s/reports?view=%s%s%s", g.zTop, zCurrentViewName,
                 zParam ? "&" : "", zParam);
  cgi_printf("<div>");
  cgi_printf("<span>Event types:</span> ");
  if('*' == statsReportType){
    cgi_printf(" <strong>all</strong>", zTop);
  }else{
    cgi_printf(" <a href='%s'>all</a>", zTop);
  }







|
|
|
<
<
|
|
<
<
<
<
<
|
<







2075
2076
2077
2078
2079
2080
2081
2082
2083
2084


2085
2086





2087

2088
2089
2090
2091
2092
2093
2094
      return "all types";
  }
}

/*
** A helper for the /reports family of pages which prints out a menu
** of links for the various type=XXX flags. zCurrentViewName must be
** the name/value of the 'view' parameter which is in effect at
** the time this is called. e.g. if called from the 'byuser' view
** then zCurrentViewName must be "byuser".


*/
static void stats_report_event_types_menu(char const * zCurrentViewName){





  char * zTop = mprintf("%s/reports?view=%s", g.zTop, zCurrentViewName);

  cgi_printf("<div>");
  cgi_printf("<span>Event types:</span> ");
  if('*' == statsReportType){
    cgi_printf(" <strong>all</strong>", zTop);
  }else{
    cgi_printf(" <a href='%s'>all</a>", zTop);
  }
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
                                        the per-year event totals */
  Blob header = empty_blob;          /* Page header text */
  int nMaxEvents  = 1;               /* for calculating length of graph
                                        bars. */
  int iterations = 0;                /* number of weeks/months we iterate
                                        over */
  stats_report_init_view();
  stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear", NULL );
  blob_appendf(&header, "Timeline Events (%s) by year%s",
               stats_report_label_for_type(),
               (includeMonth ? "/month" : ""));
  blob_appendf(&sql,
               "SELECT substr(date(mtime),1,%d) AS timeframe, "
               "count(*) AS eventCount "
               "FROM v_reports ",







|







2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
                                        the per-year event totals */
  Blob header = empty_blob;          /* Page header text */
  int nMaxEvents  = 1;               /* for calculating length of graph
                                        bars. */
  int iterations = 0;                /* number of weeks/months we iterate
                                        over */
  stats_report_init_view();
  stats_report_event_types_menu( includeMonth ? "bymonth" : "byyear" );
  blob_appendf(&header, "Timeline Events (%s) by year%s",
               stats_report_label_for_type(),
               (includeMonth ? "/month" : ""));
  blob_appendf(&sql,
               "SELECT substr(date(mtime),1,%d) AS timeframe, "
               "count(*) AS eventCount "
               "FROM v_reports ",
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
         (0!=fossil_strncmp(zPrevYear,zTimeframe,4))){
        showYearTotal = *zPrevYear;
        if(showYearTotal){
          rowClass = ++nRowNumber % 2;
          @ <tr class='row%d(rowClass)'>
          @ <td></td>
          @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td>
          @</tr>
        }
        nEventsPerYear = 0;
        memcpy(zPrevYear,zTimeframe,4);
        rowClass = ++nRowNumber % 2;
        @ <tr class='row%d(rowClass)'>
        @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th>
        @ </tr>







|







2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
         (0!=fossil_strncmp(zPrevYear,zTimeframe,4))){
        showYearTotal = *zPrevYear;
        if(showYearTotal){
          rowClass = ++nRowNumber % 2;
          @ <tr class='row%d(rowClass)'>
          @ <td></td>
          @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td>
          @</tr>    
        }
        nEventsPerYear = 0;
        memcpy(zPrevYear,zTimeframe,4);
        rowClass = ++nRowNumber % 2;
        @ <tr class='row%d(rowClass)'>
        @ <th colspan='3' class='statistics-report-row-year'>%s(zPrevYear)</th>
        @ </tr>
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
        cgi_printf("&u=%t", zUserName);
      }
      cgi_printf("'>%s</a>", zTimeframe);
    }
    @ </td><td>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
    if(includeWeeks){
      /* This part works fine for months but it terribly slow (4.5s on my PC),
         so it's only shown for by-year for now. Suggestions/patches for
         a better/faster layout are welcomed. */
      @ <tr class='row%d(rowClass)'>
      @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td>







|
|







2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
        cgi_printf("&u=%t", zUserName);
      }
      cgi_printf("'>%s</a>", zTimeframe);
    }
    @ </td><td>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='height:16px;width:%d(nSize)%%;'>
    @ </div></td>
    @</tr>
    if(includeWeeks){
      /* This part works fine for months but it terribly slow (4.5s on my PC),
         so it's only shown for by-year for now. Suggestions/patches for
         a better/faster layout are welcomed. */
      @ <tr class='row%d(rowClass)'>
      @ <td colspan='2' class='statistics-report-week-number-label'>Week #:</td>
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
  db_finalize(&query);
  if(includeMonth && !showYearTotal && *zPrevYear){
    /* Add final year total separator. */
    rowClass = ++nRowNumber % 2;
    @ <tr class='row%d(rowClass)'>
    @ <td></td>
    @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td>
    @</tr>
  }
  @ </tbody></table>
  if(nEventTotal){
    char const * zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br><div>Total events: %d(nEventTotal)
    @ <br>Average per active %s(zAvgLabel): %d(nAvg)







|







2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
  db_finalize(&query);
  if(includeMonth && !showYearTotal && *zPrevYear){
    /* Add final year total separator. */
    rowClass = ++nRowNumber % 2;
    @ <tr class='row%d(rowClass)'>
    @ <td></td>
    @ <td colspan='2'>Yearly total: %d(nEventsPerYear)</td>
    @</tr>    
  }
  @ </tbody></table>
  if(nEventTotal){
    char const * zAvgLabel = includeMonth ? "month" : "year";
    int nAvg = iterations ? (nEventTotal/iterations) : 0;
    @ <br><div>Total events: %d(nEventTotal)
    @ <br>Average per active %s(zAvgLabel): %d(nAvg)
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
  int nEventTotal = 0;               /* Total event count */
  int rowClass = 0;                  /* counter for alternating
                                        row colors */
  Blob sql = empty_blob;             /* SQL */
  int nMaxEvents = 1;                /* max number of events for
                                        all rows. */
  stats_report_init_view();
  stats_report_event_types_menu("byuser", NULL);
  blob_append(&sql,
               "SELECT user, "
               "COUNT(*) AS eventCount "
               "FROM v_reports "
               "GROUP BY user ORDER BY eventCount DESC",
              -1);
  db_prepare(&query, blob_str(&sql));







|







2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
  int nEventTotal = 0;               /* Total event count */
  int rowClass = 0;                  /* counter for alternating
                                        row colors */
  Blob sql = empty_blob;             /* SQL */
  int nMaxEvents = 1;                /* max number of events for
                                        all rows. */
  stats_report_init_view();
  stats_report_event_types_menu("byuser");
  blob_append(&sql,
               "SELECT user, "
               "COUNT(*) AS eventCount "
               "FROM v_reports "
               "GROUP BY user ORDER BY eventCount DESC",
              -1);
  db_prepare(&query, blob_str(&sql));
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
    nEventTotal += nCount;
    @<tr class='row%d(rowClass)'>
    @ <td>
    @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a>
    @ </td><td>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='width:%d(nSize)%%;'>&nbsp;</div>
    @ </td>
    @</tr>
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>







|
|







2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
    nEventTotal += nCount;
    @<tr class='row%d(rowClass)'>
    @ <td>
    @ <a href="?view=bymonth&user=%h(zUser)&type=%c((char)statsReportType)">%h(zUser)</a>
    @ </td><td>%d(nCount)</td>
    @ <td>
    @ <div class='statistics-report-graph-line'
    @  style='height:16px;width:%d(nSize)%%;'>
    @ </div></td>
    @</tr>
    /*
      Potential improvement: calculate the min/max event counts and
      use percent-based graph bars.
    */
  }
  @ </tbody></table>
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
  Stmt qYears = empty_Stmt;
  char * zDefaultYear = NULL;
  Blob sql = empty_blob;
  int nMaxEvents = 1;                /* max number of events for
                                        all rows. */
  int iterations = 0;                /* # of active time periods. */
  stats_report_init_view();
  if(4==nYear){
    Blob urlParams = empty_blob;
    blob_appendf(&urlParams, "y=%T", zYear);
    stats_report_event_types_menu("byweek", blob_str(&urlParams));
    blob_reset(&urlParams);
  }else{
    stats_report_event_types_menu("byweek", NULL);
  }
  blob_append(&sql,
              "SELECT DISTINCT substr(date(mtime),1,4) AS y "
              "FROM v_reports WHERE 1 ", -1);
  if(zUserName&&*zUserName){
    blob_appendf(&sql,"AND user=%Q ", zUserName);
  }
  blob_append(&sql,"GROUP BY y ORDER BY y", -1);
  db_prepare(&qYears, blob_str(&sql));
  blob_reset(&sql);
  cgi_printf("Select year: ");
  while( SQLITE_ROW == db_step(&qYears) ){
    const char * zT = db_column_text(&qYears, 0);
    if( i++ ){
      cgi_printf(" ");
    }
    cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT,
               (char)statsReportType);







<
<
<
|
<
|
<
<









<







2393
2394
2395
2396
2397
2398
2399



2400

2401


2402
2403
2404
2405
2406
2407
2408
2409
2410

2411
2412
2413
2414
2415
2416
2417
  Stmt qYears = empty_Stmt;
  char * zDefaultYear = NULL;
  Blob sql = empty_blob;
  int nMaxEvents = 1;                /* max number of events for
                                        all rows. */
  int iterations = 0;                /* # of active time periods. */
  stats_report_init_view();



  stats_report_event_types_menu("byweek");

  cgi_printf("Select year: ");


  blob_append(&sql,
              "SELECT DISTINCT substr(date(mtime),1,4) AS y "
              "FROM v_reports WHERE 1 ", -1);
  if(zUserName&&*zUserName){
    blob_appendf(&sql,"AND user=%Q ", zUserName);
  }
  blob_append(&sql,"GROUP BY y ORDER BY y", -1);
  db_prepare(&qYears, blob_str(&sql));
  blob_reset(&sql);

  while( SQLITE_ROW == db_step(&qYears) ){
    const char * zT = db_column_text(&qYears, 0);
    if( i++ ){
      cgi_printf(" ");
    }
    cgi_printf("<a href='?view=byweek&y=%s&type=%c", zT,
               (char)statsReportType);
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
      }
      cgi_printf("'>%s</a></td>",zWeek);

      cgi_printf("<td>%d</td>",nCount);
      cgi_printf("<td>");
      if(nCount){
        cgi_printf("<div class='statistics-report-graph-line'"
                   "style='width:%d%%;'>&nbsp;</div>",
                   nSize);
      }
      cgi_printf("</td></tr>");
    }
    db_finalize(&stWeek);
    free(zDefaultYear);
    cgi_printf("</tbody></table>");







|







2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
      }
      cgi_printf("'>%s</a></td>",zWeek);

      cgi_printf("<td>%d</td>",nCount);
      cgi_printf("<td>");
      if(nCount){
        cgi_printf("<div class='statistics-report-graph-line'"
                   "style='height:16px;width:%d%%;'></div>",
                   nSize);
      }
      cgi_printf("</td></tr>");
    }
    db_finalize(&stWeek);
    free(zDefaultYear);
    cgi_printf("</tbody></table>");
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538

2539
2540
2541
2542
2543
2544
2545
**   y=YYYY            The year to report (default is the server's
**                     current year).
*/
void stats_report_page(){
  HQuery url;                        /* URL for various branch links */
  const char * zView = P("view");    /* Which view/report to show. */
  const char *zUserName = P("user");

  login_check_credentials();
  if( !g.perm.Read ){ login_needed(); return; }
  if(!zUserName) zUserName = P("u");
  url_initialize(&url, "reports");

  if(zUserName && *zUserName){
    url_add_parameter(&url,"user", zUserName);
    timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
  }
  timeline_submenu(&url, "By Year", "view", "byyear", 0);
  timeline_submenu(&url, "By Month", "view", "bymonth", 0);
  timeline_submenu(&url, "By Week", "view", "byweek", 0);







<
<
<


>







2527
2528
2529
2530
2531
2532
2533



2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
**   y=YYYY            The year to report (default is the server's
**                     current year).
*/
void stats_report_page(){
  HQuery url;                        /* URL for various branch links */
  const char * zView = P("view");    /* Which view/report to show. */
  const char *zUserName = P("user");



  if(!zUserName) zUserName = P("u");
  url_initialize(&url, "reports");

  if(zUserName && *zUserName){
    url_add_parameter(&url,"user", zUserName);
    timeline_submenu(&url, "(Remove User Flag)", "view", zView, "user");
  }
  timeline_submenu(&url, "By Year", "view", "byyear", 0);
  timeline_submenu(&url, "By Month", "view", "bymonth", 0);
  timeline_submenu(&url, "By Week", "view", "byweek", 0);

Changes to src/tkt.c.

59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  for(i=0; i<nField; i++){
    if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
  }
  return -1;
}

/*
** Obtain a list of all fields of the TICKET and TICKETCHNG tables.  Put them
** in sorted order in aField[].
**
** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
** TICKETCHANGE tables exist, respectively.
*/
static void getAllTicketFields(void){
  Stmt q;







|







59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
  for(i=0; i<nField; i++){
    if( fossil_strcmp(aField[i].zName, zFieldName)==0 ) return i;
  }
  return -1;
}

/*
** Obtain a list of all fields of the TICKET and TICKETCHNG tables.  Put them 
** in sorted order in aField[].
**
** The haveTicket and haveTicketChng variables are set to 1 if the TICKET and
** TICKETCHANGE tables exist, respectively.
*/
static void getAllTicketFields(void){
  Stmt q;
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  if( g.perm.WrTkt || g.perm.ApndTkt ){
    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  if( g.perm.Hyperlink ){
    style_submenu_element("History", "History Of This Ticket",
        "%s/tkthistory/%T", g.zTop, zUuid);
    style_submenu_element("Timeline", "Timeline Of This Ticket",
        "%s/tkttimeline/%T", g.zTop, zUuid);
    style_submenu_element("Check-ins", "Check-ins Of This Ticket",
        "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
  }
  if( g.perm.NewTkt ){
    style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
  }
  if( g.perm.ApndTkt && g.perm.Attach ){
    style_submenu_element("Attach", "Add An Attachment",
        "%s/attachadd?tkt=%T&from=%s/tktview/%t",
        g.zTop, zUuid, g.zTop, zUuid);
  }
  if( P("plaintext") ){
    style_submenu_element("Formatted", "Formatted", "%R/tktview/%s", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tktview/%s?plaintext", zUuid);
  }
  style_header("View Ticket");
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( P("showfields")!=0 ) showAllFields();
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);

  zFullName = db_text(0,
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  if( zFullName ){
    attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");
  }

  style_footer();
}

/*
** TH command:   append_field FIELD STRING
**
** FIELD is the name of a database column to which we might want
** to append text.  STRING is the text to be appended to that
** column.  The append does not actually occur until the
** submit_ticket command is run.
*/
static int appendRemarkCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int idx;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "append_field FIELD STRING");
  }







|

|

|












|


|













|





|












|
|
|
|







427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
  login_check_credentials();
  if( !g.perm.RdTkt ){ login_needed(); return; }
  if( g.perm.WrTkt || g.perm.ApndTkt ){
    style_submenu_element("Edit", "Edit The Ticket", "%s/tktedit?name=%T",
        g.zTop, PD("name",""));
  }
  if( g.perm.Hyperlink ){
    style_submenu_element("History", "History Of This Ticket", 
        "%s/tkthistory/%T", g.zTop, zUuid);
    style_submenu_element("Timeline", "Timeline Of This Ticket", 
        "%s/tkttimeline/%T", g.zTop, zUuid);
    style_submenu_element("Check-ins", "Check-ins Of This Ticket", 
        "%s/tkttimeline/%T?y=ci", g.zTop, zUuid);
  }
  if( g.perm.NewTkt ){
    style_submenu_element("New Ticket", "Create a new ticket",
        "%s/tktnew", g.zTop);
  }
  if( g.perm.ApndTkt && g.perm.Attach ){
    style_submenu_element("Attach", "Add An Attachment",
        "%s/attachadd?tkt=%T&from=%s/tktview/%t",
        g.zTop, zUuid, g.zTop, zUuid);
  }
  if( P("plaintext") ){
    style_submenu_element("Formatted", "Formatted", "%R/tktview/%S", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tktview/%S?plaintext", zUuid);
  }
  style_header("View Ticket");
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1);
  ticket_init();
  initializeVariablesFromCGI();
  getAllTicketFields();
  initializeVariablesFromDb();
  zScript = ticket_viewpage_code();
  if( P("showfields")!=0 ) showAllFields();
  if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1);
  Th_Render(zScript);
  if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1);

  zFullName = db_text(0, 
       "SELECT tkt_uuid FROM ticket"
       " WHERE tkt_uuid GLOB '%q*'", zUuid);
  if( zFullName ){
    attachment_list(zFullName, "<hr /><h2>Attachments:</h2><ul>");
  }
 
  style_footer();
}

/*
** TH command:   append_field FIELD STRING
**
** FIELD is the name of a database column to which we might want
** to append text.  STRING is the text to be appended to that
** column.  The append does not actually occur until the
** submit_ticket command is run.
*/
static int appendRemarkCmd(
  Th_Interp *interp, 
  void *p, 
  int argc, 
  const char **argv, 
  int *argl
){
  int idx;

  if( argc!=3 ){
    return Th_WrongNumArgs(interp, "append_field FIELD STRING");
  }
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
** Construct and submit a new ticket artifact.  The fields of the artifact
** are the names of the columns in the TICKET table.  The content is
** taken from TH variables.  If the content is unchanged, the field is
** omitted from the artifact.  Fields whose names begin with "private_"
** are concealed using the db_conceal() function.
*/
static int submitTicketCmd(
  Th_Interp *interp,
  void *pUuid,
  int argc,
  const char **argv,
  int *argl
){
  char *zDate;
  const char *zUuid;
  int i;
  int nJ = 0;
  Blob tktchng, cksum;







|
|
|
|







553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
** Construct and submit a new ticket artifact.  The fields of the artifact
** are the names of the columns in the TICKET table.  The content is
** taken from TH variables.  If the content is unchanged, the field is
** omitted from the artifact.  Fields whose names begin with "private_"
** are concealed using the db_conceal() function.
*/
static int submitTicketCmd(
  Th_Interp *interp, 
  void *pUuid, 
  int argc, 
  const char **argv, 
  int *argl
){
  char *zDate;
  const char *zUuid;
  int i;
  int nJ = 0;
  Blob tktchng, cksum;
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
          blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
        }
        nJ++;
      }
    }
  }
  if( *(char**)pUuid ){
    zUuid = db_text(0,
       "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%q*'", P("name")
    );
  }else{
    zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))");
  }
  *(const char**)pUuid = zUuid;
  blob_appendf(&tktchng, "K %s\n", zUuid);
  blob_appendf(&tktchng, "U %F\n", login_name());
  md5sum_blob(&tktchng, &cksum);
  blob_appendf(&tktchng, "Z %b\n", &cksum);
  if( nJ==0 ){
    blob_reset(&tktchng);
    return TH_OK;
  }
  if( g.zPath[0]=='d' ){







|







|







604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
          blob_appendf(&tktchng, "J %s %#F\n", aField[i].zName, nValue, zValue);
        }
        nJ++;
      }
    }
  }
  if( *(char**)pUuid ){
    zUuid = db_text(0, 
       "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%q*'", P("name")
    );
  }else{
    zUuid = db_text(0, "SELECT lower(hex(randomblob(20)))");
  }
  *(const char**)pUuid = zUuid;
  blob_appendf(&tktchng, "K %s\n", zUuid);
  blob_appendf(&tktchng, "U %F\n", g.zLogin ? g.zLogin : "");
  md5sum_blob(&tktchng, &cksum);
  blob_appendf(&tktchng, "Z %b\n", &cksum);
  if( nJ==0 ){
    blob_reset(&tktchng);
    return TH_OK;
  }
  if( g.zPath[0]=='d' ){
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  login_insert_csrf_secret();
  if( P("date_override") && g.perm.Setup ){
    @ <input type="hidden" name="date_override" value="%h(P("date_override"))">
  }
  zScript = ticket_newpage_code();
  Th_Store("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                   (void*)&zNewUuid, 0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zNewUuid));
    return;







|







673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  login_insert_csrf_secret();
  if( P("date_override") && g.perm.Setup ){
    @ <input type="hidden" name="date_override" value="%h(P("date_override"))">
  }
  zScript = ticket_newpage_code();
  Th_Store("login", g.zLogin ? g.zLogin : "nobody");
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd,
                   (void*)&zNewUuid, 0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zNewUuid));
    return;
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  @ <input type="hidden" name="name" value="%s(zName)" />
  login_insert_csrf_secret();
  zScript = ticket_editpage_code();
  Th_Store("login", login_name());
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName));
    return;







|







741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
  initializeVariablesFromCGI();
  initializeVariablesFromDb();
  if( g.zPath[0]=='d' ) showAllFields();
  form_begin(0, "%R/%s", g.zPath);
  @ <input type="hidden" name="name" value="%s(zName)" />
  login_insert_csrf_secret();
  zScript = ticket_editpage_code();
  Th_Store("login", g.zLogin ? g.zLogin : "nobody");
  Th_Store("date", db_text(0, "SELECT datetime('now')"));
  Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0);
  Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0);
  if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1);
  if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){
    cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName));
    return;
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
    "%s/info/%s", g.zTop, zUuid);
  style_submenu_element("Check-ins", "Check-ins",
    "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
  style_submenu_element("Timeline", "Timeline",
    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  if( P("plaintext")!=0 ){
    style_submenu_element("Formatted", "Formatted",
                          "%R/tkthistory/%s", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tkthistory/%s?plaintext", zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)







|


|







893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
    "%s/info/%s", g.zTop, zUuid);
  style_submenu_element("Check-ins", "Check-ins",
    "%s/tkttimeline?name=%s&y=ci", g.zTop, zUuid);
  style_submenu_element("Timeline", "Timeline",
    "%s/tkttimeline?name=%s", g.zTop, zUuid);
  if( P("plaintext")!=0 ){
    style_submenu_element("Formatted", "Formatted",
                          "%R/tkthistory/%S", zUuid);
  }else{
    style_submenu_element("Plaintext", "Plaintext",
                          "%R/tkthistory/%S?plaintext", zUuid);
  }
  style_header(zTitle);
  free(zTitle);

  tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",zUuid);
  if( tagid==0 ){
    @ No such ticket: %h(zUuid)
922
923
924
925
926
927
928

929
930
931
932


933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
    " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
    "   AND blob.rid=attachid"
    " ORDER BY 1",
    timeline_utc(), tagid, timeline_utc(), tagid
  );
  while( db_step(&q)==SQLITE_ROW ){
    Manifest *pTicket;

    const char *zDate = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    const char *zChngUuid = db_column_text(&q, 2);
    const char *zFile = db_column_text(&q, 4);


    if( nChng==0 ){
      @ <ol>
    }
    nChng++;
    if( zFile!=0 ){
      const char *zSrc = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 5);
      if( zSrc==0 || zSrc[0]==0 ){
        @
        @ <li><p>Delete attachment "%h(zFile)"
      }else{
        @
        @ <li><p>Add attachment
        @ "%z(href("%R/artifact/%s",zSrc))%s(zFile)</a>"
      }
      @ [%z(href("%R/artifact/%s",zChngUuid))%.10s(zChngUuid)</a>]
      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");
      hyperlink_to_date(zDate, ".</p>");
    }else{
      pTicket = manifest_get(rid, CFTYPE_TICKET, 0);
      if( pTicket ){
        @
        @ <li><p>Ticket change
        @ [%z(href("%R/artifact/%s",zChngUuid))%.10s(zChngUuid)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(pTicket->zUser,zDate," on");
        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(pTicket, "a");
      }
      manifest_destroy(pTicket);







>




>
>








|


|

|

|








|







922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
    " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
    "   AND blob.rid=attachid"
    " ORDER BY 1",
    timeline_utc(), tagid, timeline_utc(), tagid
  );
  while( db_step(&q)==SQLITE_ROW ){
    Manifest *pTicket;
    char zShort[12];
    const char *zDate = db_column_text(&q, 0);
    int rid = db_column_int(&q, 1);
    const char *zChngUuid = db_column_text(&q, 2);
    const char *zFile = db_column_text(&q, 4);
    memcpy(zShort, zChngUuid, 10);
    zShort[10] = 0;
    if( nChng==0 ){
      @ <ol>
    }
    nChng++;
    if( zFile!=0 ){
      const char *zSrc = db_column_text(&q, 3);
      const char *zUser = db_column_text(&q, 5);
      if( zSrc==0 || zSrc[0]==0 ){
        @ 
        @ <li><p>Delete attachment "%h(zFile)"
      }else{
        @ 
        @ <li><p>Add attachment
        @ "%z(href("%R/artifact/%S",zSrc))%s(zFile)</a>"
      }
      @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
      @ (rid %d(rid)) by
      hyperlink_to_user(zUser,zDate," on");
      hyperlink_to_date(zDate, ".</p>");
    }else{
      pTicket = manifest_get(rid, CFTYPE_TICKET, 0);
      if( pTicket ){
        @
        @ <li><p>Ticket change
        @ [%z(href("%R/artifact/%T",zChngUuid))%s(zShort)</a>]
        @ (rid %d(rid)) by
        hyperlink_to_user(pTicket->zUser,zDate," on");
        hyperlink_to_date(zDate, ":");
        @ </p>
        ticket_output_change_artifact(pTicket, "a");
      }
      manifest_destroy(pTicket);
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
  const char *zTktUuid;

  /* do some ints, we want to be inside a checkout */
  db_find_and_open_repository(0, 0);
  user_select();

  zUser = find_option("user-override",0,1);
  if( zUser==0 ) zUser = login_name();
  zDate = find_option("date-override",0,1);
  if( zDate==0 ) zDate = "now";
  zDate = date_in_standard_format(zDate);
  zTktUuid = find_option("uuid-override",0,1);
  if( zTktUuid && (strlen(zTktUuid)!=40 || !validate16(zTktUuid,40)) ){
    fossil_fatal("invalid --uuid-override: must be 40 characters of hex");
  }







|







1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
  const char *zTktUuid;

  /* do some ints, we want to be inside a checkout */
  db_find_and_open_repository(0, 0);
  user_select();

  zUser = find_option("user-override",0,1);
  if( zUser==0 ) zUser = g.zLogin;
  zDate = find_option("date-override",0,1);
  if( zDate==0 ) zDate = "now";
  zDate = date_in_standard_format(zDate);
  zTktUuid = find_option("uuid-override",0,1);
  if( zTktUuid && (strlen(zTktUuid)!=40 || !validate16(zTktUuid,40)) ){
    fossil_fatal("invalid --uuid-override: must be 40 characters of hex");
  }
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
      }
    }
  }else{
    /* add a new ticket or set fields on existing tickets */
    tTktShowEncoding tktEncoding;

    tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab;

    if( strncmp(g.argv[2],"show",n)==0 ){
      if( g.argc==3 ){
        usage("show REPORTNR");
      }else{
        const char *zRep = 0;
        const char *zSep = 0;
        const char *zFilterUuid = 0;







|







1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
      }
    }
  }else{
    /* add a new ticket or set fields on existing tickets */
    tTktShowEncoding tktEncoding;

    tktEncoding = find_option("quote","q",0) ? tktFossilize : tktNoTab;
    
    if( strncmp(g.argv[2],"show",n)==0 ){
      if( g.argc==3 ){
        usage("show REPORTNR");
      }else{
        const char *zRep = 0;
        const char *zSep = 0;
        const char *zFilterUuid = 0;
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
          eCmd = history;
        }else{
          eCmd = set;
        }
        if( g.argc==3 ){
          usage("set|change|history TICKETUUID");
        }
        zTktUuid = db_text(0,
          "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3]
        );
        if( !zTktUuid ){
          fossil_fatal("unknown ticket: '%s'!",g.argv[3]);
        }
        i=4;
      }else if( strncmp(g.argv[2],"add",n)==0 ){







|







1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
          eCmd = history;
        }else{
          eCmd = set;
        }
        if( g.argc==3 ){
          usage("set|change|history TICKETUUID");
        }
        zTktUuid = db_text(0, 
          "SELECT tkt_uuid FROM ticket WHERE tkt_uuid GLOB '%s*'", g.argv[3]
        );
        if( !zTktUuid ){
          fossil_fatal("unknown ticket: '%s'!",g.argv[3]);
        }
        i=4;
      }else if( strncmp(g.argv[2],"add",n)==0 ){
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235

1236
1237

1238


1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
        if ( i != g.argc ){
          fossil_fatal("no other parameters expected to %s!",g.argv[2]);
        }
        tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
                       zTktUuid);
        if( tagid==0 ){
          fossil_fatal("no such ticket %h", zTktUuid);
        }
        db_prepare(&q,
          "SELECT datetime(mtime%s), objid, NULL, NULL, NULL"
          "  FROM event, blob"
          " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
          "   AND blob.rid=event.objid"
          " UNION "
          "SELECT datetime(mtime%s), attachid, filename, "
          "       src, user"
          "  FROM attachment, blob"
          " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
          "   AND blob.rid=attachid"
          " ORDER BY 1 DESC",
          timeline_utc(), tagid, timeline_utc(), tagid
        );
        while( db_step(&q)==SQLITE_ROW ){
          Manifest *pTicket;

          const char *zDate = db_column_text(&q, 0);
          int rid = db_column_int(&q, 1);

          const char *zFile = db_column_text(&q, 2);


          if( zFile!=0 ){
            const char *zSrc = db_column_text(&q, 3);
            const char *zUser = db_column_text(&q, 4);
            if( zSrc==0 || zSrc[0]==0 ){
              fossil_print("Delete attachment %s\n", zFile);
            }else{
              fossil_print("Add attachment %s\n", zFile);
            }
            fossil_print(" by %s on %s\n", zUser, zDate);
          }else{







|

|




|
|








>


>
|
>
>


|







1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
        if ( i != g.argc ){
          fossil_fatal("no other parameters expected to %s!",g.argv[2]);
        }
        tagid = db_int(0, "SELECT tagid FROM tag WHERE tagname GLOB 'tkt-%q*'",
                       zTktUuid);
        if( tagid==0 ){
          fossil_fatal("no such ticket %h", zTktUuid);
        }  
        db_prepare(&q,
          "SELECT datetime(mtime%s), objid, uuid, NULL, NULL, NULL"
          "  FROM event, blob"
          " WHERE objid IN (SELECT rid FROM tagxref WHERE tagid=%d)"
          "   AND blob.rid=event.objid"
          " UNION "
          "SELECT datetime(mtime%s), attachid, uuid, src, "
          "       filename, user"
          "  FROM attachment, blob"
          " WHERE target=(SELECT substr(tagname,5) FROM tag WHERE tagid=%d)"
          "   AND blob.rid=attachid"
          " ORDER BY 1 DESC",
          timeline_utc(), tagid, timeline_utc(), tagid
        );
        while( db_step(&q)==SQLITE_ROW ){
          Manifest *pTicket;
          char zShort[12];
          const char *zDate = db_column_text(&q, 0);
          int rid = db_column_int(&q, 1);
          const char *zChngUuid = db_column_text(&q, 2);
          const char *zFile = db_column_text(&q, 4);
          memcpy(zShort, zChngUuid, 10);
          zShort[10] = 0;
          if( zFile!=0 ){
            const char *zSrc = db_column_text(&q, 3);
            const char *zUser = db_column_text(&q, 5);
            if( zSrc==0 || zSrc[0]==0 ){
              fossil_print("Delete attachment %s\n", zFile);
            }else{
              fossil_print("Add attachment %s\n", zFile);
            }
            fossil_print(" by %s on %s\n", zUser, zDate);
          }else{

Changes to src/tktsetup.c.

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  const char *zDesc,            /* Description of this field */
  char *(*xText)(const char*),  /* Validity test or NULL */
  void (*xRebuild)(void),       /* Run after successful update */
  int height                    /* Height of the edit box */
){
  const char *z;
  int isSubmit;

  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed();
  }
  if( P("setup") ){
    cgi_redirect("tktsetup");
  }







|







113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
  const char *zDesc,            /* Description of this field */
  char *(*xText)(const char*),  /* Validity test or NULL */
  void (*xRebuild)(void),       /* Run after successful update */
  int height                    /* Height of the edit box */
){
  const char *z;
  int isSubmit;
  
  login_check_credentials();
  if( !g.perm.Setup ){
    login_needed();
  }
  if( P("setup") ){
    cgi_redirect("tktsetup");
  }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
@ <table cellpadding="5">
@ <tr>
@ <td colspan="3">
@ Enter a one-line summary of the ticket:<br />
@ <input type="text" name="title" size="60" value="$<title>" />
@ </td>
@ </tr>
@
@ <tr>
@ <td align="right">Type:</td>
@ <td align="left"><th1>combobox type $type_choices 1</th1></td>
@ <td align="left">What type of ticket is this?</td>
@ </tr>
@
@ <tr>
@ <td align="right">Version:</td>
@ <td align="left">
@ <input type="text" name="foundin" size="20" value="$<foundin>" />
@ </td>
@ <td align="left">In what version or build number do you observe
@ the problem?</td>
@ </tr>
@
@ <tr>
@ <td align="right">Severity:</td>
@ <td align="left"><th1>combobox severity $severity_choices 1</th1></td>
@ <td align="left">How debilitating is the problem?  How badly does the problem
@ affect the operation of the product?</td>
@ </tr>
@
@ <tr>
@ <td align="right">EMail:</td>
@ <td align="left">
@ <input type="text" name="private_contact" value="$<private_contact>"
@  size="30" />
@ </td>
@ <td align="left"><u>Not publicly visible</u>
@ Used by developers to contact you with questions.</td>
@ </tr>
@
@ <tr>
@ <td colspan="3">
@ Enter a detailed description of the problem.
@ For code defects, be sure to provide details on exactly how
@ the problem can be reproduced.  Provide as much detail as
@ possible.  Format:
@ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
@ <br />
@ <th1>set nline [linecount $comment 50 10]</th1>
@ <textarea name="icomment" cols="80" rows="$nline"
@  wrap="virtual" class="wikiedit">$<icomment></textarea><br />
@ </tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="3">
@ Description Preview:<br /><hr />
@ <th1>
@ if {$mutype eq "Wiki"} {
@   wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@   set r [randhex]
@   wiki "<verbatim-$r>[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq {[links only]}} {
@   set r [randhex]
@   wiki "<verbatim-$r links>[string trimright $icomment]\n</verbatim-$r>"
@ } else {
@   wiki "<nowiki>$icomment\n</nowiki>"
@ }
@ </th1>
@ <hr /></td></tr>
@ <th1>enable_output 1</th1>
@
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="preview" value="Preview" />
@ </td>
@ <td align="left">See how the description will appear after formatting.</td>
@ </tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="submit" value="Submit" />
@ </td>
@ <td align="left">After filling in the information above, press this
@ button to create the new ticket</td>
@ </tr>
@ <th1>enable_output 1</th1>
@
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="cancel" value="Cancel" />
@ </td>
@ <td>Abandon and forget this ticket</td>
@ </tr>
@ </table>







|





|








|






|









|












|


















|






|





|



|







312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
@ <table cellpadding="5">
@ <tr>
@ <td colspan="3">
@ Enter a one-line summary of the ticket:<br />
@ <input type="text" name="title" size="60" value="$<title>" />
@ </td>
@ </tr>
@ 
@ <tr>
@ <td align="right">Type:</td>
@ <td align="left"><th1>combobox type $type_choices 1</th1></td>
@ <td align="left">What type of ticket is this?</td>
@ </tr>
@ 
@ <tr>
@ <td align="right">Version:</td>
@ <td align="left">
@ <input type="text" name="foundin" size="20" value="$<foundin>" />
@ </td>
@ <td align="left">In what version or build number do you observe
@ the problem?</td>
@ </tr>
@ 
@ <tr>
@ <td align="right">Severity:</td>
@ <td align="left"><th1>combobox severity $severity_choices 1</th1></td>
@ <td align="left">How debilitating is the problem?  How badly does the problem
@ affect the operation of the product?</td>
@ </tr>
@ 
@ <tr>
@ <td align="right">EMail:</td>
@ <td align="left">
@ <input type="text" name="private_contact" value="$<private_contact>"
@  size="30" />
@ </td>
@ <td align="left"><u>Not publicly visible</u>
@ Used by developers to contact you with questions.</td>
@ </tr>
@ 
@ <tr>
@ <td colspan="3">
@ Enter a detailed description of the problem.
@ For code defects, be sure to provide details on exactly how
@ the problem can be reproduced.  Provide as much detail as
@ possible.  Format:
@ <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
@ <br />
@ <th1>set nline [linecount $comment 50 10]</th1>
@ <textarea name="icomment" cols="80" rows="$nline"
@  wrap="virtual" class="wikiedit">$<icomment></textarea><br />
@ </tr>
@ 
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="3">
@ Description Preview:<br /><hr />
@ <th1>
@ if {$mutype eq "Wiki"} {
@   wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@   set r [randhex]
@   wiki "<verbatim-$r>[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq {[links only]}} {
@   set r [randhex]
@   wiki "<verbatim-$r links>[string trimright $icomment]\n</verbatim-$r>"
@ } else {
@   wiki "<nowiki>$icomment\n</nowiki>"
@ }
@ </th1>
@ <hr /></td></tr>
@ <th1>enable_output 1</th1>
@ 
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="preview" value="Preview" />
@ </td>
@ <td align="left">See how the description will appear after formatting.</td>
@ </tr>
@ 
@ <th1>enable_output [info exists preview]</th1>
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="submit" value="Submit" />
@ </td>
@ <td align="left">After filling in the information above, press this 
@ button to create the new ticket</td>
@ </tr>
@ <th1>enable_output 1</th1>
@ 
@ <tr>
@ <td><td align="left">
@ <input type="submit" name="cancel" value="Cancel" />
@ </td>
@ <td>Abandon and forget this ticket</td>
@ </tr>
@ </table>
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
@   </td>
@ <th1>enable_output 1</th1>
@ </tr>
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ $<foundin>
@ </td></tr>
@
@ <th1>
@ if {[info exists comment] && [string length $comment]>10} {
@   html {
@     <tr><td class="tktDspLabel">Description:</td></tr>
@     <tr><td colspan="5" class="tktDspValue">
@   }
@   if {[info exists plaintext]} {







|







475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
@   </td>
@ <th1>enable_output 1</th1>
@ </tr>
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td>
@ <td colspan="3" valign="top" class="tktDspValue">
@ $<foundin>
@ </td></tr>
@ 
@ <th1>
@ if {[info exists comment] && [string length $comment]>10} {
@   html {
@     <tr><td class="tktDspLabel">Description:</td></tr>
@     <tr><td colspan="5" class="tktDspValue">
@   }
@   if {[info exists plaintext]} {
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
@     set preview 1
@   }
@ </th1>
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Title:</td><td>
@ <input type="text" name="title" value="$<title>" size="60" />
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Status:</td><td>
@ <th1>combobox status $status_choices 1</th1>
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Type:</td><td>
@ <th1>combobox type $type_choices 1</th1>
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Severity:</td><td>
@ <th1>combobox severity $severity_choices 1</th1>
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Priority:</td><td>
@ <th1>combobox priority $priority_choices 1</th1>
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Resolution:</td><td>
@ <th1>combobox resolution $resolution_choices 1</th1>
@ </td></tr>
@
@ <tr><td class="tktDspLabel">Subsystem:</td><td>
@ <th1>combobox subsystem $subsystem_choices 1</th1>
@ </td></tr>
@
@ <th1>enable_output [hascap e]</th1>
@   <tr><td class="tktDspLabel">Contact:</td><td>
@   <input type="text" name="private_contact" size="40"
@    value="$<private_contact>" />
@   </td></tr>
@ <th1>enable_output 1</th1>
@
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td><td>
@ <input type="text" name="foundin" size="50" value="$<foundin>" />
@ </td></tr>
@
@ <tr><td colspan="2">
@   Append Remark with format
@   <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
@   from
@   <input type="text" name="username" value="$<username>" size="30" />:<br />
@   <textarea name="icomment" cols="80" rows="15"
@    wrap="virtual" class="wikiedit">$<icomment></textarea>
@ </td></tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="2">
@ Description Preview:<br><hr>
@ <th1>
@ if {$mutype eq "Wiki"} {
@   wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@   set r [randhex]
@   wiki "<verbatim-$r>\n[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq {[links only]}} {
@   set r [randhex]
@   wiki "<verbatim-$r links>\n[string trimright $icomment]</verbatim-$r>"
@ } else {
@   wiki "<nowiki>\n[string trimright $icomment]\n</nowiki>"
@ }
@ </th1>
@ <hr>
@ </td></tr>
@ <th1>enable_output 1</th1>
@
@ <tr>
@ <td align="right">
@ <input type="submit" name="preview" value="Preview" />
@ </td>
@ <td align="left">See how the description will appear after formatting.</td>
@ </tr>
@
@ <th1>enable_output [info exists preview]</th1>
@ <tr>
@ <td align="right">
@ <input type="submit" name="submit" value="Submit" />
@ </td>
@ <td align="left">Apply the changes shown above</td>
@ </tr>
@ <th1>enable_output 1</th1>
@
@ <tr>
@ <td align="right">
@ <input type="submit" name="cancel" value="Cancel" />
@ </td>
@ <td>Abandon this edit</td>
@ </tr>
@
@ </table>
;

/*
** Return the code used to generate the edit ticket page
*/
const char *ticket_editpage_code(void){







|



|



|



|



|



|



|






|



|








|



















|






|








|






|







575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
@     set preview 1
@   }
@ </th1>
@ <table cellpadding="5">
@ <tr><td class="tktDspLabel">Title:</td><td>
@ <input type="text" name="title" value="$<title>" size="60" />
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Status:</td><td>
@ <th1>combobox status $status_choices 1</th1>
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Type:</td><td>
@ <th1>combobox type $type_choices 1</th1>
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Severity:</td><td>
@ <th1>combobox severity $severity_choices 1</th1>
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Priority:</td><td>
@ <th1>combobox priority $priority_choices 1</th1>
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Resolution:</td><td>
@ <th1>combobox resolution $resolution_choices 1</th1>
@ </td></tr>
@ 
@ <tr><td class="tktDspLabel">Subsystem:</td><td>
@ <th1>combobox subsystem $subsystem_choices 1</th1>
@ </td></tr>
@ 
@ <th1>enable_output [hascap e]</th1>
@   <tr><td class="tktDspLabel">Contact:</td><td>
@   <input type="text" name="private_contact" size="40"
@    value="$<private_contact>" />
@   </td></tr>
@ <th1>enable_output 1</th1>
@ 
@ <tr><td class="tktDspLabel">Version&nbsp;Found&nbsp;In:</td><td>
@ <input type="text" name="foundin" size="50" value="$<foundin>" />
@ </td></tr>
@ 
@ <tr><td colspan="2">
@   Append Remark with format
@   <th1>combobox mutype {Wiki HTML {Plain Text} {[links only]}} 1</th1>
@   from
@   <input type="text" name="username" value="$<username>" size="30" />:<br />
@   <textarea name="icomment" cols="80" rows="15"
@    wrap="virtual" class="wikiedit">$<icomment></textarea>
@ </td></tr>
@ 
@ <th1>enable_output [info exists preview]</th1>
@ <tr><td colspan="2">
@ Description Preview:<br><hr>
@ <th1>
@ if {$mutype eq "Wiki"} {
@   wiki $icomment
@ } elseif {$mutype eq "Plain Text"} {
@   set r [randhex]
@   wiki "<verbatim-$r>\n[string trimright $icomment]\n</verbatim-$r>"
@ } elseif {$mutype eq {[links only]}} {
@   set r [randhex]
@   wiki "<verbatim-$r links>\n[string trimright $icomment]</verbatim-$r>"
@ } else {
@   wiki "<nowiki>\n[string trimright $icomment]\n</nowiki>"
@ }
@ </th1>
@ <hr>
@ </td></tr>
@ <th1>enable_output 1</th1>
@ 
@ <tr>
@ <td align="right">
@ <input type="submit" name="preview" value="Preview" />
@ </td>
@ <td align="left">See how the description will appear after formatting.</td>
@ </tr>
@ 
@ <th1>enable_output [info exists preview]</th1>
@ <tr>
@ <td align="right">
@ <input type="submit" name="submit" value="Submit" />
@ </td>
@ <td align="left">Apply the changes shown above</td>
@ </tr>
@ <th1>enable_output 1</th1>
@ 
@ <tr>
@ <td align="right">
@ <input type="submit" name="cancel" value="Cancel" />
@ </td>
@ <td>Abandon this edit</td>
@ </tr>
@ 
@ </table>
;

/*
** Return the code used to generate the edit ticket page
*/
const char *ticket_editpage_code(void){
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
static const char zDefaultReportList[] =
@ <th1>
@ if {[hascap n]} {
@   html "<p>Enter a new ticket:</p>"
@   html "<ul><li><a href='tktnew'>New ticket</a></li></ul>"
@ }
@ </th1>
@
@ <p>Choose a report format from the following list:</p>
@ <ol>
@ <th1>html $report_items</th1>
@ </ol>
@
@ <th1>
@ if {[hascap t q]} {
@   html "<p>Other options:</p>\n<ul>\n"
@   if {[hascap t]} {
@     html "<li><a href='rptnew'>New report format</a></li>\n"
@   }
@   if {[hascap q]} {







|




|







701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
static const char zDefaultReportList[] =
@ <th1>
@ if {[hascap n]} {
@   html "<p>Enter a new ticket:</p>"
@   html "<ul><li><a href='tktnew'>New ticket</a></li></ul>"
@ }
@ </th1>
@ 
@ <p>Choose a report format from the following list:</p>
@ <ol>
@ <th1>html $report_items</th1>
@ </ol>
@ 
@ <th1>
@ if {[hascap t q]} {
@   html "<p>Other options:</p>\n<ul>\n"
@   if {[hascap t]} {
@     html "<li><a href='rptnew'>New report format</a></li>\n"
@   }
@   if {[hascap q]} {
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
    40
  );
}

/*
** The default template ticket report format:
*/
static char zDefaultReport[] =
@ SELECT
@   CASE WHEN status IN ('Open','Verified') THEN '#f2dcdc'
@        WHEN status='Review' THEN '#e8e8e8'
@        WHEN status='Fixed' THEN '#cfe8bd'
@        WHEN status='Tested' THEN '#bde5d6'
@        WHEN status='Deferred' THEN '#cacae5'
@        ELSE '#c8c8c8' END AS 'bgcolor',







|







748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
    40
  );
}

/*
** The default template ticket report format:
*/
static char zDefaultReport[] = 
@ SELECT
@   CASE WHEN status IN ('Open','Verified') THEN '#f2dcdc'
@        WHEN status='Review' THEN '#e8e8e8'
@        WHEN status='Fixed' THEN '#cfe8bd'
@        WHEN status='Tested' THEN '#bde5d6'
@        WHEN status='Deferred' THEN '#cacae5'
@        ELSE '#c8c8c8' END AS 'bgcolor',
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    20
  );
}

/*
** The default template ticket key:
*/
static const char zDefaultKey[] =
@ #ffffff Key:
@ #f2dcdc Active
@ #e8e8e8 Review
@ #cfe8bd Fixed
@ #bde5d6 Tested
@ #cacae5 Deferred
@ #c8c8c8 Closed







|







797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
    20
  );
}

/*
** The default template ticket key:
*/
static const char zDefaultKey[] = 
@ #ffffff Key:
@ #f2dcdc Active
@ #e8e8e8 Review
@ #cfe8bd Fixed
@ #bde5d6 Tested
@ #cacae5 Deferred
@ #c8c8c8 Closed
878
879
880
881
882
883
884
885
886
  @ <p>
  @ <input type="submit"  name="submit" value="Apply Changes" />
  @ <input type="submit" name="setup" value="Cancel" />
  @ </p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();

}







|

878
879
880
881
882
883
884
885
886
  @ <p>
  @ <input type="submit"  name="submit" value="Apply Changes" />
  @ <input type="submit" name="setup" value="Cancel" />
  @ </p>
  @ </div></form>
  db_end_transaction(0);
  style_footer();
  
}

Changes to src/update.c.

623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
){
  Manifest *pManifest;
  ManifestFile *pFile;
  int rid=0;
  
  if( revision ){
    rid = name_to_typed_rid(revision,"ci");
  }else if( !g.localOpen ){
    rid = name_to_typed_rid(db_get("main-branch","trunk"),"ci");
  }else{
    rid = db_lget_int("checkout", 0);
  }
  if( !is_a_version(rid) ){
    if( errCode>0 ) return errCode;
    fossil_fatal("no such checkin: %s", revision);
  }







<
<







623
624
625
626
627
628
629


630
631
632
633
634
635
636
){
  Manifest *pManifest;
  ManifestFile *pFile;
  int rid=0;
  
  if( revision ){
    rid = name_to_typed_rid(revision,"ci");


  }else{
    rid = db_lget_int("checkout", 0);
  }
  if( !is_a_version(rid) ){
    if( errCode>0 ) return errCode;
    fossil_fatal("no such checkin: %s", revision);
  }
717
718
719
720
721
722
723
724




725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742

743
744
745
746

747
748
749
750
751
752
753
      zFile = mprintf("%/", g.argv[i]);
      file_tree_name(zFile, &fname, 1);
      db_multi_exec(
        "REPLACE INTO torevert VALUES(%B);"
        "INSERT OR IGNORE INTO torevert"
        " SELECT pathname"
        "   FROM vfile"
        "  WHERE origname=%B;",




        &fname, &fname
      );
      blob_reset(&fname);
    }
  }else{
    int vid;
    vid = db_lget_int("checkout", 0);
    vfile_check_signature(vid, 0);
    db_multi_exec(
      "DELETE FROM vmerge;"
      "INSERT OR IGNORE INTO torevert "
      " SELECT pathname"
      "   FROM vfile "
      "  WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
    );
  }
  db_multi_exec(
    "INSERT OR IGNORE INTO torevert"

    " SELECT origname"
    "   FROM vfile"
    "  WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);"
  );

  blob_zero(&record);
  db_prepare(&q, "SELECT name FROM torevert");
  if( zRevision==0 ){
    int vid = db_lget_int("checkout", 0);
    zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  }
  while( db_step(&q)==SQLITE_ROW ){







|
>
>
>
>
|












|
<
<
<
<
>
|
|
|
|
>







715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740




741
742
743
744
745
746
747
748
749
750
751
752
753
      zFile = mprintf("%/", g.argv[i]);
      file_tree_name(zFile, &fname, 1);
      db_multi_exec(
        "REPLACE INTO torevert VALUES(%B);"
        "INSERT OR IGNORE INTO torevert"
        " SELECT pathname"
        "   FROM vfile"
        "  WHERE origname IN(%B)"
        " UNION ALL"
        " SELECT origname"
        "   FROM vfile"
        "  WHERE pathname IN(%B) AND origname IS NOT NULL;",
        &fname, &fname, &fname
      );
      blob_reset(&fname);
    }
  }else{
    int vid;
    vid = db_lget_int("checkout", 0);
    vfile_check_signature(vid, 0);
    db_multi_exec(
      "DELETE FROM vmerge;"
      "INSERT OR IGNORE INTO torevert "
      " SELECT pathname"
      "   FROM vfile "
      "  WHERE chnged OR deleted OR rid=0 OR pathname!=origname "




      " UNION ALL "
      " SELECT origname"
      "   FROM vfile"
      "  WHERE origname!=pathname;"
    );
  }
  blob_zero(&record);
  db_prepare(&q, "SELECT name FROM torevert");
  if( zRevision==0 ){
    int vid = db_lget_int("checkout", 0);
    zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
  }
  while( db_step(&q)==SQLITE_ROW ){
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
        fossil_print("UNMANAGE: %s\n", zFile);
      }else{
        undo_save(zFile);
        file_delete(zFull);
        fossil_print("DELETE: %s\n", zFile);
      }
      db_multi_exec(
        "UPDATE OR REPLACE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname;"
        "DELETE FROM vfile WHERE pathname=%Q",
        zFile, zFile
      );
    }else{
      sqlite3_int64 mtime;
      undo_save(zFile);
      if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){







|

|







764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
        fossil_print("UNMANAGE: %s\n", zFile);
      }else{
        undo_save(zFile);
        file_delete(zFull);
        fossil_print("DELETE: %s\n", zFile);
      }
      db_multi_exec(
        "UPDATE vfile"
        "   SET pathname=origname, origname=NULL"
        " WHERE pathname=%Q AND origname!=pathname AND origname IS NOT NULL;"
        "DELETE FROM vfile WHERE pathname=%Q",
        zFile, zFile
      );
    }else{
      sqlite3_int64 mtime;
      undo_save(zFile);
      if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(zFull)) ){

Changes to src/url.c.

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
  char *path;      /* Pathname for http: */
  char *user;      /* User id for http: */
  char *passwd;    /* Password for http: */
  char *canonical; /* Canonical representation of the URL */
  char *proxyAuth; /* Proxy-Authorizer: string */
  char *fossil;    /* The fossil query parameter on ssh: */
  unsigned flags;  /* Boolean flags controlling URL processing */
  int useProxy;    /* Used to remember that a proxy is in use */
  char *proxyUrlPath;
  int proxyOrigPort; /* Tunneled port number for https through proxy */
};
#endif /* INTERFACE */


/*
** Convert a string to lower-case.
*/







<
<
<







60
61
62
63
64
65
66



67
68
69
70
71
72
73
  char *path;      /* Pathname for http: */
  char *user;      /* User id for http: */
  char *passwd;    /* Password for http: */
  char *canonical; /* Canonical representation of the URL */
  char *proxyAuth; /* Proxy-Authorizer: string */
  char *fossil;    /* The fossil query parameter on ssh: */
  unsigned flags;  /* Boolean flags controlling URL processing */



};
#endif /* INTERFACE */


/*
** Convert a string to lower-case.
*/
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
  ){
    int iStart;
    char *zLogin;
    char *zExe;
    char cQuerySep = '?';

    pUrlData->isFile = 0;
    pUrlData->useProxy = 0;
    if( zUrl[4]=='s' ){
      pUrlData->isHttps = 1;
      pUrlData->protocol = "https";
      pUrlData->dfltPort = 443;
      iStart = 8;
    }else if( zUrl[0]=='s' ){
      pUrlData->isSsh = 1;







<







118
119
120
121
122
123
124

125
126
127
128
129
130
131
  ){
    int iStart;
    char *zLogin;
    char *zExe;
    char cQuerySep = '?';

    pUrlData->isFile = 0;

    if( zUrl[4]=='s' ){
      pUrlData->isHttps = 1;
      pUrlData->protocol = "https";
      pUrlData->dfltPort = 443;
      iStart = 8;
    }else if( zUrl[0]=='s' ){
      pUrlData->isSsh = 1;
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
  }
}

/*
** Parse the given URL, which describes a sync server.  Populate variables 
** in the global "g" structure as follows:
**
**      g.url.isFile      True if FILE:
**      g.url.isHttps     True if HTTPS: 
**      g.url.isSsh       True if SSH:
**      g.url.protocol    "http" or "https" or "file"
**      g.url.name        Hostname for HTTP:, HTTPS:, SSH:.  Filename for FILE:
**      g.url.port        TCP port number for HTTP or HTTPS.
**      g.url.dfltPort    Default TCP port number (80 or 443).
**      g.url.path        Path name for HTTP or HTTPS.
**      g.url.user        Userid.
**      g.url.passwd      Password.
**      g.url.hostname    HOST:PORT or just HOST if port is the default.
**      g.url.canonical   The URL in canonical form, omitting the password
**
** HTTP url format as follows (HTTPS is the same with a different scheme):
**
**     http://userid:password@host:port/path
**
** SSH url format is:
**
**     ssh://userid@host:port/path?fossil=path/to/fossil.exe
**
*/
void url_parse(const char *zUrl, unsigned int urlFlags){
  url_parse_local(zUrl, urlFlags, &g.url);
}

/*
** COMMAND: test-urlparser
**
** Usage: %fossil test-urlparser URL ?options?
**







|
|
|
|
|
|
|
|
|
|
|
|











|







269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
  }
}

/*
** Parse the given URL, which describes a sync server.  Populate variables 
** in the global "g" structure as follows:
**
**      g.urlIsFile      True if FILE:
**      g.urlIsHttps     True if HTTPS: 
**      g.urlIsSsh       True if SSH:
**      g.urlProtocol    "http" or "https" or "file"
**      g.urlName        Hostname for HTTP:, HTTPS:, SSH:.  Filename for FILE:
**      g.urlPort        TCP port number for HTTP or HTTPS.
**      g.urlDfltPort    Default TCP port number (80 or 443).
**      g.urlPath        Path name for HTTP or HTTPS.
**      g.urlUser        Userid.
**      g.urlPasswd      Password.
**      g.urlHostname    HOST:PORT or just HOST if port is the default.
**      g.urlCanonical   The URL in canonical form, omitting the password
**
** HTTP url format as follows (HTTPS is the same with a different scheme):
**
**     http://userid:password@host:port/path
**
** SSH url format is:
**
**     ssh://userid@host:port/path?fossil=path/to/fossil.exe
**
*/
void url_parse(const char *zUrl, unsigned int urlFlags){
  url_parse_local(zUrl, urlFlags, GLOBAL_URL());
}

/*
** COMMAND: test-urlparser
**
** Usage: %fossil test-urlparser URL ?options?
**
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
  }
  if( find_option("prompt-pw",0,0) ) fg |= URL_PROMPT_PW;
  if( g.argc!=3 && g.argc!=4 ){
    usage("URL");
  }
  url_parse(g.argv[2], fg);
  for(i=0; i<2; i++){
    fossil_print("g.url.isFile    = %d\n", g.url.isFile);
    fossil_print("g.url.isHttps   = %d\n", g.url.isHttps);
    fossil_print("g.url.isSsh     = %d\n", g.url.isSsh);
    fossil_print("g.url.protocol  = %s\n", g.url.protocol);
    fossil_print("g.url.name      = %s\n", g.url.name);
    fossil_print("g.url.port      = %d\n", g.url.port);
    fossil_print("g.url.dfltPort  = %d\n", g.url.dfltPort);
    fossil_print("g.url.hostname  = %s\n", g.url.hostname);
    fossil_print("g.url.path      = %s\n", g.url.path);
    fossil_print("g.url.user      = %s\n", g.url.user);
    fossil_print("g.url.passwd    = %s\n", g.url.passwd);
    fossil_print("g.url.canonical = %s\n", g.url.canonical);
    fossil_print("g.url.fossil    = %s\n", g.url.fossil);
    fossil_print("g.url.flags     = 0x%02x\n", g.url.flags);
    if( g.url.isFile || g.url.isSsh ) break;
    if( i==0 ){
      fossil_print("********\n");
      url_enable_proxy("Using proxy: ");
    }
  }
}








|
|
|
|
|
|
|
|
|
|
|
|
|
|
|







317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
  }
  if( find_option("prompt-pw",0,0) ) fg |= URL_PROMPT_PW;
  if( g.argc!=3 && g.argc!=4 ){
    usage("URL");
  }
  url_parse(g.argv[2], fg);
  for(i=0; i<2; i++){
    fossil_print("g.urlIsFile    = %d\n", g.urlIsFile);
    fossil_print("g.urlIsHttps   = %d\n", g.urlIsHttps);
    fossil_print("g.urlIsSsh     = %d\n", g.urlIsSsh);
    fossil_print("g.urlProtocol  = %s\n", g.urlProtocol);
    fossil_print("g.urlName      = %s\n", g.urlName);
    fossil_print("g.urlPort      = %d\n", g.urlPort);
    fossil_print("g.urlDfltPort  = %d\n", g.urlDfltPort);
    fossil_print("g.urlHostname  = %s\n", g.urlHostname);
    fossil_print("g.urlPath      = %s\n", g.urlPath);
    fossil_print("g.urlUser      = %s\n", g.urlUser);
    fossil_print("g.urlPasswd    = %s\n", g.urlPasswd);
    fossil_print("g.urlCanonical = %s\n", g.urlCanonical);
    fossil_print("g.urlFossil    = %s\n", g.urlFossil);
    fossil_print("g.urlFlags     = 0x%02x\n", g.urlFlags);
    if( g.urlIsFile || g.urlIsSsh ) break;
    if( i==0 ){
      fossil_print("********\n");
      url_enable_proxy("Using proxy: ");
    }
  }
}

383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
  if( zProxy==0 ){
    zProxy = db_get("proxy", 0);
    if( zProxy==0 || zProxy[0]==0 || is_truth(zProxy) ){
      zProxy = fossil_getenv("http_proxy");
    }
  }
  if( zProxy && zProxy[0] && !is_false(zProxy)
      && !g.url.isSsh && !g.url.isFile ){
    char *zOriginalUrl = g.url.canonical;
    char *zOriginalHost = g.url.hostname;
    int fOriginalIsHttps = g.url.isHttps;
    char *zOriginalUser = g.url.user;
    char *zOriginalPasswd = g.url.passwd;
    char *zOriginalUrlPath = g.url.path;
    int iOriginalPort = g.url.port;
    unsigned uOriginalFlags = g.url.flags;
    g.url.user = 0;
    g.url.passwd = "";
    url_parse(zProxy, 0);
    if( zMsg ) fossil_print("%s%s\n", zMsg, g.url.canonical);
    g.url.path = zOriginalUrl;
    g.url.hostname = zOriginalHost;
    if( g.url.user ){
      char *zCredentials1 = mprintf("%s:%s", g.url.user, g.url.passwd);
      char *zCredentials2 = encode64(zCredentials1, -1);
      g.url.proxyAuth = mprintf("Basic %z", zCredentials2);
      free(zCredentials1);
    }
    g.url.user = zOriginalUser;
    g.url.passwd = zOriginalPasswd;
    g.url.isHttps = fOriginalIsHttps;
    g.url.useProxy = 1;
    g.url.proxyUrlPath = zOriginalUrlPath;
    g.url.proxyOrigPort = iOriginalPort;
    g.url.flags = uOriginalFlags;
  }
}

#if INTERFACE
/*
** An instance of this object is used to build a URL with query parameters.
*/







|
|
|
<
|
|
<
<
|
|
|

|
|
|
|
|

|


|
|
<
<
<
<
|







379
380
381
382
383
384
385
386
387
388

389
390


391
392
393
394
395
396
397
398
399
400
401
402
403
404
405




406
407
408
409
410
411
412
413
  if( zProxy==0 ){
    zProxy = db_get("proxy", 0);
    if( zProxy==0 || zProxy[0]==0 || is_truth(zProxy) ){
      zProxy = fossil_getenv("http_proxy");
    }
  }
  if( zProxy && zProxy[0] && !is_false(zProxy)
      && !g.urlIsSsh && !g.urlIsFile ){
    char *zOriginalUrl = g.urlCanonical;
    char *zOriginalHost = g.urlHostname;

    char *zOriginalUser = g.urlUser;
    char *zOriginalPasswd = g.urlPasswd;


    unsigned uOriginalFlags = g.urlFlags;
    g.urlUser = 0;
    g.urlPasswd = "";
    url_parse(zProxy, 0);
    if( zMsg ) fossil_print("%s%s\n", zMsg, g.urlCanonical);
    g.urlPath = zOriginalUrl;
    g.urlHostname = zOriginalHost;
    if( g.urlUser ){
      char *zCredentials1 = mprintf("%s:%s", g.urlUser, g.urlPasswd);
      char *zCredentials2 = encode64(zCredentials1, -1);
      g.urlProxyAuth = mprintf("Basic %z", zCredentials2);
      free(zCredentials1);
    }
    g.urlUser = zOriginalUser;
    g.urlPasswd = zOriginalPasswd;




    g.urlFlags = uOriginalFlags;
  }
}

#if INTERFACE
/*
** An instance of this object is used to build a URL with query parameters.
*/
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
  }else{
    fossil_fatal("missing or incorrect password for user \"%s\"",
                 pUrlData->user);
  }
}

/*
** Prompt the user for the password for g.url.user.  Store the result
** in g.url.passwd.
*/
void url_prompt_for_password(void){
  url_prompt_for_password_local(&g.url);
}

/*
** Remember the URL and password if requested.
*/
void url_remember(void){
  if( g.url.flags & URL_REMEMBER ){
    db_set("last-sync-url", g.url.canonical, 0);
    if( g.url.user!=0 && g.url.passwd!=0 && ( g.url.flags & URL_REMEMBER_PW ) ){
      db_set("last-sync-pw", obscure(g.url.passwd), 0);
    }
  }
}

/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/
void url_get_password_if_needed(void){
  if( (g.url.user && g.url.user[0])
   && (g.url.passwd==0 || g.url.passwd[0]==0)
   && isatty(fileno(stdin))
  ){
    url_prompt_for_password();
  }
}







|
|


|






|
|
|
|








|
|





516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
  }else{
    fossil_fatal("missing or incorrect password for user \"%s\"",
                 pUrlData->user);
  }
}

/*
** Prompt the user for the password for g.urlUser.  Store the result
** in g.urlPasswd.
*/
void url_prompt_for_password(void){
  url_prompt_for_password_local(GLOBAL_URL());
}

/*
** Remember the URL and password if requested.
*/
void url_remember(void){
  if( g.urlFlags & URL_REMEMBER ){
    db_set("last-sync-url", g.urlCanonical, 0);
    if( g.urlUser!=0 && g.urlPasswd!=0 && ( g.urlFlags & URL_REMEMBER_PW ) ){
      db_set("last-sync-pw", obscure(g.urlPasswd), 0);
    }
  }
}

/* Preemptively prompt for a password if a username is given in the
** URL but no password.
*/
void url_get_password_if_needed(void){
  if( (g.urlUser && g.urlUser[0])
   && (g.urlPasswd==0 || g.urlPasswd[0]==0)
   && isatty(fileno(stdin))
  ){
    url_prompt_for_password();
  }
}

Changes to src/user.c.

373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  if( attempt_user(fossil_getenv("USER")) ) return;

  if( attempt_user(fossil_getenv("LOGNAME")) ) return;

  if( attempt_user(fossil_getenv("USERNAME")) ) return;

  url_parse(0, 0);
  if( g.url.user && attempt_user(g.url.user) ) return;

  fossil_print(
    "Cannot figure out who you are!  Consider using the --user\n"
    "command line option, setting your USER environment variable,\n"
    "or setting a default user with \"fossil user default USER\".\n"
  );
  fossil_fatal("cannot determine user");







|







373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
  if( attempt_user(fossil_getenv("USER")) ) return;

  if( attempt_user(fossil_getenv("LOGNAME")) ) return;

  if( attempt_user(fossil_getenv("USERNAME")) ) return;

  url_parse(0, 0);
  if( g.urlUser && attempt_user(g.urlUser) ) return;

  fossil_print(
    "Cannot figure out who you are!  Consider using the --user\n"
    "command line option, setting your USER environment variable,\n"
    "or setting a default user with \"fossil user default USER\".\n"
  );
  fossil_fatal("cannot determine user");

Changes to src/utf8.c.

52
53
54
55
56
57
58
59



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82



83
84
85
86
87
88
89
90
91
92
93
94
95



96

97
98
99
100
101
102
103
** Return a pointer to the translated text.
** Call fossil_unicode_free() to deallocate any memory used to store the
** returned pointer when done.
*/
char *fossil_unicode_to_utf8(const void *zUnicode){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, 0, 0, 0, 0);
  char *zUtf = fossil_malloc( nByte );



  WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, zUtf, nByte, 0, 0);
  return zUtf;
#else
  static Stmt q;
  char *zUtf8;
  db_static_prepare(&q, "SELECT :utf8");
  db_bind_text16(&q, ":utf8", zUnicode);
  db_step(&q);
  zUtf8 = fossil_strdup(db_column_text(&q, 0));
  db_reset(&q);
  return zUtf8;
#endif
}

/*
** Translate UTF-8 to unicode for use in system calls.  Return a pointer to the
** translated text..  Call fossil_unicode_free() to deallocate any memory
** used to store the returned pointer when done.
*/
void *fossil_utf8_to_unicode(const char *zUtf8){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);
  wchar_t *zUnicode = fossil_malloc( nByte * 2 );



  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nByte);
  return zUnicode;
#else
  assert( 0 );  /* Never used in unix */
  return fossil_strdup(zUtf8);  /* TODO: implement for unix */
#endif
}

/*
** Deallocate any memory that was previously allocated by
** fossil_unicode_to_utf8().
*/
void fossil_unicode_free(void *pOld){



  fossil_free(pOld);

}

#if defined(__APPLE__) && !defined(WITHOUT_ICONV)
# include <iconv.h>
#endif

/*







|
>
>
>



<
<
<
<
<
|
<
<











|
>
>
>



<









>
>
>

>







52
53
54
55
56
57
58
59
60
61
62
63
64
65





66


67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
** Return a pointer to the translated text.
** Call fossil_unicode_free() to deallocate any memory used to store the
** returned pointer when done.
*/
char *fossil_unicode_to_utf8(const void *zUnicode){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, 0, 0, 0, 0);
  char *zUtf = sqlite3_malloc( nByte );
  if( zUtf==0 ){
    return 0;
  }
  WideCharToMultiByte(CP_UTF8, 0, zUnicode, -1, zUtf, nByte, 0, 0);
  return zUtf;
#else





  return fossil_strdup(zUnicode);  /* TODO: implement for unix */


#endif
}

/*
** Translate UTF-8 to unicode for use in system calls.  Return a pointer to the
** translated text..  Call fossil_unicode_free() to deallocate any memory
** used to store the returned pointer when done.
*/
void *fossil_utf8_to_unicode(const char *zUtf8){
#if defined(_WIN32) || defined(__CYGWIN__)
  int nByte = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);
  wchar_t *zUnicode = sqlite3_malloc( nByte * 2 );
  if( zUnicode==0 ){
    return 0;
  }
  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nByte);
  return zUnicode;
#else

  return fossil_strdup(zUtf8);  /* TODO: implement for unix */
#endif
}

/*
** Deallocate any memory that was previously allocated by
** fossil_unicode_to_utf8().
*/
void fossil_unicode_free(void *pOld){
#if defined(_WIN32) || defined(__CYGWIN__)
  sqlite3_free(pOld);
#else
  fossil_free(pOld);
#endif
}

#if defined(__APPLE__) && !defined(WITHOUT_ICONV)
# include <iconv.h>
#endif

/*
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
** Translate text from UTF-8 to the filename character set.
** Return a pointer to the translated text.
** Call fossil_filename_free() to deallocate any memory used to store the
** returned pointer when done.
**
** On Windows, characters in the range U+0001 to U+0031 and the
** characters '"', '*', ':', '<', '>', '?' and '|' are invalid
** to be used, except in the 'extended path' prefix ('?') and
** as drive specifier (':'). Therefore, translate those to characters
** in the range U+F001 - U+F07F (private use area), so those
** characters never arrive in any Windows API. The filenames might
** look strange in Windows explorer, but in the cygwin shell
** everything looks as expected.
**
** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html>
**
*/
void *fossil_utf8_to_filename(const char *zUtf8){
#ifdef _WIN32
  int nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);
  /* Overallocate 6 chars, making some room for extended paths */
  wchar_t *zUnicode = sqlite3_malloc( (nChar+6) * sizeof(wchar_t) );
  wchar_t *wUnicode = zUnicode;
  if( zUnicode==0 ){
    return 0;
  }
  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar);
  /*
  ** If path starts with "//?/" or "\\?\" (extended path), translate
  ** any slashes to backslashes but leave the '?' intact
  */
  if( (zUtf8[0]=='\\' || zUtf8[0]=='/') && (zUtf8[1]=='\\' || zUtf8[1]=='/')
           && zUtf8[2]=='?' && (zUtf8[3]=='\\' || zUtf8[3]=='/')) {
    wUnicode[0] = wUnicode[1] = wUnicode[3] = '\\';
    zUtf8 += 4;
    wUnicode += 4;
  }
  /*
  ** If there is no "\\?\" prefix but there is a drive or UNC
  ** path prefix and the path is larger than MAX_PATH chars,
  ** no Win32 API function can handle that unless it is
  ** prefixed with the extended path prefix. See:
  ** <http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath>
  **/
  if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':'
           && (zUtf8[2]=='\\' || zUtf8[2]=='/') ){
    if( wUnicode==zUnicode && nChar>MAX_PATH){
      memmove(wUnicode+4, wUnicode, nChar*sizeof(wchar_t));
      memcpy(wUnicode, L"\\\\?\\", 4*sizeof(wchar_t));
      wUnicode += 4;
    }
    /*
    ** If (remainder of) path starts with "<drive>:/" or "<drive>:\",
    ** leave the ':' intact but translate the backslash to a slash.
    */
    wUnicode[2] = '\\';
    wUnicode += 3;
  }else if( wUnicode==zUnicode && nChar>MAX_PATH
            && (zUtf8[0]=='\\' || zUtf8[0]=='/')
            && (zUtf8[1]=='\\' || zUtf8[1]=='/') && zUtf8[2]!='?'){
    memmove(wUnicode+6, wUnicode, nChar*sizeof(wchar_t));
    memcpy(wUnicode, L"\\\\?\\UNC", 7*sizeof(wchar_t));
    wUnicode += 7;
  }
  /*
  ** In the remainder of the path, translate invalid characters to
  ** characters in the Unicode private use area. This is what makes
  ** Win32 fossil.exe work well in a Cygwin environment even when a
  ** filename contains characters which are invalid for Win32.
  */
  while( *wUnicode != '\0' ){
    if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){
      *wUnicode |= 0xF000;
    }else if( *wUnicode == '/' ){
      *wUnicode = '\\';
    }
    ++wUnicode;







<
|











<
|





<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

|
<
<
<
<
<
<
<
<
<
|

<
<
<
<
<
<

<
<
<
<
<
<







177
178
179
180
181
182
183

184
185
186
187
188
189
190
191
192
193
194
195

196
197
198
199
200
201

202















203
204









205
206






207






208
209
210
211
212
213
214
** Translate text from UTF-8 to the filename character set.
** Return a pointer to the translated text.
** Call fossil_filename_free() to deallocate any memory used to store the
** returned pointer when done.
**
** On Windows, characters in the range U+0001 to U+0031 and the
** characters '"', '*', ':', '<', '>', '?' and '|' are invalid

** to be used. Therefore, translate those to characters in the
** in the range U+F001 - U+F07F (private use area), so those
** characters never arrive in any Windows API. The filenames might
** look strange in Windows explorer, but in the cygwin shell
** everything looks as expected.
**
** See: <http://cygwin.com/cygwin-ug-net/using-specialnames.html>
**
*/
void *fossil_utf8_to_filename(const char *zUtf8){
#ifdef _WIN32
  int nChar = MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, 0, 0);

  wchar_t *zUnicode = sqlite3_malloc( nChar * 2 );
  wchar_t *wUnicode = zUnicode;
  if( zUnicode==0 ){
    return 0;
  }
  MultiByteToWideChar(CP_UTF8, 0, zUtf8, -1, zUnicode, nChar);

  /* If path starts with "<drive>:/" or "<drive>:\", don't translate the ':' */















  if( fossil_isalpha(zUtf8[0]) && zUtf8[1]==':'
           && (zUtf8[2]=='\\' || zUtf8[2]=='/')) {









    zUnicode[2] = '\\';
    wUnicode += 3;






  }






  while( *wUnicode != '\0' ){
    if ( (*wUnicode < ' ') || wcschr(L"\"*:<>?|", *wUnicode) ){
      *wUnicode |= 0xF000;
    }else if( *wUnicode == '/' ){
      *wUnicode = '\\';
    }
    ++wUnicode;

Changes to src/vfile.c.

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
** Given a UUID, return the corresponding record ID.  If the UUID
** does not exist, then return 0.
**
** For this routine, the UUID must be exact.  For a match against
** user input with mixed case, use resolve_uuid().
**
** If the UUID is not found and phantomize is 1 or 2, then attempt to
** create a phantom record.  A private phantom is created for 2 and
** a public phantom is created for 1.
*/
int uuid_to_rid(const char *zUuid, int phantomize){
  int rid, sz;
  char z[UUID_SIZE+1];

  sz = strlen(zUuid);
  if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){
    return 0;
  }
  memcpy(z, zUuid, UUID_SIZE+1);
  canonical16(z, sz);
  rid = fast_uuid_to_rid(z);







|






|







43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
** Given a UUID, return the corresponding record ID.  If the UUID
** does not exist, then return 0.
**
** For this routine, the UUID must be exact.  For a match against
** user input with mixed case, use resolve_uuid().
**
** If the UUID is not found and phantomize is 1 or 2, then attempt to 
** create a phantom record.  A private phantom is created for 2 and
** a public phantom is created for 1.
*/
int uuid_to_rid(const char *zUuid, int phantomize){
  int rid, sz;
  char z[UUID_SIZE+1];
  
  sz = strlen(zUuid);
  if( sz!=UUID_SIZE || !validate16(zUuid, sz) ){
    return 0;
  }
  memcpy(z, zUuid, UUID_SIZE+1);
  canonical16(z, sz);
  rid = fast_uuid_to_rid(z);
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
** VFILE.CHNGED field according to whether or not
** the file has changed.  0 means no change.  1 means edited.  2 means
** the file has changed due to a merge.  3 means the file was added
** by a merge.
**
** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either
** removed from configuration management via "fossil rm" or added via
** "fossil add", respectively, and in both cases we always know that
** the file has changed without having the check the size, mtime,
** or on-disk content.
**
** If the size of the file has changed, then we always know that the file
** changed without having to look at the mtime or on-disk content.
**
** The mtime of the file is only a factor if the mtime-changes setting







|







138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
** VFILE.CHNGED field according to whether or not
** the file has changed.  0 means no change.  1 means edited.  2 means
** the file has changed due to a merge.  3 means the file was added
** by a merge.
**
** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either
** removed from configuration management via "fossil rm" or added via
** "fossil add", respectively, and in both cases we always know that 
** the file has changed without having the check the size, mtime,
** or on-disk content.
**
** If the size of the file has changed, then we always know that the file
** changed without having to look at the mtime or on-disk content.
**
** The mtime of the file is only a factor if the mtime-changes setting
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
        continue;
      }
    }
    if( verbose ) fossil_print("%s\n", &zName[nRepos]);
    if( file_wd_isdir(zName) == 1 ){
      /*TODO(dchest): remove directories? */
      fossil_fatal("%s is directory, cannot overwrite\n", zName);
    }
    if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){
      file_delete(zName);
    }
    if( isLink ){
      symlink_create(blob_str(&content), zName);
    }else{
      blob_write_to_file(&content, zName);







|







313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
        continue;
      }
    }
    if( verbose ) fossil_print("%s\n", &zName[nRepos]);
    if( file_wd_isdir(zName) == 1 ){
      /*TODO(dchest): remove directories? */
      fossil_fatal("%s is directory, cannot overwrite\n", zName);
    }    
    if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){
      file_delete(zName);
    }
    if( isLink ){
      symlink_create(blob_str(&content), zName);
    }else{
      blob_write_to_file(&content, zName);
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
  static const char *const azTemp[] = {
     "baseline",
     "merge",
     "original",
     "output",
  };
  int i, j, n;

  if( strglob("ci-comment-????????????.txt", zName) ) return 1;
  for(; zName[0]!=0; zName++){
    if( zName[0]=='/' && strglob("/ci-comment-????????????.txt", zName) ){
      return 1;
    }
    if( zName[0]!='-' ) continue;
    for(i=0; i<sizeof(azTemp)/sizeof(azTemp[0]); i++){
      n = (int)strlen(azTemp[i]);
      if( memcmp(azTemp[i], zName+1, n) ) continue;
      if( zName[n+1]==0 ) return 1;
      if( zName[n+1]=='-' ){
        for(j=n+2; zName[j] && fossil_isdigit(zName[j]); j++){}
        if( zName[j]==0 ) return 1;
      }
    }
  }
  return 0;
}

#if INTERFACE
/*
** Values for the scanFlags parameter to vfile_scan().







|














|







390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
  static const char *const azTemp[] = {
     "baseline",
     "merge",
     "original",
     "output",
  };
  int i, j, n;
  
  if( strglob("ci-comment-????????????.txt", zName) ) return 1;
  for(; zName[0]!=0; zName++){
    if( zName[0]=='/' && strglob("/ci-comment-????????????.txt", zName) ){
      return 1;
    }
    if( zName[0]!='-' ) continue;
    for(i=0; i<sizeof(azTemp)/sizeof(azTemp[0]); i++){
      n = (int)strlen(azTemp[i]);
      if( memcmp(azTemp[i], zName+1, n) ) continue;
      if( zName[n+1]==0 ) return 1;
      if( zName[n+1]=='-' ){
        for(j=n+2; zName[j] && fossil_isdigit(zName[j]); j++){}
        if( zName[j]==0 ) return 1;
      }
    }      
  }
  return 0;
}

#if INTERFACE
/*
** Values for the scanFlags parameter to vfile_scan().
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
*/
void vfile_aggregate_checksum_disk(int vid, Blob *pOut){
  FILE *in;
  Stmt q;
  char zBuf[4096];

  db_must_be_within_tree();
  db_prepare(&q,
      "SELECT %Q || pathname, pathname, origname, is_selected(id), rid"
      "  FROM vfile"
      " WHERE (NOT deleted OR NOT is_selected(id)) AND vid=%d"
      " ORDER BY if_selected(id, pathname, origname) /*scan*/",
      g.zLocalRoot, vid
  );
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFullpath = db_column_text(&q, 0);
    const char *zName = db_column_text(&q, 1);
    int isSelected = db_column_int(&q, 3);

    if( isSelected ){
      md5sum_step_text(zName, -1);
      if( file_wd_islink(zFullpath) ){
        /* Instead of file content, use link destination path */
        Blob pathBuf;

        sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n",
                         blob_read_link(&pathBuf, zFullpath));
        md5sum_step_text(zBuf, -1);
        md5sum_step_text(blob_str(&pathBuf), -1);
        blob_reset(&pathBuf);
      }else{
        in = fossil_fopen(zFullpath,"rb");
        if( in==0 ){







|


















|







672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
*/
void vfile_aggregate_checksum_disk(int vid, Blob *pOut){
  FILE *in;
  Stmt q;
  char zBuf[4096];

  db_must_be_within_tree();
  db_prepare(&q, 
      "SELECT %Q || pathname, pathname, origname, is_selected(id), rid"
      "  FROM vfile"
      " WHERE (NOT deleted OR NOT is_selected(id)) AND vid=%d"
      " ORDER BY if_selected(id, pathname, origname) /*scan*/",
      g.zLocalRoot, vid
  );
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){
    const char *zFullpath = db_column_text(&q, 0);
    const char *zName = db_column_text(&q, 1);
    int isSelected = db_column_int(&q, 3);

    if( isSelected ){
      md5sum_step_text(zName, -1);
      if( file_wd_islink(zFullpath) ){
        /* Instead of file content, use link destination path */
        Blob pathBuf;

        sqlite3_snprintf(sizeof(zBuf), zBuf, " %ld\n", 
                         blob_read_link(&pathBuf, zFullpath));
        md5sum_step_text(zBuf, -1);
        md5sum_step_text(blob_str(&pathBuf), -1);
        blob_reset(&pathBuf);
      }else{
        in = fossil_fopen(zFullpath,"rb");
        if( in==0 ){
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
    }else{
      int rid = db_column_int(&q, 4);
      const char *zOrigName = db_column_text(&q, 2);
      char zBuf[100];
      Blob file;

      if( zOrigName ) zName = zOrigName;
      if( rid>0 || vid==0 ){
        md5sum_step_text(zName, -1);
        blob_zero(&file);
        content_get(rid, &file);
        sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file));
        md5sum_step_text(zBuf, -1);
        md5sum_step_blob(&file);
        blob_reset(&file);







|







722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
    }else{
      int rid = db_column_int(&q, 4);
      const char *zOrigName = db_column_text(&q, 2);
      char zBuf[100];
      Blob file;

      if( zOrigName ) zName = zOrigName;
      if( rid>0 ){
        md5sum_step_text(zName, -1);
        blob_zero(&file);
        content_get(rid, &file);
        sqlite3_snprintf(sizeof(zBuf), zBuf, " %d\n", blob_size(&file));
        md5sum_step_text(zBuf, -1);
        md5sum_step_blob(&file);
        blob_reset(&file);
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
** the working check-out on disk.  Report any errors.
*/
void vfile_compare_repository_to_disk(int vid){
  int rc;
  Stmt q;
  Blob disk, repo;
  char *zOut;

  db_must_be_within_tree();
  db_prepare(&q,
      "SELECT %Q || pathname, pathname, rid FROM vfile"
      " WHERE NOT deleted AND vid=%d AND is_selected(id)"
      " ORDER BY if_selected(id, pathname, origname) /*scan*/",
      g.zLocalRoot, vid
  );
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){







|

|







761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
** the working check-out on disk.  Report any errors.
*/
void vfile_compare_repository_to_disk(int vid){
  int rc;
  Stmt q;
  Blob disk, repo;
  char *zOut;
  
  db_must_be_within_tree();
  db_prepare(&q, 
      "SELECT %Q || pathname, pathname, rid FROM vfile"
      " WHERE NOT deleted AND vid=%d AND is_selected(id)"
      " ORDER BY if_selected(id, pathname, origname) /*scan*/",
      g.zLocalRoot, vid
  );
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
*/
void vfile_aggregate_checksum_repository(int vid, Blob *pOut){
  Blob file;
  Stmt q;
  char zBuf[100];

  db_must_be_within_tree();

  db_prepare(&q, "SELECT pathname, origname, rid, is_selected(id)"
                 " FROM vfile"
                 " WHERE (NOT deleted OR NOT is_selected(id))"
                 "   %s AND vid=%d"
                 " ORDER BY if_selected(id,pathname,origname) /*scan*/",
                 (vid ? "AND rid>0" : ""), vid);
  blob_zero(&file);
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zOrigName = db_column_text(&q, 1);
    int rid = db_column_int(&q, 2);
    int isSelected = db_column_int(&q, 3);







|



|

|







827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
*/
void vfile_aggregate_checksum_repository(int vid, Blob *pOut){
  Blob file;
  Stmt q;
  char zBuf[100];

  db_must_be_within_tree();
 
  db_prepare(&q, "SELECT pathname, origname, rid, is_selected(id)"
                 " FROM vfile"
                 " WHERE (NOT deleted OR NOT is_selected(id))"
                 "   AND rid>0 AND vid=%d"
                 " ORDER BY if_selected(id,pathname,origname) /*scan*/",
                 vid);
  blob_zero(&file);
  md5sum_init();
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    const char *zOrigName = db_column_text(&q, 1);
    int rid = db_column_int(&q, 2);
    int isSelected = db_column_int(&q, 3);
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
**
** Return the resulting checksum in blob pOut.
**
** If pManOut is not NULL then fill it with the checksum found in the
** "R" card near the end of the manifest.
**
** In a well-formed manifest, the two checksums computed here, pOut and
** pManOut, should be identical.
*/
void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
  int fid;
  Blob file;
  Blob err;
  Manifest *pManifest;
  ManifestFile *pFile;







|







865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
**
** Return the resulting checksum in blob pOut.
**
** If pManOut is not NULL then fill it with the checksum found in the
** "R" card near the end of the manifest.
**
** In a well-formed manifest, the two checksums computed here, pOut and
** pManOut, should be identical.  
*/
void vfile_aggregate_checksum_manifest(int vid, Blob *pOut, Blob *pManOut){
  int fid;
  Blob file;
  Blob err;
  Manifest *pManifest;
  ManifestFile *pFile;

Changes to src/wiki.c.

220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
  isSandbox = is_sandbox(zPageName);
  if( isSandbox ){
    zBody = db_get("sandbox",zBody);
    zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
    rid = 0;
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
    if( pWiki ){
      zBody = pWiki->zWiki;
      zMimetype = pWiki->zMimetype;
    }
  }
  zMimetype = wiki_filter_mimetypes(zMimetype);
  if( !g.isHome ){
    if( rid ){
      style_submenu_element("Diff", "Last change",
                 "%R/wdiff?name=%T&a=%d", zPageName, rid);
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      style_submenu_element("Details", "Details",
                   "%R/info/%s", zUuid);
    }
    if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
      if( db_get_boolean("wysiwyg-wiki", 0) ){
        style_submenu_element("Edit", "Edit Wiki Page",
             "%s/wikiedit?name=%T&wysiwyg=1",
             g.zTop, zPageName);
      }else{
        style_submenu_element("Edit", "Edit Wiki Page",
             "%s/wikiedit?name=%T",
             g.zTop, zPageName);
      }
    }
    if( rid && g.perm.ApndWiki && g.perm.Attach ){
      style_submenu_element("Attach", "Add An Attachment",
           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
           g.zTop, zPageName, g.zTop, zPageName);
    }
    if( rid && g.perm.ApndWiki ){
      style_submenu_element("Append", "Add A Comment",
           "%s/wikiappend?name=%T&mimetype=%s",
           g.zTop, zPageName, zMimetype);
    }
    if( g.perm.Hyperlink ){
      style_submenu_element("History", "History", "%s/whistory?name=%T",
           g.zTop, zPageName);
    }







|


















|


















|







220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
  isSandbox = is_sandbox(zPageName);
  if( isSandbox ){
    zBody = db_get("sandbox",zBody);
    zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
    rid = 0;
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    pWiki = manifest_get(rid, CFTYPE_WIKI, 0);
    if( pWiki ){
      zBody = pWiki->zWiki;
      zMimetype = pWiki->zMimetype;
    }
  }
  zMimetype = wiki_filter_mimetypes(zMimetype);
  if( !g.isHome ){
    if( rid ){
      style_submenu_element("Diff", "Last change",
                 "%R/wdiff?name=%T&a=%d", zPageName, rid);
      zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
      style_submenu_element("Details", "Details",
                   "%R/info/%S", zUuid);
    }
    if( (rid && g.perm.WrWiki) || (!rid && g.perm.NewWiki) ){
      if( db_get_boolean("wysiwyg-wiki", 0) ){
        style_submenu_element("Edit", "Edit Wiki Page",
             "%s/wikiedit?name=%T&wysiwyg=1",
             g.zTop, zPageName);
      }else{
        style_submenu_element("Edit", "Edit Wiki Page",
             "%s/wikiedit?name=%T",
             g.zTop, zPageName);
      }
    }
    if( rid && g.perm.ApndWiki && g.perm.Attach ){
      style_submenu_element("Attach", "Add An Attachment",
           "%s/attachadd?page=%T&from=%s/wiki%%3fname=%T",
           g.zTop, zPageName, g.zTop, zPageName);
    }
    if( rid && g.perm.ApndWiki ){
      style_submenu_element("Append", "Add A Comment", 
           "%s/wikiappend?name=%T&mimetype=%s",
           g.zTop, zPageName, zMimetype);
    }
    if( g.perm.Hyperlink ){
      style_submenu_element("History", "History", "%s/whistory?name=%T",
           g.zTop, zPageName);
    }
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
  db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
  manifest_crosslink(nrid, pWiki, MC_NONE);
}

/*
** Formal names and common names for the various wiki styles.
*/
static const char *const azStyles[] = {
  "text/x-fossil-wiki", "Fossil Wiki",
  "text/x-markdown",    "Markdown",
  "text/plain",         "Plain Text"
};

/*
** Output a selection box from which the user can select the







|







298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
  db_multi_exec("INSERT OR IGNORE INTO unclustered VALUES(%d);", nrid);
  manifest_crosslink(nrid, pWiki, MC_NONE);
}

/*
** Formal names and common names for the various wiki styles.
*/
static const char *azStyles[] = {
  "text/x-fossil-wiki", "Fossil Wiki",
  "text/x-markdown",    "Markdown",
  "text/plain",         "Plain Text"
};

/*
** Output a selection box from which the user can select the
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
    }
    if( zBody==0 ){
      zBody = db_get("sandbox","");
      zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
    }
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
      login_needed();







|







379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
    }
    if( zBody==0 ){
      zBody = db_get("sandbox","");
      zMimetype = db_get("sandbox-mimetype","text/x-fossil-wiki");
    }
  }else{
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( (rid && !g.perm.WrWiki) || (!rid && !g.perm.NewWiki) ){
      login_needed();
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
        blob_appendf(&wiki, "N %s\n", zMimetype);
      }
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }
      if( !login_is_nobody() ){
        blob_appendf(&wiki, "U %F\n", login_name());
      }
      blob_appendf(&wiki, "W %d\n%s\n", strlen(zBody), zBody);
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);
      wiki_put(&wiki, 0);
    }







|
|







418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
        blob_appendf(&wiki, "N %s\n", zMimetype);
      }
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }
      if( g.zLogin ){
        blob_appendf(&wiki, "U %F\n", g.zLogin);
      }
      blob_appendf(&wiki, "W %d\n%s\n", strlen(zBody), zBody);
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);
      wiki_put(&wiki, 0);
    }
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
  if( n<20 ) n = 20;
  if( n>30 ) n = 30;
  if( !isWysiwyg ){
    /* Traditional markup-only editing */
    form_begin(0, "%R/wikiedit");
    @ <div>
    mimetype_option_menu(zMimetype);
    @ <br /><textarea name="w" class="wikiedit" cols="80"
    @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
    @ <br />
    if( db_get_boolean("wysiwyg-wiki", 0) ){
      @ <input type="submit" name="edit-wysiwyg" value="Wysiwyg Editor"
      @  onclick='return confirm("Switching to WYSIWYG-mode\nwill erase your markup\nedits. Continue?")' />
    }
    @ <input type="submit" name="preview" value="Preview Your Changes" />







|







460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
  if( n<20 ) n = 20;
  if( n>30 ) n = 30;
  if( !isWysiwyg ){
    /* Traditional markup-only editing */
    form_begin(0, "%R/wikiedit");
    @ <div>
    mimetype_option_menu(zMimetype);
    @ <br /><textarea name="w" class="wikiedit" cols="80" 
    @  rows="%d(n)" wrap="virtual">%h(zBody)</textarea>
    @ <br />
    if( db_get_boolean("wysiwyg-wiki", 0) ){
      @ <input type="submit" name="edit-wysiwyg" value="Wysiwyg Editor"
      @  onclick='return confirm("Switching to WYSIWYG-mode\nwill erase your markup\nedits. Continue?")' />
    }
    @ <input type="submit" name="preview" value="Preview Your Changes" />
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
void wikinew_page(void){
  const char *zName;
  const char *zMimetype;
  login_check_credentials();
  if( !g.perm.NewWiki ){
    login_needed();
    return;
  }
  zName = PD("name","");
  zMimetype = wiki_filter_mimetypes(P("mimetype"));
  if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
    if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0
     && db_get_boolean("wysiwyg-wiki", 0)
    ){
      cgi_redirectf("wikiedit?name=%T&wysiwyg=1", zName);







|







512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
void wikinew_page(void){
  const char *zName;
  const char *zMimetype;
  login_check_credentials();
  if( !g.perm.NewWiki ){
    login_needed();
    return;
  }  
  zName = PD("name","");
  zMimetype = wiki_filter_mimetypes(P("mimetype"));
  if( zName[0] && wiki_name_is_wellformed((const unsigned char *)zName) ){
    if( fossil_strcmp(zMimetype,"text/x-fossil-wiki")==0
     && db_get_boolean("wysiwyg-wiki", 0)
    ){
      cgi_redirectf("wikiedit?name=%T&wysiwyg=1", zName);
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
  char *zId;

  zDate = db_text(0, "SELECT datetime('now')");
  zRemark = PD("r","");
  zUser = PD("u",g.zLogin);
  if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
    zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
    blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h",
      zId, zDate, login_name());
    if( zUser[0] && fossil_strcmp(zUser,login_name()) ){
      blob_appendf(p, " (claiming to be %h)", zUser);
    }
    blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
    blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, login_name());
    if( zUser[0] && fossil_strcmp(zUser,login_name()) ){
      blob_appendf(p, " (claiming to be %h)", zUser);
    }
    blob_appendf(p, " added:*\n\n%s\n", zRemark);
  }else{
    blob_appendf(p, "\n\n------------------------------------------------\n"
                    "On %s UTC %s", zDate, login_name());
    if( zUser[0] && fossil_strcmp(zUser,login_name()) ){
      blob_appendf(p, " (claiming to be %s)", zUser);
    }
    blob_appendf(p, " added:\n\n%s\n", zRemark);
  }
  fossil_free(zDate);
}








|
|
|




|
|





|
|







555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
  char *zId;

  zDate = db_text(0, "SELECT datetime('now')");
  zRemark = PD("r","");
  zUser = PD("u",g.zLogin);
  if( fossil_strcmp(zMimetype, "text/x-fossil-wiki")==0 ){
    zId = db_text(0, "SELECT lower(hex(randomblob(8)))");
    blob_appendf(p, "\n\n<hr><div id=\"%s\"><i>On %s UTC %h", 
      zId, zDate, g.zLogin);
    if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){
      blob_appendf(p, " (claiming to be %h)", zUser);
    }
    blob_appendf(p, " added:</i><br />\n%s</div id=\"%s\">", zRemark, zId);
  }else if( fossil_strcmp(zMimetype, "text/x-markdown")==0 ){
    blob_appendf(p, "\n\n------\n*On %s UTC %h", zDate, g.zLogin);
    if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){
      blob_appendf(p, " (claiming to be %h)", zUser);
    }
    blob_appendf(p, " added:*\n\n%s\n", zRemark);
  }else{
    blob_appendf(p, "\n\n------------------------------------------------\n"
                    "On %s UTC %s", zDate, g.zLogin);
    if( zUser[0] && fossil_strcmp(zUser,g.zLogin) ){
      blob_appendf(p, " (claiming to be %s)", zUser);
    }
    blob_appendf(p, " added:\n\n%s\n", zRemark);
  }
  fossil_free(zDate);
}

599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
  login_check_credentials();
  zPageName = PD("name","");
  zMimetype = wiki_filter_mimetypes(P("mimetype"));
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
  if( !isSandbox ){
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0,
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( !rid ){
      fossil_redirect_home();







|







599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
  login_check_credentials();
  zPageName = PD("name","");
  zMimetype = wiki_filter_mimetypes(P("mimetype"));
  if( check_name(zPageName) ) return;
  isSandbox = is_sandbox(zPageName);
  if( !isSandbox ){
    zTag = mprintf("wiki-%s", zPageName);
    rid = db_int(0, 
      "SELECT rid FROM tagxref"
      " WHERE tagid=(SELECT tagid FROM tag WHERE tagname=%Q)"
      " ORDER BY mtime DESC", zTag
    );
    free(zTag);
    if( !rid ){
      fossil_redirect_home();
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
        blob_appendf(&wiki, "N %s\n", zMimetype);
      }
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }
      if( !login_is_nobody() ){
        blob_appendf(&wiki, "U %F\n", login_name());
      }
      appendRemark(&body, zMimetype);
      blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body));
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);
      wiki_put(&wiki, rid);







|
|







648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
        blob_appendf(&wiki, "N %s\n", zMimetype);
      }
      if( rid ){
        char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
        blob_appendf(&wiki, "P %s\n", zUuid);
        free(zUuid);
      }
      if( g.zLogin ){
        blob_appendf(&wiki, "U %F\n", g.zLogin);
      }
      appendRemark(&body, zMimetype);
      blob_appendf(&wiki, "W %d\n%s\n", blob_size(&body), blob_str(&body));
      md5sum_blob(&wiki, &cksum);
      blob_appendf(&wiki, "Z %b\n", &cksum);
      blob_reset(&cksum);
      wiki_put(&wiki, rid);
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
  login_insert_csrf_secret();
  @ <input type="hidden" name="name" value="%h(zPageName)" />
  @ <input type="hidden" name="mimetype" value="%h(zMimetype)" />
  @ Your Name:
  @ <input type="text" name="u" size="20" value="%h(zUser)" /><br />
  zFormat = mimetype_common_name(zMimetype);
  @ Comment to append (formatted as %s(zFormat)):<br />
  @ <textarea name="r" class="wikiedit" cols="80"
  @  rows="10" wrap="virtual">%h(PD("r",""))</textarea>
  @ <br />
  @ <input type="submit" name="preview" value="Preview Your Comment" />
  @ <input type="submit" name="submit" value="Append Your Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  captcha_generate(0);
  @ </form>







|







688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
  login_insert_csrf_secret();
  @ <input type="hidden" name="name" value="%h(zPageName)" />
  @ <input type="hidden" name="mimetype" value="%h(zMimetype)" />
  @ Your Name:
  @ <input type="text" name="u" size="20" value="%h(zUser)" /><br />
  zFormat = mimetype_common_name(zMimetype);
  @ Comment to append (formatted as %s(zFormat)):<br />
  @ <textarea name="r" class="wikiedit" cols="80" 
  @  rows="10" wrap="virtual">%h(PD("r",""))</textarea>
  @ <br />
  @ <input type="submit" name="preview" value="Preview Your Comment" />
  @ <input type="submit" name="submit" value="Append Your Changes" />
  @ <input type="submit" name="cancel" value="Cancel" />
  captcha_generate(0);
  @ </form>
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
**
** - wiki page name
** - tagxref (whatever that really is!)
**
** Used by wcontent_page() and the JSON wiki code.
*/
void wiki_prepare_page_list( Stmt * pStmt ){
  db_prepare(pStmt,
    "SELECT"
    "  substr(tagname, 6) as name,"
    "  (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref"
    "  FROM tag WHERE tagname GLOB 'wiki-*'"
    " ORDER BY lower(tagname) /*sort*/"
  );
}







|







807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
**
** - wiki page name
** - tagxref (whatever that really is!)
**
** Used by wcontent_page() and the JSON wiki code.
*/
void wiki_prepare_page_list( Stmt * pStmt ){
  db_prepare(pStmt, 
    "SELECT"
    "  substr(tagname, 6) as name,"
    "  (SELECT value FROM tagxref WHERE tagid=tag.tagid ORDER BY mtime DESC) as tagXref"
    "  FROM tag WHERE tagname GLOB 'wiki-*'"
    " ORDER BY lower(tagname) /*sort*/"
  );
}
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
  Stmt q;
  const char * zTitle;
  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  zTitle = PD("title","*");
  style_header("Wiki Pages Found");
  @ <ul>
  db_prepare(&q,
    "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
    " ORDER BY lower(tagname) /*sort*/" ,
    zTitle);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>
  }







|







864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
  Stmt q;
  const char * zTitle;
  login_check_credentials();
  if( !g.perm.RdWiki ){ login_needed(); return; }
  zTitle = PD("title","*");
  style_header("Wiki Pages Found");
  @ <ul>
  db_prepare(&q, 
    "SELECT substr(tagname, 6, 1000) FROM tag WHERE tagname like 'wiki-%%%q%%'"
    " ORDER BY lower(tagname) /*sort*/" ,
    zTitle);
  while( db_step(&q)==SQLITE_ROW ){
    const char *zName = db_column_text(&q, 0);
    @ <li>%z(href("%R/wiki?name=%T",zName))%h(zName)</a></li>
  }
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
  @ <li> <p><span class="wikiruleHead">Enumeration Lists</span>.
  @ An enumeration list item is a line that begins with a single "#" character
  @ surrounded on both sides by two or more spaces or by a tab.  Only a single
  @ level of enumeration list is supported by wiki.  For nested lists or for
  @ enumerations that count using letters or roman numerials, use HTML.</p></li>
  @ <li> <p><span class="wikiruleHead">Indented Paragraphs</span>.
  @ Any paragraph that begins with two or more spaces or a tab and
  @ which is not a bullet or enumeration list item is rendered
  @ indented.  Only a single level of indentation is supported by wiki; use
  @ HTML for deeper indentation.</p></li>
  @ <li> <p><span class="wikiruleHead">Hyperlinks</span>.
  @ Text within square brackets ("[...]") becomes a hyperlink.  The
  @ target can be a wiki page name, the artifact ID of a check-in or ticket,
  @ the name of an image, or a URL.  By default, the target is displayed
  @ as the text of the hyperlink.  But you can specify alternative text







|







915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
  @ <li> <p><span class="wikiruleHead">Enumeration Lists</span>.
  @ An enumeration list item is a line that begins with a single "#" character
  @ surrounded on both sides by two or more spaces or by a tab.  Only a single
  @ level of enumeration list is supported by wiki.  For nested lists or for
  @ enumerations that count using letters or roman numerials, use HTML.</p></li>
  @ <li> <p><span class="wikiruleHead">Indented Paragraphs</span>.
  @ Any paragraph that begins with two or more spaces or a tab and
  @ which is not a bullet or enumeration list item is rendered 
  @ indented.  Only a single level of indentation is supported by wiki; use
  @ HTML for deeper indentation.</p></li>
  @ <li> <p><span class="wikiruleHead">Hyperlinks</span>.
  @ Text within square brackets ("[...]") becomes a hyperlink.  The
  @ target can be a wiki page name, the artifact ID of a check-in or ticket,
  @ the name of an image, or a URL.  By default, the target is displayed
  @ as the text of the hyperlink.  But you can specify alternative text
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
  blob_appendf(&wiki, "L %F\n", zPageName );
  if( rid ){
    zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    blob_appendf(&wiki, "P %s\n", zUuid);
    free(zUuid);
  }
  user_select();
  if( !login_is_nobody() ){
      blob_appendf(&wiki, "U %F\n", login_name());
  }
  blob_appendf( &wiki, "W %d\n%s\n", blob_size(pContent),
                blob_str(pContent) );
  md5sum_blob(&wiki, &cksum);
  blob_appendf(&wiki, "Z %b\n", &cksum);
  blob_reset(&cksum);
  db_begin_transaction();







|
|







991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
  blob_appendf(&wiki, "L %F\n", zPageName );
  if( rid ){
    zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid);
    blob_appendf(&wiki, "P %s\n", zUuid);
    free(zUuid);
  }
  user_select();
  if( g.zLogin ){
      blob_appendf(&wiki, "U %F\n", g.zLogin);
  }
  blob_appendf( &wiki, "W %d\n%s\n", blob_size(pContent),
                blob_str(pContent) );
  md5sum_blob(&wiki, &cksum);
  blob_appendf(&wiki, "Z %b\n", &cksum);
  blob_reset(&cksum);
  db_begin_transaction();
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
    if( (g.argc!=4) && (g.argc!=5) ){
      usage("export PAGENAME ?FILE?");
    }
    zPageName = g.argv[3];
    rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
      " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
      " ORDER BY x.mtime DESC LIMIT 1",
      zPageName
    );
    if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
      zBody = pWiki->zWiki;
    }
    if( zBody==0 ){
      fossil_fatal("wiki page [%s] not found",zPageName);
    }







|







1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
    if( (g.argc!=4) && (g.argc!=5) ){
      usage("export PAGENAME ?FILE?");
    }
    zPageName = g.argv[3];
    rid = db_int(0, "SELECT x.rid FROM tag t, tagxref x"
      " WHERE x.tagid=t.tagid AND t.tagname='wiki-%q'"
      " ORDER BY x.mtime DESC LIMIT 1",
      zPageName 
    );
    if( (pWiki = manifest_get(rid, CFTYPE_WIKI, 0))!=0 ){
      zBody = pWiki->zWiki;
    }
    if( zBody==0 ){
      fossil_fatal("wiki page [%s] not found",zPageName);
    }
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
    if( g.argc!=5 ){
      usage("delete PAGENAME");
    }
    fossil_fatal("delete not yet implemented.");
  }else
  if( strncmp(g.argv[2],"list",n)==0 ){
    Stmt q;
    db_prepare(&q,
      "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
      " ORDER BY lower(tagname) /*sort*/"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      fossil_print( "%s\n",zName);
    }







|







1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
    if( g.argc!=5 ){
      usage("delete PAGENAME");
    }
    fossil_fatal("delete not yet implemented.");
  }else
  if( strncmp(g.argv[2],"list",n)==0 ){
    Stmt q;
    db_prepare(&q, 
      "SELECT substr(tagname, 6) FROM tag WHERE tagname GLOB 'wiki-*'"
      " ORDER BY lower(tagname) /*sort*/"
    );
    while( db_step(&q)==SQLITE_ROW ){
      const char *zName = db_column_text(&q, 0);
      fossil_print( "%s\n",zName);
    }

Changes to src/wikiformat.c.

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#define WIKI_LINKSONLY      0x020  /* No markup.  Only decorate links */
#endif


/*
** These are the only markup attributes allowed.
*/
enum allowed_attr_t {
  ATTR_ALIGN = 1,
  ATTR_ALT,
  ATTR_BGCOLOR,
  ATTR_BORDER,
  ATTR_CELLPADDING,
  ATTR_CELLSPACING,
  ATTR_CLASS,
  ATTR_CLEAR,
  ATTR_COLOR,
  ATTR_COLSPAN,
  ATTR_COMPACT,
  ATTR_FACE,
  ATTR_HEIGHT,
  ATTR_HREF,
  ATTR_HSPACE,
  ATTR_ID,
  ATTR_LINKS,
  ATTR_NAME,
  ATTR_ROWSPAN,
  ATTR_SIZE,
  ATTR_SRC,
  ATTR_START,
  ATTR_STYLE,
  ATTR_TARGET,
  ATTR_TYPE,
  ATTR_VALIGN,
  ATTR_VALUE,
  ATTR_VSPACE,
  ATTR_WIDTH
};

enum amsk_t {
  AMSK_ALIGN        = 0x00000001,
  AMSK_ALT          = 0x00000002,
  AMSK_BGCOLOR      = 0x00000004,
  AMSK_BORDER       = 0x00000008,
  AMSK_CELLPADDING  = 0x00000010,
  AMSK_CELLSPACING  = 0x00000020,
  AMSK_CLASS        = 0x00000040,
  AMSK_CLEAR        = 0x00000080,
  AMSK_COLOR        = 0x00000100,
  AMSK_COLSPAN      = 0x00000200,
  AMSK_COMPACT      = 0x00000400,
  /* re-use         = 0x00000800, */
  AMSK_FACE         = 0x00001000,
  AMSK_HEIGHT       = 0x00002000,
  AMSK_HREF         = 0x00004000,
  AMSK_HSPACE       = 0x00008000,
  AMSK_ID           = 0x00010000,
  AMSK_LINKS        = 0x00020000,
  AMSK_NAME         = 0x00040000,
  AMSK_ROWSPAN      = 0x00080000,
  AMSK_SIZE         = 0x00100000,
  AMSK_SRC          = 0x00200000,
  AMSK_START        = 0x00400000,
  AMSK_STYLE        = 0x00800000,
  AMSK_TARGET       = 0x01000000,
  AMSK_TYPE         = 0x02000000,
  AMSK_VALIGN       = 0x04000000,
  AMSK_VALUE        = 0x08000000,
  AMSK_VSPACE       = 0x10000000,
  AMSK_WIDTH        = 0x20000000
};

static const struct AllowedAttribute {
  const char *zName;
  unsigned int iMask;
} aAttribute[] = {
  /* These indexes MUST line up with their
     corresponding allowed_attr_t enum values.
  */
  { 0, 0 },
  { "align",         AMSK_ALIGN          },
  { "alt",           AMSK_ALT            },
  { "bgcolor",       AMSK_BGCOLOR        },
  { "border",        AMSK_BORDER         },
  { "cellpadding",   AMSK_CELLPADDING    },
  { "cellspacing",   AMSK_CELLSPACING    },
  { "class",         AMSK_CLASS          },
  { "clear",         AMSK_CLEAR          },
  { "color",         AMSK_COLOR          },
  { "colspan",       AMSK_COLSPAN        },
  { "compact",       AMSK_COMPACT        },
  { "face",          AMSK_FACE           },
  { "height",        AMSK_HEIGHT         },
  { "href",          AMSK_HREF           },
  { "hspace",        AMSK_HSPACE         },
  { "id",            AMSK_ID             },
  { "links",         AMSK_LINKS          },
  { "name",          AMSK_NAME           },
  { "rowspan",       AMSK_ROWSPAN        },
  { "size",          AMSK_SIZE           },
  { "src",           AMSK_SRC            },
  { "start",         AMSK_START          },
  { "style",         AMSK_STYLE          },
  { "target",        AMSK_TARGET         },
  { "type",          AMSK_TYPE           },
  { "valign",        AMSK_VALIGN         },
  { "value",         AMSK_VALUE          },
  { "vspace",        AMSK_VSPACE         },
  { "width",         AMSK_WIDTH          },
};

/*
** Use binary search to locate a tag in the aAttribute[] table.
*/
static int findAttr(const char *z){
  int i, c, first, last;







<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<





<
<
<

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|







33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68



69
70
71
72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

98
99
100
101
102



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#define WIKI_LINKSONLY      0x020  /* No markup.  Only decorate links */
#endif


/*
** These are the only markup attributes allowed.
*/

#define ATTR_ALIGN              1
#define ATTR_ALT                2
#define ATTR_BGCOLOR            3
#define ATTR_BORDER             4
#define ATTR_CELLPADDING        5
#define ATTR_CELLSPACING        6
#define ATTR_CLASS              7
#define ATTR_CLEAR              8
#define ATTR_COLOR              9
#define ATTR_COLSPAN            10
#define ATTR_COMPACT            11
#define ATTR_FACE               12
#define ATTR_HEIGHT             13
#define ATTR_HREF               14
#define ATTR_HSPACE             15
#define ATTR_ID                 16
#define ATTR_LINKS              17
#define ATTR_NAME               18
#define ATTR_ROWSPAN            19
#define ATTR_SIZE               20
#define ATTR_SRC                21
#define ATTR_START              22
#define ATTR_STYLE              23
#define ATTR_TARGET             24
#define ATTR_TYPE               25
#define ATTR_VALIGN             26
#define ATTR_VALUE              27
#define ATTR_VSPACE             28
#define ATTR_WIDTH              29



#define AMSK_ALIGN              0x00000001
#define AMSK_ALT                0x00000002
#define AMSK_BGCOLOR            0x00000004
#define AMSK_BORDER             0x00000008
#define AMSK_CELLPADDING        0x00000010
#define AMSK_CELLSPACING        0x00000020
#define AMSK_CLASS              0x00000040
#define AMSK_CLEAR              0x00000080
#define AMSK_COLOR              0x00000100
#define AMSK_COLSPAN            0x00000200
#define AMSK_COMPACT            0x00000400

#define AMSK_FACE               0x00000800
#define AMSK_HEIGHT             0x00001000
#define AMSK_HREF               0x00002000
#define AMSK_HSPACE             0x00004000
#define AMSK_ID                 0x00008000
#define AMSK_LINKS              0x00010000
#define AMSK_NAME               0x00020000
#define AMSK_ROWSPAN            0x00040000
#define AMSK_SIZE               0x00080000
#define AMSK_SRC                0x00100000
#define AMSK_START              0x00200000
#define AMSK_STYLE              0x00400000
#define AMSK_TARGET             0x00800000
#define AMSK_TYPE               0x01000000
#define AMSK_VALIGN             0x02000000
#define AMSK_VALUE              0x04000000
#define AMSK_VSPACE             0x08000000
#define AMSK_WIDTH              0x10000000


static const struct AllowedAttribute {
  const char *zName;
  unsigned int iMask;
} aAttribute[] = {



  { 0, 0 },
  { "align",         AMSK_ALIGN,          },
  { "alt",           AMSK_ALT,            },
  { "bgcolor",       AMSK_BGCOLOR,        },
  { "border",        AMSK_BORDER,         },
  { "cellpadding",   AMSK_CELLPADDING,    },
  { "cellspacing",   AMSK_CELLSPACING,    },
  { "class",         AMSK_CLASS,          },
  { "clear",         AMSK_CLEAR,          },
  { "color",         AMSK_COLOR,          },
  { "colspan",       AMSK_COLSPAN,        },
  { "compact",       AMSK_COMPACT,        },
  { "face",          AMSK_FACE,           },
  { "height",        AMSK_HEIGHT,         },
  { "href",          AMSK_HREF,           },
  { "hspace",        AMSK_HSPACE,         },
  { "id",            AMSK_ID,             },
  { "links",         AMSK_LINKS,          },
  { "name",          AMSK_NAME,           },
  { "rowspan",       AMSK_ROWSPAN,        },
  { "size",          AMSK_SIZE,           },
  { "src",           AMSK_SRC,            },
  { "start",         AMSK_START,          },
  { "style",         AMSK_STYLE,          },
  { "target",        AMSK_TARGET,         },
  { "type",          AMSK_TYPE,           },
  { "valign",        AMSK_VALIGN,         },
  { "value",         AMSK_VALUE,          },
  { "vspace",        AMSK_VSPACE,         },
  { "width",         AMSK_WIDTH,          },
};

/*
** Use binary search to locate a tag in the aAttribute[] table.
*/
static int findAttr(const char *z){
  int i, c, first, last;
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
** and in numerical sequence.  The first markup type must be zero.
** The value for MARKUP_XYZ must correspond to the <xyz> entry
** in aAllowedMarkup[].
*/
#define MARKUP_INVALID            0
#define MARKUP_A                  1
#define MARKUP_ADDRESS            2
#define MARKUP_HTML5_ARTICLE      3
#define MARKUP_HTML5_ASIDE        4
#define MARKUP_B                  5
#define MARKUP_BIG                6
#define MARKUP_BLOCKQUOTE         7
#define MARKUP_BR                 8
#define MARKUP_CENTER             9
#define MARKUP_CITE               10
#define MARKUP_CODE               11
#define MARKUP_COL                12
#define MARKUP_COLGROUP           13
#define MARKUP_DD                 14
#define MARKUP_DFN                15
#define MARKUP_DIV                16
#define MARKUP_DL                 17
#define MARKUP_DT                 18
#define MARKUP_EM                 19
#define MARKUP_FONT               20
#define MARKUP_HTML5_FOOTER       21
#define MARKUP_H1                 22
#define MARKUP_H2                 23
#define MARKUP_H3                 24
#define MARKUP_H4                 25
#define MARKUP_H5                 26
#define MARKUP_H6                 27
#define MARKUP_HTML5_HEADER       28
#define MARKUP_HR                 29
#define MARKUP_I                  30
#define MARKUP_IMG                31
#define MARKUP_KBD                32
#define MARKUP_LI                 33
#define MARKUP_HTML5_NAV          34
#define MARKUP_NOBR               35
#define MARKUP_NOWIKI             36
#define MARKUP_OL                 37
#define MARKUP_P                  38
#define MARKUP_PRE                39
#define MARKUP_S                  40
#define MARKUP_SAMP               41
#define MARKUP_HTML5_SECTION      42
#define MARKUP_SMALL              43
#define MARKUP_SPAN               44
#define MARKUP_STRIKE             45
#define MARKUP_STRONG             46
#define MARKUP_SUB                47
#define MARKUP_SUP                48
#define MARKUP_TABLE              49
#define MARKUP_TBODY              50
#define MARKUP_TD                 51
#define MARKUP_TFOOT              52
#define MARKUP_TH                 53
#define MARKUP_THEAD              54
#define MARKUP_TITLE              55
#define MARKUP_TR                 56
#define MARKUP_TT                 57
#define MARKUP_U                  58
#define MARKUP_UL                 59
#define MARKUP_VAR                60
#define MARKUP_VERBATIM           61

/*
** The various markup is divided into the following types:
*/
#define MUTYPE_SINGLE      0x0001   /* <img>, <br>, or <hr> */
#define MUTYPE_BLOCK       0x0002   /* Forms a new paragraph. ex: <p>, <h2> */
#define MUTYPE_FONT        0x0004   /* Font changes. ex: <b>, <font>, <sub> */







<
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|
<
|
|
|
|
|
<
|
|
|
|
|
|
|
<
|
|
|
|
|
|
|
|
|
|
|
|
<
|
|
|
|
|
|







162
163
164
165
166
167
168


169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

185
186
187
188
189
190

191
192
193
194
195

196
197
198
199
200
201
202

203
204
205
206
207
208
209
210
211
212
213
214

215
216
217
218
219
220
221
222
223
224
225
226
227
** and in numerical sequence.  The first markup type must be zero.
** The value for MARKUP_XYZ must correspond to the <xyz> entry
** in aAllowedMarkup[].
*/
#define MARKUP_INVALID            0
#define MARKUP_A                  1
#define MARKUP_ADDRESS            2


#define MARKUP_B                  3
#define MARKUP_BIG                4
#define MARKUP_BLOCKQUOTE         5
#define MARKUP_BR                 6
#define MARKUP_CENTER             7
#define MARKUP_CITE               8
#define MARKUP_CODE               9
#define MARKUP_COL                10
#define MARKUP_COLGROUP           11
#define MARKUP_DD                 12
#define MARKUP_DFN                13
#define MARKUP_DIV                14
#define MARKUP_DL                 15
#define MARKUP_DT                 16
#define MARKUP_EM                 17
#define MARKUP_FONT               18

#define MARKUP_H1                 19
#define MARKUP_H2                 20
#define MARKUP_H3                 21
#define MARKUP_H4                 22
#define MARKUP_H5                 23
#define MARKUP_H6                 24

#define MARKUP_HR                 25
#define MARKUP_I                  26
#define MARKUP_IMG                27
#define MARKUP_KBD                28
#define MARKUP_LI                 29

#define MARKUP_NOBR               30
#define MARKUP_NOWIKI             31
#define MARKUP_OL                 32
#define MARKUP_P                  33
#define MARKUP_PRE                34
#define MARKUP_S                  35
#define MARKUP_SAMP               36

#define MARKUP_SMALL              37
#define MARKUP_SPAN               38
#define MARKUP_STRIKE             39
#define MARKUP_STRONG             40
#define MARKUP_SUB                41
#define MARKUP_SUP                42
#define MARKUP_TABLE              43
#define MARKUP_TBODY              44
#define MARKUP_TD                 45
#define MARKUP_TFOOT              46
#define MARKUP_TH                 47
#define MARKUP_THEAD              48

#define MARKUP_TR                 49
#define MARKUP_TT                 50
#define MARKUP_U                  51
#define MARKUP_UL                 52
#define MARKUP_VAR                53
#define MARKUP_VERBATIM           54

/*
** The various markup is divided into the following types:
*/
#define MUTYPE_SINGLE      0x0001   /* <img>, <br>, or <hr> */
#define MUTYPE_BLOCK       0x0002   /* Forms a new paragraph. ex: <p>, <h2> */
#define MUTYPE_FONT        0x0004   /* Font changes. ex: <b>, <font>, <sub> */
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
  short int iType;         /* The MUTYPE_* code */
  int allowedAttr;         /* Allowed attributes on this markup */
} aMarkup[] = {
 { 0,               MARKUP_INVALID,      0,                    0  },
 { "a",             MARKUP_A,            MUTYPE_HYPERLINK,
                    AMSK_HREF|AMSK_NAME|AMSK_CLASS|AMSK_TARGET|AMSK_STYLE },
 { "address",       MARKUP_ADDRESS,      MUTYPE_BLOCK,         AMSK_STYLE },
 { "article",       MARKUP_HTML5_ARTICLE, MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },
 { "aside",         MARKUP_HTML5_ASIDE,  MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },

 { "b",             MARKUP_B,            MUTYPE_FONT,          AMSK_STYLE },
 { "big",           MARKUP_BIG,          MUTYPE_FONT,          AMSK_STYLE },
 { "blockquote",    MARKUP_BLOCKQUOTE,   MUTYPE_BLOCK,         AMSK_STYLE },
 { "br",            MARKUP_BR,           MUTYPE_SINGLE,        AMSK_CLEAR },
 { "center",        MARKUP_CENTER,       MUTYPE_BLOCK,         AMSK_STYLE },
 { "cite",          MARKUP_CITE,         MUTYPE_FONT,          AMSK_STYLE },
 { "code",          MARKUP_CODE,         MUTYPE_FONT,          AMSK_STYLE },







<
<
<
<
<







249
250
251
252
253
254
255





256
257
258
259
260
261
262
  short int iType;         /* The MUTYPE_* code */
  int allowedAttr;         /* Allowed attributes on this markup */
} aMarkup[] = {
 { 0,               MARKUP_INVALID,      0,                    0  },
 { "a",             MARKUP_A,            MUTYPE_HYPERLINK,
                    AMSK_HREF|AMSK_NAME|AMSK_CLASS|AMSK_TARGET|AMSK_STYLE },
 { "address",       MARKUP_ADDRESS,      MUTYPE_BLOCK,         AMSK_STYLE },





 { "b",             MARKUP_B,            MUTYPE_FONT,          AMSK_STYLE },
 { "big",           MARKUP_BIG,          MUTYPE_FONT,          AMSK_STYLE },
 { "blockquote",    MARKUP_BLOCKQUOTE,   MUTYPE_BLOCK,         AMSK_STYLE },
 { "br",            MARKUP_BR,           MUTYPE_SINGLE,        AMSK_CLEAR },
 { "center",        MARKUP_CENTER,       MUTYPE_BLOCK,         AMSK_STYLE },
 { "cite",          MARKUP_CITE,         MUTYPE_FONT,          AMSK_STYLE },
 { "code",          MARKUP_CODE,         MUTYPE_FONT,          AMSK_STYLE },
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
                    AMSK_ID|AMSK_CLASS|AMSK_STYLE },
 { "dl",            MARKUP_DL,           MUTYPE_LIST,
                    AMSK_COMPACT|AMSK_STYLE },
 { "dt",            MARKUP_DT,           MUTYPE_LI,            AMSK_STYLE },
 { "em",            MARKUP_EM,           MUTYPE_FONT,          AMSK_STYLE },
 { "font",          MARKUP_FONT,         MUTYPE_FONT,
                    AMSK_COLOR|AMSK_FACE|AMSK_SIZE|AMSK_STYLE },
 { "footer",        MARKUP_HTML5_FOOTER, MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },

 { "h1",            MARKUP_H1,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h2",            MARKUP_H2,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h3",            MARKUP_H3,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h4",            MARKUP_H4,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h5",            MARKUP_H5,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h6",            MARKUP_H6,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },

 { "header",        MARKUP_HTML5_HEADER, MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },

 { "hr",            MARKUP_HR,           MUTYPE_SINGLE,
                    AMSK_ALIGN|AMSK_COLOR|AMSK_SIZE|AMSK_WIDTH|
                    AMSK_STYLE|AMSK_CLASS  },
 { "i",             MARKUP_I,            MUTYPE_FONT,          AMSK_STYLE },
 { "img",           MARKUP_IMG,          MUTYPE_SINGLE,
                    AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
                    AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE  },
 { "kbd",           MARKUP_KBD,          MUTYPE_FONT,          AMSK_STYLE },
 { "li",            MARKUP_LI,           MUTYPE_LI,
                    AMSK_TYPE|AMSK_VALUE|AMSK_STYLE  },
 { "nav",           MARKUP_HTML5_NAV,    MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },
 { "nobr",          MARKUP_NOBR,         MUTYPE_FONT,          0  },
 { "nowiki",        MARKUP_NOWIKI,       MUTYPE_SPECIAL,       0  },
 { "ol",            MARKUP_OL,           MUTYPE_LIST,
                    AMSK_START|AMSK_TYPE|AMSK_COMPACT|AMSK_STYLE  },
 { "p",             MARKUP_P,            MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "pre",           MARKUP_PRE,          MUTYPE_BLOCK,         AMSK_STYLE },
 { "s",             MARKUP_S,            MUTYPE_FONT,          AMSK_STYLE },
 { "samp",          MARKUP_SAMP,         MUTYPE_FONT,          AMSK_STYLE },
 { "section",       MARKUP_HTML5_SECTION, MUTYPE_BLOCK,
                                            AMSK_ID|AMSK_CLASS|AMSK_STYLE },
 { "small",         MARKUP_SMALL,        MUTYPE_FONT,          AMSK_STYLE },
 { "span",          MARKUP_SPAN,         MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "strike",        MARKUP_STRIKE,       MUTYPE_FONT,          AMSK_STYLE },
 { "strong",        MARKUP_STRONG,       MUTYPE_FONT,          AMSK_STYLE },
 { "sub",           MARKUP_SUB,          MUTYPE_FONT,          AMSK_STYLE },
 { "sup",           MARKUP_SUP,          MUTYPE_FONT,          AMSK_STYLE },







<
<
<












<
<
<
<










<
<









<
<







270
271
272
273
274
275
276



277
278
279
280
281
282
283
284
285
286
287
288




289
290
291
292
293
294
295
296
297
298


299
300
301
302
303
304
305
306
307


308
309
310
311
312
313
314
                    AMSK_ID|AMSK_CLASS|AMSK_STYLE },
 { "dl",            MARKUP_DL,           MUTYPE_LIST,
                    AMSK_COMPACT|AMSK_STYLE },
 { "dt",            MARKUP_DT,           MUTYPE_LI,            AMSK_STYLE },
 { "em",            MARKUP_EM,           MUTYPE_FONT,          AMSK_STYLE },
 { "font",          MARKUP_FONT,         MUTYPE_FONT,
                    AMSK_COLOR|AMSK_FACE|AMSK_SIZE|AMSK_STYLE },



 { "h1",            MARKUP_H1,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h2",            MARKUP_H2,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h3",            MARKUP_H3,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h4",            MARKUP_H4,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h5",            MARKUP_H5,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "h6",            MARKUP_H6,           MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },




 { "hr",            MARKUP_HR,           MUTYPE_SINGLE,
                    AMSK_ALIGN|AMSK_COLOR|AMSK_SIZE|AMSK_WIDTH|
                    AMSK_STYLE|AMSK_CLASS  },
 { "i",             MARKUP_I,            MUTYPE_FONT,          AMSK_STYLE },
 { "img",           MARKUP_IMG,          MUTYPE_SINGLE,
                    AMSK_ALIGN|AMSK_ALT|AMSK_BORDER|AMSK_HEIGHT|
                    AMSK_HSPACE|AMSK_SRC|AMSK_VSPACE|AMSK_WIDTH|AMSK_STYLE  },
 { "kbd",           MARKUP_KBD,          MUTYPE_FONT,          AMSK_STYLE },
 { "li",            MARKUP_LI,           MUTYPE_LI,
                    AMSK_TYPE|AMSK_VALUE|AMSK_STYLE  },


 { "nobr",          MARKUP_NOBR,         MUTYPE_FONT,          0  },
 { "nowiki",        MARKUP_NOWIKI,       MUTYPE_SPECIAL,       0  },
 { "ol",            MARKUP_OL,           MUTYPE_LIST,
                    AMSK_START|AMSK_TYPE|AMSK_COMPACT|AMSK_STYLE  },
 { "p",             MARKUP_P,            MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "pre",           MARKUP_PRE,          MUTYPE_BLOCK,         AMSK_STYLE },
 { "s",             MARKUP_S,            MUTYPE_FONT,          AMSK_STYLE },
 { "samp",          MARKUP_SAMP,         MUTYPE_FONT,          AMSK_STYLE },


 { "small",         MARKUP_SMALL,        MUTYPE_FONT,          AMSK_STYLE },
 { "span",          MARKUP_SPAN,         MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "strike",        MARKUP_STRIKE,       MUTYPE_FONT,          AMSK_STYLE },
 { "strong",        MARKUP_STRONG,       MUTYPE_FONT,          AMSK_STYLE },
 { "sub",           MARKUP_SUB,          MUTYPE_FONT,          AMSK_STYLE },
 { "sup",           MARKUP_SUP,          MUTYPE_FONT,          AMSK_STYLE },
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376

377
378
379
380
381
382
383
 { "tfoot",         MARKUP_TFOOT,        MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "th",            MARKUP_TH,           MUTYPE_TD,
                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_COLSPAN|
                    AMSK_ROWSPAN|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "thead",         MARKUP_THEAD,        MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "title",         MARKUP_TITLE,        MUTYPE_BLOCK, 0 },
 { "tr",            MARKUP_TR,           MUTYPE_TR,
                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE },
 { "tt",            MARKUP_TT,           MUTYPE_FONT,          AMSK_STYLE },
 { "u",             MARKUP_U,            MUTYPE_FONT,          AMSK_STYLE },
 { "ul",            MARKUP_UL,           MUTYPE_LIST,
                    AMSK_TYPE|AMSK_COMPACT|AMSK_STYLE  },
 { "var",           MARKUP_VAR,          MUTYPE_FONT,          AMSK_STYLE },
 { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,
                    AMSK_ID|AMSK_TYPE },
};

void show_allowed_wiki_markup( void ){
  int i; /* loop over allowedAttr */

  for( i=1 ; i<=sizeof(aMarkup)/sizeof(aMarkup[0]) - 1 ; i++ ){
    @ &lt;%s(aMarkup[i].zName)&gt;
  }
}

/*
** Use binary search to locate a tag in the aMarkup[] table.







<













>







324
325
326
327
328
329
330

331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
 { "tfoot",         MARKUP_TFOOT,        MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "th",            MARKUP_TH,           MUTYPE_TD,
                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_COLSPAN|
                    AMSK_ROWSPAN|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE  },
 { "thead",         MARKUP_THEAD,        MUTYPE_BLOCK,
                    AMSK_ALIGN|AMSK_CLASS|AMSK_STYLE  },

 { "tr",            MARKUP_TR,           MUTYPE_TR,
                    AMSK_ALIGN|AMSK_BGCOLOR|AMSK_VALIGN|AMSK_CLASS|AMSK_STYLE },
 { "tt",            MARKUP_TT,           MUTYPE_FONT,          AMSK_STYLE },
 { "u",             MARKUP_U,            MUTYPE_FONT,          AMSK_STYLE },
 { "ul",            MARKUP_UL,           MUTYPE_LIST,
                    AMSK_TYPE|AMSK_COMPACT|AMSK_STYLE  },
 { "var",           MARKUP_VAR,          MUTYPE_FONT,          AMSK_STYLE },
 { "verbatim",      MARKUP_VERBATIM,     MUTYPE_SPECIAL,
                    AMSK_ID|AMSK_TYPE },
};

void show_allowed_wiki_markup( void ){
  int i; /* loop over allowedAttr */

  for( i=1 ; i<=sizeof(aMarkup)/sizeof(aMarkup[0]) - 1 ; i++ ){
    @ &lt;%s(aMarkup[i].zName)&gt;
  }
}

/*
** Use binary search to locate a tag in the aMarkup[] table.
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
    p->aAttr[0].cTerm = c = z[i];
    z[i++] = 0;
    p->nAttr = 1;
    if( c=='>' ) return;
  }
  while( fossil_isspace(z[i]) ){ i++; }
  while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
    int attrOk;    /* True to preserve attribute.  False to ignore it */
    j = 0;
    while( fossil_isalnum(z[i]) ){
      if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
      i++;
    }
    zTag[j] = 0;
    p->aAttr[p->nAttr].iACode = iACode = findAttr(zTag);







|







764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
    p->aAttr[0].cTerm = c = z[i];
    z[i++] = 0;
    p->nAttr = 1;
    if( c=='>' ) return;
  }
  while( fossil_isspace(z[i]) ){ i++; }
  while( c!='>' && p->nAttr<8 && fossil_isalpha(z[i]) ){
    int attrOk;    /* True to preserver attribute.  False to ignore it */
    j = 0;
    while( fossil_isalnum(z[i]) ){
      if( j<sizeof(zTag)-1 ) zTag[j++] = fossil_tolower(z[i]);
      i++;
    }
    zTag[j] = 0;
    p->aAttr[p->nAttr].iACode = iACode = findAttr(zTag);
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
** original content.
*/
static void unparseMarkup(ParsedMarkup *p){
  int i, n;
  for(i=0; i<p->nAttr; i++){
    char *z = p->aAttr[i].zValue;
    if( z==0 ) continue;
    if( p->aAttr[i].cTerm ){
      n = strlen(z);
      z[n] = p->aAttr[i].cTerm;
    }
  }
}

/*
** Return the value of attribute attrId.  Return NULL if there is no
** ID attribute.
*/







<
|
|
<







846
847
848
849
850
851
852

853
854

855
856
857
858
859
860
861
** original content.
*/
static void unparseMarkup(ParsedMarkup *p){
  int i, n;
  for(i=0; i<p->nAttr; i++){
    char *z = p->aAttr[i].zValue;
    if( z==0 ) continue;

    n = strlen(z);
    z[n] = p->aAttr[i].cTerm;

  }
}

/*
** Return the value of attribute attrId.  Return NULL if there is no
** ID attribute.
*/
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
        break;
      }
      case TOKEN_MARKUP: {
        const char *zId;
        int iDiv;
        parseMarkup(&markup, z);

        /* Convert <title> to <h1 align='center'> */
        if( markup.iCode==MARKUP_TITLE && !p->inVerbatim ){
          markup.iCode = MARKUP_H1;
          markup.nAttr = 1;
          markup.aAttr[0].iACode = AMSK_ALIGN;
          markup.aAttr[0].zValue = "center";
          markup.aAttr[0].cTerm = 0;
        }

        /* Markup of the form </div id=ID> where there is a matching
        ** ID somewhere on the stack.  Exit any contained verbatim.
        ** Pop the stack up to the matching <div>.  Discard the </div>
        */
        if( markup.iCode==MARKUP_DIV && markup.endTag &&
             (zId = markupId(&markup))!=0 &&
             (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0







<
<
<
<
<
<
<
<
<







1446
1447
1448
1449
1450
1451
1452









1453
1454
1455
1456
1457
1458
1459
        break;
      }
      case TOKEN_MARKUP: {
        const char *zId;
        int iDiv;
        parseMarkup(&markup, z);










        /* Markup of the form </div id=ID> where there is a matching
        ** ID somewhere on the stack.  Exit any contained verbatim.
        ** Pop the stack up to the matching <div>.  Discard the </div>
        */
        if( markup.iCode==MARKUP_DIV && markup.endTag &&
             (zId = markupId(&markup))!=0 &&
             (iDiv = findTagWithId(p, MARKUP_DIV, zId))>=0
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
  int iStart;
  blob_to_utf8_no_bom(pIn, 0);
  z = blob_str(pIn);
  for(i=0; fossil_isspace(z[i]); i++){}
  if( z[i]!='<' ) return 0;
  i++;
  if( strncmp(&z[i],"title>", 6)!=0 ) return 0;
  for(iStart=i+6; fossil_isspace(z[iStart]); iStart++){}
  for(i=iStart; z[i] && (z[i]!='<' || strncmp(&z[i],"</title>",8)!=0); i++){}
  if( strncmp(&z[i],"</title>",8)!=0 ){
    blob_init(pTitle, 0, 0);
    blob_init(pTail, &z[iStart], -1);
    return 1;
  }
  if( i-iStart>0 ){
    blob_init(pTitle, &z[iStart], i-iStart);
  }else{
    blob_init(pTitle, 0, 0);
  }
  blob_init(pTail, &z[i+8], -1);
  return 1;
}

/*
** Parse text looking for wiki hyperlinks in one of the formats:
**







|

<
<
<
|
<
<
|
<
<
<







1716
1717
1718
1719
1720
1721
1722
1723
1724



1725


1726



1727
1728
1729
1730
1731
1732
1733
  int iStart;
  blob_to_utf8_no_bom(pIn, 0);
  z = blob_str(pIn);
  for(i=0; fossil_isspace(z[i]); i++){}
  if( z[i]!='<' ) return 0;
  i++;
  if( strncmp(&z[i],"title>", 6)!=0 ) return 0;
  iStart = i+6;
  for(i=iStart; z[i] && (z[i]!='<' || strncmp(&z[i],"</title>",8)!=0); i++){}



  if( z[i]!='<' ) return 0;


  blob_init(pTitle, &z[iStart], i-iStart);



  blob_init(pTail, &z[i+8], -1);
  return 1;
}

/*
** Parse text looking for wiki hyperlinks in one of the formats:
**

Changes to src/winhttp.c.

65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
*/
static void win32_http_request(void *pAppData){
  HttpRequest *p = (HttpRequest*)pAppData;
  FILE *in = 0, *out = 0;
  int amt, got;
  int wanted = 0;
  char *z;
  char zCmdFName[MAX_PATH];
  char zRequestFName[MAX_PATH];
  char zReplyFName[MAX_PATH];
  char zCmd[2000];          /* Command-line to process the request */
  char zHdr[2000];          /* The HTTP request header */

  sqlite3_snprintf(MAX_PATH, zCmdFName,
                   "%s_cmd%d.txt", zTempPrefix, p->id);
  sqlite3_snprintf(MAX_PATH, zRequestFName,
                   "%s_in%d.txt", zTempPrefix, p->id);
  sqlite3_snprintf(MAX_PATH, zReplyFName,
                   "%s_out%d.txt", zTempPrefix, p->id);
  amt = 0;
  while( amt<sizeof(zHdr) ){
    got = recv(p->s, &zHdr[amt], sizeof(zHdr)-1-amt, 0);







<





<
<







65
66
67
68
69
70
71

72
73
74
75
76


77
78
79
80
81
82
83
*/
static void win32_http_request(void *pAppData){
  HttpRequest *p = (HttpRequest*)pAppData;
  FILE *in = 0, *out = 0;
  int amt, got;
  int wanted = 0;
  char *z;

  char zRequestFName[MAX_PATH];
  char zReplyFName[MAX_PATH];
  char zCmd[2000];          /* Command-line to process the request */
  char zHdr[2000];          /* The HTTP request header */



  sqlite3_snprintf(MAX_PATH, zRequestFName,
                   "%s_in%d.txt", zTempPrefix, p->id);
  sqlite3_snprintf(MAX_PATH, zReplyFName,
                   "%s_out%d.txt", zTempPrefix, p->id);
  amt = 0;
  while( amt<sizeof(zHdr) ){
    got = recv(p->s, &zHdr[amt], sizeof(zHdr)-1-amt, 0);
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
    }else{
      break;
    }
    wanted -= got;
  }
  fclose(out);
  out = 0;
  sqlite3_snprintf(sizeof(zCmd), zCmd, "%s%s\n%s\n%s\n%s",
    get_utf8_bom(0), g.zRepositoryName, zRequestFName, zReplyFName,
    inet_ntoa(p->addr.sin_addr)
  );
  out = fossil_fopen(zCmdFName, "wb");
  if( out==0 ) goto end_request;
  fwrite(zCmd, 1, strlen(zCmd), out);
  fclose(out);

  sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http -args \"%s\" --nossl%s",
    g.nameOfExe, zCmdFName, p->zOptions
  );
  fossil_system(zCmd);
  in = fossil_fopen(zReplyFName, "rb");
  if( in ){
    while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){
      send(p->s, zHdr, got, 0);
    }
  }

end_request:
  if( out ) fclose(out);
  if( in ) fclose(in);
  closesocket(p->s);
  file_delete(zRequestFName);
  file_delete(zReplyFName);
  file_delete(zCmdFName);
  free(p);
}

/*
** Process a single incoming SCGI request.
*/
static void win32_scgi_request(void *pAppData){







|
|
|
<
<
<
<
<
<
<
<















<







106
107
108
109
110
111
112
113
114
115








116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

131
132
133
134
135
136
137
    }else{
      break;
    }
    wanted -= got;
  }
  fclose(out);
  out = 0;
  sqlite3_snprintf(sizeof(zCmd), zCmd, "\"%s\" http \"%s\" %s %s %s --nossl%s",
    g.nameOfExe, g.zRepositoryName, zRequestFName, zReplyFName,
    inet_ntoa(p->addr.sin_addr), p->zOptions








  );
  fossil_system(zCmd);
  in = fossil_fopen(zReplyFName, "rb");
  if( in ){
    while( (got = fread(zHdr, 1, sizeof(zHdr), in))>0 ){
      send(p->s, zHdr, got, 0);
    }
  }

end_request:
  if( out ) fclose(out);
  if( in ) fclose(in);
  closesocket(p->s);
  file_delete(zRequestFName);
  file_delete(zReplyFName);

  free(p);
}

/*
** Process a single incoming SCGI request.
*/
static void win32_scgi_request(void *pAppData){

Changes to src/wysiwyg.c.

226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

  @ </div>
  @ <div id="wysiwygBox"
  @  style="resize:both; overflow:auto; width: %d(w)em; height: %d(h)em;"
  @  contenteditable="true">%s(zContent)</div>
  @ <script>
  @ var oDoc;
  @
  @ /* Initialize the document editor */
  @ function initDoc() {
  @   oDoc = document.getElementById("wysiwygBox");
  @   if (!isWysiwyg()) { setDocMode(true); }
  @ }
  @
  @ /* Return true if the document editor is in WYSIWYG mode.  Return
  @ ** false if it is in Markup mode */
  @ function isWysiwyg() {
  @   return document.getElementById("editMode").selectedIndex==0;
  @ }
  @
  @ /* Invoke this routine prior to submitting the HTML content back
  @ ** to the server */
  @ function wysiwygSubmit() {
  @   if(oDoc.style.whiteSpace=="pre-wrap"){setDocMode(0);}
  @   document.getElementById("wysiwygValue").value=oDoc.innerHTML;
  @ }
  @
  @ /* Run the editing command if in WYSIWYG mode */
  @ function formatDoc(sCmd, sValue) {
  @   if (isWysiwyg()){
  @     document.execCommand("styleWithCSS", false, false);
  @     document.execCommand(sCmd, false, sValue);
  @     oDoc.focus();
  @   }
  @ }
  @
  @ /* Change the editing mode.  Convert to markup if the argument
  @ ** is true and wysiwyg if the argument is false. */
  @ function setDocMode(bToMarkup) {
  @   var oContent;
  @   if (bToMarkup) {
  @     /* WYSIWYG -> Markup */
  @     var linebreak = new RegExp("</p><p>","ig");
  @     oContent = document.createTextNode(
  @                  oDoc.innerHTML.replace(linebreak,"</p>\n\n<p>"));







|





|
|











|
|







|

|







226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270

  @ </div>
  @ <div id="wysiwygBox"
  @  style="resize:both; overflow:auto; width: %d(w)em; height: %d(h)em;"
  @  contenteditable="true">%s(zContent)</div>
  @ <script>
  @ var oDoc;
  @ 
  @ /* Initialize the document editor */
  @ function initDoc() {
  @   oDoc = document.getElementById("wysiwygBox");
  @   if (!isWysiwyg()) { setDocMode(true); }
  @ }
  @ 
  @ /* Return true if the document editor is in WYSIWYG mode.  Return 
  @ ** false if it is in Markup mode */
  @ function isWysiwyg() {
  @   return document.getElementById("editMode").selectedIndex==0;
  @ }
  @
  @ /* Invoke this routine prior to submitting the HTML content back
  @ ** to the server */
  @ function wysiwygSubmit() {
  @   if(oDoc.style.whiteSpace=="pre-wrap"){setDocMode(0);}
  @   document.getElementById("wysiwygValue").value=oDoc.innerHTML;
  @ }
  @ 
  @ /* Run the editing command if in WYSIWYG mode */ 
  @ function formatDoc(sCmd, sValue) {
  @   if (isWysiwyg()){
  @     document.execCommand("styleWithCSS", false, false);
  @     document.execCommand(sCmd, false, sValue);
  @     oDoc.focus();
  @   }
  @ }
  @ 
  @ /* Change the editing mode.  Convert to markup if the argument
  @ ** is true and wysiwyg if the argument is false. */ 
  @ function setDocMode(bToMarkup) {
  @   var oContent;
  @   if (bToMarkup) {
  @     /* WYSIWYG -> Markup */
  @     var linebreak = new RegExp("</p><p>","ig");
  @     oContent = document.createTextNode(
  @                  oDoc.innerHTML.replace(linebreak,"</p>\n\n<p>"));

Changes to src/xfer.c.

92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
    db_bind_int(&q, ":r", rid);
    db_step(&q);
    db_reset(&q);
  }
}

/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line
** message.  This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
**
**      file UUID SIZE \n CONTENT
**      file UUID DELTASRC SIZE \n CONTENT







|







92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
    db_bind_int(&q, ":r", rid);
    db_step(&q);
    db_reset(&q);
  }
}

/*
** The aToken[0..nToken-1] blob array is a parse of a "file" line 
** message.  This routine finishes parsing that message and does
** a record insert of the file.
**
** The file line is in one of the following two forms:
**
**      file UUID SIZE \n CONTENT
**      file UUID DELTASRC SIZE \n CONTENT
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
*/
static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
  int n;
  int rid;
  int srcid = 0;
  Blob content, hash;
  int isPriv;

  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<3
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed file line");







|


|







117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
*/
static void xfer_accept_file(Xfer *pXfer, int cloneFlag){
  int n;
  int rid;
  int srcid = 0;
  Blob content, hash;
  int isPriv;
  
  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<3 
   || pXfer->nToken>4
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &n)
   || n<0
   || (pXfer->nToken==4 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
    blob_appendf(&pXfer->err, "malformed file line");
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    manifest_crosslink(rid, &content, MC_NONE);
  }
  assert( blob_is_reset(&content) );
  remote_has(rid);
}

/*
** The aToken[0..nToken-1] blob array is a parse of a "cfile" line
** message.  This routine finishes parsing that message and does
** a record insert of the file.  The difference between "file" and
** "cfile" is that with "cfile" the content is already compressed.
**
** The file line is in one of the following two forms:
**
**      cfile UUID USIZE CSIZE \n CONTENT







|







196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    manifest_crosslink(rid, &content, MC_NONE);
  }
  assert( blob_is_reset(&content) );
  remote_has(rid);
}

/*
** The aToken[0..nToken-1] blob array is a parse of a "cfile" line 
** message.  This routine finishes parsing that message and does
** a record insert of the file.  The difference between "file" and
** "cfile" is that with "cfile" the content is already compressed.
**
** The file line is in one of the following two forms:
**
**      cfile UUID USIZE CSIZE \n CONTENT
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
static void xfer_accept_compressed_file(Xfer *pXfer){
  int szC;   /* CSIZE */
  int szU;   /* USIZE */
  int rid;
  int srcid = 0;
  Blob content;
  int isPriv;

  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<4
   || pXfer->nToken>5
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
   || szC<0 || szU<0
   || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
  ){







|


|







225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
static void xfer_accept_compressed_file(Xfer *pXfer){
  int szC;   /* CSIZE */
  int szU;   /* USIZE */
  int rid;
  int srcid = 0;
  Blob content;
  int isPriv;
  
  isPriv = pXfer->nextIsPrivate;
  pXfer->nextIsPrivate = 0;
  if( pXfer->nToken<4 
   || pXfer->nToken>5
   || !blob_is_uuid(&pXfer->aToken[1])
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-2], &szU)
   || !blob_is_int(&pXfer->aToken[pXfer->nToken-1], &szC)
   || szC<0 || szU<0
   || (pXfer->nToken==5 && !blob_is_uuid(&pXfer->aToken[2]))
  ){
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid             /* The UUID of the file to send */
){
  static const char *const azQuery[] = {
    "SELECT pid FROM plink x"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",

    "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
    " WHERE fid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
  };
  int i;
  Blob src, delta;
  int size = 0;







|







282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
  Blob *pContent,         /* The content of the file to send */
  Blob *pUuid             /* The UUID of the file to send */
){
  static const char *const azQuery[] = {
    "SELECT pid FROM plink x"
    " WHERE cid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",
    
    "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
    " WHERE fid=%d"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
  };
  int i;
  Blob src, delta;
  int size = 0;
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}

/*
** Try to send a file as a native delta.
** If successful, return the number of bytes in the delta.
** If we cannot generate an appropriate delta, then send
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_native(







|







319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    free(zUuid);
    blob_reset(&src);
  }
  return size;
}

/*
** Try to send a file as a native delta.  
** If successful, return the number of bytes in the delta.
** If we cannot generate an appropriate delta, then send
** nothing and return zero.
**
** Never send a delta against a private artifact.
*/
static int send_delta_native(
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
  }else{
    pUuid = &uuid;
  }
  if( uuid_is_shunned(blob_str(pUuid)) ){
    blob_reset(&uuid);
    return;
  }
  if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) ||
       pXfer->mxSend<=blob_size(pXfer->pOut) ){
    const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
    blob_appendf(pXfer->pOut, zFormat, pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }







|







401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
  }else{
    pUuid = &uuid;
  }
  if( uuid_is_shunned(blob_str(pUuid)) ){
    blob_reset(&uuid);
    return;
  }
  if( (pXfer->maxTime != -1 && time(NULL) >= pXfer->maxTime) || 
       pXfer->mxSend<=blob_size(pXfer->pOut) ){
    const char *zFormat = isPriv ? "igot %b 1\n" : "igot %b\n";
    blob_appendf(pXfer->pOut, zFormat, pUuid);
    pXfer->nIGotSent++;
    blob_reset(&uuid);
    return;
  }
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
    blob_appendf(pXfer->pOut, "\n", 1);
  }
#endif
}

/*
** Send the file identified by rid as a compressed artifact.  Basically,
** send the content exactly as it appears in the BLOB table using
** a "cfile" card.
*/
static void send_compressed_file(Xfer *pXfer, int rid){
  const char *zContent;
  const char *zUuid;
  const char *zDelta;
  int szU;







|







443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
    blob_appendf(pXfer->pOut, "\n", 1);
  }
#endif
}

/*
** Send the file identified by rid as a compressed artifact.  Basically,
** send the content exactly as it appears in the BLOB table using 
** a "cfile" card.
*/
static void send_compressed_file(Xfer *pXfer, int rid){
  const char *zContent;
  const char *zUuid;
  const char *zDelta;
  int szU;
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
** Send a gimme message for every phantom.
**
** Except: do not request shunned artifacts.  And do not request
** private artifacts if we are not doing a private transfer.
*/
static void request_phantoms(Xfer *pXfer, int maxReq){
  Stmt q;
  db_prepare(&q,
    "SELECT uuid FROM phantom JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
    (pXfer->syncPrivate ? "" :
         "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
  );
  while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
    const char *zUuid = db_column_text(&q, 0);







|







513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
** Send a gimme message for every phantom.
**
** Except: do not request shunned artifacts.  And do not request
** private artifacts if we are not doing a private transfer.
*/
static void request_phantoms(Xfer *pXfer, int maxReq){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM phantom JOIN blob USING(rid)"
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid) %s",
    (pXfer->syncPrivate ? "" :
         "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)")
  );
  while( db_step(&q)==SQLITE_ROW && maxReq-- > 0 ){
    const char *zUuid = db_column_text(&q, 0);
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**
**        login LOGIN NONCE SIGNATURE
**
** The NONCE is the SHA1 hash of the remainder of the input.
** SIGNATURE is the SHA1 checksum of the NONCE concatenated
** with the users password.
**
** The parameters to this routine are ephemeral blobs holding the
** LOGIN, NONCE and SIGNATURE.
**
** This routine attempts to locate the user and verify the signature.
** If everything checks out, the USER.CAP column for the USER table
** is consulted to set privileges in the global g variable.
**
** If anything fails to check out, no changes are made to privileges.
**
** Signature generation on the client side is handled by the
** http_exchange() routine.
**
** Return non-zero for a login failure and zero for success.
*/
int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  if( fossil_strcmp(zLogin, "nobody")==0 || fossil_strcmp(zLogin,"anonymous")==0 ){
    return 0;   /* Anybody is allowed to sync as "nobody" or "anonymous" */
  }
  if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0
      && db_get_boolean("remote_user_ok",0) ){
    return 0;   /* Accept Basic Authorization */
  }
  db_prepare(&q,
     "SELECT pw, cap, uid FROM user"
     " WHERE login=%Q"
     "   AND login NOT IN ('anonymous','nobody','developer','reader')"
     "   AND length(pw)>0",







|
|











|













|
<







549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583

584
585
586
587
588
589
590

/*
** Check the signature on an application/x-fossil payload received by
** the HTTP server.  The signature is a line of the following form:
**
**        login LOGIN NONCE SIGNATURE
**
** The NONCE is the SHA1 hash of the remainder of the input.  
** SIGNATURE is the SHA1 checksum of the NONCE concatenated 
** with the users password.
**
** The parameters to this routine are ephemeral blobs holding the
** LOGIN, NONCE and SIGNATURE.
**
** This routine attempts to locate the user and verify the signature.
** If everything checks out, the USER.CAP column for the USER table
** is consulted to set privileges in the global g variable.
**
** If anything fails to check out, no changes are made to privileges.
**
** Signature generation on the client side is handled by the 
** http_exchange() routine.
**
** Return non-zero for a login failure and zero for success.
*/
int check_login(Blob *pLogin, Blob *pNonce, Blob *pSig){
  Stmt q;
  int rc = -1;
  char *zLogin = blob_terminate(pLogin);
  defossilize(zLogin);

  if( fossil_strcmp(zLogin, "nobody")==0 || fossil_strcmp(zLogin,"anonymous")==0 ){
    return 0;   /* Anybody is allowed to sync as "nobody" or "anonymous" */
  }
  if( fossil_strcmp(P("REMOTE_USER"), zLogin)==0 ){

    return 0;   /* Accept Basic Authorization */
  }
  db_prepare(&q,
     "SELECT pw, cap, uid FROM user"
     " WHERE login=%Q"
     "   AND login NOT IN ('anonymous','nobody','developer','reader')"
     "   AND length(pw)>0",
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
        nUncl -= nRow;
        nRow = 0;
        blob_appendf(&deleteWhere, ",%d", rid);
      }
    }
    db_finalize(&q);
    db_multi_exec(
      "DELETE FROM unclustered WHERE rid NOT IN (0 %s)",
      blob_str(&deleteWhere)
    );
    blob_reset(&deleteWhere);
    if( nRow>0 ){
      md5sum_blob(&cluster, &cksum);
      blob_appendf(&cluster, "Z %b\n", &cksum);
      blob_reset(&cksum);







|







697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
        nUncl -= nRow;
        nRow = 0;
        blob_appendf(&deleteWhere, ",%d", rid);
      }
    }
    db_finalize(&q);
    db_multi_exec(
      "DELETE FROM unclustered WHERE rid NOT IN (0 %s)", 
      blob_str(&deleteWhere)
    );
    blob_reset(&deleteWhere);
    if( nRow>0 ){
      md5sum_blob(&cluster, &cksum);
      blob_appendf(&cluster, "Z %b\n", &cksum);
      blob_reset(&cksum);
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  if( pXfer->resync ){
    db_prepare(&q,
      "SELECT uuid, rid FROM blob"
      " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
      "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
      "   AND blob.rid<=%d"
      " ORDER BY blob.rid DESC",
      pXfer->resync
    );
  }else{
    db_prepare(&q,
      "SELECT uuid FROM unclustered JOIN blob USING(rid)"
      " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
      "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    );
  }
  while( db_step(&q)==SQLITE_ROW ){







|









|







736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
** Send an igot message for every entry in unclustered table.
** Return the number of cards sent.
*/
static int send_unclustered(Xfer *pXfer){
  Stmt q;
  int cnt = 0;
  if( pXfer->resync ){
    db_prepare(&q, 
      "SELECT uuid, rid FROM blob"
      " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
      "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
      "   AND blob.rid<=%d"
      " ORDER BY blob.rid DESC",
      pXfer->resync
    );
  }else{
    db_prepare(&q, 
      "SELECT uuid FROM unclustered JOIN blob USING(rid)"
      " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
      "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
      "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    );
  }
  while( db_step(&q)==SQLITE_ROW ){
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
}

/*
** Send an igot message for every artifact.
*/
static void send_all(Xfer *pXfer){
  Stmt q;
  db_prepare(&q,
    "SELECT uuid FROM blob "
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));







|







770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
}

/*
** Send an igot message for every artifact.
*/
static void send_all(Xfer *pXfer){
  Stmt q;
  db_prepare(&q, 
    "SELECT uuid FROM blob "
    " WHERE NOT EXISTS(SELECT 1 FROM shun WHERE uuid=blob.uuid)"
    "   AND NOT EXISTS(SELECT 1 FROM private WHERE rid=blob.rid)"
    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=blob.rid)"
  );
  while( db_step(&q)==SQLITE_ROW ){
    blob_appendf(pXfer->pOut, "igot %s\n", db_column_text(&q, 0));
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
        }else if( g.perm.Private ){
          rid_from_uuid(&xfer.aToken[1], 1, 1);
        }else{
          server_private_xfer_not_authorized();
        }
      }
    }else


    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches.
    */
    if( xfer.nToken==3







|
|







1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
        }else if( g.perm.Private ){
          rid_from_uuid(&xfer.aToken[1], 1, 1);
        }else{
          server_private_xfer_not_authorized();
        }
      }
    }else
  
    
    /*    pull  SERVERCODE  PROJECTCODE
    **    push  SERVERCODE  PROJECTCODE
    **
    ** The client wants either send or receive.  The server should
    ** verify that the project code matches.
    */
    if( xfer.nToken==3
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
          break;
        }
      }
    }else

    /*    reqconfig  NAME
    **
    ** Request a configuration value
    */
    if( blob_eq(&xfer.aToken[0], "reqconfig")
     && xfer.nToken==2
    ){







|


|







1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
        if( check_tail_hash(&xfer.aToken[2], xfer.pIn)
         || check_login(&xfer.aToken[1], &xfer.aToken[2], &xfer.aToken[3])
        ){
          cgi_reset_content();
          @ error login\sfailed
          nErr++;
          break;
        }        
      }
    }else
    
    /*    reqconfig  NAME
    **
    ** Request a configuration value
    */
    if( blob_eq(&xfer.aToken[0], "reqconfig")
     && xfer.nToken==2
    ){
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
          configure_send_group(xfer.pOut, groupMask, 0);
        }else if( configure_is_exportable(zName) ){
          /* Old style configuration transfer */
          send_legacy_config_card(&xfer, zName);
        }
      }
    }else

    /*   config NAME SIZE \n CONTENT
    **
    ** Receive a configuration value from the client.  This is only
    ** permitted for high-privilege users.
    */
    if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
        && blob_is_int(&xfer.aToken[2], &size) ){







|







1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
          configure_send_group(xfer.pOut, groupMask, 0);
        }else if( configure_is_exportable(zName) ){
          /* Old style configuration transfer */
          send_legacy_config_card(&xfer, zName);
        }
      }
    }else
    
    /*   config NAME SIZE \n CONTENT
    **
    ** Receive a configuration value from the client.  This is only
    ** permitted for high-privilege users.
    */
    if( blob_eq(&xfer.aToken[0],"config") && xfer.nToken==3
        && blob_is_int(&xfer.aToken[2], &size) ){
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
        recvConfig = 1;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else



    /*    cookie TEXT
    **
    ** A cookie contains a arbitrary-length argument that is server-defined.
    ** The argument must be encoded so as not to contain any whitespace.
    ** The server can optionally send a cookie to the client.  The client
    ** might then return the same cookie back to the server on its next







|







1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
        recvConfig = 1;
      }
      configure_receive(zName, &content, CONFIGSET_ALL);
      blob_reset(&content);
      blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
    }else

      

    /*    cookie TEXT
    **
    ** A cookie contains a arbitrary-length argument that is server-defined.
    ** The argument must be encoded so as not to contain any whitespace.
    ** The server can optionally send a cookie to the client.  The client
    ** might then return the same cookie back to the server on its next
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
** Floating-point absolute value
*/
static double fossil_fabs(double x){
  return x>0.0 ? x : -x;
}

/*
** Sync to the host identified in g.url.name and g.url.path.  This
** routine is called by the client.
**
** Records are pushed to the server if pushFlag is true.  Records
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
int client_sync(







|







1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
** Floating-point absolute value
*/
static double fossil_fabs(double x){
  return x>0.0 ? x : -x;
}

/*
** Sync to the host identified in g.urlName and g.urlPath.  This
** routine is called by the client.
**
** Records are pushed to the server if pushFlag is true.  Records
** are pulled if pullFlag is true.  A full sync occurs if both are
** true.
*/
int client_sync(
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
  int nRoundtrip= 0;      /* Number of HTTP requests */
  int nArtifactSent = 0;  /* Total artifacts sent */
  int nArtifactRcvd = 0;  /* Total artifacts received */
  const char *zOpType = 0;/* Push, Pull, Sync, Clone */
  double rSkew = 0.0;     /* Maximum time skew */

  if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0
     && configRcvMask==0 && configSendMask==0 ) return 0;

  transport_stats(0, 0, 1);
  socket_global_init();
  memset(&xfer, 0, sizeof(xfer));
  xfer.pIn = &recv;
  xfer.pOut = &send;







|







1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
  int nRoundtrip= 0;      /* Number of HTTP requests */
  int nArtifactSent = 0;  /* Total artifacts sent */
  int nArtifactRcvd = 0;  /* Total artifacts received */
  const char *zOpType = 0;/* Push, Pull, Sync, Clone */
  double rSkew = 0.0;     /* Maximum time skew */

  if( db_get_boolean("dont-push", 0) ) syncFlags &= ~SYNC_PUSH;
  if( (syncFlags & (SYNC_PUSH|SYNC_PULL|SYNC_CLONE))==0 
     && configRcvMask==0 && configSendMask==0 ) return 0;

  transport_stats(0, 0, 1);
  socket_global_init();
  memset(&xfer, 0, sizeof(xfer));
  xfer.pIn = &recv;
  xfer.pOut = &send;
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
    /* Send make the most recently received cookie.  Let the server
    ** figure out if this is a cookie that it cares about.
    */
    zCookie = db_get("cookie", 0);
    if( zCookie ){
      blob_appendf(&send, "cookie %s\n", zCookie);
    }

    /* Generate gimme cards for phantoms and leaf cards
    ** for all leaves.
    */
    if( (syncFlags & SYNC_PULL)!=0
     || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
    ){
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( syncFlags & SYNC_PUSH ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
      if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
    }

    /* Send configuration parameter requests.  On a clone, delay sending
    ** this until the second cycle since the login card might fail on
    ** the first cycle.
    */
    if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){
      const char *zName;
      if( zOpType==0 ) zOpType = "Pull";
      zName = configure_first_name(configRcvMask);
      while( zName ){







|















|







1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
    /* Send make the most recently received cookie.  Let the server
    ** figure out if this is a cookie that it cares about.
    */
    zCookie = db_get("cookie", 0);
    if( zCookie ){
      blob_appendf(&send, "cookie %s\n", zCookie);
    }
    
    /* Generate gimme cards for phantoms and leaf cards
    ** for all leaves.
    */
    if( (syncFlags & SYNC_PULL)!=0
     || ((syncFlags & SYNC_CLONE)!=0 && cloneSeqno==1)
    ){
      request_phantoms(&xfer, mxPhantomReq);
    }
    if( syncFlags & SYNC_PUSH ){
      send_unsent(&xfer);
      nCardSent += send_unclustered(&xfer);
      if( syncFlags & SYNC_PRIVATE ) send_private(&xfer);
    }

    /* Send configuration parameter requests.  On a clone, delay sending
    ** this until the second cycle since the login card might fail on 
    ** the first cycle.
    */
    if( configRcvMask && ((syncFlags & SYNC_CLONE)==0 || nCycle>0) ){
      const char *zName;
      if( zOpType==0 ) zOpType = "Pull";
      zName = configure_first_name(configRcvMask);
      while( zName ){
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
       && blob_is_uuid(&xfer.aToken[1])
      ){
        if( syncFlags & SYNC_PUSH ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
          if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }else

      /*   igot UUID  ?PRIVATEFLAG?
      **
      ** Server announces that it has a particular file.  If this is
      ** not a file that we have and we are pulling, then create a
      ** phantom to cause this file to be requested on the next cycle.
      ** Always remember that the server has this file so that we do
      ** not transmit it by accident.
      **
      ** If the PRIVATE argument exists and is 1, then the file is
      ** private.  Pretend it does not exists if we are not pulling
      ** private files.
      */
      if( xfer.nToken>=2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid;
        int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
        rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
        if( rid>0 ){
          if( !isPriv ) content_make_public(rid);
        }else if( isPriv && !g.perm.Private ){
          /* ignore private files */
        }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else


      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.  This message tells
      ** the client what product to use for the new database.
      */
      if( blob_eq(&xfer.aToken[0],"push")
       && xfer.nToken==3







|








|




















|
|







1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
       && blob_is_uuid(&xfer.aToken[1])
      ){
        if( syncFlags & SYNC_PUSH ){
          int rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
          if( rid ) send_file(&xfer, rid, &xfer.aToken[1], 0);
        }
      }else
  
      /*   igot UUID  ?PRIVATEFLAG?
      **
      ** Server announces that it has a particular file.  If this is
      ** not a file that we have and we are pulling, then create a
      ** phantom to cause this file to be requested on the next cycle.
      ** Always remember that the server has this file so that we do
      ** not transmit it by accident.
      **
      ** If the PRIVATE argument exists and is 1, then the file is 
      ** private.  Pretend it does not exists if we are not pulling
      ** private files.
      */
      if( xfer.nToken>=2
       && blob_eq(&xfer.aToken[0], "igot")
       && blob_is_uuid(&xfer.aToken[1])
      ){
        int rid;
        int isPriv = xfer.nToken>=3 && blob_eq(&xfer.aToken[2],"1");
        rid = rid_from_uuid(&xfer.aToken[1], 0, 0);
        if( rid>0 ){
          if( !isPriv ) content_make_public(rid);
        }else if( isPriv && !g.perm.Private ){
          /* ignore private files */
        }else if( (syncFlags & (SYNC_PULL|SYNC_CLONE))!=0 ){
          rid = content_new(blob_str(&xfer.aToken[1]), isPriv);
          if( rid ) newPhantom = 1;
        }
        remote_has(rid);
      }else
    
      
      /*   push  SERVERCODE  PRODUCTCODE
      **
      ** Should only happen in response to a clone.  This message tells
      ** the client what product to use for the new database.
      */
      if( blob_eq(&xfer.aToken[0],"push")
       && xfer.nToken==3
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
        if( zPCode==0 ){
          zPCode = mprintf("%b", &xfer.aToken[2]);
          db_set("project-code", zPCode, 0);
        }
        if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
        nCardSent++;
      }else

      /*   config NAME SIZE \n CONTENT
      **
      ** Receive a configuration value from the server.
      **
      ** The received configuration setting is silently ignored if it was
      ** not requested by a prior "reqconfig" sent from client to server.
      */







|







1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
        if( zPCode==0 ){
          zPCode = mprintf("%b", &xfer.aToken[2]);
          db_set("project-code", zPCode, 0);
        }
        if( cloneSeqno>0 ) blob_appendf(&send, "clone 3 %d\n", cloneSeqno);
        nCardSent++;
      }else
      
      /*   config NAME SIZE \n CONTENT
      **
      ** Receive a configuration value from the server.
      **
      ** The received configuration setting is silently ignored if it was
      ** not requested by a prior "reqconfig" sent from client to server.
      */
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
        configure_receive(zName, &content, origConfigRcvMask);
        nCardRcvd++;
        nArtifactRcvd++;
        blob_reset(&content);
        blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
      }else


      /*    cookie TEXT
      **
      ** The server might include a cookie in its reply.  The client
      ** should remember this cookie and send it back to the server
      ** in its next query.
      **
      ** Each cookie received overwrites the prior cookie from the







|







1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
        configure_receive(zName, &content, origConfigRcvMask);
        nCardRcvd++;
        nArtifactRcvd++;
        blob_reset(&content);
        blob_seek(xfer.pIn, 1, BLOB_SEEK_CUR);
      }else

      
      /*    cookie TEXT
      **
      ** The server might include a cookie in its reply.  The client
      ** should remember this cookie and send it back to the server
      ** in its next query.
      **
      ** Each cookie received overwrites the prior cookie from the
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839

      /*   message MESSAGE
      **
      ** Print a message.  Similar to "error" but does not stop processing.
      **
      ** If the "login failed" message is seen, clear the sync password prior
      ** to the next cycle.
      */
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        if( (syncFlags & SYNC_PUSH) && zMsg && strglob("pull only *", zMsg) ){
          syncFlags &= ~SYNC_PUSH;
          zMsg = 0;
        }
        if( zMsg && zMsg[0] ){
          fossil_force_newline();
          fossil_print("Server says: %s\n", zMsg);
        }
      }else

      /*    pragma NAME VALUE...
      **
      ** The server can send pragmas to try to convey meta-information to
      ** the client.  These are informational only.  Unknown pragmas are
      ** silently ignored.
      */
      if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the
      ** first message exchange because the project-code is unknown
      ** and so the login card on the request was invalid.  The project-code
      ** is returned in the reply before the error card, so second and
      ** subsequent messages should be OK.  Nevertheless, we need to ignore
      ** the error card on the first message of a clone.
      */
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        if( (syncFlags & SYNC_CLONE)==0 || nCycle>0 ){
          char *zMsg = blob_terminate(&xfer.aToken[1]);
          defossilize(zMsg);
          fossil_force_newline();
          fossil_print("Error: %s\n", zMsg);
          if( fossil_strcmp(zMsg, "login failed")==0 ){
            if( nCycle<2 ){
              g.url.passwd = 0;
              go = 1;
              if( g.cgiOutput==0 ){
                g.url.flags |= URL_PROMPT_PW;
                g.url.flags &= ~URL_PROMPTED;
                url_prompt_for_password();
                url_remember();
              }
            }
          }else{
            blob_appendf(&xfer.err, "server says: %s\n", zMsg);
            nErr++;







|
















|












|


|








|


|
|







1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838

      /*   message MESSAGE
      **
      ** Print a message.  Similar to "error" but does not stop processing.
      **
      ** If the "login failed" message is seen, clear the sync password prior
      ** to the next cycle.
      */        
      if( blob_eq(&xfer.aToken[0],"message") && xfer.nToken==2 ){
        char *zMsg = blob_terminate(&xfer.aToken[1]);
        defossilize(zMsg);
        if( (syncFlags & SYNC_PUSH) && zMsg && strglob("pull only *", zMsg) ){
          syncFlags &= ~SYNC_PUSH;
          zMsg = 0;
        }
        if( zMsg && zMsg[0] ){
          fossil_force_newline();
          fossil_print("Server says: %s\n", zMsg);
        }
      }else

      /*    pragma NAME VALUE...
      **
      ** The server can send pragmas to try to convey meta-information to
      ** the client.  These are informational only.  Unknown pragmas are 
      ** silently ignored.
      */
      if( blob_eq(&xfer.aToken[0], "pragma") && xfer.nToken>=2 ){
      }else

      /*   error MESSAGE
      **
      ** Report an error and abandon the sync session.
      **
      ** Except, when cloning we will sometimes get an error on the
      ** first message exchange because the project-code is unknown
      ** and so the login card on the request was invalid.  The project-code
      ** is returned in the reply before the error card, so second and 
      ** subsequent messages should be OK.  Nevertheless, we need to ignore
      ** the error card on the first message of a clone.
      */        
      if( blob_eq(&xfer.aToken[0],"error") && xfer.nToken==2 ){
        if( (syncFlags & SYNC_CLONE)==0 || nCycle>0 ){
          char *zMsg = blob_terminate(&xfer.aToken[1]);
          defossilize(zMsg);
          fossil_force_newline();
          fossil_print("Error: %s\n", zMsg);
          if( fossil_strcmp(zMsg, "login failed")==0 ){
            if( nCycle<2 ){
              g.urlPasswd = 0;
              go = 1;
              if( g.cgiOutput==0 ){
                g.urlFlags |= URL_PROMPT_PW;
                g.urlFlags &= ~URL_PROMPTED;
                url_prompt_for_password();
                url_remember();
              }
            }
          }else{
            blob_appendf(&xfer.err, "server says: %s\n", zMsg);
            nErr++;
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
    }
    nCardRcvd = 0;
    xfer.nFileRcvd = 0;
    xfer.nDeltaRcvd = 0;
    xfer.nDanglingFile = 0;

    /* If we have one or more files queued to send, then go
    ** another round
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }

    /* If this is a clone, the go at least two rounds */
    if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;

    /* Stop the cycle if the server sends a "clone_seqno 0" card and
    ** we have gone at least two rounds.  Always go at least two rounds
    ** on a clone in order to be sure to retrieve the configuration
    ** information which is only sent on the second round.
    */
    if( cloneSeqno<=0 && nCycle>1 ) go = 0;
  };
  transport_stats(&nSent, &nRcvd, 1);
  if( (rSkew*24.0*3600.0) > 10.0 ){
     fossil_warning("*** time skew *** server is fast by %s",
                    db_timespan_name(rSkew));
     g.clockSkewSeen = 1;
  }else if( rSkew*24.0*3600.0 < -10.0 ){
     fossil_warning("*** time skew *** server is slow by %s",
                    db_timespan_name(-rSkew));
     g.clockSkewSeen = 1;
  }

  fossil_force_newline();
  fossil_print(
     "%s finished with %lld bytes sent, %lld bytes received\n",
     zOpType, nSent, nRcvd);
  transport_close(&g.url);
  transport_global_shutdown(&g.url);
  db_multi_exec("DROP TABLE onremote");
  manifest_crosslink_end(MC_PERMIT_HOOKS);
  content_enable_dephantomize(1);
  db_end_transaction(0);
  return nErr;
}







|













|
















|
|






1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
    }
    nCardRcvd = 0;
    xfer.nFileRcvd = 0;
    xfer.nDeltaRcvd = 0;
    xfer.nDanglingFile = 0;

    /* If we have one or more files queued to send, then go
    ** another round 
    */
    if( xfer.nFileSent+xfer.nDeltaSent>0 ){
      go = 1;
    }

    /* If this is a clone, the go at least two rounds */
    if( (syncFlags & SYNC_CLONE)!=0 && nCycle==1 ) go = 1;

    /* Stop the cycle if the server sends a "clone_seqno 0" card and
    ** we have gone at least two rounds.  Always go at least two rounds
    ** on a clone in order to be sure to retrieve the configuration
    ** information which is only sent on the second round.
    */
    if( cloneSeqno<=0 && nCycle>1 ) go = 0;   
  };
  transport_stats(&nSent, &nRcvd, 1);
  if( (rSkew*24.0*3600.0) > 10.0 ){
     fossil_warning("*** time skew *** server is fast by %s",
                    db_timespan_name(rSkew));
     g.clockSkewSeen = 1;
  }else if( rSkew*24.0*3600.0 < -10.0 ){
     fossil_warning("*** time skew *** server is slow by %s",
                    db_timespan_name(-rSkew));
     g.clockSkewSeen = 1;
  }

  fossil_force_newline();
  fossil_print(
     "%s finished with %lld bytes sent, %lld bytes received\n",
     zOpType, nSent, nRcvd);
  transport_close(GLOBAL_URL());
  transport_global_shutdown(GLOBAL_URL());
  db_multi_exec("DROP TABLE onremote");
  manifest_crosslink_end(MC_PERMIT_HOOKS);
  content_enable_dephantomize(1);
  db_end_transaction(0);
  return nErr;
}

Changes to src/xfersetup.c.

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  setup_menu_entry("Commit", "xfersetup_commit",
    "Specific TH1 code to run after processing a commit.");
  setup_menu_entry("Ticket", "xfersetup_ticket",
    "Specific TH1 code to run after processing a ticket change.");
  @ </table>

  url_parse(0, 0);
  if( g.url.protocol ){
    unsigned syncFlags;
    const char *zButton;
    char *zWarning;

    if( db_get_boolean("dont-push", 0) ){
      syncFlags = SYNC_PULL;
      zButton = "Pull";
      zWarning = 0;
    }else{
      syncFlags = SYNC_PUSH | SYNC_PULL;
      zButton = "Synchronize";
      zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.",
                         g.url.canonical);
    }
    if( P("sync") ){
      user_select();
      url_enable_proxy(0);
      client_sync(syncFlags, 0, 0);
    }
    @ <p>Press the %h(zButton) button below to synchronize with the
    @ "%h(g.url.canonical)" repository now.  This may be useful when
    @ testing the various transfer scripts.</p>
    @ <p>You can use the "http -async" command in your scripts, but
    @ make sure the "th1-uri-regexp" setting is set first.</p>
    if( zWarning ){
      @
      @ <big><b>%h(zWarning)</b></big>
      free(zWarning);







|












|







|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
  setup_menu_entry("Commit", "xfersetup_commit",
    "Specific TH1 code to run after processing a commit.");
  setup_menu_entry("Ticket", "xfersetup_ticket",
    "Specific TH1 code to run after processing a ticket change.");
  @ </table>

  url_parse(0, 0);
  if( g.urlProtocol ){
    unsigned syncFlags;
    const char *zButton;
    char *zWarning;

    if( db_get_boolean("dont-push", 0) ){
      syncFlags = SYNC_PULL;
      zButton = "Pull";
      zWarning = 0;
    }else{
      syncFlags = SYNC_PUSH | SYNC_PULL;
      zButton = "Synchronize";
      zWarning = mprintf("WARNING: Pushing to \"%s\" is enabled.",
                         g.urlCanonical);
    }
    if( P("sync") ){
      user_select();
      url_enable_proxy(0);
      client_sync(syncFlags, 0, 0);
    }
    @ <p>Press the %h(zButton) button below to synchronize with the
    @ "%h(g.urlCanonical)" repository now.  This may be useful when
    @ testing the various transfer scripts.</p>
    @ <p>You can use the "http -async" command in your scripts, but
    @ make sure the "th1-uri-regexp" setting is set first.</p>
    if( zWarning ){
      @
      @ <big><b>%h(zWarning)</b></big>
      free(zWarning);

Changes to src/zip.c.

79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

/*
** Set the date and time from a julian day number.
*/
void zip_set_timedate(double rDate){
  char *zDate = db_text(0, "SELECT datetime(%.17g)", rDate);
  zip_set_timedate_from_str(zDate);
  fossil_free(zDate);
  unixTime = (rDate - 2440587.5)*86400.0;
}

/*
** If the given filename includes one or more directory entries, make
** sure the directories are already in the archive.  If they are not
** in the archive, add them.







|







79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

/*
** Set the date and time from a julian day number.
*/
void zip_set_timedate(double rDate){
  char *zDate = db_text(0, "SELECT datetime(%.17g)", rDate);
  zip_set_timedate_from_str(zDate);
  free(zDate);
  unixTime = (rDate - 2440587.5)*86400.0;
}

/*
** If the given filename includes one or more directory entries, make
** sure the directories are already in the archive.  If they are not
** in the archive, add them.
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  put16(&zHdr[4], 0x000a);
  put16(&zHdr[6], 0x0800);
  put16(&zHdr[8], iMethod);
  put16(&zHdr[10], dosTime);
  put16(&zHdr[12], dosDate);
  put16(&zHdr[26], nameLen);
  put16(&zHdr[28], 13);

  put16(&zExTime[0], 0x5455);
  put16(&zExTime[2], 9);
  zExTime[4] = 3;
  put32(&zExTime[5], unixTime);
  put32(&zExTime[9], unixTime);


  /* Write the header and filename.
  */
  iStart = blob_size(&body);
  blob_append(&body, zHdr, 30);
  blob_append(&body, zName, nameLen);
  blob_append(&body, zExTime, 13);







|





|







156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
  put16(&zHdr[4], 0x000a);
  put16(&zHdr[6], 0x0800);
  put16(&zHdr[8], iMethod);
  put16(&zHdr[10], dosTime);
  put16(&zHdr[12], dosDate);
  put16(&zHdr[26], nameLen);
  put16(&zHdr[28], 13);
  
  put16(&zExTime[0], 0x5455);
  put16(&zExTime[2], 9);
  zExTime[4] = 3;
  put32(&zExTime[5], unixTime);
  put32(&zExTime[9], unixTime);
  

  /* Write the header and filename.
  */
  iStart = blob_size(&body);
  blob_append(&body, zHdr, 30);
  blob_append(&body, zName, nameLen);
  blob_append(&body, zExTime, 13);
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
      deflate(&stream, Z_FINISH);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      blob_append(&body, zOutBuf, toOut);
    }while( stream.avail_out==0 );
    nByte = stream.total_in;
    nByteCompr = stream.total_out;
    deflateEnd(&stream);

    /* Go back and write the header, now that we know the compressed file size.
    */
    z = &blob_buffer(&body)[iStart];
    put32(&z[14], iCRC);
    put32(&z[18], nByteCompr);
    put32(&z[22], nByte);
  }

  /* Make an entry in the tables of contents
  */
  memset(zBuf, 0, sizeof(zBuf));
  put32(&zBuf[0], 0x02014b50);
  put16(&zBuf[4], 0x0317);
  put16(&zBuf[6], 0x000a);
  put16(&zBuf[8], 0x0800);







|







|







200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
      deflate(&stream, Z_FINISH);
      toOut = sizeof(zOutBuf) - stream.avail_out;
      blob_append(&body, zOutBuf, toOut);
    }while( stream.avail_out==0 );
    nByte = stream.total_in;
    nByteCompr = stream.total_out;
    deflateEnd(&stream);
  
    /* Go back and write the header, now that we know the compressed file size.
    */
    z = &blob_buffer(&body)[iStart];
    put32(&z[14], iCRC);
    put32(&z[18], nByteCompr);
    put32(&z[22], nByte);
  }
  
  /* Make an entry in the tables of contents
  */
  memset(zBuf, 0, sizeof(zBuf));
  put32(&zBuf[0], 0x02014b50);
  put16(&zBuf[4], 0x0317);
  put16(&zBuf[6], 0x000a);
  put16(&zBuf[8], 0x0800);
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
  put16(&zBuf[20], 0);
  blob_append(&body, zBuf, 22);
  blob_reset(&toc);
  *pZip = body;
  blob_zero(&body);
  nEntry = 0;
  for(i=0; i<nDir; i++){
    fossil_free(azDir[i]);
  }
  fossil_free(azDir);
  nDir = 0;
  azDir = 0;
}

/*
** COMMAND: test-filezip
**







|

|







265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
  put16(&zBuf[20], 0);
  blob_append(&body, zBuf, 22);
  blob_reset(&toc);
  *pZip = body;
  blob_zero(&body);
  nEntry = 0;
  for(i=0; i<nDir; i++){
    free(azDir[i]);
  }
  free(azDir);
  nDir = 0;
  azDir = 0;
}

/*
** COMMAND: test-filezip
**
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
*/
void zip_of_baseline(int rid, Blob *pZip, const char *zDir){
  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;

  content_get(rid, &mfile);
  if( blob_size(&mfile)==0 ){
    blob_zero(pZip);
    return;
  }
  blob_zero(&hash);
  blob_zero(&filename);







|







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
*/
void zip_of_baseline(int rid, Blob *pZip, const char *zDir){
  Blob mfile, hash, file;
  Manifest *pManifest;
  ManifestFile *pFile;
  Blob filename;
  int nPrefix;
  
  content_get(rid, &mfile);
  if( blob_size(&mfile)==0 ){
    blob_zero(pZip);
    return;
  }
  blob_zero(&hash);
  blob_zero(&filename);
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**
** Generate a ZIP archive for the baseline.
** Return that ZIP archive as the HTTP reply content.
**
** Optional URL Parameters:
**
** - name=base name of the output file. Defaults to
** something project/version-specific.
**
** - uuid=the version to zip (may be a tag/branch name).
** Defaults to trunk.
**
*/
void baseline_zip_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob zip;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(); return; }
  load_control();
  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  for(nName=strlen(zName)-1; nName>5; nName--){
    if( zName[nName]=='.' ){
      zName[nName] = 0;
      break;
    }
  }
  rid = name_to_typed_rid(nRid?zRid:zName,"ci");
  if( rid==0 ){
    @ Not found
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;
  zip_of_baseline(rid, &zip, zName);
  fossil_free( zName );
  fossil_free( zRid );
  cgi_set_content(&zip);
  cgi_set_content_type("application/zip");
}







<
<
<
<
<
<
<
<
<









<

















|
|



416
417
418
419
420
421
422









423
424
425
426
427
428
429
430
431

432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453

/*
** WEBPAGE: zip
** URL: /zip/RID.zip
**
** Generate a ZIP archive for the baseline.
** Return that ZIP archive as the HTTP reply content.









*/
void baseline_zip_page(void){
  int rid;
  char *zName, *zRid;
  int nName, nRid;
  Blob zip;

  login_check_credentials();
  if( !g.perm.Zip ){ login_needed(); return; }

  zName = mprintf("%s", PD("name",""));
  nName = strlen(zName);
  zRid = mprintf("%s", PD("uuid","trunk"));
  nRid = strlen(zRid);
  for(nName=strlen(zName)-1; nName>5; nName--){
    if( zName[nName]=='.' ){
      zName[nName] = 0;
      break;
    }
  }
  rid = name_to_typed_rid(nRid?zRid:zName,"ci");
  if( rid==0 ){
    @ Not found
    return;
  }
  if( nRid==0 && nName>10 ) zName[10] = 0;
  zip_of_baseline(rid, &zip, zName);
  free( zName );
  free( zRid );
  cgi_set_content(&zip);
  cgi_set_content_type("application/zip");
}

Changes to test/file1.test.

31
32
33
34
35
36
37
38
39
40
41
42
simplify-name 101 {} {} / / ///////// / ././././ .
simplify-name 102 x x /x /x ///x //x
simplify-name 103 a/b a/b /a/b /a/b a///b a/b ///a///b///// //a/b
simplify-name 104 a/b/../c/ a/c /a/b/../c /a/c /a/b//../c /a/c /a/b/..///c /a/c
simplify-name 105 a/b/../../x/y x/y /a/b/../../x/y /x/y
simplify-name 106 a/b/../../../x/y ../x/y /a/b/../../../x/y /../x/y
simplify-name 107 a/./b/.././../x/y x/y a//.//b//..//.//..//x//y/// x/y

if {$::tcl_platform(os)=="Windows NT"} {
  simplify-name 108 //?/a:/a/b a:/a/b //?/UNC/a/b //a/b //?/ {}
  simplify-name 109 \\\\?\\a:\\a\\b a:/a/b \\\\?\\UNC\\a\\b //a/b \\\\?\\ {}
}







<
<
<
<
<
31
32
33
34
35
36
37





simplify-name 101 {} {} / / ///////// / ././././ .
simplify-name 102 x x /x /x ///x //x
simplify-name 103 a/b a/b /a/b /a/b a///b a/b ///a///b///// //a/b
simplify-name 104 a/b/../c/ a/c /a/b/../c /a/c /a/b//../c /a/c /a/b/..///c /a/c
simplify-name 105 a/b/../../x/y x/y /a/b/../../x/y /x/y
simplify-name 106 a/b/../../../x/y ../x/y /a/b/../../../x/y /../x/y
simplify-name 107 a/./b/.././../x/y x/y a//.//b//..//.//..//x//y/// x/y





Changes to test/merge2.test.

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Tests of the delta mechanism.
#

set filelist [glob $testdir/*]
foreach f $filelist {
  if {[file isdir $f]} continue
  set base [file root [file tail $f]]
  if {[string match "utf16*" $base]} continue
  set f1 [read_file $f]
  write_file t1 $f1
  for {set i 0} {$i<100} {incr i} {
    expr {srand($i*2)}
    write_file t2 [set f2 [random_changes $f1 2 4 0 0.1]]
    expr {srand($i*2+1)}
    write_file t3 [set f3 [random_changes $f1 2 4 2 0.1]]







|







18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# Tests of the delta mechanism.
#

set filelist [glob $testdir/*]
foreach f $filelist {
  if {[file isdir $f]} continue
  set base [file root [file tail $f]]
  if {$base eq "utf"} continue
  set f1 [read_file $f]
  write_file t1 $f1
  for {set i 0} {$i<100} {incr i} {
    expr {srand($i*2)}
    write_file t2 [set f2 [random_changes $f1 2 4 0 0.1]]
    expr {srand($i*2+1)}
    write_file t3 [set f3 [random_changes $f1 2 4 2 0.1]]

Changes to test/merge5.test.

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
set env(HOME) [pwd]

# Construct a test repository
#
exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
fossil rebuild m5.fossil
fossil open m5.fossil
fossil user default drh --user drh
fossil update baseline
checkout-test 10 {
  da5c8346496f3421cb58f84b6e59e9531d9d424d  one.txt
  ed24d19d726d173f18dbf4a9a0f8514daa3e3ca4  three.txt
  278a402316510f6ae4a77186796a6bde78c7dbc1  two.txt
}








<







50
51
52
53
54
55
56

57
58
59
60
61
62
63
set env(HOME) [pwd]

# Construct a test repository
#
exec sqlite3 m5.fossil <$testdir/${testfile}_repo.sql
fossil rebuild m5.fossil
fossil open m5.fossil

fossil update baseline
checkout-test 10 {
  da5c8346496f3421cb58f84b6e59e9531d9d424d  one.txt
  ed24d19d726d173f18dbf4a9a0f8514daa3e3ca4  three.txt
  278a402316510f6ae4a77186796a6bde78c7dbc1  two.txt
}

Changes to test/merge_renames.test.

1
2
3
4
5
6
7
8
9
10
11






12
13
14
15
16
17
18

19
20
21
22
23
24
25
#
# Tests for merging with renames
# 
#

catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
  puts stderr "Cannot run this test within an open checkout"
  return
}







######################################
#  Test 1                            #
#  Reported: Ticket [554f44ee74e3d]  #
######################################

repo_init


write_file f1 "line"
fossil add f1
fossil commit -m "c1"
fossil tag add pivot current

write_file f1 "line2"











>
>
>
>
>
>






|
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#
# Tests for merging with renames
# 
#

catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
  puts stderr "Cannot run this test within an open checkout"
  return
}


# Fossil will write data on $HOME, running 'fossil new' here.
# We need not to clutter the $HOME of the test caller.
set env(HOME) [pwd]


######################################
#  Test 1                            #
#  Reported: Ticket [554f44ee74e3d]  #
######################################

fossil new rep.fossil
fossil open rep.fossil

write_file f1 "line"
fossil add f1
fossil commit -m "c1"
fossil tag add pivot current

write_file f1 "line2"
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
fossil commit -m "c4"

write_file f1 "line6"
fossil commit -m "c4"

fossil update pivot
fossil mv f1 f2
file rename -force f1 f2
fossil commit -b rename -m "c5"

fossil merge trunk
fossil commit -m "trunk merged"

fossil update pivot
write_file f3 "someline"







|







42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
fossil commit -m "c4"

write_file f1 "line6"
fossil commit -m "c4"

fossil update pivot
fossil mv f1 f2
exec mv f1 f2
fossil commit -b rename -m "c5"

fossil merge trunk
fossil commit -m "trunk merged"

fossil update pivot
write_file f3 "someline"
64
65
66
67
68
69
70



71
72
73
74
75


76



















































77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-1 0
} else {
    test merge_renames-1 1
}




######################################
#  Test 2                            #
#  Reported: Ticket [74413366fe5067] #
######################################



repo_init




















































write_file f1 "line"
fossil add f1
fossil commit -m "base file"
fossil tag add pivot current

write_file f2 "line2"
fossil add f2
fossil commit -m "newfile"

fossil mv f2 f2new
file rename -force f2 f2new
fossil commit -m "rename"

fossil update pivot
write_file f1 "line3"
fossil commit -b branch -m "change"

fossil merge trunk







>
>
>





>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











|







71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-1 0
} else {
    test merge_renames-1 1
}

fossil close -f
exec rm rep.fossil

######################################
#  Test 2                            #
#  Reported: Ticket [74413366fe5067] #
######################################

fossil new rep.fossil
fossil open rep.fossil

write_file f1 "line"
fossil add f1
fossil commit -m "base file"
fossil tag add pivot current

write_file f2 "line2"
fossil add f2
fossil commit -m "newfile"

fossil mv f2 f2new
exec mv f2 f2new
fossil commit -m "rename"

fossil update pivot
write_file f1 "line3"
fossil commit -b branch -m "change"

fossil merge trunk
fossil commit -m "trunk merged"

fossil update trunk

fossil merge branch
puts $RESULT

# Not a nice way to check, but I don't know more tcl now
set deletes 0
foreach {status filename} $RESULT {
    if {$status=="DELETE"} {
        set deletes [expr $deletes + 1]
    }
}

if {$deletes!=0} {
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-2 0
} else {
    test merge_renames-2 1
}

fossil close -f
exec rm rep.fossil

######################################
#  Test 3                            #
#  Reported: Ticket [30b28cf351]     #
######################################

fossil new rep.fossil
fossil open rep.fossil

write_file f1 "line"
fossil add f1
fossil commit -m "base file"
fossil tag add pivot current

write_file f2 "line2"
fossil add f2
fossil commit -m "newfile"

fossil mv f2 f2new
exec mv f2 f2new
fossil commit -m "rename"

fossil update pivot
write_file f1 "line3"
fossil commit -b branch -m "change"

fossil merge trunk
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-2 0
} else {
    test merge_renames-2 1
}

######################################
#  Test 3                            #
#  Reported: Ticket [30b28cf351]     #
######################################

repo_init

write_file f1 "line"
fossil add f1
fossil commit -m "base file"
fossil tag add pivot current

write_file f2 "line2"
fossil add f2
fossil commit -m "newfile"

fossil mv f2 f2new
file rename -force f2 f2new
fossil commit -m "rename"

fossil update pivot
write_file f1 "line3"
fossil commit -b branch -m "change"

fossil merge trunk
fossil commit -m "trunk merged"

fossil update trunk

fossil merge branch
puts $RESULT

# Not a nice way to check, but I don't know more tcl now
set deletes 0
foreach {status filename} $RESULT {
    if {$status=="DELETE"} {
        set deletes [expr $deletes + 1]
    }
}

if {$deletes!=0} {
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-2 0
} else {
    test merge_renames-2 1
}

######################################
#  Test 4                            #
#  Reported: Ticket [67176c3aa4]     #
######################################

# TO BE WRITTEN.







<
<
<
<
<
<
<
<
|
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







175
176
177
178
179
180
181








182




183

































184
185
186
187
188
189
190
    # failed
    protOut "Error, the merge should not delete any file"
    test merge_renames-2 0
} else {
    test merge_renames-2 1
}









fossil close -f




exec rm rep.fossil


































######################################
#  Test 4                            #
#  Reported: Ticket [67176c3aa4]     #
######################################

# TO BE WRITTEN.

Changes to test/revert.test.

1
2
3
4
5


























6
7
8
9
10
11
12
13






14
15
16
17
18
19
20
21
22
23
24
25




26
27
28
29
30
31
32
33
34
35
36
37
38
39

40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
#
# Tests for 'fossil revert'
# 
#



























# Test 'fossil revert' against expected results from 'fossil changes' and
# 'fossil addremove -n', as well as by verifying the existence of files
# on the file system. 'fossil undo' is called after each test
#
proc revert-test {testid revertArgs expectedRevertOutput args} {
  global RESULT
  set passed 1
  






  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {}
  } $args]
  
  set result [fossil revert {*}$revertArgs]
  test_status_list revert-$testid $result $expectedRevertOutput
  
  set statusListTests [list -changes changes -addremove {addremove -n}]
  foreach {key fossilArgs} $statusListTests {
    set expected [dict get $args $key]
    set result [fossil {*}$fossilArgs] 
    test_status_list revert-$testid$key $result $expected




  }
  
  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }
    test revert-$testid$key $passed
  }
  
  fossil undo

}

catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
  puts stderr "Cannot run this test within an open checkout"
  return
}

repo_init


# Prepare first commit
#
write_file f1 "f1"
write_file f2 "f2"
write_file f3 "f3"
fossil add f1 f2 f3
fossil commit -m "c1"

# Make changes to be reverted
#
# Add f0
write_file f0 "f0"
fossil add f0
# Remove f1
file delete f1
fossil rm f1
# Edit f2
write_file f2 "f2.1"
# Rename f3 to f3n
file rename -force f3 f3n
fossil mv f3 f3n

# Test 'fossil revert' with no arguments
#
revert-test 1-1 {} {
  UNMANAGE: f0
  REVERTED: f1
  REVERTED: f2
  REVERTED: f3
  DELETE: f3n
} -addremove {
  ADDED f0
} -exists {f0 f1 f2 f3} -notexists f3n

# Test with a single filename argument
#
revert-test 1-2 f0 {
  UNMANAGE: f0
} -changes {
  DELETED f1
  EDITED f2
  RENAMED f3n
} -addremove {
  ADDED f0
} -exists {f0 f2 f3n} -notexists f3

revert-test 1-3 f1 {
  REVERTED: f1
} -changes {
  ADDED f0
  EDITED f2
  RENAMED f3n
} -exists {f0 f1 f2 f3n} -notexists f3

revert-test 1-4 f2 {
  REVERTED: f2
} -changes {
  ADDED f0
  DELETED f1
  RENAMED f3n
} -exists {f0 f2 f3n} -notexists {f1 f3}

# Both files involved in a rename are reverted regardless of which filename
# is used as an argument to 'fossil revert'
#
revert-test 1-5 f3 {
  REVERTED: f3
  DELETE: f3n
} -changes {
  ADDED f0
  DELETED f1
  EDITED f2
} -exists {f0 f2 f3} -notexists {f1 f3n}

revert-test 1-6 f3n {
  REVERTED: f3
  DELETE: f3n
} -changes {
  ADDED f0
  DELETED f1
  EDITED f2
} -exists {f0 f2 f3} -notexists {f1 f3n}

# Test with multiple filename arguments
#
revert-test 1-7 {f0 f2 f3n} {
  UNMANAGE: f0
  REVERTED: f2
  REVERTED: f3
  DELETE: f3n
} -changes {
  DELETED f1
} -addremove {
  ADDED f0
} -exists {f0 f2 f3} -notexists {f1 f3n}


# Test reverting the combination of a renamed file and an added file that
# uses the renamed file's original filename.
#
repo_init
write_file f1 "f1"
fossil add f1
fossil commit -m "add f1"

write_file f1n "f1n"
fossil mv f1 f1n
write_file f1 "f1b"
fossil add f1

revert-test 2-1 {} {
  REVERTED: f1
  DELETE: f1n
} -exists {f1} -notexists {f1n}





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|


|



>
>
>
>
>
>




|
<

|

|
|
|
>
>
>
>










<



>


<
|
<
<
<
<
|
|
>















|




|




|
<
<
<
<
<
<





|
<
<







|
<
<





|
<
<








|
<
<
<





|
<
<
<







<
<
<
<
<
|




<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

71
72
73
74
75
76

77




78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106






107
108
109
110
111
112


113
114
115
116
117
118
119
120


121
122
123
124
125
126


127
128
129
130
131
132
133
134
135



136
137
138
139
140
141



142
143
144
145
146
147
148





149
150
151
152
153



















#
# Tests for 'fossil revert'
# 
#

catch {exec $::fossilexe info} res
puts res=$res
if {![regexp {use --repository} $res]} {
  puts stderr "Cannot run this test within an open checkout"
  return
}

# Fossil will write data on $HOME, running 'fossil new' here.
# We need not to clutter the $HOME of the test caller.
#
set env(HOME) [pwd]


# Normalize file status lists (like those returned by 'fossil changes')
# so they can be compared using simple string comparison
#
proc normalize-status-list {list} {
  set normalized [list]
  set matches [regexp -all -inline -line {^\s*([A-Z]+)\s+(.*)$} $list]
  foreach {_ status file} $matches {
    lappend normalized [list $status [string trim $file]]
  }
  set normalized [lsort -index 1 $normalized]
  return $normalized
}

# Test 'fossil revert' against expected results from 'fossil changes' and
# 'fossil addremove --test', as well as by verifying the existence of files
# on the file system. 'fossil undo' is called after each test
#
proc revert-test {testid args} {
  global RESULT
  set passed 1
  
  if {[llength $args] % 2} {
    set revertArgs [lindex $args 0]
    set args [lrange $args 1 end]
  } else {
    set revertArgs {}
  }
  set args [dict merge {
    -changes {} -addremove {} -exists {} -notexists {}
  } $args]
  
  fossil revert {*}$revertArgs

  
  set statusListTests [list -changes changes -addremove {addremove --test}]
  foreach {key fossilArgs} $statusListTests {
    set expected [normalize-status-list [dict get $args $key]]
    set result [normalize-status-list [fossil {*}$fossilArgs]]
    if {$result ne $expected} {
      set passed 0
      protOut "  Expected:\n    [join $expected "\n    "]"
      protOut "  Got:\n    [join $result "\n    "]"
    }
  }
  
  set fileExistsTests [list -exists 1 does -notexists 0 should]
  foreach {key expected verb} $fileExistsTests {
    foreach path [dict get $args $key] {
      if {[file exists $path] != $expected} {
        set passed 0
        protOut "  Failure: File $verb not exist: $path"
      }
    }

  }
  
  fossil undo
  test revert-$testid $passed
}


# Create the repo




#
fossil new rep.fossil
fossil open rep.fossil

# Prepare first commit
#
write_file f1 "f1"
write_file f2 "f2"
write_file f3 "f3"
fossil add f1 f2 f3
fossil commit -m "c1"

# Make changes to be reverted
#
# Add f0
write_file f0 "f0"
fossil add f0
# Remove f1
exec rm f1
fossil rm f1
# Edit f2
write_file f2 "f2.1"
# Rename f3 to f3n
exec mv f3 f3n
fossil mv f3 f3n

# Test 'fossil revert' with no arguments
#
revert-test 1 -addremove {






  ADDED f0
} -exists {f0 f1 f2 f3} -notexists f3n

# Test with a single filename argument
#
revert-test 2 f0 -changes {


  DELETED f1
  EDITED f2
  RENAMED f3n
} -addremove {
  ADDED f0
} -exists {f0 f2 f3n} -notexists f3

revert-test 3 f1 -changes {


  ADDED f0
  EDITED f2
  RENAMED f3n
} -exists {f0 f1 f2 f3n} -notexists f3

revert-test 4 f2 -changes {


  ADDED f0
  DELETED f1
  RENAMED f3n
} -exists {f0 f2 f3n} -notexists {f1 f3}

# Both files involved in a rename are reverted regardless of which filename
# is used as an argument to 'fossil revert'
#
revert-test 5 f3 -changes {



  ADDED f0
  DELETED f1
  EDITED f2
} -exists {f0 f2 f3} -notexists {f1 f3n}

revert-test 6 f3n -changes {



  ADDED f0
  DELETED f1
  EDITED f2
} -exists {f0 f2 f3} -notexists {f1 f3n}

# Test with multiple filename arguments
#





revert-test 7 {f0 f2 f3n} -changes {
  DELETED f1
} -addremove {
  ADDED f0
} -exists {f0 f2 f3} -notexists {f1 f3n}



















Changes to test/tester.tcl.

119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
  set x [read_file $a]
  regsub -all { +\n} $x \n x
  set y [read_file $b]
  regsub -all { +\n} $y \n y
  return [expr {$x==$y}]
}

# Create and open a new Fossil repository and clean the checkout
#
proc repo_init {{filename ".rep.fossil"}} {
  if {$::env(HOME) ne [pwd]} {
    catch {exec $::fossilexe info} res
    if {![regexp {use --repository} $res]} {
      error "In an open checkout: cannot initialize a new repository here."
    }
    # Fossil will write data on $HOME, running 'fossil new' here.
    # We need not to clutter the $HOME of the test caller.
    #
    set ::env(HOME) [pwd]
  }
  catch {exec $::fossilexe close -f}
  file delete $filename
  exec $::fossilexe new $filename
  exec $::fossilexe open $filename
  exec $::fossilexe clean -f
  exec $::fossilexe set mtime-changes off
}

# Normalize file status lists (like those returned by 'fossil changes')
# so they can be compared using simple string comparison
#
proc normalize_status_list {list} {
  set normalized [list]
  set matches [regexp -all -inline -line {^\s*([A-Z_]+:?)\x20+(\S.*)$} $list]
  foreach {_ status file} $matches {
    lappend normalized [list $status [string trim $file]]
  }
  set normalized [lsort -index 1 $normalized]
  return $normalized
}

# Perform a test comparing two status lists
#
proc test_status_list {name result expected} {
  set expected [normalize_status_list $expected]
  set result [normalize_status_list $result]
  if {$result eq $expected} {
    test $name 1
  } else {
    protOut "  Expected:\n    [join $expected "\n    "]"
    protOut "  Got:\n    [join $result "\n    "]"
    test $name 0
  } 
}

# Perform a test
#
set test_count 0
proc test {name expr} {
  global bad_test test_count
  incr test_count
  set r [uplevel 1 [list expr $expr]]







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







119
120
121
122
123
124
125
















































126
127
128
129
130
131
132
  set x [read_file $a]
  regsub -all { +\n} $x \n x
  set y [read_file $b]
  regsub -all { +\n} $y \n y
  return [expr {$x==$y}]
}

















































# Perform a test
#
set test_count 0
proc test {name expr} {
  global bad_test test_count
  incr test_count
  set r [uplevel 1 [list expr $expr]]

Changes to test/th1.test.

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

fossil test-th-eval --th-open-config "setting -strict -- abc"
test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}}

###############################################################################

fossil test-th-eval --th-open-config "setting autosync"
test th1-setting-5 {$RESULT eq 0 || $RESULT eq 1}

###############################################################################

fossil test-th-eval --th-open-config "setting -strict autosync"
test th1-setting-6 {$RESULT eq 0 || $RESULT eq 1}

###############################################################################

fossil test-th-eval --th-open-config "setting --"
test th1-setting-7 {$RESULT eq \
{TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}








|




|







35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54

fossil test-th-eval --th-open-config "setting -strict -- abc"
test th1-setting-4 {$RESULT eq {TH_ERROR: no value for setting "abc"}}

###############################################################################

fossil test-th-eval --th-open-config "setting autosync"
test th1-setting-5 {$RESULT eq 1}

###############################################################################

fossil test-th-eval --th-open-config "setting -strict autosync"
test th1-setting-6 {$RESULT eq 1}

###############################################################################

fossil test-th-eval --th-open-config "setting --"
test th1-setting-7 {$RESULT eq \
{TH_ERROR: wrong # args: should be "setting ?-strict? ?--? name"}}

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
###############################################################################

fossil test-th-eval "string last {AB} {abc}"
test th1-string-last-9 {$RESULT eq {-1}}

###############################################################################

fossil test-th-eval "expr -2147483649.0"
test th1-expr-1 {$RESULT eq {-2147483649.0}}

###############################################################################

fossil test-th-eval "expr -2147483649"
test th1-expr-2 {$RESULT eq {2147483647}}

###############################################################################

fossil test-th-eval "expr -2147483648"
test th1-expr-3 {$RESULT eq {-2147483648}}

###############################################################################

fossil test-th-eval "expr -2147483647"
test th1-expr-4 {$RESULT eq {-2147483647}}

###############################################################################

fossil test-th-eval "expr -1"
test th1-expr-5 {$RESULT eq {-1}}

###############################################################################

fossil test-th-eval "expr 0"
test th1-expr-6 {$RESULT eq {0}}

###############################################################################

fossil test-th-eval "expr 0.0"
test th1-expr-7 {$RESULT eq {0.0}}

###############################################################################

fossil test-th-eval "expr 1"
test th1-expr-8 {$RESULT eq {1}}

###############################################################################

fossil test-th-eval "expr 2147483647"
test th1-expr-9 {$RESULT eq {2147483647}}

###############################################################################

fossil test-th-eval "expr 2147483648"
test th1-expr-10 {$RESULT eq {2147483648}}

###############################################################################

fossil test-th-eval "expr 2147483649"
test th1-expr-11 {$RESULT eq {2147483649}}

###############################################################################

fossil test-th-eval "expr +2147483649"
test th1-expr-12 {$RESULT eq {-2147483647}}

###############################################################################

fossil test-th-eval "expr +2147483649.0"
test th1-expr-13 {$RESULT eq {2147483649.0}}

###############################################################################

fossil test-th-eval "expr ~(-1)"
test th1-expr-14 {$RESULT eq {0}}

###############################################################################

fossil test-th-eval "expr ~-1"
test th1-expr-15 {$RESULT eq {0}}







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







298
299
300
301
302
303
304

































































305
306
307
308
309
310
311
###############################################################################

fossil test-th-eval "string last {AB} {abc}"
test th1-string-last-9 {$RESULT eq {-1}}

###############################################################################


































































fossil test-th-eval "expr ~(-1)"
test th1-expr-14 {$RESULT eq {0}}

###############################################################################

fossil test-th-eval "expr ~-1"
test th1-expr-15 {$RESULT eq {0}}
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
fossil test-th-eval "expr ~+1"
test th1-expr-22 {$RESULT eq {-2}}

###############################################################################

fossil test-th-eval "expr ~(+1)"
test th1-expr-23 {$RESULT eq {-2}}

###############################################################################

fossil test-th-eval "expr 0+0b11"
test th1-expr-24 {$RESULT eq 3}

###############################################################################

fossil test-th-eval "expr 0+0o15"
test th1-expr-25 {$RESULT eq 13}

###############################################################################

fossil test-th-eval "expr 0+0x15"
test th1-expr-26 {$RESULT eq 21}

###############################################################################

fossil test-th-eval "expr 0+0b2"
test th1-expr-27 {$RESULT eq {TH_ERROR: expected number, got: "0b2"}}

###############################################################################

fossil test-th-eval "expr 0+0o8"
test th1-expr-28 {$RESULT eq {TH_ERROR: expected number, got: "0o8"}}

###############################################################################

fossil test-th-eval "expr 0+0xg"
test th1-expr-29 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0xg"}}

###############################################################################

fossil test-th-eval "expr 0+0b1."
test th1-expr-30 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0b1."}}

###############################################################################

fossil test-th-eval "expr 0+0o1."
test th1-expr-31 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0o1."}}

###############################################################################

fossil test-th-eval "expr 0+0x1."
test th1-expr-32 {$RESULT eq {TH_ERROR: syntax error in expression: "0+0x1."}}

###############################################################################

fossil test-th-eval "expr 0ne5"
test th1-expr-33 {$RESULT eq {1}}

###############################################################################

fossil test-th-eval "expr 0b1+5"
test th1-expr-34 {$RESULT eq {6}}


###############################################################################

fossil test-th-eval "expr 0+0b"
test th1-expr-35 {$RESULT eq {TH_ERROR: expected number, got: "0b"}}









<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
345
346
347
348
349
350
351































































fossil test-th-eval "expr ~+1"
test th1-expr-22 {$RESULT eq {-2}}

###############################################################################

fossil test-th-eval "expr ~(+1)"
test th1-expr-23 {$RESULT eq {-2}}































































Deleted test/utf16be.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This file contains utf-16be text.
The purpose for including this file in the Fossil
repository is to provide the ability to test Fossil's
handling of UTF-16 using its own repository.

Browsing to this file in the web interface should display the file as
text on the screen.

When there are changes to this file those changes should show
up in the "diff" output and be properly displayed on the
screen.

Test procedures:

  1.  Verify that this file is correctly display using the /artifact
      webpage.

  2.  Verify that this file is correctly displayed by the /doc webpage.

  3.  Verify that changes to are correctly displayed by the /fdiff webpage.

  4.  Verify that the "fossil diff" command correctly displays changes
      in this file.  Do the same with the --tk option.
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































Deleted test/utf16le.txt.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
This file contains utf-16le text.
The purpose for including this file in the Fossil
repository is to provide the ability to test Fossil's
handling of UTF-16 using its own repository.

Browsing to this file in the web interface should display the file as
text on the screen.

When there are changes to this file those changes should show
up in the "diff" output and be properly displayed on the
screen.

Test procedures:

  1.  Verify that this file is correctly display using the /artifact
      webpage.

  2.  Verify that this file is correctly displayed by the /doc webpage.

  3.  Verify that changes to are correctly displayed by the /fdiff webpage.

  4.  Verify that the "fossil diff" command correctly displays changes
      in this file.  Do the same with the --tk option.
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<














































Changes to test/valgrind-www.tcl.

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#
# Then examine the valgrind-out.txt file for issues.
#
proc run_query {url} {
  set fd [open q.txt w]
  puts $fd "GET $url HTTP/1.0\r\n\r"
  close $fd
  set msg {}
  catch {exec valgrind ./fossil test-http <q.txt 2>@ stderr} msg
  return $msg
}
set todo {}
foreach url {
  /home
  /timeline
  /brlist
  /taglist







<
|
<







10
11
12
13
14
15
16

17

18
19
20
21
22
23
24
#
# Then examine the valgrind-out.txt file for issues.
#
proc run_query {url} {
  set fd [open q.txt w]
  puts $fd "GET $url HTTP/1.0\r\n\r"
  close $fd

  return [exec valgrind ./fossil test-http <q.txt 2>@ stderr]

}
set todo {}
foreach url {
  /home
  /timeline
  /brlist
  /taglist

Changes to win/Makefile.dmc.

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32

SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS

SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=win32_access -Dgetenv=fossil_getenv -Dfopen=fossil_fopen

SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c loadctrl_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c 

OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\loadctrl$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O 


RC=$(DMDIR)\bin\rcc
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__

APPNAME = $(OBJDIR)\fossil$(E)

all: $(APPNAME)

$(APPNAME) : translate$E mkindex$E headers  $(OBJ) $(OBJDIR)\link
	cd $(OBJDIR) 
	$(DMDIR)\bin\link @link

$(OBJDIR)\fossil.res:	$B\win\fossil.rc
	$(RC) $(RCFLAGS) -o$@ $**

$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
	+echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf loadctrl login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path pivot popen pqueue printf rebuild regexp report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo unicode update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
	+echo fossil >> $@
	+echo fossil >> $@
	+echo $(LIBS) >> $@
	+echo. >> $@
	+echo fossil >> $@

translate$E: $(SRCDIR)\translate.c







|

|

















|







26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
TCC    = $(DMDIR)\bin\dmc $(CFLAGS) $(DMCDEF) $(SSL) $(INCL)
LIBS   = $(DMDIR)\extra\lib\ zlib wsock32 advapi32

SQLITE_OPTIONS = -DSQLITE_OMIT_LOAD_EXTENSION=1 -DSQLITE_ENABLE_LOCKING_STYLE=0 -DSQLITE_THREADSAFE=0 -DSQLITE_DEFAULT_FILE_FORMAT=4 -DSQLITE_OMIT_DEPRECATED -DSQLITE_ENABLE_EXPLAIN_COMMENTS

SHELL_OPTIONS = -Dmain=sqlite3_shell -DSQLITE_OMIT_LOAD_EXTENSION=1 -DUSE_SYSTEM_SQLITE=$(USE_SYSTEM_SQLITE) -DSQLITE_SHELL_DBNAME_PROC=fossil_open -Daccess=win32_access -Dgetenv=fossil_getenv -Dfopen=fossil_fopen

SRC   = add_.c allrepo_.c attach_.c bag_.c bisect_.c blob_.c branch_.c browse_.c captcha_.c cgi_.c checkin_.c checkout_.c clearsign_.c clone_.c comformat_.c configure_.c content_.c db_.c delta_.c deltacmd_.c descendants_.c diff_.c diffcmd_.c doc_.c encode_.c event_.c export_.c file_.c finfo_.c glob_.c graph_.c gzip_.c http_.c http_socket_.c http_ssl_.c http_transport_.c import_.c info_.c json_.c json_artifact_.c json_branch_.c json_config_.c json_diff_.c json_dir_.c json_finfo_.c json_login_.c json_query_.c json_report_.c json_status_.c json_tag_.c json_timeline_.c json_user_.c json_wiki_.c leaf_.c login_.c lookslike_.c main_.c manifest_.c markdown_.c markdown_html_.c md5_.c merge_.c merge3_.c moderate_.c name_.c path_.c pivot_.c popen_.c pqueue_.c printf_.c rebuild_.c regexp_.c report_.c rss_.c schema_.c search_.c setup_.c sha1_.c shun_.c skins_.c sqlcmd_.c stash_.c stat_.c style_.c sync_.c tag_.c tar_.c th_main_.c timeline_.c tkt_.c tktsetup_.c undo_.c unicode_.c update_.c url_.c user_.c utf8_.c util_.c verify_.c vfile_.c wiki_.c wikiformat_.c winfile_.c winhttp_.c wysiwyg_.c xfer_.c xfersetup_.c zip_.c 

OBJ   = $(OBJDIR)\add$O $(OBJDIR)\allrepo$O $(OBJDIR)\attach$O $(OBJDIR)\bag$O $(OBJDIR)\bisect$O $(OBJDIR)\blob$O $(OBJDIR)\branch$O $(OBJDIR)\browse$O $(OBJDIR)\captcha$O $(OBJDIR)\cgi$O $(OBJDIR)\checkin$O $(OBJDIR)\checkout$O $(OBJDIR)\clearsign$O $(OBJDIR)\clone$O $(OBJDIR)\comformat$O $(OBJDIR)\configure$O $(OBJDIR)\content$O $(OBJDIR)\db$O $(OBJDIR)\delta$O $(OBJDIR)\deltacmd$O $(OBJDIR)\descendants$O $(OBJDIR)\diff$O $(OBJDIR)\diffcmd$O $(OBJDIR)\doc$O $(OBJDIR)\encode$O $(OBJDIR)\event$O $(OBJDIR)\export$O $(OBJDIR)\file$O $(OBJDIR)\finfo$O $(OBJDIR)\glob$O $(OBJDIR)\graph$O $(OBJDIR)\gzip$O $(OBJDIR)\http$O $(OBJDIR)\http_socket$O $(OBJDIR)\http_ssl$O $(OBJDIR)\http_transport$O $(OBJDIR)\import$O $(OBJDIR)\info$O $(OBJDIR)\json$O $(OBJDIR)\json_artifact$O $(OBJDIR)\json_branch$O $(OBJDIR)\json_config$O $(OBJDIR)\json_diff$O $(OBJDIR)\json_dir$O $(OBJDIR)\json_finfo$O $(OBJDIR)\json_login$O $(OBJDIR)\json_query$O $(OBJDIR)\json_report$O $(OBJDIR)\json_status$O $(OBJDIR)\json_tag$O $(OBJDIR)\json_timeline$O $(OBJDIR)\json_user$O $(OBJDIR)\json_wiki$O $(OBJDIR)\leaf$O $(OBJDIR)\login$O $(OBJDIR)\lookslike$O $(OBJDIR)\main$O $(OBJDIR)\manifest$O $(OBJDIR)\markdown$O $(OBJDIR)\markdown_html$O $(OBJDIR)\md5$O $(OBJDIR)\merge$O $(OBJDIR)\merge3$O $(OBJDIR)\moderate$O $(OBJDIR)\name$O $(OBJDIR)\path$O $(OBJDIR)\pivot$O $(OBJDIR)\popen$O $(OBJDIR)\pqueue$O $(OBJDIR)\printf$O $(OBJDIR)\rebuild$O $(OBJDIR)\regexp$O $(OBJDIR)\report$O $(OBJDIR)\rss$O $(OBJDIR)\schema$O $(OBJDIR)\search$O $(OBJDIR)\setup$O $(OBJDIR)\sha1$O $(OBJDIR)\shun$O $(OBJDIR)\skins$O $(OBJDIR)\sqlcmd$O $(OBJDIR)\stash$O $(OBJDIR)\stat$O $(OBJDIR)\style$O $(OBJDIR)\sync$O $(OBJDIR)\tag$O $(OBJDIR)\tar$O $(OBJDIR)\th_main$O $(OBJDIR)\timeline$O $(OBJDIR)\tkt$O $(OBJDIR)\tktsetup$O $(OBJDIR)\undo$O $(OBJDIR)\unicode$O $(OBJDIR)\update$O $(OBJDIR)\url$O $(OBJDIR)\user$O $(OBJDIR)\utf8$O $(OBJDIR)\util$O $(OBJDIR)\verify$O $(OBJDIR)\vfile$O $(OBJDIR)\wiki$O $(OBJDIR)\wikiformat$O $(OBJDIR)\winfile$O $(OBJDIR)\winhttp$O $(OBJDIR)\wysiwyg$O $(OBJDIR)\xfer$O $(OBJDIR)\xfersetup$O $(OBJDIR)\zip$O $(OBJDIR)\shell$O $(OBJDIR)\sqlite3$O $(OBJDIR)\th$O $(OBJDIR)\th_lang$O 


RC=$(DMDIR)\bin\rcc
RCFLAGS=-32 -w1 -I$(SRCDIR) /D__DMC__

APPNAME = $(OBJDIR)\fossil$(E)

all: $(APPNAME)

$(APPNAME) : translate$E mkindex$E headers  $(OBJ) $(OBJDIR)\link
	cd $(OBJDIR) 
	$(DMDIR)\bin\link @link

$(OBJDIR)\fossil.res:	$B\win\fossil.rc
	$(RC) $(RCFLAGS) -o$@ $**

$(OBJDIR)\link: $B\win\Makefile.dmc $(OBJDIR)\fossil.res
	+echo add allrepo attach bag bisect blob branch browse captcha cgi checkin checkout clearsign clone comformat configure content db delta deltacmd descendants diff diffcmd doc encode event export file finfo glob graph gzip http http_socket http_ssl http_transport import info json json_artifact json_branch json_config json_diff json_dir json_finfo json_login json_query json_report json_status json_tag json_timeline json_user json_wiki leaf login lookslike main manifest markdown markdown_html md5 merge merge3 moderate name path pivot popen pqueue printf rebuild regexp report rss schema search setup sha1 shun skins sqlcmd stash stat style sync tag tar th_main timeline tkt tktsetup undo unicode update url user utf8 util verify vfile wiki wikiformat winfile winhttp wysiwyg xfer xfersetup zip shell sqlite3 th th_lang > $@
	+echo fossil >> $@
	+echo fossil >> $@
	+echo $(LIBS) >> $@
	+echo. >> $@
	+echo fossil >> $@

translate$E: $(SRCDIR)\translate.c
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454

$(OBJDIR)\leaf$O : leaf_.c leaf.h
	$(TCC) -o$@ -c leaf_.c

leaf_.c : $(SRCDIR)\leaf.c
	+translate$E $** > $@

$(OBJDIR)\loadctrl$O : loadctrl_.c loadctrl.h
	$(TCC) -o$@ -c loadctrl_.c

loadctrl_.c : $(SRCDIR)\loadctrl.c
	+translate$E $** > $@

$(OBJDIR)\login$O : login_.c login.h
	$(TCC) -o$@ -c login_.c

login_.c : $(SRCDIR)\login.c
	+translate$E $** > $@

$(OBJDIR)\lookslike$O : lookslike_.c lookslike.h







<
<
<
<
<
<







435
436
437
438
439
440
441






442
443
444
445
446
447
448

$(OBJDIR)\leaf$O : leaf_.c leaf.h
	$(TCC) -o$@ -c leaf_.c

leaf_.c : $(SRCDIR)\leaf.c
	+translate$E $** > $@







$(OBJDIR)\login$O : login_.c login.h
	$(TCC) -o$@ -c login_.c

login_.c : $(SRCDIR)\login.c
	+translate$E $** > $@

$(OBJDIR)\lookslike$O : lookslike_.c lookslike.h
766
767
768
769
770
771
772
773
774
$(OBJDIR)\zip$O : zip_.c zip.h
	$(TCC) -o$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	+translate$E $** > $@

headers: makeheaders$E page_index.h VERSION.h
	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h loadctrl_.c:loadctrl.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
	@copy /Y nul: headers







|

760
761
762
763
764
765
766
767
768
$(OBJDIR)\zip$O : zip_.c zip.h
	$(TCC) -o$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	+translate$E $** > $@

headers: makeheaders$E page_index.h VERSION.h
	 +makeheaders$E add_.c:add.h allrepo_.c:allrepo.h attach_.c:attach.h bag_.c:bag.h bisect_.c:bisect.h blob_.c:blob.h branch_.c:branch.h browse_.c:browse.h captcha_.c:captcha.h cgi_.c:cgi.h checkin_.c:checkin.h checkout_.c:checkout.h clearsign_.c:clearsign.h clone_.c:clone.h comformat_.c:comformat.h configure_.c:configure.h content_.c:content.h db_.c:db.h delta_.c:delta.h deltacmd_.c:deltacmd.h descendants_.c:descendants.h diff_.c:diff.h diffcmd_.c:diffcmd.h doc_.c:doc.h encode_.c:encode.h event_.c:event.h export_.c:export.h file_.c:file.h finfo_.c:finfo.h glob_.c:glob.h graph_.c:graph.h gzip_.c:gzip.h http_.c:http.h http_socket_.c:http_socket.h http_ssl_.c:http_ssl.h http_transport_.c:http_transport.h import_.c:import.h info_.c:info.h json_.c:json.h json_artifact_.c:json_artifact.h json_branch_.c:json_branch.h json_config_.c:json_config.h json_diff_.c:json_diff.h json_dir_.c:json_dir.h json_finfo_.c:json_finfo.h json_login_.c:json_login.h json_query_.c:json_query.h json_report_.c:json_report.h json_status_.c:json_status.h json_tag_.c:json_tag.h json_timeline_.c:json_timeline.h json_user_.c:json_user.h json_wiki_.c:json_wiki.h leaf_.c:leaf.h login_.c:login.h lookslike_.c:lookslike.h main_.c:main.h manifest_.c:manifest.h markdown_.c:markdown.h markdown_html_.c:markdown_html.h md5_.c:md5.h merge_.c:merge.h merge3_.c:merge3.h moderate_.c:moderate.h name_.c:name.h path_.c:path.h pivot_.c:pivot.h popen_.c:popen.h pqueue_.c:pqueue.h printf_.c:printf.h rebuild_.c:rebuild.h regexp_.c:regexp.h report_.c:report.h rss_.c:rss.h schema_.c:schema.h search_.c:search.h setup_.c:setup.h sha1_.c:sha1.h shun_.c:shun.h skins_.c:skins.h sqlcmd_.c:sqlcmd.h stash_.c:stash.h stat_.c:stat.h style_.c:style.h sync_.c:sync.h tag_.c:tag.h tar_.c:tar.h th_main_.c:th_main.h timeline_.c:timeline.h tkt_.c:tkt.h tktsetup_.c:tktsetup.h undo_.c:undo.h unicode_.c:unicode.h update_.c:update.h url_.c:url.h user_.c:user.h utf8_.c:utf8.h util_.c:util.h verify_.c:verify.h vfile_.c:vfile.h wiki_.c:wiki.h wikiformat_.c:wikiformat.h winfile_.c:winfile.h winhttp_.c:winhttp.h wysiwyg_.c:wysiwyg.h xfer_.c:xfer.h xfersetup_.c:xfersetup.h zip_.c:zip.h $(SRCDIR)\sqlite3.h $(SRCDIR)\th.h VERSION.h $(SRCDIR)\cson_amalgamation.h
	@copy /Y nul: headers

Changes to win/Makefile.mingw.

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \
  $(SRCDIR)/loadctrl.c \
  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \







<







307
308
309
310
311
312
313

314
315
316
317
318
319
320
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \

  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \
  $(OBJDIR)/loadctrl_.c \
  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \







<







417
418
419
420
421
422
423

424
425
426
427
428
429
430
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \

  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \
 $(OBJDIR)/loadctrl.o \
 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \







<







527
528
529
530
531
532
533

534
535
536
537
538
539
540
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \

 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \
667
668
669
670
671
672
673
674




675
676
677
678
679
680
681
# the repository after running the tests.
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/th_tcl.o  $(OBJDIR)/cson_amalgamation.o





zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean








|
>
>
>
>







664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
# the repository after running the tests.
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/cson_amalgamation.o

ifdef FOSSIL_ENABLE_TCL
EXTRAOBJ +=  $(OBJDIR)/th_tcl.o
endif

zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean

768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
		$(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \
		$(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \
		$(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \
		$(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \
		$(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \
		$(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \
		$(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \
		$(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \
		$(OBJDIR)/login_.c:$(OBJDIR)/login.h \
		$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
		$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
		$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
		$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
		$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
		$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \







<







769
770
771
772
773
774
775

776
777
778
779
780
781
782
		$(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \
		$(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \
		$(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \
		$(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \
		$(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \
		$(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \
		$(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \

		$(OBJDIR)/login_.c:$(OBJDIR)/login.h \
		$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
		$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
		$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
		$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
		$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
		$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
	$(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers

$(OBJDIR)/loadctrl_.c:	$(SRCDIR)/loadctrl.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/loadctrl.c >$(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.o:	$(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.h:	$(OBJDIR)/headers

$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<
<







1264
1265
1266
1267
1268
1269
1270








1271
1272
1273
1274
1275
1276
1277
	$(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers









$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers
1739
1740
1741
1742
1743
1744
1745

1746
1747
1748

$(OBJDIR)/th.o:	$(SRCDIR)/th.c
	$(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o

$(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
	$(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o


$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o








>


|
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741

$(OBJDIR)/th.o:	$(SRCDIR)/th.c
	$(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o

$(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
	$(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o

ifdef FOSSIL_ENABLE_TCL
$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o
endif

Changes to win/Makefile.mingw.mistachkin.

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \
  $(SRCDIR)/loadctrl.c \
  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \







<







307
308
309
310
311
312
313

314
315
316
317
318
319
320
  $(SRCDIR)/json_report.c \
  $(SRCDIR)/json_status.c \
  $(SRCDIR)/json_tag.c \
  $(SRCDIR)/json_timeline.c \
  $(SRCDIR)/json_user.c \
  $(SRCDIR)/json_wiki.c \
  $(SRCDIR)/leaf.c \

  $(SRCDIR)/login.c \
  $(SRCDIR)/lookslike.c \
  $(SRCDIR)/main.c \
  $(SRCDIR)/manifest.c \
  $(SRCDIR)/markdown.c \
  $(SRCDIR)/markdown_html.c \
  $(SRCDIR)/md5.c \
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \
  $(OBJDIR)/loadctrl_.c \
  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \







<







417
418
419
420
421
422
423

424
425
426
427
428
429
430
  $(OBJDIR)/json_report_.c \
  $(OBJDIR)/json_status_.c \
  $(OBJDIR)/json_tag_.c \
  $(OBJDIR)/json_timeline_.c \
  $(OBJDIR)/json_user_.c \
  $(OBJDIR)/json_wiki_.c \
  $(OBJDIR)/leaf_.c \

  $(OBJDIR)/login_.c \
  $(OBJDIR)/lookslike_.c \
  $(OBJDIR)/main_.c \
  $(OBJDIR)/manifest_.c \
  $(OBJDIR)/markdown_.c \
  $(OBJDIR)/markdown_html_.c \
  $(OBJDIR)/md5_.c \
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \
 $(OBJDIR)/loadctrl.o \
 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \







<







527
528
529
530
531
532
533

534
535
536
537
538
539
540
 $(OBJDIR)/json_report.o \
 $(OBJDIR)/json_status.o \
 $(OBJDIR)/json_tag.o \
 $(OBJDIR)/json_timeline.o \
 $(OBJDIR)/json_user.o \
 $(OBJDIR)/json_wiki.o \
 $(OBJDIR)/leaf.o \

 $(OBJDIR)/login.o \
 $(OBJDIR)/lookslike.o \
 $(OBJDIR)/main.o \
 $(OBJDIR)/manifest.o \
 $(OBJDIR)/markdown.o \
 $(OBJDIR)/markdown_html.o \
 $(OBJDIR)/md5.o \
667
668
669
670
671
672
673
674




675
676
677
678
679
680
681
# the repository after running the tests.
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/th_tcl.o  $(OBJDIR)/cson_amalgamation.o





zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean








|
>
>
>
>







664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
# the repository after running the tests.
test:	$(OBJDIR) $(APPNAME)
	$(TCLSH) $(SRCDIR)/../test/tester.tcl $(APPNAME)

$(OBJDIR)/VERSION.h:	$(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(VERSION)
	$(VERSION) $(SRCDIR)/../manifest.uuid $(SRCDIR)/../manifest $(SRCDIR)/../VERSION >$(OBJDIR)/VERSION.h

EXTRAOBJ =  $(OBJDIR)/sqlite3.o  $(OBJDIR)/shell.o  $(OBJDIR)/th.o  $(OBJDIR)/th_lang.o  $(OBJDIR)/cson_amalgamation.o

ifdef FOSSIL_ENABLE_TCL
EXTRAOBJ +=  $(OBJDIR)/th_tcl.o
endif

zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc libz.a

clean-zlib:
	$(MAKE) -C $(ZLIBDIR) PREFIX=$(PREFIX) -f win32/Makefile.gcc clean

768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
		$(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \
		$(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \
		$(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \
		$(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \
		$(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \
		$(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \
		$(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \
		$(OBJDIR)/loadctrl_.c:$(OBJDIR)/loadctrl.h \
		$(OBJDIR)/login_.c:$(OBJDIR)/login.h \
		$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
		$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
		$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
		$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
		$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
		$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \







<







769
770
771
772
773
774
775

776
777
778
779
780
781
782
		$(OBJDIR)/json_report_.c:$(OBJDIR)/json_report.h \
		$(OBJDIR)/json_status_.c:$(OBJDIR)/json_status.h \
		$(OBJDIR)/json_tag_.c:$(OBJDIR)/json_tag.h \
		$(OBJDIR)/json_timeline_.c:$(OBJDIR)/json_timeline.h \
		$(OBJDIR)/json_user_.c:$(OBJDIR)/json_user.h \
		$(OBJDIR)/json_wiki_.c:$(OBJDIR)/json_wiki.h \
		$(OBJDIR)/leaf_.c:$(OBJDIR)/leaf.h \

		$(OBJDIR)/login_.c:$(OBJDIR)/login.h \
		$(OBJDIR)/lookslike_.c:$(OBJDIR)/lookslike.h \
		$(OBJDIR)/main_.c:$(OBJDIR)/main.h \
		$(OBJDIR)/manifest_.c:$(OBJDIR)/manifest.h \
		$(OBJDIR)/markdown_.c:$(OBJDIR)/markdown.h \
		$(OBJDIR)/markdown_html_.c:$(OBJDIR)/markdown_html.h \
		$(OBJDIR)/md5_.c:$(OBJDIR)/md5.h \
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
	$(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers

$(OBJDIR)/loadctrl_.c:	$(SRCDIR)/loadctrl.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/loadctrl.c >$(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.o:	$(OBJDIR)/loadctrl_.c $(OBJDIR)/loadctrl.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/loadctrl.o -c $(OBJDIR)/loadctrl_.c

$(OBJDIR)/loadctrl.h:	$(OBJDIR)/headers

$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers







<
<
<
<
<
<
<
<







1264
1265
1266
1267
1268
1269
1270








1271
1272
1273
1274
1275
1276
1277
	$(TRANSLATE) $(SRCDIR)/leaf.c >$(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.o:	$(OBJDIR)/leaf_.c $(OBJDIR)/leaf.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/leaf.o -c $(OBJDIR)/leaf_.c

$(OBJDIR)/leaf.h:	$(OBJDIR)/headers









$(OBJDIR)/login_.c:	$(SRCDIR)/login.c $(OBJDIR)/translate
	$(TRANSLATE) $(SRCDIR)/login.c >$(OBJDIR)/login_.c

$(OBJDIR)/login.o:	$(OBJDIR)/login_.c $(OBJDIR)/login.h  $(SRCDIR)/config.h
	$(XTCC) -o $(OBJDIR)/login.o -c $(OBJDIR)/login_.c

$(OBJDIR)/login.h:	$(OBJDIR)/headers
1739
1740
1741
1742
1743
1744
1745

1746
1747


$(OBJDIR)/th.o:	$(SRCDIR)/th.c
	$(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o

$(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
	$(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o


$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o








>


>
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741

$(OBJDIR)/th.o:	$(SRCDIR)/th.c
	$(XTCC) -c $(SRCDIR)/th.c -o $(OBJDIR)/th.o

$(OBJDIR)/th_lang.o:	$(SRCDIR)/th_lang.c
	$(XTCC) -c $(SRCDIR)/th_lang.c -o $(OBJDIR)/th_lang.o

ifdef FOSSIL_ENABLE_TCL
$(OBJDIR)/th_tcl.o:	$(SRCDIR)/th_tcl.c
	$(XTCC) -c $(SRCDIR)/th_tcl.c -o $(OBJDIR)/th_tcl.o
endif

Changes to win/Makefile.msc.

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

# Uncomment to enable JSON API
# FOSSIL_ENABLE_JSON = 1

# Uncomment to enable SSL support
# FOSSIL_ENABLE_SSL = 1

# Uncomment to enable Tcl support
# FOSSIL_ENABLE_TCL = 1

!ifdef FOSSIL_ENABLE_SSL
SSLINCDIR = $(B)\compat\openssl-1.0.1g\include
SSLLIBDIR = $(B)\compat\openssl-1.0.1g\out32
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib
!endif

!ifdef FOSSIL_ENABLE_TCL
TCLDIR    = $(B)\compat\tcl-8.6
TCLSRCDIR = $(TCLDIR)
TCLINCDIR = $(TCLSRCDIR)\generic
!endif

# zlib options
ZINCDIR   = $(B)\compat\zlib
ZLIBDIR   = $(B)\compat\zlib
ZLIB      = zlib.lib

INCL      = /I. /I$(SRCDIR) /I$B\win\include /I$(ZINCDIR)

!ifdef FOSSIL_ENABLE_SSL
INCL      = $(INCL) /I$(SSLINCDIR)
!endif

!ifdef FOSSIL_ENABLE_TCL
INCL      = $(INCL) /I$(TCLINCDIR)
!endif

CFLAGS    = /nologo
LDFLAGS   = /NODEFAULTLIB:msvcrt /MANIFEST:NO

!ifdef DEBUG
CFLAGS    = $(CFLAGS) /Zi /MTd /Od
LDFLAGS   = $(LDFLAGS) /DEBUG
!else
CFLAGS    = $(CFLAGS) /MT /O2
!endif

BCC       = $(CC) $(CFLAGS)
TCC       = $(CC) /c $(CFLAGS) $(MSCDEF) $(INCL)
RCC       = rc /D_WIN32 /D_MSC_VER $(MSCDEF) $(INCL)
LIBS      = $(ZLIB) ws2_32.lib advapi32.lib
LIBDIR    = /LIBPATH:$(ZLIBDIR)

!ifdef FOSSIL_ENABLE_JSON
TCC       = $(TCC) /DFOSSIL_ENABLE_JSON=1
RCC       = $(RCC) /DFOSSIL_ENABLE_JSON=1
!endif

!ifdef FOSSIL_ENABLE_SSL
TCC       = $(TCC) /DFOSSIL_ENABLE_SSL=1
RCC       = $(RCC) /DFOSSIL_ENABLE_SSL=1
LIBS      = $(LIBS) $(SSLLIB)
LIBDIR    = $(LIBDIR) /LIBPATH:$(SSLLIBDIR)
!endif

!ifdef FOSSIL_ENABLE_TCL
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL=1
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL_STUBS=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL_STUBS=1
TCC       = $(TCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1
RCC       = $(RCC) /DFOSSIL_ENABLE_TCL_PRIVATE_STUBS=1
TCC       = $(TCC) /DUSE_TCL_STUBS=1
RCC       = $(RCC) /DUSE_TCL_STUBS=1
!endif

SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 \
                 /DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 /DSQLITE_THREADSAFE=0 \
                 /DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 /DSQLITE_OMIT_DEPRECATED \







<
<
<






<
<
<
<
<
<





|


|


<
<
<
<
|



|


|



|
|

|


|
|



|
|

|
<
<
<
<
<
<
<
<
<
<
<







20
21
22
23
24
25
26



27
28
29
30
31
32






33
34
35
36
37
38
39
40
41
42
43




44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69











70
71
72
73
74
75
76

# Uncomment to enable JSON API
# FOSSIL_ENABLE_JSON = 1

# Uncomment to enable SSL support
# FOSSIL_ENABLE_SSL = 1




!ifdef FOSSIL_ENABLE_SSL
SSLINCDIR = $(B)\compat\openssl-1.0.1g\include
SSLLIBDIR = $(B)\compat\openssl-1.0.1g\out32
SSLLIB    = ssleay32.lib libeay32.lib user32.lib gdi32.lib
!endif







# zlib options
ZINCDIR   = $(B)\compat\zlib
ZLIBDIR   = $(B)\compat\zlib
ZLIB      = zlib.lib

INCL      = -I. -I$(SRCDIR) -I$B\win\include -I$(ZINCDIR)

!ifdef FOSSIL_ENABLE_SSL
INCL      = $(INCL) -I$(SSLINCDIR)
!endif





CFLAGS    = -nologo
LDFLAGS   = /NODEFAULTLIB:msvcrt /MANIFEST:NO

!ifdef DEBUG
CFLAGS    = $(CFLAGS) -Zi -MTd -Od
LDFLAGS   = $(LDFLAGS) /DEBUG
!else
CFLAGS    = $(CFLAGS) -MT -O2
!endif

BCC       = $(CC) $(CFLAGS)
TCC       = $(CC) -c $(CFLAGS) $(MSCDEF) $(INCL)
RCC       = rc -D_WIN32 -D_MSC_VER $(MSCDEF) $(INCL)
LIBS      = $(ZLIB) ws2_32.lib advapi32.lib
LIBDIR    = -LIBPATH:$(ZLIBDIR)

!ifdef FOSSIL_ENABLE_JSON
TCC       = $(TCC) -DFOSSIL_ENABLE_JSON=1
RCC       = $(RCC) -DFOSSIL_ENABLE_JSON=1
!endif

!ifdef FOSSIL_ENABLE_SSL
TCC       = $(TCC) -DFOSSIL_ENABLE_SSL=1
RCC       = $(RCC) -DFOSSIL_ENABLE_SSL=1
LIBS      = $(LIBS) $(SSLLIB)
LIBDIR    = $(LIBDIR) -LIBPATH:$(SSLLIBDIR)











!endif

SQLITE_OPTIONS = /DSQLITE_OMIT_LOAD_EXTENSION=1 \
                 /DSQLITE_ENABLE_LOCKING_STYLE=0 \
                 /DSQLITE_THREADSAFE=0 \
                 /DSQLITE_DEFAULT_FILE_FORMAT=4 \
                 /DSQLITE_OMIT_DEPRECATED \
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
        json_report_.c \
        json_status_.c \
        json_tag_.c \
        json_timeline_.c \
        json_user_.c \
        json_wiki_.c \
        leaf_.c \
        loadctrl_.c \
        login_.c \
        lookslike_.c \
        main_.c \
        manifest_.c \
        markdown_.c \
        markdown_html_.c \
        md5_.c \







<







134
135
136
137
138
139
140

141
142
143
144
145
146
147
        json_report_.c \
        json_status_.c \
        json_tag_.c \
        json_timeline_.c \
        json_user_.c \
        json_wiki_.c \
        leaf_.c \

        login_.c \
        lookslike_.c \
        main_.c \
        manifest_.c \
        markdown_.c \
        markdown_html_.c \
        md5_.c \
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
        $(OX)\json_report$O \
        $(OX)\json_status$O \
        $(OX)\json_tag$O \
        $(OX)\json_timeline$O \
        $(OX)\json_user$O \
        $(OX)\json_wiki$O \
        $(OX)\leaf$O \
        $(OX)\loadctrl$O \
        $(OX)\login$O \
        $(OX)\lookslike$O \
        $(OX)\main$O \
        $(OX)\manifest$O \
        $(OX)\markdown$O \
        $(OX)\markdown_html$O \
        $(OX)\md5$O \







<







244
245
246
247
248
249
250

251
252
253
254
255
256
257
        $(OX)\json_report$O \
        $(OX)\json_status$O \
        $(OX)\json_tag$O \
        $(OX)\json_timeline$O \
        $(OX)\json_user$O \
        $(OX)\json_wiki$O \
        $(OX)\leaf$O \

        $(OX)\login$O \
        $(OX)\lookslike$O \
        $(OX)\main$O \
        $(OX)\manifest$O \
        $(OX)\markdown$O \
        $(OX)\markdown_html$O \
        $(OX)\md5$O \
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
        $(OX)\style$O \
        $(OX)\sync$O \
        $(OX)\tag$O \
        $(OX)\tar$O \
        $(OX)\th$O \
        $(OX)\th_lang$O \
        $(OX)\th_main$O \
        $(OX)\th_tcl$O \
        $(OX)\timeline$O \
        $(OX)\tkt$O \
        $(OX)\tktsetup$O \
        $(OX)\undo$O \
        $(OX)\unicode$O \
        $(OX)\update$O \
        $(OX)\url$O \







<







282
283
284
285
286
287
288

289
290
291
292
293
294
295
        $(OX)\style$O \
        $(OX)\sync$O \
        $(OX)\tag$O \
        $(OX)\tar$O \
        $(OX)\th$O \
        $(OX)\th_lang$O \
        $(OX)\th_main$O \

        $(OX)\timeline$O \
        $(OX)\tkt$O \
        $(OX)\tktsetup$O \
        $(OX)\undo$O \
        $(OX)\unicode$O \
        $(OX)\update$O \
        $(OX)\url$O \
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
        $(OX)\winhttp$O \
        $(OX)\wysiwyg$O \
        $(OX)\xfer$O \
        $(OX)\xfersetup$O \
        $(OX)\zip$O \
        $(OX)\fossil.res


APPNAME = $(OX)\fossil$(E)
PDBNAME = $(OX)\fossil$(P)

all: $(OX) $(APPNAME)

zlib:
	@echo Building zlib from "$(ZLIBDIR)"...
	@pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd

$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib
	cd $(OX) 
	link $(LDFLAGS) /OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts

$(OX)\linkopts: $B\win\Makefile.msc
	echo $(OX)\add.obj > $@
	echo $(OX)\allrepo.obj >> $@
	echo $(OX)\attach.obj >> $@
	echo $(OX)\bag.obj >> $@
	echo $(OX)\bisect.obj >> $@







<











|







304
305
306
307
308
309
310

311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
        $(OX)\winhttp$O \
        $(OX)\wysiwyg$O \
        $(OX)\xfer$O \
        $(OX)\xfersetup$O \
        $(OX)\zip$O \
        $(OX)\fossil.res


APPNAME = $(OX)\fossil$(E)
PDBNAME = $(OX)\fossil$(P)

all: $(OX) $(APPNAME)

zlib:
	@echo Building zlib from "$(ZLIBDIR)"...
	@pushd "$(ZLIBDIR)" && nmake /f win32\Makefile.msc $(ZLIB) && popd

$(APPNAME) : translate$E mkindex$E headers $(OBJ) $(OX)\linkopts zlib
	cd $(OX) 
	link $(LDFLAGS) -OUT:$@ $(LIBDIR) Wsetargv.obj fossil.res @linkopts

$(OX)\linkopts: $B\win\Makefile.msc
	echo $(OX)\add.obj > $@
	echo $(OX)\allrepo.obj >> $@
	echo $(OX)\attach.obj >> $@
	echo $(OX)\bag.obj >> $@
	echo $(OX)\bisect.obj >> $@
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
	echo $(OX)\json_report.obj >> $@
	echo $(OX)\json_status.obj >> $@
	echo $(OX)\json_tag.obj >> $@
	echo $(OX)\json_timeline.obj >> $@
	echo $(OX)\json_user.obj >> $@
	echo $(OX)\json_wiki.obj >> $@
	echo $(OX)\leaf.obj >> $@
	echo $(OX)\loadctrl.obj >> $@
	echo $(OX)\login.obj >> $@
	echo $(OX)\lookslike.obj >> $@
	echo $(OX)\main.obj >> $@
	echo $(OX)\manifest.obj >> $@
	echo $(OX)\markdown.obj >> $@
	echo $(OX)\markdown_html.obj >> $@
	echo $(OX)\md5.obj >> $@







<







373
374
375
376
377
378
379

380
381
382
383
384
385
386
	echo $(OX)\json_report.obj >> $@
	echo $(OX)\json_status.obj >> $@
	echo $(OX)\json_tag.obj >> $@
	echo $(OX)\json_timeline.obj >> $@
	echo $(OX)\json_user.obj >> $@
	echo $(OX)\json_wiki.obj >> $@
	echo $(OX)\leaf.obj >> $@

	echo $(OX)\login.obj >> $@
	echo $(OX)\lookslike.obj >> $@
	echo $(OX)\main.obj >> $@
	echo $(OX)\manifest.obj >> $@
	echo $(OX)\markdown.obj >> $@
	echo $(OX)\markdown_html.obj >> $@
	echo $(OX)\md5.obj >> $@
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
	echo $(OX)\style.obj >> $@
	echo $(OX)\sync.obj >> $@
	echo $(OX)\tag.obj >> $@
	echo $(OX)\tar.obj >> $@
	echo $(OX)\th.obj >> $@
	echo $(OX)\th_lang.obj >> $@
	echo $(OX)\th_main.obj >> $@
	echo $(OX)\th_tcl.obj >> $@
	echo $(OX)\timeline.obj >> $@
	echo $(OX)\tkt.obj >> $@
	echo $(OX)\tktsetup.obj >> $@
	echo $(OX)\undo.obj >> $@
	echo $(OX)\unicode.obj >> $@
	echo $(OX)\update.obj >> $@
	echo $(OX)\url.obj >> $@







<







411
412
413
414
415
416
417

418
419
420
421
422
423
424
	echo $(OX)\style.obj >> $@
	echo $(OX)\sync.obj >> $@
	echo $(OX)\tag.obj >> $@
	echo $(OX)\tar.obj >> $@
	echo $(OX)\th.obj >> $@
	echo $(OX)\th_lang.obj >> $@
	echo $(OX)\th_main.obj >> $@

	echo $(OX)\timeline.obj >> $@
	echo $(OX)\tkt.obj >> $@
	echo $(OX)\tktsetup.obj >> $@
	echo $(OX)\undo.obj >> $@
	echo $(OX)\unicode.obj >> $@
	echo $(OX)\update.obj >> $@
	echo $(OX)\url.obj >> $@
462
463
464
465
466
467
468



469
470
471
472
473
474
475
	echo $(OX)\winfile.obj >> $@
	echo $(OX)\winhttp.obj >> $@
	echo $(OX)\wysiwyg.obj >> $@
	echo $(OX)\xfer.obj >> $@
	echo $(OX)\xfersetup.obj >> $@
	echo $(OX)\zip.obj >> $@
	echo $(LIBS) >> $@




$(OX):
	@-mkdir $@

translate$E: $(SRCDIR)\translate.c
	$(BCC) $**








>
>
>







432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
	echo $(OX)\winfile.obj >> $@
	echo $(OX)\winhttp.obj >> $@
	echo $(OX)\wysiwyg.obj >> $@
	echo $(OX)\xfer.obj >> $@
	echo $(OX)\xfersetup.obj >> $@
	echo $(OX)\zip.obj >> $@
	echo $(LIBS) >> $@




$(OX):
	@-mkdir $@

translate$E: $(SRCDIR)\translate.c
	$(BCC) $**

490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510

$(OX)\th$O : $(SRCDIR)\th.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_lang$O : $(SRCDIR)\th_lang.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_tcl$O : $(SRCDIR)\th_tcl.c
	$(TCC) /Fo$@ -c $**

VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
	$** > $@
$(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c
	$(TCC) /Fo$@ /c $**

page_index.h: mkindex$E $(SRC) 
	$** > $@

clean:
	-del $(OX)\*.obj
	-del *.obj







<
<
<



|







463
464
465
466
467
468
469



470
471
472
473
474
475
476
477
478
479
480

$(OX)\th$O : $(SRCDIR)\th.c
	$(TCC) /Fo$@ -c $**

$(OX)\th_lang$O : $(SRCDIR)\th_lang.c
	$(TCC) /Fo$@ -c $**




VERSION.h : mkversion$E $B\manifest.uuid $B\manifest $B\VERSION
	$** > $@
$(OX)\cson_amalgamation$O : $(SRCDIR)\cson_amalgamation.c
	$(TCC) /Fo$@ -c $**

page_index.h: mkindex$E $(SRC) 
	$** > $@

clean:
	-del $(OX)\*.obj
	-del *.obj
535
536
537
538
539
540
541

542
543
544
545
546
547
548
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h


$(OX)\add$O : add_.c add.h
	$(TCC) /Fo$@ -c add_.c

add_.c : $(SRCDIR)\add.c
	translate$E $** > $@








>







505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
$(OBJDIR)\json_query$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_report$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_status$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_tag$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_timeline$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_user$O : $(SRCDIR)\json_detail.h
$(OBJDIR)\json_wiki$O : $(SRCDIR)\json_detail.h


$(OX)\add$O : add_.c add.h
	$(TCC) /Fo$@ -c add_.c

add_.c : $(SRCDIR)\add.c
	translate$E $** > $@

860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879

$(OX)\leaf$O : leaf_.c leaf.h
	$(TCC) /Fo$@ -c leaf_.c

leaf_.c : $(SRCDIR)\leaf.c
	translate$E $** > $@

$(OX)\loadctrl$O : loadctrl_.c loadctrl.h
	$(TCC) /Fo$@ -c loadctrl_.c

loadctrl_.c : $(SRCDIR)\loadctrl.c
	translate$E $** > $@

$(OX)\login$O : login_.c login.h
	$(TCC) /Fo$@ -c login_.c

login_.c : $(SRCDIR)\login.c
	translate$E $** > $@

$(OX)\lookslike$O : lookslike_.c lookslike.h







<
<
<
<
<
<







831
832
833
834
835
836
837






838
839
840
841
842
843
844

$(OX)\leaf$O : leaf_.c leaf.h
	$(TCC) /Fo$@ -c leaf_.c

leaf_.c : $(SRCDIR)\leaf.c
	translate$E $** > $@







$(OX)\login$O : login_.c login.h
	$(TCC) /Fo$@ -c login_.c

login_.c : $(SRCDIR)\login.c
	translate$E $** > $@

$(OX)\lookslike$O : lookslike_.c lookslike.h
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
$(OX)\zip$O : zip_.c zip.h
	$(TCC) /Fo$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	translate$E $** > $@

fossil.res : $B\win\fossil.rc
	$(RCC)  /fo $@ $**

headers: makeheaders$E page_index.h VERSION.h
	makeheaders$E add_.c:add.h \
			allrepo_.c:allrepo.h \
			attach_.c:attach.h \
			bag_.c:bag.h \
			bisect_.c:bisect.h \
			blob_.c:blob.h \







|
<







1156
1157
1158
1159
1160
1161
1162
1163

1164
1165
1166
1167
1168
1169
1170
$(OX)\zip$O : zip_.c zip.h
	$(TCC) /Fo$@ -c zip_.c

zip_.c : $(SRCDIR)\zip.c
	translate$E $** > $@

fossil.res : $B\win\fossil.rc
	$(RCC)  -fo $@ $**

headers: makeheaders$E page_index.h VERSION.h
	makeheaders$E add_.c:add.h \
			allrepo_.c:allrepo.h \
			attach_.c:attach.h \
			bag_.c:bag.h \
			bisect_.c:bisect.h \
			blob_.c:blob.h \
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
			json_report_.c:json_report.h \
			json_status_.c:json_status.h \
			json_tag_.c:json_tag.h \
			json_timeline_.c:json_timeline.h \
			json_user_.c:json_user.h \
			json_wiki_.c:json_wiki.h \
			leaf_.c:leaf.h \
			loadctrl_.c:loadctrl.h \
			login_.c:login.h \
			lookslike_.c:lookslike.h \
			main_.c:main.h \
			manifest_.c:manifest.h \
			markdown_.c:markdown.h \
			markdown_html_.c:markdown_html.h \
			md5_.c:md5.h \







<







1212
1213
1214
1215
1216
1217
1218

1219
1220
1221
1222
1223
1224
1225
			json_report_.c:json_report.h \
			json_status_.c:json_status.h \
			json_tag_.c:json_tag.h \
			json_timeline_.c:json_timeline.h \
			json_user_.c:json_user.h \
			json_wiki_.c:json_wiki.h \
			leaf_.c:leaf.h \

			login_.c:login.h \
			lookslike_.c:lookslike.h \
			main_.c:main.h \
			manifest_.c:manifest.h \
			markdown_.c:markdown.h \
			markdown_html_.c:markdown_html.h \
			md5_.c:md5.h \

Changes to www/changes.wiki.

1
2
3
4
5
6
7
8
9
10
11


12
13
14
15
16
17
18
19


20
21








22



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<title>Change Log</title>

<h2>Changes For Version 1.29 (as yet unreleased)</h2>
  *  Add the ability to display content, diffs and annotations for UTF16
     text files in the web interface.
  *  Add the "SaveAs..." button to the graphical diff display that results
     from using the --tk option with the [/help/diff | fossil diff] command.
  *  Honor timezones in imports from git.
  *  The [/reports] page now requires Read ("o") permissions. The "byweek"
     report now properly propagates the selected year through the event type
     filter links.


  *  The [/help/info | info command] now shows leaf status of the checkout.
  *  Add support for tunneling https through a http proxy (Ticket [e854101c4f]).
  *  Add option --empty to the "[/help?cmd=open | fossil open]" command.
  *  Enhanced [/help?cmd=/fileage|the fileage page] to support a glob parameter.
  *  Add -w|--ignore-all-space and -Z|--ignore-trailing-space options to
     [/help?cmd=annotate|fossil annotate], [/help?cmd=blame|fossil blame],
     [/help?cmd=diff|fossil (g)diff], [/help?cmd=stash|fossil stash diff].
  *  Add --strip-trailing-cr option to [/help?cmd=diff|fossil (g)diff] and


     [/help?cmd=stash|fossil stash diff].
  *  Add button "Ignore Whitespace" to /annotate, /blame, /ci, /fdiff








     and /vdiff UI pages.




<h2>Changes For Version 1.28 (2014-01-27)</h2>
  *  Enhance [/help?cmd=/reports | /reports] to support event type filtering.
  *  When cloning a repository, the user name passed via the URL (if any)
     is now used as the default local admin user's name.
  *  Enhance the SSH transport mechanism so that it runs a single instance of
     the "fossil" executable on the remote side, obviating the need for a shell
     on the remote side.  Some users may need to add the "?fossil=/path/to/fossil"
     query parameter to "ssh:" URIs if their fossil binary is not in a standard
     place.
  *  Add the "[/help?cmd=blame | fossil blame]" command that works just like
     "fossil annotate" but uses a different output format that includes the
     user who made each changes and omits line numbers.
  *  Add the "Tarball and ZIP-archive Prefix" configuration parameter under
     Admin/Configuration.
  *  Fix CGI processing so that it works on web servers that do not
     supply REQUEST_URI.
  *  Add options --dirsonly, --emptydirs, and --allckouts to the
     "[/help?cmd=clean | fossil clean]" command.
  *  Ten-fold performance improvement in large "fossil blame" or
     "fossil annotate" commands.
  *  Add option -W|--width and --offset to "[/help?cmd=timeline | fossil timeline]"
     and  "[/help?cmd=finfo | fossil finfo]" commands.
  *  Option -n|--limit of "[/help?cmd=timeline | fossil timeline]" now
     specifies the number of entries, just like all other commands which
     have the -n|--limit option. The various timeline-related functions
     now output "--- ?? limit (??) reached ---" at the end whenever
     appropriate. Use "-n 0" if no limit is desired.
  *  Fix handling of password embedded in Fossil URL.
  *  New <tt>--once</tt> option to [/help?cmd=clone | fossil clone] command
     which does not store the URL or password when cloning.
  *  Modify [/help?cmd=ui | fossil ui] to respect "default user" in an open
     repository.
  *  Fossil now hides check-ins that have the "hidden" tag in timeline webpages.
  *  Enhance <tt>/ci_edit</tt> page to add the "hidden" tag to check-ins.
  *  Advanced possibilities for commit and ticket change notifications over
     http using TH1 scripting.
  *  Add --sha1sum and --integrate options
     to the "[/help?cmd=commit | fossil commit]" command.
  *  Add the "clean" and "extra" subcommands to the
     "[/help?cmd=all | fossil all]" command
  *  Add the --whatif option to "[/help?cmd=clean|fossil clean]" that works the
     same as "--dry-run",
     so that the name does not collide with the --dry-run option of "fossil all".
  *  Provide a configuration option to show dates on the web timeline
     as "YYMMMDD HH:MM"
  *  Add an option to the "stats" webpage that allows an administrator to see
     the current repository schema.
  *  Enhancements to the "[/help?cmd=/vdiff|/vdiff]" webpage for more difference
     display options.
  *  Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative
     to "/dir" and make it the default way of showing file lists.
  *  Send gzipped HTTP responses to clients that support it.

<h2>Changes For Version 1.27 (2013-09-11)</h2>
  *  Enhance the [/help?cmd=changes | fossil changes],


|
|
|
|
|
|
<
<
|
>
>
|
|
|
|
|
|
|
|
>
>
|
<
>
>
>
>
>
>
>
>
|
>
>
>



















|









|







|

|








|







1
2
3
4
5
6
7
8


9
10
11
12
13
14
15
16
17
18
19
20
21
22

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
<title>Change Log</title>

<h2>Changes For Version 1.28.1 (most likely never being released, those
are candidate cherry-picks just in case)</h2>
  *  Upgraded internal SQLite to version 3.8.4.3, which adds support for
     CTE and the WITH clause, and fixes various (minor) SQLite bugs.
  *  Relax minimum SQLite version check to 3.7.17, as Fossil 1.28
     actually works fine with this version and higher, when configured


     with --disable-internal-sqlite.
     See: [https://www.mail-archive.com/fossil-users@lists.fossil-scm.org/msg14520.html]
  *  [252aff3e62]: Make "fossil diff --tk" work out-of-the-box on Windows
     when Activestate Tcl is installed.
  *  [d5d7e640d0]: After successful "login" return to web-page immediately
     before logging in.
  *  [cec85224c1]: Make 'th1-setup' a textarea in settings UI.
  *  [d752140c7a]: RECONSTRUCT failure.
  *  [d8a588ba76]: Fix checking for "clearsign" option in "fossil branch"
     command.
  *  [1c39f113d1]: Crash on STASH DIFF command.
  *  [b4dffdac5e]: Avoid unnecessary no-op write transactions on the server
     during a pull.
  *  [3fbdaa243d]: Speedup "fossil extras" and other commands which traverse

     the local filesystem.
  *  [684eb478e7]: Fix the SCGI processing so that it works with Nginx.
  *  [ee1aa460a4]: Fix using the unary bitwise NOT operator in TH1
  *  [f2ebd7e52d]: Make use of a recursive query capability (if available) to
     replace the compute_ancestors() function with a single query.
  *  [a138dc97fc]: Fix a potential segfault when the SSH_CONNECTION environment
     variable is defined.
  *  [bfdabaecc8]: Fix the EXPLAIN indenter in the command-line shell to
     correctly handle NextIfOpen and PrevIfOpen opcodes.
  *  [http://www.sqlite.org/src/info/9d2ae6342c|9d2ae6342c]: In the command-line
     shell, run set writable_schema before running the ".clone" command.
  *  [fc6bb93689]: Add the "httpize" TH1 command.

<h2>Changes For Version 1.28 (2014-01-27)</h2>
  *  Enhance [/help?cmd=/reports | /reports] to support event type filtering.
  *  When cloning a repository, the user name passed via the URL (if any)
     is now used as the default local admin user's name.
  *  Enhance the SSH transport mechanism so that it runs a single instance of
     the "fossil" executable on the remote side, obviating the need for a shell
     on the remote side.  Some users may need to add the "?fossil=/path/to/fossil"
     query parameter to "ssh:" URIs if their fossil binary is not in a standard
     place.
  *  Add the "[/help?cmd=blame | fossil blame]" command that works just like
     "fossil annotate" but uses a different output format that includes the
     user who made each changes and omits line numbers.
  *  Add the "Tarball and ZIP-archive Prefix" configuration parameter under
     Admin/Configuration.
  *  Fix CGI processing so that it works on web servers that do not
     supply REQUEST_URI.
  *  Add options --dirsonly, --emptydirs, and --allckouts to the
     "[/help?cmd=clean | fossil clean]" command.
  *  Ten-fold performance improvement in large "fossil blame" or 
     "fossil annotate" commands.
  *  Add option -W|--width and --offset to "[/help?cmd=timeline | fossil timeline]"
     and  "[/help?cmd=finfo | fossil finfo]" commands.
  *  Option -n|--limit of "[/help?cmd=timeline | fossil timeline]" now
     specifies the number of entries, just like all other commands which
     have the -n|--limit option. The various timeline-related functions
     now output "--- ?? limit (??) reached ---" at the end whenever
     appropriate. Use "-n 0" if no limit is desired.
  *  Fix handling of password embedded in Fossil URL.
  *  New <tt>--once</tt> option to [/help?cmd=clone | fossil clone] command 
     which does not store the URL or password when cloning.
  *  Modify [/help?cmd=ui | fossil ui] to respect "default user" in an open
     repository.
  *  Fossil now hides check-ins that have the "hidden" tag in timeline webpages.
  *  Enhance <tt>/ci_edit</tt> page to add the "hidden" tag to check-ins.
  *  Advanced possibilities for commit and ticket change notifications over
     http using TH1 scripting.
  *  Add --sha1sum and --integrate options 
     to the "[/help?cmd=commit | fossil commit]" command.
  *  Add the "clean" and "extra" subcommands to the 
     "[/help?cmd=all | fossil all]" command
  *  Add the --whatif option to "[/help?cmd=clean|fossil clean]" that works the
     same as "--dry-run",
     so that the name does not collide with the --dry-run option of "fossil all".
  *  Provide a configuration option to show dates on the web timeline
     as "YYMMMDD HH:MM"
  *  Add an option to the "stats" webpage that allows an administrator to see
     the current repository schema.
  *  Enhancements to the "[/help?cmd=/vdiff|/vdiff]" webpage for more difference 
     display options.
  *  Added the "[/tree?ci=trunk&expand | /tree]" webpage as an alternative
     to "/dir" and make it the default way of showing file lists.
  *  Send gzipped HTTP responses to clients that support it.

<h2>Changes For Version 1.27 (2013-09-11)</h2>
  *  Enhance the [/help?cmd=changes | fossil changes],
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
     letter everywhere. The default value of the "case-sensitive" setting is
     now FALSE, except when case-sensitivity is enabled in the Windows kernel.
     See
     [http://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-casesensitive]
  *  Enhancements to /timeline.rss, adding more flags for filtering
     results, including the ability to subscribe to changes made
     to individual tickets. For example: [/timeline.rss?y=t&tkt=12fceeec82].
  *  Improved handling of the differences between case-sensitive and
     case-insensitive filesystems.
  *  JSON API: added the 'status' command to report local checkout status.
  *  Fixes to the <tt>--args</tt> support and documented this feature in the help.
  *  Added [/stats_report] page.
  *  Added <tt>ym=YYYY-MM</tt> filter to the [/timeline?ym=2013-06].
  *  Fixed: <tt>config reset</tt> now re-installs default ticket report format.
  *  <tt>ssh://</tt> and <tt>file://</tt> protocols now ignore proxy settings.
  *  Added [/hash-color-test] web page.
  *  Cherry-pick merges are recorded internally (though no yet displayed on the
     timeline graph.)
  *  Bring in the latest versions of SQLite, zlib, and autosetup from upstream.


<h2>Changes For Version 1.25 (2013-02-16)</h2>
  *  Enhancements to ticket processing. There are now two tables: TICKET and
     TICKETCHNG. There is one row in TICKETCHNG for each ticket artifact.
     Fields from ticket artifacts go into either or both of TICKET and
     TICKETCHNG, whichever contain matching column names. Default ticket
     edit and viewing scripts are updated to use TICKETCHNG. The TH1
     scripting language is enhanced to support this, including the new
     "query" command for doing SQL queries against the repository database.
     All changes should be backwards compatible.
  *  Add the ability to moderate ticket and wiki changes.  Unmoderated changes
     do not sync and may be deleted by the moderator if found to contain spam
     or other objectionable content.
  *  Add javascript so that clicking on a node of the timeline graph selects
     that node.  Then clicking on a second node shows a diff between the
     two nodes.  Clicking on the selected node unselects it.
  *  Warn of unresolved merge conflicts in "fossil status" and disallow
     commits of unresolved conflicts unless the --allow-conflict option
     is used.
  *  Add javascript so that clicking on column headers in a ticket report
     sorts by the indicated column.
  *  Add the "fossil cat" command which is basically an alias for
     "fossil finfo -p".
  *  Hyperlinks with the class "button" are rendered as submenu buttons
     on embedded documentation.
  *  The check-in comment editor on windows now defaults to NotePad.exe.
  *  Correctly deal with BOMs in check-in comments.  Also attempt to convert
     check-in comments to UTF8 from other encodings.
  *  Allow the deletion of multiple stash entries using multiple arguments
     to the "fossil stash rm" command.
  *  Enhance the "fossil server DIRECTORY" command to serve static content
     files contained in DIRECTORY.  For security, only files with a
     recognized suffix (such as *.html, *.jpg, *.txt, etc) will be delivered
     as static content, and *.fossil files are not on the list of recognized
     suffixes.  There are additional restrictions on the names of the files.
  *  Allow the "fossil ui" command to specify a directory as long as the
     the --notfound option is used.
  *  Add a configuration option that causes timeline messages to be rendered
     as text/x-fossil-plain (which is the same as text/plain except that







|

















|



|
















|




|







139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
     letter everywhere. The default value of the "case-sensitive" setting is
     now FALSE, except when case-sensitivity is enabled in the Windows kernel.
     See
     [http://cygwin.com/cygwin-ug-net/using-specialnames.html#pathnames-casesensitive]
  *  Enhancements to /timeline.rss, adding more flags for filtering
     results, including the ability to subscribe to changes made
     to individual tickets. For example: [/timeline.rss?y=t&tkt=12fceeec82].
  *  Improved handling of the differences between case-sensitive and 
     case-insensitive filesystems.
  *  JSON API: added the 'status' command to report local checkout status.
  *  Fixes to the <tt>--args</tt> support and documented this feature in the help.
  *  Added [/stats_report] page.
  *  Added <tt>ym=YYYY-MM</tt> filter to the [/timeline?ym=2013-06].
  *  Fixed: <tt>config reset</tt> now re-installs default ticket report format.
  *  <tt>ssh://</tt> and <tt>file://</tt> protocols now ignore proxy settings.
  *  Added [/hash-color-test] web page.
  *  Cherry-pick merges are recorded internally (though no yet displayed on the
     timeline graph.)
  *  Bring in the latest versions of SQLite, zlib, and autosetup from upstream.


<h2>Changes For Version 1.25 (2013-02-16)</h2>
  *  Enhancements to ticket processing. There are now two tables: TICKET and
     TICKETCHNG. There is one row in TICKETCHNG for each ticket artifact.
     Fields from ticket artifacts go into either or both of TICKET and
     TICKETCHNG, whichever contain matching column names. Default ticket 
     edit and viewing scripts are updated to use TICKETCHNG. The TH1
     scripting language is enhanced to support this, including the new
     "query" command for doing SQL queries against the repository database.
     All changes should be backwards compatible. 
  *  Add the ability to moderate ticket and wiki changes.  Unmoderated changes
     do not sync and may be deleted by the moderator if found to contain spam
     or other objectionable content.
  *  Add javascript so that clicking on a node of the timeline graph selects
     that node.  Then clicking on a second node shows a diff between the
     two nodes.  Clicking on the selected node unselects it.
  *  Warn of unresolved merge conflicts in "fossil status" and disallow
     commits of unresolved conflicts unless the --allow-conflict option
     is used.
  *  Add javascript so that clicking on column headers in a ticket report
     sorts by the indicated column.
  *  Add the "fossil cat" command which is basically an alias for
     "fossil finfo -p".
  *  Hyperlinks with the class "button" are rendered as submenu buttons
     on embedded documentation.
  *  The check-in comment editor on windows now defaults to NotePad.exe.
  *  Correctly deal with BOMs in check-in comments.  Also attempt to convert 
     check-in comments to UTF8 from other encodings.
  *  Allow the deletion of multiple stash entries using multiple arguments
     to the "fossil stash rm" command.
  *  Enhance the "fossil server DIRECTORY" command to serve static content
     files contained in DIRECTORY.  For security, only files with a 
     recognized suffix (such as *.html, *.jpg, *.txt, etc) will be delivered
     as static content, and *.fossil files are not on the list of recognized
     suffixes.  There are additional restrictions on the names of the files.
  *  Allow the "fossil ui" command to specify a directory as long as the
     the --notfound option is used.
  *  Add a configuration option that causes timeline messages to be rendered
     as text/x-fossil-plain (which is the same as text/plain except that
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
     dry-run merge.  Display an improved merge-summary message at the end of
     the merge.
  *  Add options to "fossil commit" to override the various sanity checks.
     Options added: --allow-empty, --allow-fork, --allow-older, and
     --allow-conflict.
  *  Optionally require a CAPTCHA (controlled by a setting on the
     Admin/Access webpage) when a user who is not logged in tries to
     edit wiki, or a ticket, or an attachment.
  *  Improvements to the "ssh://" sync protocol, to help it move past
     noisey motd comments.
  *  Add the uf=FILE-SHA1-HASH query parameter to the timeline, causing the
     timeline to show only check-ins that contain the specific file identified
     by FILE-SHA1-HASH.  ("uf" stands for "uses file".)
  *  Enhance the file change annotator so that it follows the file across
     name changes.







|







210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
     dry-run merge.  Display an improved merge-summary message at the end of
     the merge.
  *  Add options to "fossil commit" to override the various sanity checks.
     Options added: --allow-empty, --allow-fork, --allow-older, and
     --allow-conflict.
  *  Optionally require a CAPTCHA (controlled by a setting on the
     Admin/Access webpage) when a user who is not logged in tries to
     edit wiki, or a ticket, or an attachment. 
  *  Improvements to the "ssh://" sync protocol, to help it move past
     noisey motd comments.
  *  Add the uf=FILE-SHA1-HASH query parameter to the timeline, causing the
     timeline to show only check-ins that contain the specific file identified
     by FILE-SHA1-HASH.  ("uf" stands for "uses file".)
  *  Enhance the file change annotator so that it follows the file across
     name changes.
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
  *  Added the "fossil all changes" command
  *  Added the --ckout option to the "fossil all list" command
  *  Added the "public-pages" glob pattern that can be configured to allow
     anonymous users to see embedded documentation on sites where source
     code should not be accessible to anonymous users.
  *  Allow multiple --tag options on the same "fossil commit" command.
  *  Change the meaning of the --bgcolor option for "fossil commit" to only
     change the color for that one commit.  The new --branchcolor option
     is available to set a persistent background color.
  *  Add the branch= query parameter to the vdiff page and the --branch option
     to the "fossil diff" command.
  *  Check-in names of the form "root:BRANCH" now refer to the origin of
     the branch.  Hence to see all changes in a branch, use
     "fossil diff --from root:BRANCH --to BRANCH".  The --branch option on
     the diff command is an alias for the same.
  *  Add the ability to configure ad-units to be displayed between the menu
     bar and the content.
  *  Add the ability to set a background image as part of server configuration.
  *  Allow partial commits of cherrypick merges.
  *  Updates against an uncommitted merge are now a warning, not a fatal error.
  *  Prompt the user to continue if a check-in comment is unedited.
  *  Fixes to case sensitivity settings with the /dir webpage.
  *  Repositories now try to remember the locations of all checkouts and
     web-access URLs and display this information with the
     "fossil info $REPO" command.
  *  Improved defense against spiders:  The src= attribute of
     &lt;a&gt; elements is set using javascript after the page loads.
  *  Enhanced formatting of the user list page.
  *  If a file named in "fossil add" is missing, that is now a warning instead
     of a fatal error.
  *  Fix side-by-side diff so that it displays correctly with
     multi-byte UTF8 characters.
  *  Performance improvements in the diff logic.
  *  Other performance tweaks and documentation updates.

<h2>Changes For Version 1.22 (2012-03-17)</h2>
  *  Greatly improved "diff" processing including the new --brief option,
     partial line matching, colorized in-line diffs, and better performance.
  *  Promote "allow-symlinks" to a versionable setting
  *  Harden the CGI processing logic against DOS attacks
  *  Add the ability to run TH1 scripts after sync requests
  *  Store the repository name in _FOSSIL_ as it is type in the "open" command,
     possibly as a relative pathname.
  *  Make ".fslckout" the alternative name for the "_FOSSIL_" file.
  *  Change the "ssh:" transfer method to allow all access regardless of
     user permission.
  *  Improvements to the timeline messages associated with tag changes.
     (Requires a "[/help/rebuild | fossil rebuild]" to take effect.)
  *  Various additions and fixes for the JSON API.
  *  Improved merge-with-rename handling.
  *  --cherrypick merges use their origin's commit message by default.
  *  Added support for multiple concurrent logins per user.
  *  Update to use SQLite version 3.7.11.
  *  Various minor bug fixes.

<h2>Changes For Version 1.21 (2011-12-13)</h2>
  *  Added side-by-side diffs in the command-line interface
  *  Automatically enable hyperlinks if the UserAgent string in the
     HTTP header suggests that the requestor is a human and not a bot.
  *  Show only commonly used commands with "fossil help".  Use
     "fossil help --all" to see the complete list now.
  *  Improvements to the "stash" command:  (1) Stash all files, not just
     those below the working directory. (2) Add the --detail option to
     "list". (3) Confirm before "drop --all". (4) Add the "help"
     subcommand.
  *  Add an Admin/Access setting to change the number of octets of the
     IP address that are saved in login cookies - allowing this setting
     to be changed to zero
  *  Promote the "test-md5sum" command to "md5sum".
  *  Added the "whatis" command.
  *  Stop showing the server-code in status outputs - it is no longer used







|




|










|






|












|













|



|
|
|







323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
  *  Added the "fossil all changes" command
  *  Added the --ckout option to the "fossil all list" command
  *  Added the "public-pages" glob pattern that can be configured to allow
     anonymous users to see embedded documentation on sites where source
     code should not be accessible to anonymous users.
  *  Allow multiple --tag options on the same "fossil commit" command.
  *  Change the meaning of the --bgcolor option for "fossil commit" to only
     change the color for that one commit.  The new --branchcolor option 
     is available to set a persistent background color.
  *  Add the branch= query parameter to the vdiff page and the --branch option
     to the "fossil diff" command.
  *  Check-in names of the form "root:BRANCH" now refer to the origin of
     the branch.  Hence to see all changes in a branch, use 
     "fossil diff --from root:BRANCH --to BRANCH".  The --branch option on
     the diff command is an alias for the same.
  *  Add the ability to configure ad-units to be displayed between the menu
     bar and the content.
  *  Add the ability to set a background image as part of server configuration.
  *  Allow partial commits of cherrypick merges.
  *  Updates against an uncommitted merge are now a warning, not a fatal error.
  *  Prompt the user to continue if a check-in comment is unedited.
  *  Fixes to case sensitivity settings with the /dir webpage.
  *  Repositories now try to remember the locations of all checkouts and
     web-access URLs and display this information with the 
     "fossil info $REPO" command.
  *  Improved defense against spiders:  The src= attribute of
     &lt;a&gt; elements is set using javascript after the page loads.
  *  Enhanced formatting of the user list page.
  *  If a file named in "fossil add" is missing, that is now a warning instead
     of a fatal error.
  *  Fix side-by-side diff so that it displays correctly with 
     multi-byte UTF8 characters.
  *  Performance improvements in the diff logic.
  *  Other performance tweaks and documentation updates.

<h2>Changes For Version 1.22 (2012-03-17)</h2>
  *  Greatly improved "diff" processing including the new --brief option,
     partial line matching, colorized in-line diffs, and better performance.
  *  Promote "allow-symlinks" to a versionable setting
  *  Harden the CGI processing logic against DOS attacks
  *  Add the ability to run TH1 scripts after sync requests
  *  Store the repository name in _FOSSIL_ as it is type in the "open" command,
     possibly as a relative pathname.
  *  Make ".fslckout" the alternative name for the "_FOSSIL_" file. 
  *  Change the "ssh:" transfer method to allow all access regardless of
     user permission.
  *  Improvements to the timeline messages associated with tag changes.
     (Requires a "[/help/rebuild | fossil rebuild]" to take effect.)
  *  Various additions and fixes for the JSON API.
  *  Improved merge-with-rename handling.
  *  --cherrypick merges use their origin's commit message by default.
  *  Added support for multiple concurrent logins per user.
  *  Update to use SQLite version 3.7.11.
  *  Various minor bug fixes.

<h2>Changes For Version 1.21 (2011-12-13)</h2>
  *  Added side-by-side diffs in the command-line interface
  *  Automatically enable hyperlinks if the UserAgent string in the 
     HTTP header suggests that the requestor is a human and not a bot.
  *  Show only commonly used commands with "fossil help".  Use
     "fossil help --all" to see the complete list now.
  *  Improvements to the "stash" command:  (1) Stash all files, not just 
     those below the working directory. (2) Add the --detail option to 
     "list". (3) Confirm before "drop --all". (4) Add the "help" 
     subcommand.
  *  Add an Admin/Access setting to change the number of octets of the
     IP address that are saved in login cookies - allowing this setting
     to be changed to zero
  *  Promote the "test-md5sum" command to "md5sum".
  *  Added the "whatis" command.
  *  Stop showing the server-code in status outputs - it is no longer used
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419

<h2>Changes For Version 1.20 (2011-10-21)</h2>
  *  Added side-by-side diffs in HTML interface. [0bde74ea1e]
  *  Added support for symlinks. (Controlled by "allow-symlinks" setting,
     off by default). [e4f1c1fe95]
  *  Fixed CLI annotate to show the proper file version in case there
     are multiple equal versions in history. [e161670939]
  *  Timeline now shows tag changes (requires rebuild).[87540ed6e6]
  *  Fixed annotate to show "more relevant" versions of lines in
     some cases. [e161670939]
  *  New command: ticket history. [98a855c508]
  *  Disabled SSLv2 in HTTPS client.[ea1d369d23]
  *  Fixed constant prompting regarding previously-saved SSL
     certificates. [636804745b]
  *  Other SSL improvements.
  *  Added -R REPOFILE support to several more CLI commands. [e080560378]
  *  Generated tarballs now have constant timestamps, so they are
     always identical for any given checkin. [e080560378]
  *  A number of minor HTML-related tweaks and fixes.
  *  Added --args FILENAME global CLI argument to import arbitrary
     CLI arguments from a file (e.g. long file lists). [e080560378]
  *  Fixed significant memory leak in annotation of files with long
     histories.[9929bab702]
  *  Added warnings when a merge operation overwrites local copies
     (UNDO is available, but previously this condition normally went
     silently unnoticed). [39f979b08c]
  *  Improved performance when adding many files. [a369dc7721]
  *  Improve merges which contain many file renames. [0b93b0f958]
  *  Added protection against timing attacks. [d4a341b49d]
  *  Firefox now remembers filled fields when returning to forms. [3fac77d7b0]







|














|







402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431

<h2>Changes For Version 1.20 (2011-10-21)</h2>
  *  Added side-by-side diffs in HTML interface. [0bde74ea1e]
  *  Added support for symlinks. (Controlled by "allow-symlinks" setting,
     off by default). [e4f1c1fe95]
  *  Fixed CLI annotate to show the proper file version in case there
     are multiple equal versions in history. [e161670939]
  *  Timeline now shows tag changes (requires rebuild).[87540ed6e6]  
  *  Fixed annotate to show "more relevant" versions of lines in
     some cases. [e161670939]
  *  New command: ticket history. [98a855c508]
  *  Disabled SSLv2 in HTTPS client.[ea1d369d23]
  *  Fixed constant prompting regarding previously-saved SSL
     certificates. [636804745b]
  *  Other SSL improvements.
  *  Added -R REPOFILE support to several more CLI commands. [e080560378]
  *  Generated tarballs now have constant timestamps, so they are
     always identical for any given checkin. [e080560378]
  *  A number of minor HTML-related tweaks and fixes.
  *  Added --args FILENAME global CLI argument to import arbitrary
     CLI arguments from a file (e.g. long file lists). [e080560378]
  *  Fixed significant memory leak in annotation of files with long
     histories.[9929bab702] 
  *  Added warnings when a merge operation overwrites local copies
     (UNDO is available, but previously this condition normally went
     silently unnoticed). [39f979b08c]
  *  Improved performance when adding many files. [a369dc7721]
  *  Improve merges which contain many file renames. [0b93b0f958]
  *  Added protection against timing attacks. [d4a341b49d]
  *  Firefox now remembers filled fields when returning to forms. [3fac77d7b0]

Changes to www/fileformat.wiki.

136
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160

161
162
163
164
165
166
167
168
169
170

<blockquote>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i><br>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i><b>.</b><i>SSS</i>
</blockquote>

A manifest has zero or more F-cards.  Each F-card identifies a file
that is part of the check-in.  There are one, two, three, or four

arguments.  The first argument is the pathname of the file in the
check-in relative to the root of the project file hierarchy.  No ".."
or "." directories are allowed within the filename.  Space characters
are escaped as in C-card comment text.  Backslash characters and
newlines are not allowed within filenames.  The directory separator
character is a forward slash (ASCII 0x2F).  The second argument to the
F-card is the full 40-character lower-case hexadecimal SHA1 hash of
the content artifact.  The second argument is required for baseline
manifests but is optional for delta manifests.  When the second
argument to the F-card is omitted, it means that the file has been
deleted relative to the baseline (files removed in baseline manifests
versions are <em>not</em> added as F-cards). The optional 3rd argument
defines any special access permissions associated with the file.  This
can be defined as "x" to mean that the file is executable or "l"
(small letter ell) to mean a symlink.  All files are always readable
and writable.  This can be expressed by "w" permission if desired but
is optional.  The file format might be extended with new permission

letters in the future.  The optional 4th argument is the name of the
same file as it existed in the parent check-in.  If the name of the
file is unchanged from its parent, then the 4th argument is omitted.

A manifest has zero or one N-cards.  The N-card specifies the mimetype for the
text in the comment of the C-card.  If the N-card is omitted, a default mimetype
is used.

A manifest has zero or one P-cards.  Most manifests have one P-card.
The P-card has a varying number of arguments that







|
>
|
|
|
|
|
|
|
|
|
|
<
|
|
|
<
|
|
>
|
|
|







136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154

155
156
157

158
159
160
161
162
163
164
165
166
167
168
169
170

<blockquote>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i><br>
<i>YYYY</i><b>-</b><i>MM</i><b>-</b><i>DD</i><b>T</b><i>HH</i><b>:</b><i>MM</i><b>:</b><i>SS</i><b>.</b><i>SSS</i>
</blockquote>

A manifest has zero or more F-cards.  Each F-card identifies a file
that is part of the check-in.  There are one, two, three, or four arguments.
The first argument
is the pathname of the file in the check-in relative to the root
of the project file hierarchy.  No ".." or "." directories are allowed
within the filename.  Space characters are escaped as in C-card
comment text.  Backslash characters and newlines are not allowed
within filenames.  The directory separator character is a forward
slash (ASCII 0x2F).  The second argument to the F-card is the
full 40-character lower-case hexadecimal SHA1 hash of the content
artifact.  The second argument is required for baseline manifests
but is optional for delta manifests.  When the second argument to the
F-card is omitted, it means that the file has been deleted relative

to the baseline.  The optional 3rd argument defines any special access 
permissions associated with the file.  The only special code currently
defined is "x" which means that the file is executable.  All files are

always readable and writable.  This can be expressed by "w" permission
if desired but is optional.  The file format might be extended with
new permission letters in the future.
The optional 4th argument is the name of the same file as it existed in
the parent check-in.  If the name of the file is unchanged from its
parent, then the 4th argument is omitted.

A manifest has zero or one N-cards.  The N-card specifies the mimetype for the
text in the comment of the C-card.  If the N-card is omitted, a default mimetype
is used.

A manifest has zero or one P-cards.  Most manifests have one P-card.
The P-card has a varying number of arguments that

Changes to www/index.wiki.

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<li> [./build.wiki | Install]
<li> [../COPYRIGHT-BSD2.txt | License]
<li> [/timeline | Recent changes]
<li> [./faq.wiki | FAQ]
<li> [./hacker-howto.wiki | Hacker How-To]
<li> [./changes.wiki | Change Log]
<li> [./hints.wiki | Tip &amp; Hints]
<li> [./permutedindex.wiki | Documentation Index]
<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
<li> Mailing list
     <ul>
     <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up]
     <li> [http://www.mail-archive.com/fossil-users@lists.fossil-scm.org | archives]
     <ul>
</ul>







|







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<li> [./build.wiki | Install]
<li> [../COPYRIGHT-BSD2.txt | License]
<li> [/timeline | Recent changes]
<li> [./faq.wiki | FAQ]
<li> [./hacker-howto.wiki | Hacker How-To]
<li> [./changes.wiki | Change Log]
<li> [./hints.wiki | Tip &amp; Hints]
<li> [./permutedindex.wiki#pindex | Documentation Index]
<li> [http://www.fossil-scm.org/schimpf-book/home | Jim Schimpf's book]
<li> Mailing list
     <ul>
     <li> [http://lists.fossil-scm.org:8080/cgi-bin/mailman/listinfo/fossil-users | sign-up]
     <li> [http://www.mail-archive.com/fossil-users@lists.fossil-scm.org | archives]
     <ul>
</ul>

Changes to www/quickstart.wiki.

118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    </blockquote>

    <p>Note that Fossil allows you to make multiple check-outs in
    separate directories from the same repository.  This enables you,
    for example, to do builds from multiple branches or versions at 
    the same time without having to generate extra clones.</p>

    <p>To switch a checkout between different versions and branches,
    use:</p>

    <blockquote>
    <b>[/help/update | fossil update]</b><br>
    <b>[/help/checkout | fossil checkout]</b><br>
    </blockquote>

    <p>[/help/update | update] honors the "autosync" option and
    does a "soft" switch, merging any local changes into the target
    version, whereas [/help/checkout | checkout] does not
    automatically sync and does a "hard" switch, overwriting local
    changes if told to do so.</p>

<h2>Configuring Your Local Repository</h2>
    
    <p>When you create a new repository, either by cloning an existing
    project or create a new project of your own, you usually want to do some
    local configuration.  This is easily accomplished using the web-server
    that is built into fossil.  Start the fossil webserver like this:
    ([/help/ui | more info])</p>







<
<
<
<
<
<
<
<
<
<
<
<
<
<







118
119
120
121
122
123
124














125
126
127
128
129
130
131
    </blockquote>

    <p>Note that Fossil allows you to make multiple check-outs in
    separate directories from the same repository.  This enables you,
    for example, to do builds from multiple branches or versions at 
    the same time without having to generate extra clones.</p>















<h2>Configuring Your Local Repository</h2>
    
    <p>When you create a new repository, either by cloning an existing
    project or create a new project of your own, you usually want to do some
    local configuration.  This is easily accomplished using the web-server
    that is built into fossil.  Start the fossil webserver like this:
    ([/help/ui | more info])</p>
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
    <p>The default behavior is for [./concepts.wiki#workflow|autosync] to
    be turned on.  That means that a [/help/pull|pull] automatically occurs
    when you run [/help/update|update] and a [/help/push|push] happens 
    automatically after you [/help/commit|commit].  So in normal practice,
    the push, pull, and sync commands are rarely used.  But it is important
    to know about them, all the same.</p>

    <blockquote>
    <b>[/help/checkout | fossil checkout]</b> <i>VERSION</i>
    </blockquote>

    <p>Is similar to update except that it does not honor the autosync
    setting, nor does it merge in local changes - it prefers to overwrite
    them and fails if local changes exist unless the <tt>--force</tt>
    flag is used.</p>

<h2>Branching And Merging</h2>

    <p>Use the --branch option to the [/help/commit | commit] command
    to start a new branch.  Note that in Fossil, branches are normally
    created when you commit, not before you start editing.  You can
    use the [/help/branch | branch new] command to create a new branch
    before you start editing, if you want, but most people just wait







<
<
<
<
<
<
<
<
<







225
226
227
228
229
230
231









232
233
234
235
236
237
238
    <p>The default behavior is for [./concepts.wiki#workflow|autosync] to
    be turned on.  That means that a [/help/pull|pull] automatically occurs
    when you run [/help/update|update] and a [/help/push|push] happens 
    automatically after you [/help/commit|commit].  So in normal practice,
    the push, pull, and sync commands are rarely used.  But it is important
    to know about them, all the same.</p>










<h2>Branching And Merging</h2>

    <p>Use the --branch option to the [/help/commit | commit] command
    to start a new branch.  Note that in Fossil, branches are normally
    created when you commit, not before you start editing.  You can
    use the [/help/branch | branch new] command to create a new branch
    before you start editing, if you want, but most people just wait

Changes to www/server.wiki.

239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
support SSL.
</p>
<p>
For more information, see <a href="./ssl.wiki">Using SSL with Fossil</a>.
</p>
</blockquote>

<a name="loadmgmt"></a>
<h2>Managing Server Load</h2><blockquote>
<p>
A Fossil server is very efficient and normally presents a very light 
load on the server.
The Fossil [./selfhost.wiki | self-host server] is a 1/24th slice VM at
[http://www.linode.com | Linode.com] hosting 65 other repositories in
addition to Fossil (and including some very high-traffic sites such
as [http://www.sqlite.org] and [http://system.data.sqlite.org]) and
it has a typical load of 0.05 to 0.1.  A single HTTP request to Fossil
normally takes less than 10 milliseconds of CPU time to complete.  So
requests can be arriving at a continuous rate of 20 or more per second 
and the CPU can still be mostly idle.
<p>
However, there are some Fossil web pages that can consume large 
amounts of CPU time, expecially on repositories with a large number
of files or with long revision histories.  High CPU usage pages include
[/help?cmd=/zip | /zip], [/help?cmd=/tarball | /tarball],
[/help?cmd=/annotate | /annotate] and others.  On very large repositories,
these commands can take 15 seconds or more of CPU time.  
If these kinds of requests arrive too quickly, the load average on the
server can grow dramatically, making the server unresponsive.
<p>
To help avoid problems, Fossil has the ability to fail CPU-intensive
web page requests with a 
[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.5.3 | "503 Server Overload"]
HTTP error if an expensive request is received while the host load 
average is too high.  To activate this
feature, visit the /Admin/Access setup page in the administrative web
interface and in the "<b>Server Load Average Limit</b>" box
enter the load average threshold above which "503 Server
Overload" replies will be issued for expensive requests.  On the
self-host Fossil server, that value is set to 1.5.  But you could easily
set it higher on a multi-core server.
<p>
The maximum load average can also be set on the command line using
commands like this:
<blockquote><pre>
fossil set max-loadavg 1.5
fossil all set max-loadavg 1.5
</pre></blockquote>
The second form is especially useful for changing the maximum load average
simultaneously on a large number of repositories.
<p>
Note that this load-average limiting feature is only available on operating
systems that support the "getloadavg()" API.  Most modern unix systems have
this interface, but Windows does not, so the feature will not work on Windows.
Note also that Linux implements "getloadavg()" by accessing the "/proc/loadavg"
file in the "proc" virtual filesystem.  If you are running a Fossil instance
inside a chroot() jail on Linux, you will need to make the "/proc" file
system available inside that jail in order for this feature to work.  On
the self-hosting Fossil repository, this was accomplished by adding a line
to the "/etc/mtab" file that looks like:
<blockquote><pre>
chroot_jail_proc /home/www/proc proc r 0 0
</pre></blockquote>
Pathnames should be adjusted for individual systems, of course.
<p>
To see if the load-average limiter is functional, visit the [/test_env] page
of the server to view the current load average.  If the value for the load
average is greater than zero, that means that it is possible to activate
the load-average limiter on that repository.  If the load average shows
exactly "0.0", then that means that Fossil is unable to find the load average
(either because it is in a chroot() jail without /proc access, or because
it is running on a system that does not support "getloadavg()") and so the
load-average limiter will not function.

</blockquote>







<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

239
240
241
242
243
244
245



































































246
support SSL.
</p>
<p>
For more information, see <a href="./ssl.wiki">Using SSL with Fossil</a>.
</p>
</blockquote>




































































</blockquote>

Changes to www/sync.wiki.

354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370

371
372
373
374
375
376
377
<li> project-name
<li> project-description
<li> manifest
<li> index-page
<ul></td><td valign="top"><ul>
<li> timeline-block-markup
<li> timeline-max-comment
<li> timeline-plaintext
<li> ticket-table
<li> ticket-common
<li> ticket-change
<li> ticket-newpage
<li> ticket-viewpage
<li> ticket-editpage
<ul></td><td valign="top"><ul>
<li> ticket-reportlist
<li> ticket-report-template

<li> ticket-key-template
<li> ticket-title-expr
<li> ticket-closed-expr
<li> @reportfmt
<li> @user
<li> @concealed
<li> @shun







<


<



<


>







354
355
356
357
358
359
360

361
362

363
364
365

366
367
368
369
370
371
372
373
374
375
<li> project-name
<li> project-description
<li> manifest
<li> index-page
<ul></td><td valign="top"><ul>
<li> timeline-block-markup
<li> timeline-max-comment

<li> ticket-table
<li> ticket-common

<li> ticket-newpage
<li> ticket-viewpage
<li> ticket-editpage

<li> ticket-reportlist
<li> ticket-report-template
<ul></td><td valign="top"><ul>
<li> ticket-key-template
<li> ticket-title-expr
<li> ticket-closed-expr
<li> @reportfmt
<li> @user
<li> @concealed
<li> @shun